From 5146cf4b5f28b95a64aba2fb0aeb9c58713752e3 Mon Sep 17 00:00:00 2001 From: artonge Date: Wed, 9 May 2018 17:15:01 +0200 Subject: [PATCH] feat(@ngtools/webpack): add support for multiple entry modules When the webpack config is building two different apps from two entry points, one of the apps will not work because metadatas are not generated. The commit add a new property (entryModules) in AngularCompilerPluginOptions that permit to regsiter multiple entry modules --- .../webpack/src/angular_compiler_plugin.ts | 55 +++++++++++-------- .../src/transformers/export_ngfactory.ts | 17 +++++- .../src/transformers/export_ngfactory_spec.ts | 6 +- .../multiple_transformers_spec.ts | 8 +-- .../src/transformers/register_locale_data.ts | 17 +++++- .../transformers/register_locale_data_spec.ts | 2 +- .../src/transformers/replace_bootstrap.ts | 20 ++++++- .../transformers/replace_bootstrap_spec.ts | 4 +- .../transformers/replace_server_bootstrap.ts | 18 +++++- .../replace_server_bootstrap_spec.ts | 8 +-- 10 files changed, 107 insertions(+), 48 deletions(-) diff --git a/packages/ngtools/webpack/src/angular_compiler_plugin.ts b/packages/ngtools/webpack/src/angular_compiler_plugin.ts index d39cfebd57..4bb1041cc8 100644 --- a/packages/ngtools/webpack/src/angular_compiler_plugin.ts +++ b/packages/ngtools/webpack/src/angular_compiler_plugin.ts @@ -67,6 +67,7 @@ export interface AngularCompilerPluginOptions { tsConfigPath: string; basePath?: string; entryModule?: string; + entryModules?: string[]; mainPath?: string; skipCodeGeneration?: boolean; hostReplacementPaths?: { [path: string]: string }; @@ -112,6 +113,7 @@ export class AngularCompilerPlugin { private _lazyRoutes: LazyRouteMap = Object.create(null); private _tsConfigPath: string; private _entryModule: string | null; + private _entryModules: string[] | null; private _mainPath: string | undefined; private _basePath: string; private _transformers: ts.TransformerFactory[] = []; @@ -148,15 +150,18 @@ export class AngularCompilerPlugin { get options() { return this._options; } get done() { return this._donePromise; } - get entryModule() { - if (!this._entryModule) { + get entryModules() { + if (!this._entryModules) { return null; } - const splitted = this._entryModule.split(/(#[a-zA-Z_]([\w]+))$/); - const path = splitted[0]; - const className = !!splitted[1] ? splitted[1].substring(1) : 'default'; - return { path, className }; + return this._entryModules.map((entryModule) => { + const splitted = entryModule.split(/(#[a-zA-Z_]([\w]+))$/); + const path = splitted[0]; + const className = !!splitted[1] ? splitted[1].substring(1) : 'default'; + + return { path, className }; + }); } static isSupported() { @@ -294,13 +299,13 @@ export class AngularCompilerPlugin { this._mainPath = this._compilerHost.resolve(options.mainPath); } - // Use entryModule if available in options, otherwise resolve it from mainPath after program + // Use entryModules if available in options, otherwise resolve it from mainPath after program // creation. - if (this._options.entryModule) { - this._entryModule = this._options.entryModule; + if (this._options.entryModules) { + this._entryModules = this._options.entryModules || [this._options.entryModule]; } else if (this._compilerOptions.entryModule) { - this._entryModule = path.resolve(this._basePath, - this._compilerOptions.entryModule as string); // temporary cast for type issue + this._entryModules = [path.resolve(this._basePath, + this._compilerOptions.entryModule as string)]; // temporary cast for type issue } // Set platform. @@ -391,11 +396,12 @@ export class AngularCompilerPlugin { } }) .then(() => { - // If there's still no entryModule try to resolve from mainPath. - if (!this._entryModule && this._mainPath) { + // If there's still no entryModules try to resolve from mainPath. + if (!this._entryModules && this._mainPath) { time('AngularCompilerPlugin._make.resolveEntryModuleFromMain'); - this._entryModule = resolveEntryModuleFromMain( + const entryModule = resolveEntryModuleFromMain( this._mainPath, this._compilerHost, this._getTsProgram()); + this._entryModules = entryModule !== null ? [entryModule] : entryModule; timeEnd('AngularCompilerPlugin._make.resolveEntryModuleFromMain'); } }); @@ -413,7 +419,7 @@ export class AngularCompilerPlugin { }), // TODO: fix compiler-cli typings; entryModule should not be string, but also optional. // tslint:disable-next-line:non-null-operator - entryModule: this._entryModule !, + entryModules: this._entryModules !, }); timeEnd('AngularCompilerPlugin._getLazyRoutesFromNgtools'); @@ -742,9 +748,12 @@ export class AngularCompilerPlugin { const isMainPath = (fileName: string) => fileName === ( this._mainPath ? workaroundResolve(this._mainPath) : this._mainPath ); - const getEntryModule = () => this.entryModule - ? { path: workaroundResolve(this.entryModule.path), className: this.entryModule.className } - : this.entryModule; + // TODO: fix fn usage + const getEntryModules = () => this.entryModules + ? this.entryModules.map((entryModule) => { + return { path: workaroundResolve(entryModule.path), className: entryModule.className }; + }) + : this.entryModules; const getLazyRoutes = () => this._lazyRoutes; const getTypeChecker = () => this._getTsProgram().getTypeChecker(); @@ -761,20 +770,20 @@ export class AngularCompilerPlugin { // This transform must go before replaceBootstrap because it looks for the entry module // import, which will be replaced. if (this._normalizedLocale) { - this._transformers.push(registerLocaleData(isAppPath, getEntryModule, + this._transformers.push(registerLocaleData(isAppPath, getEntryModules, this._normalizedLocale)); } if (!this._JitMode) { // Replace bootstrap in browser AOT. - this._transformers.push(replaceBootstrap(isAppPath, getEntryModule, getTypeChecker)); + this._transformers.push(replaceBootstrap(isAppPath, getEntryModules, getTypeChecker)); } } else if (this._platform === PLATFORM.Server) { this._transformers.push(exportLazyModuleMap(isMainPath, getLazyRoutes)); if (!this._JitMode) { this._transformers.push( - exportNgFactory(isMainPath, getEntryModule), - replaceServerBootstrap(isMainPath, getEntryModule, getTypeChecker)); + exportNgFactory(isMainPath, getEntryModules), + replaceServerBootstrap(isMainPath, getEntryModules, getTypeChecker)); } } } @@ -794,7 +803,7 @@ export class AngularCompilerPlugin { // Make a new program and load the Angular structure. .then(() => this._createOrUpdateProgram()) .then(() => { - if (this.entryModule) { + if (this.entryModules) { // Try to find lazy routes if we have an entry module. // We need to run the `listLazyRoutes` the first time because it also navigates libraries // and other things that we might miss using the (faster) findLazyRoutesInAst. diff --git a/packages/ngtools/webpack/src/transformers/export_ngfactory.ts b/packages/ngtools/webpack/src/transformers/export_ngfactory.ts index 04a8afd1bc..c09e086993 100644 --- a/packages/ngtools/webpack/src/transformers/export_ngfactory.ts +++ b/packages/ngtools/webpack/src/transformers/export_ngfactory.ts @@ -13,18 +13,29 @@ import { makeTransform } from './make_transform'; export function exportNgFactory( shouldTransform: (fileName: string) => boolean, - getEntryModule: () => { path: string, className: string } | null, + getEntryModules: () => { path: string, className: string }[] | null, ): ts.TransformerFactory { const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { + const ops: TransformOperation[] = []; - const entryModule = getEntryModule(); + const entryModules = getEntryModules(); - if (!shouldTransform(sourceFile.fileName) || !entryModule) { + if (!shouldTransform(sourceFile.fileName) || !entryModules) { return ops; } + return entryModules.reduce((ops, entryModule) => { + return ops.concat(standardTransformHelper(sourceFile, entryModule)); + }, ops); + }; + + const standardTransformHelper = function ( + sourceFile: ts.SourceFile, + entryModule: { path: string, className: string }) { + const ops: TransformOperation[] = []; + // Find all identifiers using the entry module class name. const entryModuleIdentifiers = collectDeepNodes(sourceFile, ts.SyntaxKind.Identifier) diff --git a/packages/ngtools/webpack/src/transformers/export_ngfactory_spec.ts b/packages/ngtools/webpack/src/transformers/export_ngfactory_spec.ts index 52dc70c098..9217b85828 100644 --- a/packages/ngtools/webpack/src/transformers/export_ngfactory_spec.ts +++ b/packages/ngtools/webpack/src/transformers/export_ngfactory_spec.ts @@ -22,7 +22,7 @@ describe('@ngtools/webpack transformers', () => { const transformer = exportNgFactory( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), ); const result = transformTypescript(input, [transformer]); @@ -40,7 +40,7 @@ describe('@ngtools/webpack transformers', () => { const transformer = exportNgFactory( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), ); const result = transformTypescript(input, [transformer]); @@ -54,7 +54,7 @@ describe('@ngtools/webpack transformers', () => { const transformer = exportNgFactory( () => false, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), ); const result = transformTypescript(input, [transformer]); diff --git a/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts b/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts index 0726efb20d..32e26fddd0 100644 --- a/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts +++ b/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts @@ -67,14 +67,14 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const shouldTransform = () => true; - const getEntryModule = () => - ({ path: '/project/src/app/app.module', className: 'AppModule' }); + const getEntryModules = () => + ([{ path: '/project/src/app/app.module', className: 'AppModule' }]); const getTypeChecker = () => program.getTypeChecker(); const transformers = [ - replaceBootstrap(shouldTransform, getEntryModule, getTypeChecker), - exportNgFactory(shouldTransform, getEntryModule), + replaceBootstrap(shouldTransform, getEntryModules, getTypeChecker), + exportNgFactory(shouldTransform, getEntryModules), exportLazyModuleMap(shouldTransform, () => ({ './lazy/lazy.module.ngfactory#LazyModuleNgFactory': diff --git a/packages/ngtools/webpack/src/transformers/register_locale_data.ts b/packages/ngtools/webpack/src/transformers/register_locale_data.ts index fd2aa20566..7a5bec2774 100644 --- a/packages/ngtools/webpack/src/transformers/register_locale_data.ts +++ b/packages/ngtools/webpack/src/transformers/register_locale_data.ts @@ -14,19 +14,30 @@ import { makeTransform } from './make_transform'; export function registerLocaleData( shouldTransform: (fileName: string) => boolean, - getEntryModule: () => { path: string, className: string } | null, + getEntryModules: () => { path: string, className: string }[] | null, locale: string, ): ts.TransformerFactory { const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { + const ops: TransformOperation[] = []; - const entryModule = getEntryModule(); + const entryModules = getEntryModules(); - if (!shouldTransform(sourceFile.fileName) || !entryModule || !locale) { + if (!shouldTransform(sourceFile.fileName) || !entryModules || !locale) { return ops; } + return entryModules.reduce((ops, entryModule) => { + return ops.concat(standardTransformHelper(sourceFile, entryModule)); + }, ops); + }; + + const standardTransformHelper = function ( + sourceFile: ts.SourceFile, + entryModule: { path: string, className: string }) { + const ops: TransformOperation[] = []; + // Find all identifiers using the entry module class name. const entryModuleIdentifiers = collectDeepNodes(sourceFile, ts.SyntaxKind.Identifier) diff --git a/packages/ngtools/webpack/src/transformers/register_locale_data_spec.ts b/packages/ngtools/webpack/src/transformers/register_locale_data_spec.ts index 1310bfce3e..717dcfdb04 100644 --- a/packages/ngtools/webpack/src/transformers/register_locale_data_spec.ts +++ b/packages/ngtools/webpack/src/transformers/register_locale_data_spec.ts @@ -45,7 +45,7 @@ describe('@ngtools/webpack transformers', () => { const transformer = registerLocaleData( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), 'fr', ); const result = transformTypescript(input, [transformer]); diff --git a/packages/ngtools/webpack/src/transformers/replace_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_bootstrap.ts index fc05f86b92..701d822bc3 100644 --- a/packages/ngtools/webpack/src/transformers/replace_bootstrap.ts +++ b/packages/ngtools/webpack/src/transformers/replace_bootstrap.ts @@ -15,14 +15,30 @@ import { makeTransform } from './make_transform'; export function replaceBootstrap( shouldTransform: (fileName: string) => boolean, - getEntryModule: () => { path: string, className: string } | null, + getEntryModules: () => { path: string, className: string }[] | null, getTypeChecker: () => ts.TypeChecker, ): ts.TransformerFactory { + const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { + const ops: TransformOperation[] = []; - const entryModule = getEntryModule(); + const entryModules = getEntryModules(); + + if (!shouldTransform(sourceFile.fileName) || !entryModules) { + return ops; + } + + return entryModules.reduce((ops, entryModule) => { + return ops.concat(standardTransformHelper(sourceFile, entryModule)); + }, ops); + }; + + const standardTransformHelper = function ( + sourceFile: ts.SourceFile, + entryModule: { path: string, className: string }) { + const ops: TransformOperation[] = []; if (!shouldTransform(sourceFile.fileName) || !entryModule) { return ops; diff --git a/packages/ngtools/webpack/src/transformers/replace_bootstrap_spec.ts b/packages/ngtools/webpack/src/transformers/replace_bootstrap_spec.ts index c56e6d8f74..1683665917 100644 --- a/packages/ngtools/webpack/src/transformers/replace_bootstrap_spec.ts +++ b/packages/ngtools/webpack/src/transformers/replace_bootstrap_spec.ts @@ -44,7 +44,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost); @@ -85,7 +85,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost); diff --git a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts index 483080bf4a..41c7c06a60 100644 --- a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts +++ b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts @@ -14,19 +14,31 @@ import { makeTransform } from './make_transform'; export function replaceServerBootstrap( shouldTransform: (fileName: string) => boolean, - getEntryModule: () => { path: string, className: string } | null, + getEntryModules: () => { path: string, className: string }[] | null, getTypeChecker: () => ts.TypeChecker, ): ts.TransformerFactory { const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { + const ops: TransformOperation[] = []; - const entryModule = getEntryModule(); + const entryModules = getEntryModules(); - if (!shouldTransform(sourceFile.fileName) || !entryModule) { + if (!shouldTransform(sourceFile.fileName) || !entryModules) { return ops; } + return entryModules.reduce((ops, entryModule) => { + return ops.concat(standardTransformHelper(sourceFile, entryModule)); + }, ops); + }; + + const standardTransformHelper = function ( + sourceFile: ts.SourceFile, + entryModule: { path: string, className: string }) { + + const ops: TransformOperation[] = []; + // Find all identifiers. const entryModuleIdentifiers = collectDeepNodes(sourceFile, ts.SyntaxKind.Identifier) diff --git a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap_spec.ts b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap_spec.ts index 1d7ebcd0b8..6ed17244e9 100644 --- a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap_spec.ts +++ b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap_spec.ts @@ -44,7 +44,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceServerBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost); @@ -91,7 +91,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceServerBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost); @@ -144,7 +144,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceServerBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost); @@ -185,7 +185,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceServerBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost);