From 727b1aa03b9b143e11036cead3d7729cb7ba0921 Mon Sep 17 00:00:00 2001 From: FrozenPandaz Date: Wed, 20 Dec 2017 14:47:51 -0500 Subject: [PATCH] feat(@ngtools/webpack): replace bootstrap code for server apps --- .../webpack/src/angular_compiler_plugin.ts | 5 +- .../webpack/src/transformers/index.ts | 1 + .../replace_server_bootstrap.spec.ts | 215 ++++++++++++++++++ .../transformers/replace_server_bootstrap.ts | 135 +++++++++++ .../app/app.component.html | 5 + .../app/app.component.scss | 3 + .../test-server-app-ng5/app/app.component.ts | 15 ++ .../test-server-app-ng5/app/app.module.ts | 36 +++ .../app/feature/feature.module.ts | 20 ++ .../app/feature/lazy-feature.module.ts | 23 ++ .../test-server-app-ng5/app/injectable.ts | 8 + .../test-server-app-ng5/app/lazy.module.ts | 26 +++ .../test-server-app-ng5/app/main.commonjs.ts | 1 + .../webpack/test-server-app-ng5/app/main.ts | 12 + .../webpack/test-server-app-ng5/index.html | 12 + .../webpack/test-server-app-ng5/index.js | 12 + .../webpack/test-server-app-ng5/package.json | 28 +++ .../webpack/test-server-app-ng5/tsconfig.json | 24 ++ .../test-server-app-ng5/webpack.config.js | 32 +++ .../e2e/tests/packages/webpack/server-ng5.ts | 23 ++ 20 files changed, 635 insertions(+), 1 deletion(-) create mode 100644 packages/@ngtools/webpack/src/transformers/replace_server_bootstrap.spec.ts create mode 100644 packages/@ngtools/webpack/src/transformers/replace_server_bootstrap.ts create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.html create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.scss create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.ts create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/app/app.module.ts create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/app/feature/feature.module.ts create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/app/feature/lazy-feature.module.ts create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/app/injectable.ts create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/app/lazy.module.ts create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/app/main.commonjs.ts create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/app/main.ts create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/index.html create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/index.js create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/package.json create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/tsconfig.json create mode 100644 tests/e2e/assets/webpack/test-server-app-ng5/webpack.config.js create mode 100644 tests/e2e/tests/packages/webpack/server-ng5.ts diff --git a/packages/@ngtools/webpack/src/angular_compiler_plugin.ts b/packages/@ngtools/webpack/src/angular_compiler_plugin.ts index 10bb8819fe81..290d0321ca19 100644 --- a/packages/@ngtools/webpack/src/angular_compiler_plugin.ts +++ b/packages/@ngtools/webpack/src/angular_compiler_plugin.ts @@ -19,6 +19,7 @@ import { import { resolveEntryModuleFromMain } from './entry_resolver'; import { replaceBootstrap, + replaceServerBootstrap, exportNgFactory, exportLazyModuleMap, removeDecorators, @@ -692,7 +693,9 @@ export class AngularCompilerPlugin implements Tapable { } else if (this._platform === PLATFORM.Server) { this._transformers.push(exportLazyModuleMap(isMainPath, getLazyRoutes)); if (!this._JitMode) { - this._transformers.push(exportNgFactory(isMainPath, getEntryModule)); + this._transformers.push( + exportNgFactory(isMainPath, getEntryModule), + replaceServerBootstrap(isMainPath, getEntryModule, getTypeChecker)); } } } diff --git a/packages/@ngtools/webpack/src/transformers/index.ts b/packages/@ngtools/webpack/src/transformers/index.ts index 4ee76368978c..e523cc7782e8 100644 --- a/packages/@ngtools/webpack/src/transformers/index.ts +++ b/packages/@ngtools/webpack/src/transformers/index.ts @@ -4,6 +4,7 @@ export * from './make_transform'; export * from './insert_import'; export * from './elide_imports'; export * from './replace_bootstrap'; +export * from './replace_server_bootstrap'; export * from './export_ngfactory'; export * from './export_lazy_module_map'; export * from './register_locale_data'; diff --git a/packages/@ngtools/webpack/src/transformers/replace_server_bootstrap.spec.ts b/packages/@ngtools/webpack/src/transformers/replace_server_bootstrap.spec.ts new file mode 100644 index 000000000000..5ca49b0e2ab3 --- /dev/null +++ b/packages/@ngtools/webpack/src/transformers/replace_server_bootstrap.spec.ts @@ -0,0 +1,215 @@ +import { oneLine, stripIndent } from 'common-tags'; +import { createTypescriptContext, transformTypescript } from './ast_helpers'; +import { replaceServerBootstrap } from './replace_server_bootstrap'; + +describe('@ngtools/webpack transformers', () => { + describe('replace_server_bootstrap', () => { + it('should replace bootstrap', () => { + const input = stripIndent` + import { enableProdMode } from '@angular/core'; + import { platformDynamicServer } from '@angular/platform-server'; + + import { AppModule } from './app/app.module'; + import { environment } from './environments/environment'; + + if (environment.production) { + enableProdMode(); + } + + platformDynamicServer().bootstrapModule(AppModule); + `; + + // tslint:disable:max-line-length + const output = stripIndent` + import { enableProdMode } from '@angular/core'; + import { environment } from './environments/environment'; + + import * as __NgCli_bootstrap_1 from "./app/app.module.ngfactory"; + import * as __NgCli_bootstrap_2 from "@angular/platform-server"; + + if (environment.production) { + enableProdMode(); + } + __NgCli_bootstrap_2.platformServer().bootstrapModuleFactory(__NgCli_bootstrap_1.AppModuleNgFactory); + `; + // tslint:enable:max-line-length + + const { program, compilerHost } = createTypescriptContext(input); + const transformer = replaceServerBootstrap( + () => true, + () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => program.getTypeChecker(), + ); + const result = transformTypescript(undefined, [transformer], program, compilerHost); + + expect(oneLine`${result}`).toEqual(oneLine`${output}`); + }); + + it('should replace renderModule', () => { + const input = stripIndent` + import { enableProdMode } from '@angular/core'; + import { renderModule } from '@angular/platform-server'; + + import { AppModule } from './app/app.module'; + import { environment } from './environments/environment'; + + if (environment.production) { + enableProdMode(); + } + + renderModule(AppModule, { + document: '', + url: '/' + }); + `; + + // tslint:disable:max-line-length + const output = stripIndent` + import { enableProdMode } from '@angular/core'; + import { environment } from './environments/environment'; + + import * as __NgCli_bootstrap_1 from "./app/app.module.ngfactory"; + import * as __NgCli_bootstrap_2 from "@angular/platform-server"; + + if (environment.production) { + enableProdMode(); + } + __NgCli_bootstrap_2.renderModuleFactory(__NgCli_bootstrap_1.AppModuleNgFactory, { + document: '', + url: '/' + }); + `; + // tslint:enable:max-line-length + + const { program, compilerHost } = createTypescriptContext(input); + const transformer = replaceServerBootstrap( + () => true, + () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => program.getTypeChecker(), + ); + const result = transformTypescript(undefined, [transformer], program, compilerHost); + + expect(oneLine`${result}`).toEqual(oneLine`${output}`); + }); + + it('should replace when the module is used in a config object', () => { + const input = stripIndent` + import * as express from 'express'; + + import { enableProdMode } from '@angular/core'; + import { ngExpressEngine } from '@nguniversal/express-engine'; + + import { AppModule } from './app/app.module'; + import { environment } from './environments/environment'; + + if (environment.production) { + enableProdMode(); + } + + const server = express(); + server.engine('html', ngExpressEngine({ + bootstrap: AppModule + })); + `; + + // tslint:disable:max-line-length + const output = stripIndent` + import * as express from 'express'; + + import { enableProdMode } from '@angular/core'; + import { ngExpressEngine } from '@nguniversal/express-engine'; + + import { environment } from './environments/environment'; + + import * as __NgCli_bootstrap_1 from "./app/app.module.ngfactory"; + + if (environment.production) { + enableProdMode(); + } + + const server = express(); + server.engine('html', ngExpressEngine({ + bootstrap: __NgCli_bootstrap_1.AppModuleNgFactory + })); + `; + // tslint:enable:max-line-length + + const { program, compilerHost } = createTypescriptContext(input); + const transformer = replaceServerBootstrap( + () => true, + () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => program.getTypeChecker(), + ); + const result = transformTypescript(undefined, [transformer], program, compilerHost); + + expect(oneLine`${result}`).toEqual(oneLine`${output}`); + }); + + it('should replace bootstrap when barrel files are used', () => { + const input = stripIndent` + import { enableProdMode } from '@angular/core'; + import { platformDynamicServer } from '@angular/platform-browser-dynamic'; + + import { AppModule } from './app'; + import { environment } from './environments/environment'; + + if (environment.production) { + enableProdMode(); + } + + platformDynamicServer().bootstrapModule(AppModule); + `; + + // tslint:disable:max-line-length + const output = stripIndent` + import { enableProdMode } from '@angular/core'; + import { environment } from './environments/environment'; + + import * as __NgCli_bootstrap_1 from "./app/app.module.ngfactory"; + import * as __NgCli_bootstrap_2 from "@angular/platform-server"; + + if (environment.production) { + enableProdMode(); + } + __NgCli_bootstrap_2.platformServer().bootstrapModuleFactory(__NgCli_bootstrap_1.AppModuleNgFactory); + `; + // tslint:enable:max-line-length + + const { program, compilerHost } = createTypescriptContext(input); + const transformer = replaceServerBootstrap( + () => true, + () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => program.getTypeChecker(), + ); + const result = transformTypescript(undefined, [transformer], program, compilerHost); + + expect(oneLine`${result}`).toEqual(oneLine`${output}`); + }); + + it('should not replace bootstrap when there is no entry module', () => { + const input = stripIndent` + import { enableProdMode } from '@angular/core'; + import { platformDynamicServer } from '@angular/platform-browser-dynamic'; + + import { AppModule } from './app/app.module'; + import { environment } from './environments/environment'; + + if (environment.production) { + enableProdMode(); + } + + platformDynamicServer().bootstrapModule(AppModule); + `; + + const { program, compilerHost } = createTypescriptContext(input); + const transformer = replaceServerBootstrap( + () => true, + () => undefined, + () => program.getTypeChecker(), + ); + const result = transformTypescript(undefined, [transformer], program, compilerHost); + + expect(oneLine`${result}`).toEqual(oneLine`${input}`); + }); + }); +}); diff --git a/packages/@ngtools/webpack/src/transformers/replace_server_bootstrap.ts b/packages/@ngtools/webpack/src/transformers/replace_server_bootstrap.ts new file mode 100644 index 000000000000..a38ee8982c26 --- /dev/null +++ b/packages/@ngtools/webpack/src/transformers/replace_server_bootstrap.ts @@ -0,0 +1,135 @@ +// @ignoreDep typescript +import * as ts from 'typescript'; +import { relative, dirname } from 'path'; + +import { collectDeepNodes } from './ast_helpers'; +import { insertStarImport } from './insert_import'; +import { StandardTransform, ReplaceNodeOperation, TransformOperation } from './interfaces'; +import { makeTransform } from './make_transform'; + +export function replaceServerBootstrap( + shouldTransform: (fileName: string) => boolean, + getEntryModule: () => { path: string, className: string }, + getTypeChecker: () => ts.TypeChecker, +): ts.TransformerFactory { + + const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { + const ops: TransformOperation[] = []; + + const entryModule = getEntryModule(); + + if (!shouldTransform(sourceFile.fileName) || !entryModule) { + return ops; + } + + // Find all identifiers. + const entryModuleIdentifiers = collectDeepNodes(sourceFile, + ts.SyntaxKind.Identifier) + .filter(identifier => identifier.text === entryModule.className); + + if (entryModuleIdentifiers.length === 0) { + return []; + } + + const relativeEntryModulePath = relative(dirname(sourceFile.fileName), entryModule.path); + const normalizedEntryModulePath = `./${relativeEntryModulePath}`.replace(/\\/g, '/'); + const factoryClassName = entryModule.className + 'NgFactory'; + const factoryModulePath = normalizedEntryModulePath + '.ngfactory'; + + // Find the bootstrap calls. + entryModuleIdentifiers.forEach(entryModuleIdentifier => { + if (!entryModuleIdentifier.parent) { + return; + } + + if (entryModuleIdentifier.parent.kind !== ts.SyntaxKind.CallExpression && + entryModuleIdentifier.parent.kind !== ts.SyntaxKind.PropertyAssignment) { + return; + } + + if (entryModuleIdentifier.parent.kind === ts.SyntaxKind.CallExpression) { + // Figure out if it's a `platformDynamicServer().bootstrapModule(AppModule)` call. + + const callExpr = entryModuleIdentifier.parent as ts.CallExpression; + + if (callExpr.expression.kind === ts.SyntaxKind.PropertyAccessExpression) { + + const propAccessExpr = callExpr.expression as ts.PropertyAccessExpression; + + if (!(propAccessExpr.name.text === 'bootstrapModule' + && propAccessExpr.expression.kind === ts.SyntaxKind.CallExpression)) { + return; + } + + const bootstrapModuleIdentifier = propAccessExpr.name; + const innerCallExpr = propAccessExpr.expression as ts.CallExpression; + + if (!( + innerCallExpr.expression.kind === ts.SyntaxKind.Identifier + && (innerCallExpr.expression as ts.Identifier).text === 'platformDynamicServer' + )) { + return; + } + + const platformDynamicServerIdentifier = innerCallExpr.expression as ts.Identifier; + + const idPlatformServer = ts.createUniqueName('__NgCli_bootstrap_'); + const idNgFactory = ts.createUniqueName('__NgCli_bootstrap_'); + + // Add the transform operations. + ops.push( + // Replace the entry module import. + ...insertStarImport(sourceFile, idNgFactory, factoryModulePath), + new ReplaceNodeOperation(sourceFile, entryModuleIdentifier, + ts.createPropertyAccess(idNgFactory, ts.createIdentifier(factoryClassName))), + // Replace the platformBrowserDynamic import. + ...insertStarImport(sourceFile, idPlatformServer, '@angular/platform-server'), + new ReplaceNodeOperation(sourceFile, platformDynamicServerIdentifier, + ts.createPropertyAccess(idPlatformServer, 'platformServer')), + new ReplaceNodeOperation(sourceFile, bootstrapModuleIdentifier, + ts.createIdentifier('bootstrapModuleFactory')), + ); + } else if (callExpr.expression.kind === ts.SyntaxKind.Identifier) { + // Figure out if it is renderModule + + const identifierExpr = callExpr.expression as ts.Identifier; + + if (identifierExpr.text !== 'renderModule') { + return; + } + + const renderModuleIdentifier = identifierExpr as ts.Identifier; + + const idPlatformServer = ts.createUniqueName('__NgCli_bootstrap_'); + const idNgFactory = ts.createUniqueName('__NgCli_bootstrap_'); + + ops.push( + // Replace the entry module import. + ...insertStarImport(sourceFile, idNgFactory, factoryModulePath), + new ReplaceNodeOperation(sourceFile, entryModuleIdentifier, + ts.createPropertyAccess(idNgFactory, ts.createIdentifier(factoryClassName))), + // Replace the renderModule import. + ...insertStarImport(sourceFile, idPlatformServer, '@angular/platform-server'), + new ReplaceNodeOperation(sourceFile, renderModuleIdentifier, + ts.createPropertyAccess(idPlatformServer, 'renderModuleFactory')), + ); + } + } else if (entryModuleIdentifier.parent.kind === ts.SyntaxKind.PropertyAssignment) { + // This is for things that accept a module as a property in a config object + // .ie the express engine + + const idNgFactory = ts.createUniqueName('__NgCli_bootstrap_'); + + ops.push( + ...insertStarImport(sourceFile, idNgFactory, factoryModulePath), + new ReplaceNodeOperation(sourceFile, entryModuleIdentifier, + ts.createPropertyAccess(idNgFactory, ts.createIdentifier(factoryClassName))) + ); + } + }); + + return ops; + }; + + return makeTransform(standardTransform, getTypeChecker); +} diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.html b/tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.html new file mode 100644 index 000000000000..5a532db9308f --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.html @@ -0,0 +1,5 @@ +
+

hello world

+ lazy + +
diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.scss b/tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.scss new file mode 100644 index 000000000000..5cde7b922336 --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.scss @@ -0,0 +1,3 @@ +:host { + background-color: blue; +} diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.ts b/tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.ts new file mode 100644 index 000000000000..82a4059565d3 --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/app/app.component.ts @@ -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); + } +} diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/app/app.module.ts b/tests/e2e/assets/webpack/test-server-app-ng5/app/app.module.ts new file mode 100644 index 000000000000..7c8a0c296448 --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/app/app.module.ts @@ -0,0 +1,36 @@ +import { NgModule, Component } from '@angular/core'; +import { ServerModule } from '@angular/platform-server'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { MyInjectable } from './injectable'; + +@Component({ + selector: 'home-view', + template: 'home!' +}) +export class HomeView {} + + +@NgModule({ + declarations: [ + AppComponent, + HomeView + ], + imports: [ + BrowserModule.withServerTransition({ + appId: 'app' + }), + ServerModule, + RouterModule.forRoot([ + {path: 'lazy', loadChildren: './lazy.module#LazyModule'}, + {path: '', component: HomeView} + ]) + ], + providers: [MyInjectable], + bootstrap: [AppComponent] +}) +export class AppModule { + static testProp: string; +} diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/app/feature/feature.module.ts b/tests/e2e/assets/webpack/test-server-app-ng5/app/feature/feature.module.ts new file mode 100644 index 000000000000..f464ca028b05 --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/app/feature/feature.module.ts @@ -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 {} diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/app/feature/lazy-feature.module.ts b/tests/e2e/assets/webpack/test-server-app-ng5/app/feature/lazy-feature.module.ts new file mode 100644 index 000000000000..8fafca158b24 --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/app/feature/lazy-feature.module.ts @@ -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) {} +} diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/app/injectable.ts b/tests/e2e/assets/webpack/test-server-app-ng5/app/injectable.ts new file mode 100644 index 000000000000..b357678ae77a --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/app/injectable.ts @@ -0,0 +1,8 @@ +import {Injectable, Inject, ViewContainerRef} from '@angular/core'; +import {DOCUMENT} from '@angular/platform-browser'; + + +@Injectable() +export class MyInjectable { + constructor(@Inject(DOCUMENT) public doc) {} +} diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/app/lazy.module.ts b/tests/e2e/assets/webpack/test-server-app-ng5/app/lazy.module.ts new file mode 100644 index 000000000000..96da4de7515b --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/app/lazy.module.ts @@ -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 {} diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/app/main.commonjs.ts b/tests/e2e/assets/webpack/test-server-app-ng5/app/main.commonjs.ts new file mode 100644 index 000000000000..ce26d93a11de --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/app/main.commonjs.ts @@ -0,0 +1 @@ +export { AppModule } from './app.module'; diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/app/main.ts b/tests/e2e/assets/webpack/test-server-app-ng5/app/main.ts new file mode 100644 index 000000000000..5d57aafca8ae --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/app/main.ts @@ -0,0 +1,12 @@ +import 'core-js/es7/reflect'; +import {platformDynamicServer, renderModule} from '@angular/platform-server'; +import {AppModule} from './app.module'; + +AppModule.testProp = 'testing'; + +platformDynamicServer().bootstrapModule(AppModule); + +renderModule(AppModule, { + document: '', + url: '/' +}); diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/index.html b/tests/e2e/assets/webpack/test-server-app-ng5/index.html new file mode 100644 index 000000000000..89fb0893c35d --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/index.html @@ -0,0 +1,12 @@ + + + + Document + + + + + + + + diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/index.js b/tests/e2e/assets/webpack/test-server-app-ng5/index.js new file mode 100644 index 000000000000..bdfb2e792acd --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/index.js @@ -0,0 +1,12 @@ +const fs = require('fs'); +const { AppModuleNgFactory } = require('./dist/app.main'); +const { renderModuleFactory } = require('@angular/platform-server'); + +require('zone.js/dist/zone-node'); + +renderModuleFactory(AppModuleNgFactory, { + url: '/', + document: '' +}).then(html => { + fs.writeFileSync('dist/index.html', html); +}) diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/package.json b/tests/e2e/assets/webpack/test-server-app-ng5/package.json new file mode 100644 index 000000000000..b499eaaa4e25 --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/package.json @@ -0,0 +1,28 @@ +{ + "name": "test", + "license": "MIT", + "dependencies": { + "@angular/animations": "^5.0.0", + "@angular/common": "^5.0.0", + "@angular/compiler": "^5.0.0", + "@angular/compiler-cli": "^5.0.0", + "@angular/core": "^5.0.0", + "@angular/http": "^5.0.0", + "@angular/platform-browser": "^5.0.0", + "@angular/platform-browser-dynamic": "^5.0.0", + "@angular/platform-server": "^5.0.0", + "@angular/router": "^5.0.0", + "@ngtools/webpack": "0.0.0", + "core-js": "^2.4.1", + "rxjs": "^5.4.2", + "zone.js": "^0.8.14" + }, + "devDependencies": { + "node-sass": "^4.5.0", + "performance-now": "^0.2.0", + "raw-loader": "^0.5.1", + "sass-loader": "^6.0.3", + "typescript": "~2.5.0", + "webpack": "2.2.1" + } +} diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/tsconfig.json b/tests/e2e/assets/webpack/test-server-app-ng5/tsconfig.json new file mode 100644 index 000000000000..5822e780bfc7 --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "baseUrl": "", + "module": "es2015", + "moduleResolution": "node", + "target": "es5", + "noImplicitAny": false, + "sourceMap": true, + "mapRoot": "", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2017", + "dom" + ], + "outDir": "lib", + "skipLibCheck": true, + "rootDir": "." + }, + "angularCompilerOptions": { + "genDir": "./app/ngfactory", + "entryModule": "app/app.module#AppModule" + } +} diff --git a/tests/e2e/assets/webpack/test-server-app-ng5/webpack.config.js b/tests/e2e/assets/webpack/test-server-app-ng5/webpack.config.js new file mode 100644 index 000000000000..4764b17b23dc --- /dev/null +++ b/tests/e2e/assets/webpack/test-server-app-ng5/webpack.config.js @@ -0,0 +1,32 @@ +const { AngularCompilerPlugin, PLATFORM } = 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 AngularCompilerPlugin({ + tsConfigPath: './tsconfig.json', + mainPath: './app/main.ts', + platform: PLATFORM.Server + }) + ], + 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 + } +}; diff --git a/tests/e2e/tests/packages/webpack/server-ng5.ts b/tests/e2e/tests/packages/webpack/server-ng5.ts new file mode 100644 index 000000000000..021b984508d3 --- /dev/null +++ b/tests/e2e/tests/packages/webpack/server-ng5.ts @@ -0,0 +1,23 @@ +import { normalize } from 'path'; +import { createProjectFromAsset } from '../../../utils/assets'; +import { exec } from '../../../utils/process'; +import { expectFileToMatch, rimraf } 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('MyInjectable.ctorParameters = .*' + + 'type: undefined, decorators.*Inject.*args: .*DOCUMENT.*')) + .then(() => expectFileToMatch('dist/app.main.js', + new RegExp('AppComponent.ctorParameters = .*MyInjectable')) + .then(() => expectFileToMatch('dist/app.main.js', + /AppModule \*\/\].*\.testProp = \'testing\'/)) + .then(() => expectFileToMatch('dist/app.main.js', + /platformServer \*\/\]\)\(\)\.bootstrapModuleFactory\(.*\/\* AppModuleNgFactory \*\/\]/)) + .then(() => expectFileToMatch('dist/app.main.js', + /renderModuleFactory \*\/\].*\/\* AppModuleNgFactory \*\/\]/)) + .then(() => skipCleaning()); +}