Skip to content

Commit

Permalink
feat(@ngtools/webpack): replace server bootstrap code (#5194)
Browse files Browse the repository at this point in the history
  • Loading branch information
FrozenPandaz authored and hansl committed May 22, 2017
1 parent 9760ef9 commit c268b58
Show file tree
Hide file tree
Showing 15 changed files with 274 additions and 4 deletions.
32 changes: 28 additions & 4 deletions packages/@ngtools/webpack/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,25 @@ import {AotPlugin} from './plugin';
import {TypeScriptFileRefactor} from './refactor';
import {LoaderContext, ModuleReason} from './webpack';

interface Platform {
name: string;
importLocation: string;
}

const loaderUtils = require('loader-utils');
const NormalModule = require('webpack/lib/NormalModule');

// This is a map of changes which need to be made
const changeMap: {[key: string]: Platform} = {
platformBrowserDynamic: {
name: 'platformBrowser',
importLocation: '@angular/platform-browser'
},
platformDynamicServer: {
name: 'platformServer',
importLocation: '@angular/platform-server'
}
};

function _getContentOfKeyLiteral(_source: ts.SourceFile, node: ts.Node): string {
if (!node) {
Expand Down Expand Up @@ -205,9 +221,10 @@ function _replaceBootstrap(plugin: AotPlugin, refactor: TypeScriptFileRefactor)
= refactor.findAstNodes(access, ts.SyntaxKind.CallExpression, true) as ts.CallExpression[];
return previous.concat(expressions);
}, [])
.filter((call: ts.CallExpression) => call.expression.kind == ts.SyntaxKind.Identifier)
.filter((call: ts.CallExpression) => {
return call.expression.kind == ts.SyntaxKind.Identifier
&& (call.expression as ts.Identifier).text == 'platformBrowserDynamic';
// Find if the expression matches one of the replacement targets
return !!changeMap[(call.expression as ts.Identifier).text];
});

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

calls.forEach(call => refactor.replaceNode(call.expression, 'platformBrowser'));
calls.forEach(call => {
const platform = changeMap[(call.expression as ts.Identifier).text];

// Replace with mapped replacement
refactor.replaceNode(call.expression, platform.name);

// Add the appropriate import
refactor.insertImport(platform.name, platform.importLocation);
});

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

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>
<h1>hello world</h1>
<a [routerLink]="['lazy']">lazy</a>
<router-outlet></router-outlet>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
background-color: blue;
}
15 changes: 15 additions & 0 deletions tests/e2e/assets/webpack/test-server-app/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Component, ViewEncapsulation} from '@angular/core';
import {MyInjectable} from './injectable';


@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
constructor(public inj: MyInjectable) {
console.log(inj);
}
}
27 changes: 27 additions & 0 deletions tests/e2e/assets/webpack/test-server-app/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NgModule, Component } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';

@Component({
selector: 'home-view',
template: 'home!'
})
export class HomeView {}


@NgModule({
declarations: [
AppComponent,
HomeView
],
imports: [
ServerModule,
RouterModule.forRoot([
{path: 'lazy', loadChildren: './lazy.module#LazyModule'},
{path: '', component: HomeView}
])
],
bootstrap: [AppComponent]
})
export class AppModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {NgModule, Component} from '@angular/core';
import {RouterModule} from '@angular/router';

@Component({
selector: 'feature-component',
template: 'foo.html'
})
export class FeatureComponent {}

@NgModule({
declarations: [
FeatureComponent
],
imports: [
RouterModule.forChild([
{ path: '', component: FeatureComponent}
])
]
})
export class FeatureModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {NgModule, Component} from '@angular/core';
import {RouterModule} from '@angular/router';
import {HttpModule, Http} from '@angular/http';

@Component({
selector: 'lazy-feature-comp',
template: 'lazy feature!'
})
export class LazyFeatureComponent {}

@NgModule({
imports: [
RouterModule.forChild([
{path: '', component: LazyFeatureComponent, pathMatch: 'full'},
{path: 'feature', loadChildren: './feature.module#FeatureModule'}
]),
HttpModule
],
declarations: [LazyFeatureComponent]
})
export class LazyFeatureModule {
constructor(http: Http) {}
}
8 changes: 8 additions & 0 deletions tests/e2e/assets/webpack/test-server-app/app/injectable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {Injectable, Inject, ViewContainerRef} from '@angular/core';
import {DOCUMENT} from '@angular/platform-browser';


@Injectable()
export class MyInjectable {
constructor(public viewContainer: ViewContainerRef, @Inject(DOCUMENT) public doc) {}
}
26 changes: 26 additions & 0 deletions tests/e2e/assets/webpack/test-server-app/app/lazy.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {NgModule, Component} from '@angular/core';
import {RouterModule} from '@angular/router';
import {HttpModule, Http} from '@angular/http';

@Component({
selector: 'lazy-comp',
template: 'lazy!'
})
export class LazyComponent {}

@NgModule({
imports: [
RouterModule.forChild([
{path: '', component: LazyComponent, pathMatch: 'full'},
{path: 'feature', loadChildren: './feature/feature.module#FeatureModule'},
{path: 'lazy-feature', loadChildren: './feature/lazy-feature.module#LazyFeatureModule'}
]),
HttpModule
],
declarations: [LazyComponent]
})
export class LazyModule {
constructor(http: Http) {}
}

export class SecondModule {}
5 changes: 5 additions & 0 deletions tests/e2e/assets/webpack/test-server-app/app/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'core-js/es7/reflect';
import {platformDynamicServer} from '@angular/platform-server';
import {AppModule} from './app.module';

platformDynamicServer().bootstrapModule(AppModule);
12 changes: 12 additions & 0 deletions tests/e2e/assets/webpack/test-server-app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<base href="">
</head>
<body>
<app-root></app-root>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="dist/app.main.js"></script>
</body>
</html>
28 changes: 28 additions & 0 deletions tests/e2e/assets/webpack/test-server-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "test",
"license": "MIT",
"dependencies": {
"@angular/animations": "^4.0.0",
"@angular/common": "^4.0.0",
"@angular/compiler": "^4.0.0",
"@angular/compiler-cli": "^4.0.0",
"@angular/core": "^4.0.0",
"@angular/http": "^4.0.0",
"@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/platform-server": "^4.0.0",
"@angular/router": "^4.0.0",
"@ngtools/webpack": "0.0.0",
"core-js": "^2.4.1",
"rxjs": "^5.3.1",
"zone.js": "^0.8.10"
},
"devDependencies": {
"node-sass": "^4.5.0",
"performance-now": "^0.2.0",
"raw-loader": "^0.5.1",
"sass-loader": "^6.0.3",
"typescript": "^2.3.2",
"webpack": "2.2.1"
}
}
24 changes: 24 additions & 0 deletions tests/e2e/assets/webpack/test-server-app/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"baseUrl": "",
"module": "es2015",
"moduleResolution": "node",
"target": "es5",
"noImplicitAny": false,
"sourceMap": true,
"mapRoot": "",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es2016",
"dom"
],
"outDir": "lib",
"skipLibCheck": true,
"rootDir": "."
},
"angularCompilerOptions": {
"genDir": "./app/ngfactory",
"entryModule": "app/app.module#AppModule"
}
}
30 changes: 30 additions & 0 deletions tests/e2e/assets/webpack/test-server-app/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const ngToolsWebpack = require('@ngtools/webpack');

module.exports = {
resolve: {
extensions: ['.ts', '.js']
},
target: 'web',
entry: './app/main.ts',
output: {
path: './dist',
publicPath: 'dist/',
filename: 'app.main.js'
},
plugins: [
new ngToolsWebpack.AotPlugin({
tsConfigPath: './tsconfig.json'
})
],
module: {
loaders: [
{ test: /\.scss$/, loaders: ['raw-loader', 'sass-loader'] },
{ test: /\.css$/, loader: 'raw-loader' },
{ test: /\.html$/, loader: 'raw-loader' },
{ test: /\.ts$/, loader: '@ngtools/webpack' }
]
},
devServer: {
historyApiFallback: true
}
};
20 changes: 20 additions & 0 deletions tests/e2e/tests/packages/webpack/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {normalize} from 'path';
import {createProjectFromAsset} from '../../../utils/assets';
import {exec} from '../../../utils/process';
import {expectFileToMatch} from '../../../utils/fs';


export default function(skipCleaning: () => void) {
return Promise.resolve()
.then(() => createProjectFromAsset('webpack/test-server-app'))
.then(() => exec(normalize('node_modules/.bin/webpack')))
.then(() => expectFileToMatch('dist/app.main.js',
new RegExp('.bootstrapModuleFactory'))
.then(() => expectFileToMatch('dist/app.main.js',
new RegExp('MyInjectable.ctorParameters = .*'
+ 'type: .*ViewContainerRef.*'
+ 'type: undefined, decorators.*Inject.*args: .*DOCUMENT.*'))
.then(() => expectFileToMatch('dist/app.main.js',
new RegExp('AppComponent.ctorParameters = .*MyInjectable'))
.then(() => skipCleaning());
}

0 comments on commit c268b58

Please sign in to comment.