diff --git a/packages/ngtools/webpack/src/angular_compiler_plugin.ts b/packages/ngtools/webpack/src/angular_compiler_plugin.ts index 3b42bd1605..cf91cad442 100644 --- a/packages/ngtools/webpack/src/angular_compiler_plugin.ts +++ b/packages/ngtools/webpack/src/angular_compiler_plugin.ts @@ -43,7 +43,8 @@ import { findResources, registerLocaleData, removeDecorators, - replaceBootstrap, + replaceBrowserBootstrap, + replaceNativeScriptBootstrap, replaceResources, replaceServerBootstrap, } from './transformers'; @@ -95,6 +96,7 @@ export interface AngularCompilerPluginOptions { export enum PLATFORM { Browser, Server, + NativeScript, } export class AngularCompilerPlugin { @@ -768,7 +770,7 @@ export class AngularCompilerPlugin { if (!this._JitMode) { // Replace bootstrap in browser AOT. - this._transformers.push(replaceBootstrap(isAppPath, getEntryModule, getTypeChecker)); + this._transformers.push(replaceBrowserBootstrap(isAppPath, getEntryModule, getTypeChecker)); } } else if (this._platform === PLATFORM.Server) { this._transformers.push(exportLazyModuleMap(isMainPath, getLazyRoutes)); @@ -777,6 +779,11 @@ export class AngularCompilerPlugin { exportNgFactory(isMainPath, getEntryModule), replaceServerBootstrap(isMainPath, getEntryModule, getTypeChecker)); } + } else if (this._platform === PLATFORM.NativeScript) { + if (!this._JitMode) { + this._transformers.push( + replaceNativeScriptBootstrap(isAppPath, getEntryModule, getTypeChecker)); + } } } diff --git a/packages/ngtools/webpack/src/transformers/index.ts b/packages/ngtools/webpack/src/transformers/index.ts index bb3d03dc92..d1fcf13e5d 100644 --- a/packages/ngtools/webpack/src/transformers/index.ts +++ b/packages/ngtools/webpack/src/transformers/index.ts @@ -10,8 +10,9 @@ export * from './ast_helpers'; export * from './make_transform'; export * from './insert_import'; export * from './elide_imports'; -export * from './replace_bootstrap'; +export * from './replace_browser_bootstrap'; export * from './replace_server_bootstrap'; +export * from './replace_nativescript_bootstrap'; export * from './export_ngfactory'; export * from './export_lazy_module_map'; export * from './register_locale_data'; diff --git a/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts b/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts index 0726efb20d..1f2cfbb60f 100644 --- a/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts +++ b/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts @@ -10,7 +10,7 @@ import { createTypescriptContext, transformTypescript } from './ast_helpers'; import { exportLazyModuleMap } from './export_lazy_module_map'; import { exportNgFactory } from './export_ngfactory'; import { removeDecorators } from './remove_decorators'; -import { replaceBootstrap } from './replace_bootstrap'; +import { replaceBrowserBootstrap } from './replace_browser_bootstrap'; describe('@ngtools/webpack transformers', () => { @@ -73,7 +73,7 @@ describe('@ngtools/webpack transformers', () => { const transformers = [ - replaceBootstrap(shouldTransform, getEntryModule, getTypeChecker), + replaceBrowserBootstrap(shouldTransform, getEntryModule, getTypeChecker), exportNgFactory(shouldTransform, getEntryModule), exportLazyModuleMap(shouldTransform, () => ({ diff --git a/packages/ngtools/webpack/src/transformers/replace_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_bootstrap.ts deleted file mode 100644 index fc05f86b92..0000000000 --- a/packages/ngtools/webpack/src/transformers/replace_bootstrap.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { dirname, relative } from 'path'; -import * as ts from 'typescript'; -import { collectDeepNodes } from './ast_helpers'; -import { insertStarImport } from './insert_import'; -import { ReplaceNodeOperation, StandardTransform, TransformOperation } from './interfaces'; -import { makeTransform } from './make_transform'; - - -export function replaceBootstrap( - shouldTransform: (fileName: string) => boolean, - getEntryModule: () => { path: string, className: string } | null, - 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, '/'); - - // Find the bootstrap calls. - entryModuleIdentifiers.forEach(entryModuleIdentifier => { - // Figure out if it's a `platformBrowserDynamic().bootstrapModule(AppModule)` call. - if (!( - entryModuleIdentifier.parent - && entryModuleIdentifier.parent.kind === ts.SyntaxKind.CallExpression - )) { - return; - } - - const callExpr = entryModuleIdentifier.parent as ts.CallExpression; - - if (callExpr.expression.kind !== ts.SyntaxKind.PropertyAccessExpression) { - return; - } - - 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 === 'platformBrowserDynamic' - )) { - return; - } - - const platformBrowserDynamicIdentifier = innerCallExpr.expression as ts.Identifier; - - const idPlatformBrowser = ts.createUniqueName('__NgCli_bootstrap_'); - const idNgFactory = ts.createUniqueName('__NgCli_bootstrap_'); - - // Add the transform operations. - const factoryClassName = entryModule.className + 'NgFactory'; - const factoryModulePath = normalizedEntryModulePath + '.ngfactory'; - 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, idPlatformBrowser, '@angular/platform-browser'), - new ReplaceNodeOperation(sourceFile, platformBrowserDynamicIdentifier, - ts.createPropertyAccess(idPlatformBrowser, 'platformBrowser')), - new ReplaceNodeOperation(sourceFile, bootstrapModuleIdentifier, - ts.createIdentifier('bootstrapModuleFactory')), - ); - }); - - return ops; - }; - - return makeTransform(standardTransform, getTypeChecker); -} diff --git a/packages/ngtools/webpack/src/transformers/replace_bootstrap_helpers.ts b/packages/ngtools/webpack/src/transformers/replace_bootstrap_helpers.ts new file mode 100644 index 0000000000..741e84bf39 --- /dev/null +++ b/packages/ngtools/webpack/src/transformers/replace_bootstrap_helpers.ts @@ -0,0 +1,137 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { dirname, relative } from 'path'; +import * as ts from 'typescript'; +import { collectDeepNodes } from './ast_helpers'; +import { insertStarImport } from './insert_import'; +import { ReplaceNodeOperation, StandardTransform, TransformOperation } from './interfaces'; +import { makeTransform } from './make_transform'; + +export interface PlatformBootstrapOptions { + dynamicPlatformName: string; + staticPlatformName: string; + staticPlatformPath: string; +} + +export interface BootstrapReplaceOptions extends PlatformBootstrapOptions { + factoryClassName: string; + factoryModulePath: string; +} + +export type BootstrapReplaceFunction = ( + identifiers: ts.Identifier[], + sourceFile: ts.SourceFile, + platformOptions: PlatformBootstrapOptions) + => TransformOperation[]; + +export function replaceBootstrap( + shouldTransform: (fileName: string) => boolean, + getEntryModule: () => { path: string, className: string } | null, + getTypeChecker: () => ts.TypeChecker, + replaceFunctions: BootstrapReplaceFunction[], + platformOptions: PlatformBootstrapOptions, +): ts.TransformerFactory { + + const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { + const entryModule = getEntryModule(); + + if (!shouldTransform(sourceFile.fileName) || !entryModule) { + return []; + } + + // Find all identifiers. + const entryModuleIdentifiers = collectDeepNodes(sourceFile, + ts.SyntaxKind.Identifier) + .filter(identifier => identifier.text === entryModule.className); + + const relativeEntryModulePath = relative(dirname(sourceFile.fileName), entryModule.path); + const normalizedEntryModulePath = `./${relativeEntryModulePath}`.replace(/\\/g, '/'); + const factoryClassName = entryModule.className + 'NgFactory'; + const factoryModulePath = normalizedEntryModulePath + '.ngfactory'; + + const bootstrapReplaceOptions: BootstrapReplaceOptions = { + ...platformOptions, + factoryClassName, + factoryModulePath, + }; + + return replaceFunctions.reduce((ops, fn) => + [ + ...ops, + ...fn(entryModuleIdentifiers, sourceFile, bootstrapReplaceOptions), + ], []); + }; + + return makeTransform(standardTransform, getTypeChecker); +} + +// Figure out if it's a `platformDynamic().bootstrapModule(AppModule)` call. +export function replacePlatformBootstrap( + identifiers: ts.Identifier[], + sourceFile: ts.SourceFile, + bootstrapOptions: BootstrapReplaceOptions, +): TransformOperation[] { + + const ops: TransformOperation[] = []; + + identifiers.forEach(identifier => { + + if (!( + identifier.parent + && identifier.parent.kind === ts.SyntaxKind.CallExpression + )) { + return; + } + + const callExpr = identifier.parent as ts.CallExpression; + + if (callExpr.expression.kind !== ts.SyntaxKind.PropertyAccessExpression) { + return; + } + + 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 === bootstrapOptions.dynamicPlatformName + )) { + return; + } + + const dynamicPlatformIdentifier = innerCallExpr.expression as ts.Identifier; + + const idPlatformStatic = ts.createUniqueName('__NgCli_bootstrap_'); + const idNgFactory = ts.createUniqueName('__NgCli_bootstrap_'); + + // Add the transform operations. + ops.push( + // Replace the entry module import. + ...insertStarImport(sourceFile, idNgFactory, bootstrapOptions.factoryModulePath), + new ReplaceNodeOperation(sourceFile, identifier, + ts.createPropertyAccess( + idNgFactory, + ts.createIdentifier(bootstrapOptions.factoryClassName))), + // Replace the platformDynamic import. + ...insertStarImport(sourceFile, idPlatformStatic, bootstrapOptions.staticPlatformPath), + new ReplaceNodeOperation(sourceFile, dynamicPlatformIdentifier, + ts.createPropertyAccess(idPlatformStatic, bootstrapOptions.staticPlatformName)), + new ReplaceNodeOperation(sourceFile, bootstrapModuleIdentifier, + ts.createIdentifier('bootstrapModuleFactory')), + ); + }); + + return ops; +} diff --git a/packages/ngtools/webpack/src/transformers/replace_browser_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_browser_bootstrap.ts new file mode 100644 index 0000000000..4b0135a707 --- /dev/null +++ b/packages/ngtools/webpack/src/transformers/replace_browser_bootstrap.ts @@ -0,0 +1,39 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as ts from 'typescript'; +import { + PlatformBootstrapOptions, + replaceBootstrap, + replacePlatformBootstrap, +} from './replace_bootstrap_helpers'; + +export function replaceBrowserBootstrap( + shouldTransform: (fileName: string) => boolean, + getEntryModule: () => { path: string, className: string } | null, + getTypeChecker: () => ts.TypeChecker, +): ts.TransformerFactory { + + const platformOptions: PlatformBootstrapOptions = { + dynamicPlatformName: 'platformBrowserDynamic', + staticPlatformName: 'platformBrowser', + staticPlatformPath: '@angular/platform-browser', + }; + + const replaceFunctions = [ + replacePlatformBootstrap, + ]; + + return replaceBootstrap( + shouldTransform, + getEntryModule, + getTypeChecker, + replaceFunctions, + platformOptions, + ); +} diff --git a/packages/ngtools/webpack/src/transformers/replace_bootstrap_spec.ts b/packages/ngtools/webpack/src/transformers/replace_browser_bootstrap_spec.ts similarity index 93% rename from packages/ngtools/webpack/src/transformers/replace_bootstrap_spec.ts rename to packages/ngtools/webpack/src/transformers/replace_browser_bootstrap_spec.ts index c56e6d8f74..4683e9892b 100644 --- a/packages/ngtools/webpack/src/transformers/replace_bootstrap_spec.ts +++ b/packages/ngtools/webpack/src/transformers/replace_browser_bootstrap_spec.ts @@ -7,10 +7,10 @@ */ import { tags } from '@angular-devkit/core'; // tslint:disable-line:no-implicit-dependencies import { createTypescriptContext, transformTypescript } from './ast_helpers'; -import { replaceBootstrap } from './replace_bootstrap'; +import { replaceBrowserBootstrap } from './replace_browser_bootstrap'; describe('@ngtools/webpack transformers', () => { - describe('replace_bootstrap', () => { + describe('replace_browser_bootstrap', () => { it('should replace bootstrap', () => { const input = tags.stripIndent` import { enableProdMode } from '@angular/core'; @@ -42,7 +42,7 @@ describe('@ngtools/webpack transformers', () => { // tslint:enable:max-line-length const { program, compilerHost } = createTypescriptContext(input); - const transformer = replaceBootstrap( + const transformer = replaceBrowserBootstrap( () => true, () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), () => program.getTypeChecker(), @@ -83,7 +83,7 @@ describe('@ngtools/webpack transformers', () => { // tslint:enable:max-line-length const { program, compilerHost } = createTypescriptContext(input); - const transformer = replaceBootstrap( + const transformer = replaceBrowserBootstrap( () => true, () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), () => program.getTypeChecker(), @@ -109,7 +109,7 @@ describe('@ngtools/webpack transformers', () => { `; const { program, compilerHost } = createTypescriptContext(input); - const transformer = replaceBootstrap( + const transformer = replaceBrowserBootstrap( () => true, () => null, () => program.getTypeChecker(), diff --git a/packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap.ts new file mode 100644 index 0000000000..caf2178670 --- /dev/null +++ b/packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap.ts @@ -0,0 +1,39 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as ts from 'typescript'; +import { + PlatformBootstrapOptions, + replaceBootstrap, + replacePlatformBootstrap, +} from './replace_bootstrap_helpers'; + +export function replaceNativeScriptBootstrap( + shouldTransform: (fileName: string) => boolean, + getEntryModule: () => { path: string, className: string } | null, + getTypeChecker: () => ts.TypeChecker, +): ts.TransformerFactory { + + const platformOptions: PlatformBootstrapOptions = { + dynamicPlatformName: 'platformNativeScriptDynamic', + staticPlatformName: 'platformNativeScript', + staticPlatformPath: 'nativescript-angular/platform-static', + }; + + const replaceFunctions = [ + replacePlatformBootstrap, + ]; + + return replaceBootstrap( + shouldTransform, + getEntryModule, + getTypeChecker, + replaceFunctions, + platformOptions, + ); +} diff --git a/packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap_spec.ts b/packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap_spec.ts new file mode 100644 index 0000000000..0c67aa3478 --- /dev/null +++ b/packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap_spec.ts @@ -0,0 +1,118 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { tags } from '@angular-devkit/core'; // tslint:disable-line:no-implicit-dependencies +import { createTypescriptContext, transformTypescript } from './ast_helpers'; +import { replaceNativeScriptBootstrap } from './replace_nativescript_bootstrap'; + +describe('@ngtools/webpack transformers', () => { + describe('replace_browser_bootstrap', () => { + it('should replace bootstrap', () => { + const input = tags.stripIndent` + import { platformNativeScriptDynamic } from "nativescript-angular/platform"; + import { AppModule } from "./app.module"; + + platformNativeScriptDynamic().bootstrapModule(AppModule); + `; + + // tslint:disable:max-line-length + const output = tags.stripIndent` + import * as __NgCli_bootstrap_1 from "./app.module.ngfactory"; + import * as __NgCli_bootstrap_2 from "nativescript-angular/platform-static"; + + __NgCli_bootstrap_2.platformNativeScript().bootstrapModuleFactory(__NgCli_bootstrap_1.AppModuleNgFactory); + `; + // tslint:enable:max-line-length + + const { program, compilerHost } = createTypescriptContext(input); + const transformer = replaceNativeScriptBootstrap( + () => true, + () => ({ path: '/project/src/app.module', className: 'AppModule' }), + () => program.getTypeChecker(), + ); + const result = transformTypescript(undefined, [transformer], program, compilerHost); + + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); + }); + + it('should replace bootstrap when barrel files are used', () => { + const input = tags.stripIndent` + import { platformNativeScriptDynamic } from "nativescript-angular/platform"; + import { AppModule } from './app'; + + platformNativeScriptDynamic().bootstrapModule(AppModule); + `; + + // tslint:disable:max-line-length + const output = tags.stripIndent` + import * as __NgCli_bootstrap_1 from "./app/app.module.ngfactory"; + import * as __NgCli_bootstrap_2 from "nativescript-angular/platform-static"; + + __NgCli_bootstrap_2.platformNativeScript().bootstrapModuleFactory(__NgCli_bootstrap_1.AppModuleNgFactory); + `; + // tslint:enable:max-line-length + + const { program, compilerHost } = createTypescriptContext(input); + const transformer = replaceNativeScriptBootstrap( + () => true, + () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => program.getTypeChecker(), + ); + const result = transformTypescript(undefined, [transformer], program, compilerHost); + + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); + }); + + it('should replace bootstrap when config object is passed as argument', () => { + const input = tags.stripIndent` + import { platformNativeScriptDynamic } from "nativescript-angular/platform"; + import { AppModule } from './app'; + + platformNativeScriptDynamic({ createFrameOnBootstrap: true }).bootstrapModule(AppModule); + `; + + // tslint:disable:max-line-length + const output = tags.stripIndent` + import * as __NgCli_bootstrap_1 from "./app/app.module.ngfactory"; + import * as __NgCli_bootstrap_2 from "nativescript-angular/platform-static"; + + __NgCli_bootstrap_2.platformNativeScript({ createFrameOnBootstrap: true }).bootstrapModuleFactory(__NgCli_bootstrap_1.AppModuleNgFactory); + `; + // tslint:enable:max-line-length + + const { program, compilerHost } = createTypescriptContext(input); + const transformer = replaceNativeScriptBootstrap( + () => true, + () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => program.getTypeChecker(), + ); + const result = transformTypescript(undefined, [transformer], program, compilerHost); + + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); + }); + + it('should not replace bootstrap when there is no entry module', () => { + const input = tags.stripIndent` + import { platformNativeScriptDynamic } from 'nativescript-angular/platform'; + + import { AppModule } from './app.module'; + + platformNativeScriptDynamic().bootstrapModule(AppModule); + `; + + const { program, compilerHost } = createTypescriptContext(input); + const transformer = replaceNativeScriptBootstrap( + () => true, + () => null, + () => program.getTypeChecker(), + ); + const result = transformTypescript(undefined, [transformer], program, compilerHost); + + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${input}`); + }); + }); +}); diff --git a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts index 483080bf4a..7bb4026360 100644 --- a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts +++ b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts @@ -5,12 +5,15 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { dirname, relative } from 'path'; import * as ts from 'typescript'; -import { collectDeepNodes } from './ast_helpers'; import { insertStarImport } from './insert_import'; -import { ReplaceNodeOperation, StandardTransform, TransformOperation } from './interfaces'; -import { makeTransform } from './make_transform'; +import { ReplaceNodeOperation, TransformOperation } from './interfaces'; +import { + BootstrapReplaceOptions, + PlatformBootstrapOptions, + replaceBootstrap, + replacePlatformBootstrap, +} from './replace_bootstrap_helpers'; export function replaceServerBootstrap( shouldTransform: (fileName: string) => boolean, @@ -18,123 +21,102 @@ export function replaceServerBootstrap( getTypeChecker: () => ts.TypeChecker, ): ts.TransformerFactory { - const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { - const ops: TransformOperation[] = []; + const platformOptions: PlatformBootstrapOptions = { + dynamicPlatformName: 'platformDynamicServer', + staticPlatformName: 'platformServer', + staticPlatformPath: '@angular/platform-server', + }; - const entryModule = getEntryModule(); + const replaceFunctions = [ + replacePlatformBootstrap, + replaceRenderModule, + replaceEntryModuleInObject, + ]; + + return replaceBootstrap( + shouldTransform, + getEntryModule, + getTypeChecker, + replaceFunctions, + platformOptions, + ); +} - if (!shouldTransform(sourceFile.fileName) || !entryModule) { - return ops; +// Figure out if it is renderModule +function replaceRenderModule( + identifiers: ts.Identifier[], + sourceFile: ts.SourceFile, + bootstrapOptions: BootstrapReplaceOptions, +): TransformOperation[] { + const ops: TransformOperation[] = []; + + identifiers.forEach(identifier => { + if (!identifier.parent) { + return; } - // Find all identifiers. - const entryModuleIdentifiers = collectDeepNodes(sourceFile, - ts.SyntaxKind.Identifier) - .filter(identifier => identifier.text === entryModule.className); + if (identifier.parent.kind !== ts.SyntaxKind.CallExpression && + identifier.parent.kind !== ts.SyntaxKind.PropertyAssignment) { + return; + } - if (entryModuleIdentifiers.length === 0) { - return []; + if (identifier.parent.kind !== ts.SyntaxKind.CallExpression) { + 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; - }; + const callExpr = identifier.parent as ts.CallExpression; + if (callExpr.expression.kind !== ts.SyntaxKind.Identifier) { + return; + } + + const identifierExpr = callExpr.expression as ts.Identifier; - return makeTransform(standardTransform, getTypeChecker); + 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, bootstrapOptions.factoryModulePath), + new ReplaceNodeOperation(sourceFile, identifier, + ts.createPropertyAccess( + idNgFactory, + ts.createIdentifier(bootstrapOptions.factoryClassName))), + // Replace the renderModule import. + ...insertStarImport(sourceFile, idPlatformServer, bootstrapOptions.staticPlatformPath), + new ReplaceNodeOperation(sourceFile, renderModuleIdentifier, + ts.createPropertyAccess(idPlatformServer, 'renderModuleFactory')), + ); + }); + + return ops; +} + +// This is for things that accept a module as a property in a config object +// .ie the express engine +function replaceEntryModuleInObject( + identifiers: ts.Identifier[], + sourceFile: ts.SourceFile, + bootstrapOptions: BootstrapReplaceOptions, +): TransformOperation[] { + return identifiers + .filter(({ parent }) => parent && parent.kind === ts.SyntaxKind.PropertyAssignment) + .reduce((ops: TransformOperation[], identifier) => { + const idNgFactory = ts.createUniqueName('__NgCli_bootstrap_'); + + ops.push( + ...insertStarImport(sourceFile, idNgFactory, bootstrapOptions.factoryModulePath), + new ReplaceNodeOperation(sourceFile, identifier, + ts.createPropertyAccess( + idNgFactory, + ts.createIdentifier(bootstrapOptions.factoryClassName))), + ); + + return ops; + }, []); }