Skip to content

Commit 9c42417

Browse files
FrozenPandazdond2clouds
authored andcommitted
feat(@ngtools/webpack): replace server bootstrap code (angular#5194)
1 parent 130c49b commit 9c42417

File tree

15 files changed

+274
-4
lines changed

15 files changed

+274
-4
lines changed

packages/@ngtools/webpack/src/loader.ts

+28-4
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,25 @@ import {AotPlugin} from './plugin';
44
import {TypeScriptFileRefactor} from './refactor';
55
import {LoaderContext, ModuleReason} from './webpack';
66

7+
interface Platform {
8+
name: string;
9+
importLocation: string;
10+
}
11+
712
const loaderUtils = require('loader-utils');
813
const NormalModule = require('webpack/lib/NormalModule');
914

15+
// This is a map of changes which need to be made
16+
const changeMap: {[key: string]: Platform} = {
17+
platformBrowserDynamic: {
18+
name: 'platformBrowser',
19+
importLocation: '@angular/platform-browser'
20+
},
21+
platformDynamicServer: {
22+
name: 'platformServer',
23+
importLocation: '@angular/platform-server'
24+
}
25+
};
1026

1127
function _getContentOfKeyLiteral(_source: ts.SourceFile, node: ts.Node): string {
1228
if (!node) {
@@ -205,9 +221,10 @@ function _replaceBootstrap(plugin: AotPlugin, refactor: TypeScriptFileRefactor)
205221
= refactor.findAstNodes(access, ts.SyntaxKind.CallExpression, true) as ts.CallExpression[];
206222
return previous.concat(expressions);
207223
}, [])
224+
.filter((call: ts.CallExpression) => call.expression.kind == ts.SyntaxKind.Identifier)
208225
.filter((call: ts.CallExpression) => {
209-
return call.expression.kind == ts.SyntaxKind.Identifier
210-
&& (call.expression as ts.Identifier).text == 'platformBrowserDynamic';
226+
// Find if the expression matches one of the replacement targets
227+
return !!changeMap[(call.expression as ts.Identifier).text];
211228
});
212229

213230
if (calls.length == 0) {
@@ -222,15 +239,22 @@ function _replaceBootstrap(plugin: AotPlugin, refactor: TypeScriptFileRefactor)
222239
refactor.replaceNode(call.arguments[0], entryModule.className + 'NgFactory');
223240
});
224241

225-
calls.forEach(call => refactor.replaceNode(call.expression, 'platformBrowser'));
242+
calls.forEach(call => {
243+
const platform = changeMap[(call.expression as ts.Identifier).text];
244+
245+
// Replace with mapped replacement
246+
refactor.replaceNode(call.expression, platform.name);
247+
248+
// Add the appropriate import
249+
refactor.insertImport(platform.name, platform.importLocation);
250+
});
226251

227252
bootstraps
228253
.forEach((bs: ts.PropertyAccessExpression) => {
229254
// This changes the call.
230255
refactor.replaceNode(bs.name, 'bootstrapModuleFactory');
231256
});
232257

233-
refactor.insertImport('platformBrowser', '@angular/platform-browser');
234258
refactor.insertImport(entryModule.className + 'NgFactory', ngFactoryPath);
235259
}
236260

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<div>
2+
<h1>hello world</h1>
3+
<a [routerLink]="['lazy']">lazy</a>
4+
<router-outlet></router-outlet>
5+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:host {
2+
background-color: blue;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {Component, ViewEncapsulation} from '@angular/core';
2+
import {MyInjectable} from './injectable';
3+
4+
5+
@Component({
6+
selector: 'app-root',
7+
templateUrl: './app.component.html',
8+
styleUrls: ['./app.component.scss'],
9+
encapsulation: ViewEncapsulation.None
10+
})
11+
export class AppComponent {
12+
constructor(public inj: MyInjectable) {
13+
console.log(inj);
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { NgModule, Component } from '@angular/core';
2+
import { ServerModule } from '@angular/platform-server';
3+
import { RouterModule } from '@angular/router';
4+
import { AppComponent } from './app.component';
5+
6+
@Component({
7+
selector: 'home-view',
8+
template: 'home!'
9+
})
10+
export class HomeView {}
11+
12+
13+
@NgModule({
14+
declarations: [
15+
AppComponent,
16+
HomeView
17+
],
18+
imports: [
19+
ServerModule,
20+
RouterModule.forRoot([
21+
{path: 'lazy', loadChildren: './lazy.module#LazyModule'},
22+
{path: '', component: HomeView}
23+
])
24+
],
25+
bootstrap: [AppComponent]
26+
})
27+
export class AppModule { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {NgModule, Component} from '@angular/core';
2+
import {RouterModule} from '@angular/router';
3+
4+
@Component({
5+
selector: 'feature-component',
6+
template: 'foo.html'
7+
})
8+
export class FeatureComponent {}
9+
10+
@NgModule({
11+
declarations: [
12+
FeatureComponent
13+
],
14+
imports: [
15+
RouterModule.forChild([
16+
{ path: '', component: FeatureComponent}
17+
])
18+
]
19+
})
20+
export class FeatureModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {NgModule, Component} from '@angular/core';
2+
import {RouterModule} from '@angular/router';
3+
import {HttpModule, Http} from '@angular/http';
4+
5+
@Component({
6+
selector: 'lazy-feature-comp',
7+
template: 'lazy feature!'
8+
})
9+
export class LazyFeatureComponent {}
10+
11+
@NgModule({
12+
imports: [
13+
RouterModule.forChild([
14+
{path: '', component: LazyFeatureComponent, pathMatch: 'full'},
15+
{path: 'feature', loadChildren: './feature.module#FeatureModule'}
16+
]),
17+
HttpModule
18+
],
19+
declarations: [LazyFeatureComponent]
20+
})
21+
export class LazyFeatureModule {
22+
constructor(http: Http) {}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {Injectable, Inject, ViewContainerRef} from '@angular/core';
2+
import {DOCUMENT} from '@angular/platform-browser';
3+
4+
5+
@Injectable()
6+
export class MyInjectable {
7+
constructor(public viewContainer: ViewContainerRef, @Inject(DOCUMENT) public doc) {}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {NgModule, Component} from '@angular/core';
2+
import {RouterModule} from '@angular/router';
3+
import {HttpModule, Http} from '@angular/http';
4+
5+
@Component({
6+
selector: 'lazy-comp',
7+
template: 'lazy!'
8+
})
9+
export class LazyComponent {}
10+
11+
@NgModule({
12+
imports: [
13+
RouterModule.forChild([
14+
{path: '', component: LazyComponent, pathMatch: 'full'},
15+
{path: 'feature', loadChildren: './feature/feature.module#FeatureModule'},
16+
{path: 'lazy-feature', loadChildren: './feature/lazy-feature.module#LazyFeatureModule'}
17+
]),
18+
HttpModule
19+
],
20+
declarations: [LazyComponent]
21+
})
22+
export class LazyModule {
23+
constructor(http: Http) {}
24+
}
25+
26+
export class SecondModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import 'core-js/es7/reflect';
2+
import {platformDynamicServer} from '@angular/platform-server';
3+
import {AppModule} from './app.module';
4+
5+
platformDynamicServer().bootstrapModule(AppModule);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<html>
2+
<head>
3+
<meta charset="UTF-8">
4+
<title>Document</title>
5+
<base href="">
6+
</head>
7+
<body>
8+
<app-root></app-root>
9+
<script src="node_modules/zone.js/dist/zone.js"></script>
10+
<script src="dist/app.main.js"></script>
11+
</body>
12+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "test",
3+
"license": "MIT",
4+
"dependencies": {
5+
"@angular/animations": "^4.0.0",
6+
"@angular/common": "^4.0.0",
7+
"@angular/compiler": "^4.0.0",
8+
"@angular/compiler-cli": "^4.0.0",
9+
"@angular/core": "^4.0.0",
10+
"@angular/http": "^4.0.0",
11+
"@angular/platform-browser": "^4.0.0",
12+
"@angular/platform-browser-dynamic": "^4.0.0",
13+
"@angular/platform-server": "^4.0.0",
14+
"@angular/router": "^4.0.0",
15+
"@ngtools/webpack": "0.0.0",
16+
"core-js": "^2.4.1",
17+
"rxjs": "^5.3.1",
18+
"zone.js": "^0.8.10"
19+
},
20+
"devDependencies": {
21+
"node-sass": "^4.5.0",
22+
"performance-now": "^0.2.0",
23+
"raw-loader": "^0.5.1",
24+
"sass-loader": "^6.0.3",
25+
"typescript": "^2.3.2",
26+
"webpack": "2.2.1"
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": "",
4+
"module": "es2015",
5+
"moduleResolution": "node",
6+
"target": "es5",
7+
"noImplicitAny": false,
8+
"sourceMap": true,
9+
"mapRoot": "",
10+
"emitDecoratorMetadata": true,
11+
"experimentalDecorators": true,
12+
"lib": [
13+
"es2016",
14+
"dom"
15+
],
16+
"outDir": "lib",
17+
"skipLibCheck": true,
18+
"rootDir": "."
19+
},
20+
"angularCompilerOptions": {
21+
"genDir": "./app/ngfactory",
22+
"entryModule": "app/app.module#AppModule"
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const ngToolsWebpack = require('@ngtools/webpack');
2+
3+
module.exports = {
4+
resolve: {
5+
extensions: ['.ts', '.js']
6+
},
7+
target: 'web',
8+
entry: './app/main.ts',
9+
output: {
10+
path: './dist',
11+
publicPath: 'dist/',
12+
filename: 'app.main.js'
13+
},
14+
plugins: [
15+
new ngToolsWebpack.AotPlugin({
16+
tsConfigPath: './tsconfig.json'
17+
})
18+
],
19+
module: {
20+
loaders: [
21+
{ test: /\.scss$/, loaders: ['raw-loader', 'sass-loader'] },
22+
{ test: /\.css$/, loader: 'raw-loader' },
23+
{ test: /\.html$/, loader: 'raw-loader' },
24+
{ test: /\.ts$/, loader: '@ngtools/webpack' }
25+
]
26+
},
27+
devServer: {
28+
historyApiFallback: true
29+
}
30+
};
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {normalize} from 'path';
2+
import {createProjectFromAsset} from '../../../utils/assets';
3+
import {exec} from '../../../utils/process';
4+
import {expectFileToMatch} from '../../../utils/fs';
5+
6+
7+
export default function(skipCleaning: () => void) {
8+
return Promise.resolve()
9+
.then(() => createProjectFromAsset('webpack/test-server-app'))
10+
.then(() => exec(normalize('node_modules/.bin/webpack')))
11+
.then(() => expectFileToMatch('dist/app.main.js',
12+
new RegExp('.bootstrapModuleFactory'))
13+
.then(() => expectFileToMatch('dist/app.main.js',
14+
new RegExp('MyInjectable.ctorParameters = .*'
15+
+ 'type: .*ViewContainerRef.*'
16+
+ 'type: undefined, decorators.*Inject.*args: .*DOCUMENT.*'))
17+
.then(() => expectFileToMatch('dist/app.main.js',
18+
new RegExp('AppComponent.ctorParameters = .*MyInjectable'))
19+
.then(() => skipCleaning());
20+
}

0 commit comments

Comments
 (0)