From 71650e7e500ba61b4959391cac8e207705f7a685 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Wed, 23 May 2018 10:01:48 +0300 Subject: [PATCH 1/4] refactor(@ngtools/webpack): extract common logic from replace_bootstrap transformers --- .../webpack/src/angular_compiler_plugin.ts | 4 +- .../ngtools/webpack/src/transformers/index.ts | 2 +- .../multiple_transformers_spec.ts | 4 +- .../src/transformers/replace_bootstrap.ts | 102 --------- .../transformers/replace_bootstrap_helpers.ts | 129 +++++++++++ .../transformers/replace_browser_bootstrap.ts | 39 ++++ ...c.ts => replace_browser_bootstrap_spec.ts} | 10 +- .../transformers/replace_server_bootstrap.ts | 202 ++++++++---------- 8 files changed, 269 insertions(+), 223 deletions(-) delete mode 100644 packages/ngtools/webpack/src/transformers/replace_bootstrap.ts create mode 100644 packages/ngtools/webpack/src/transformers/replace_bootstrap_helpers.ts create mode 100644 packages/ngtools/webpack/src/transformers/replace_browser_bootstrap.ts rename packages/ngtools/webpack/src/transformers/{replace_bootstrap_spec.ts => replace_browser_bootstrap_spec.ts} (93%) diff --git a/packages/ngtools/webpack/src/angular_compiler_plugin.ts b/packages/ngtools/webpack/src/angular_compiler_plugin.ts index 3b42bd1605..b65583d20d 100644 --- a/packages/ngtools/webpack/src/angular_compiler_plugin.ts +++ b/packages/ngtools/webpack/src/angular_compiler_plugin.ts @@ -43,7 +43,7 @@ import { findResources, registerLocaleData, removeDecorators, - replaceBootstrap, + replaceBrowserBootstrap, replaceResources, replaceServerBootstrap, } from './transformers'; @@ -768,7 +768,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)); diff --git a/packages/ngtools/webpack/src/transformers/index.ts b/packages/ngtools/webpack/src/transformers/index.ts index bb3d03dc92..2a6de4500c 100644 --- a/packages/ngtools/webpack/src/transformers/index.ts +++ b/packages/ngtools/webpack/src/transformers/index.ts @@ -10,7 +10,7 @@ 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 './export_ngfactory'; export * from './export_lazy_module_map'; 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..d482af689b --- /dev/null +++ b/packages/ngtools/webpack/src/transformers/replace_bootstrap_helpers.ts @@ -0,0 +1,129 @@ +/** + * @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 function replaceBootstrap( + shouldTransform: (fileName: string) => boolean, + getEntryModule: () => { path: string, className: string } | null, + getTypeChecker: () => ts.TypeChecker, + replaceFunctions: ((identifiers: ts.Identifier[], sourceFile: ts.SourceFile, platformOptions: PlatformBootstrapOptions) => TransformOperation[])[], + 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..e2ad6a00f7 --- /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, + replacePlatformBootstrap, + replaceBootstrap +} 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_server_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts index 483080bf4a..bab1261837 100644 --- a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts +++ b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts @@ -11,6 +11,11 @@ import { collectDeepNodes } from './ast_helpers'; import { insertStarImport } from './insert_import'; import { ReplaceNodeOperation, StandardTransform, TransformOperation } from './interfaces'; import { makeTransform } from './make_transform'; +import { + PlatformBootstrapOptions, + replaceBootstrap, + replacePlatformBootstrap +} from './replace_bootstrap_helpers'; export function replaceServerBootstrap( shouldTransform: (fileName: string) => boolean, @@ -18,123 +23,98 @@ 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: PlatformBootstrapOptions +): TransformOperation[] { + const ops: TransformOperation[] = []; + + identifiers.forEach(identifier => { + if (!identifier.parent) { + return; + } + + if (identifier.parent.kind !== ts.SyntaxKind.CallExpression && + identifier.parent.kind !== ts.SyntaxKind.PropertyAssignment) { + return; } - // Find all identifiers. - const entryModuleIdentifiers = collectDeepNodes(sourceFile, - ts.SyntaxKind.Identifier) - .filter(identifier => identifier.text === entryModule.className); + if (identifier.parent.kind !== ts.SyntaxKind.CallExpression) { + return; + } - if (entryModuleIdentifiers.length === 0) { - return []; + const callExpr = identifier.parent as ts.CallExpression; + if (callExpr.expression.kind !== ts.SyntaxKind.Identifier) { + 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 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, 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 makeTransform(standardTransform, getTypeChecker); + 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: PlatformBootstrapOptions +): 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; + }, []); } From 967a593560c9fa6a25b388399da260ea7d40d3a8 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Wed, 23 May 2018 10:26:46 +0300 Subject: [PATCH 2/4] feat(@ngtools/webpack): add transformer to replace bootstrap for NativeScript apps --- .../ngtools/webpack/src/transformers/index.ts | 1 + .../replace_nativescript_bootstrap.ts | 39 ++++++ .../replace_nativescript_bootstrap_spec.ts | 118 ++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap.ts create mode 100644 packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap_spec.ts diff --git a/packages/ngtools/webpack/src/transformers/index.ts b/packages/ngtools/webpack/src/transformers/index.ts index 2a6de4500c..d1fcf13e5d 100644 --- a/packages/ngtools/webpack/src/transformers/index.ts +++ b/packages/ngtools/webpack/src/transformers/index.ts @@ -12,6 +12,7 @@ export * from './insert_import'; export * from './elide_imports'; 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/replace_nativescript_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap.ts new file mode 100644 index 0000000000..f16cc9e3ac --- /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, + replacePlatformBootstrap, + replaceBootstrap +} 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}`); + }); + }); +}); From c932b2d6ca27d432ee9083a3734c405c6eae62c4 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Wed, 23 May 2018 11:53:16 +0300 Subject: [PATCH 3/4] feat(@ngtools/webpack): replace bootstrap code for NativeScript apps --- packages/ngtools/webpack/src/angular_compiler_plugin.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/ngtools/webpack/src/angular_compiler_plugin.ts b/packages/ngtools/webpack/src/angular_compiler_plugin.ts index b65583d20d..cf91cad442 100644 --- a/packages/ngtools/webpack/src/angular_compiler_plugin.ts +++ b/packages/ngtools/webpack/src/angular_compiler_plugin.ts @@ -44,6 +44,7 @@ import { registerLocaleData, removeDecorators, replaceBrowserBootstrap, + replaceNativeScriptBootstrap, replaceResources, replaceServerBootstrap, } from './transformers'; @@ -95,6 +96,7 @@ export interface AngularCompilerPluginOptions { export enum PLATFORM { Browser, Server, + NativeScript, } export class AngularCompilerPlugin { @@ -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)); + } } } From 59f90fd2aaafa24d06d946459b07d4c2009373b6 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Wed, 23 May 2018 11:53:46 +0300 Subject: [PATCH 4/4] refactor(@ngtools/webpack): fix lint errors --- .../transformers/replace_bootstrap_helpers.ts | 20 +++++++++++----- .../transformers/replace_browser_bootstrap.ts | 8 +++---- .../replace_nativescript_bootstrap.ts | 8 +++---- .../transformers/replace_server_bootstrap.ts | 24 ++++++++++--------- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/packages/ngtools/webpack/src/transformers/replace_bootstrap_helpers.ts b/packages/ngtools/webpack/src/transformers/replace_bootstrap_helpers.ts index d482af689b..741e84bf39 100644 --- a/packages/ngtools/webpack/src/transformers/replace_bootstrap_helpers.ts +++ b/packages/ngtools/webpack/src/transformers/replace_bootstrap_helpers.ts @@ -23,12 +23,18 @@ export interface BootstrapReplaceOptions extends PlatformBootstrapOptions { 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: ((identifiers: ts.Identifier[], sourceFile: ts.SourceFile, platformOptions: PlatformBootstrapOptions) => TransformOperation[])[], - platformOptions: PlatformBootstrapOptions + replaceFunctions: BootstrapReplaceFunction[], + platformOptions: PlatformBootstrapOptions, ): ts.TransformerFactory { const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { @@ -54,10 +60,10 @@ export function replaceBootstrap( factoryModulePath, }; - return replaceFunctions.reduce((ops, fn) => + return replaceFunctions.reduce((ops, fn) => [ ...ops, - ...fn(entryModuleIdentifiers, sourceFile, bootstrapReplaceOptions) + ...fn(entryModuleIdentifiers, sourceFile, bootstrapReplaceOptions), ], []); }; @@ -68,7 +74,7 @@ export function replaceBootstrap( export function replacePlatformBootstrap( identifiers: ts.Identifier[], sourceFile: ts.SourceFile, - bootstrapOptions: BootstrapReplaceOptions + bootstrapOptions: BootstrapReplaceOptions, ): TransformOperation[] { const ops: TransformOperation[] = []; @@ -115,7 +121,9 @@ export function replacePlatformBootstrap( // Replace the entry module import. ...insertStarImport(sourceFile, idNgFactory, bootstrapOptions.factoryModulePath), new ReplaceNodeOperation(sourceFile, identifier, - ts.createPropertyAccess(idNgFactory, ts.createIdentifier(bootstrapOptions.factoryClassName))), + ts.createPropertyAccess( + idNgFactory, + ts.createIdentifier(bootstrapOptions.factoryClassName))), // Replace the platformDynamic import. ...insertStarImport(sourceFile, idPlatformStatic, bootstrapOptions.staticPlatformPath), new ReplaceNodeOperation(sourceFile, dynamicPlatformIdentifier, diff --git a/packages/ngtools/webpack/src/transformers/replace_browser_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_browser_bootstrap.ts index e2ad6a00f7..4b0135a707 100644 --- a/packages/ngtools/webpack/src/transformers/replace_browser_bootstrap.ts +++ b/packages/ngtools/webpack/src/transformers/replace_browser_bootstrap.ts @@ -9,8 +9,8 @@ import * as ts from 'typescript'; import { PlatformBootstrapOptions, + replaceBootstrap, replacePlatformBootstrap, - replaceBootstrap } from './replace_bootstrap_helpers'; export function replaceBrowserBootstrap( @@ -22,11 +22,11 @@ export function replaceBrowserBootstrap( const platformOptions: PlatformBootstrapOptions = { dynamicPlatformName: 'platformBrowserDynamic', staticPlatformName: 'platformBrowser', - staticPlatformPath: '@angular/platform-browser' + staticPlatformPath: '@angular/platform-browser', }; const replaceFunctions = [ - replacePlatformBootstrap + replacePlatformBootstrap, ]; return replaceBootstrap( @@ -34,6 +34,6 @@ export function replaceBrowserBootstrap( getEntryModule, getTypeChecker, replaceFunctions, - platformOptions + platformOptions, ); } diff --git a/packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap.ts index f16cc9e3ac..caf2178670 100644 --- a/packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap.ts +++ b/packages/ngtools/webpack/src/transformers/replace_nativescript_bootstrap.ts @@ -9,8 +9,8 @@ import * as ts from 'typescript'; import { PlatformBootstrapOptions, + replaceBootstrap, replacePlatformBootstrap, - replaceBootstrap } from './replace_bootstrap_helpers'; export function replaceNativeScriptBootstrap( @@ -22,11 +22,11 @@ export function replaceNativeScriptBootstrap( const platformOptions: PlatformBootstrapOptions = { dynamicPlatformName: 'platformNativeScriptDynamic', staticPlatformName: 'platformNativeScript', - staticPlatformPath: 'nativescript-angular/platform-static' + staticPlatformPath: 'nativescript-angular/platform-static', }; const replaceFunctions = [ - replacePlatformBootstrap + replacePlatformBootstrap, ]; return replaceBootstrap( @@ -34,6 +34,6 @@ export function replaceNativeScriptBootstrap( getEntryModule, getTypeChecker, replaceFunctions, - platformOptions + platformOptions, ); } diff --git a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts index bab1261837..7bb4026360 100644 --- a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts +++ b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts @@ -5,16 +5,14 @@ * 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 + replacePlatformBootstrap, } from './replace_bootstrap_helpers'; export function replaceServerBootstrap( @@ -32,7 +30,7 @@ export function replaceServerBootstrap( const replaceFunctions = [ replacePlatformBootstrap, replaceRenderModule, - replaceEntryModuleInObject + replaceEntryModuleInObject, ]; return replaceBootstrap( @@ -40,7 +38,7 @@ export function replaceServerBootstrap( getEntryModule, getTypeChecker, replaceFunctions, - platformOptions + platformOptions, ); } @@ -48,7 +46,7 @@ export function replaceServerBootstrap( function replaceRenderModule( identifiers: ts.Identifier[], sourceFile: ts.SourceFile, - bootstrapOptions: PlatformBootstrapOptions + bootstrapOptions: BootstrapReplaceOptions, ): TransformOperation[] { const ops: TransformOperation[] = []; @@ -86,7 +84,9 @@ function replaceRenderModule( // Replace the entry module import. ...insertStarImport(sourceFile, idNgFactory, bootstrapOptions.factoryModulePath), new ReplaceNodeOperation(sourceFile, identifier, - ts.createPropertyAccess(idNgFactory, ts.createIdentifier(bootstrapOptions.factoryClassName))), + ts.createPropertyAccess( + idNgFactory, + ts.createIdentifier(bootstrapOptions.factoryClassName))), // Replace the renderModule import. ...insertStarImport(sourceFile, idPlatformServer, bootstrapOptions.staticPlatformPath), new ReplaceNodeOperation(sourceFile, renderModuleIdentifier, @@ -102,7 +102,7 @@ function replaceRenderModule( function replaceEntryModuleInObject( identifiers: ts.Identifier[], sourceFile: ts.SourceFile, - bootstrapOptions: PlatformBootstrapOptions + bootstrapOptions: BootstrapReplaceOptions, ): TransformOperation[] { return identifiers .filter(({ parent }) => parent && parent.kind === ts.SyntaxKind.PropertyAssignment) @@ -112,7 +112,9 @@ function replaceEntryModuleInObject( ops.push( ...insertStarImport(sourceFile, idNgFactory, bootstrapOptions.factoryModulePath), new ReplaceNodeOperation(sourceFile, identifier, - ts.createPropertyAccess(idNgFactory, ts.createIdentifier(bootstrapOptions.factoryClassName))), + ts.createPropertyAccess( + idNgFactory, + ts.createIdentifier(bootstrapOptions.factoryClassName))), ); return ops;