-
Notifications
You must be signed in to change notification settings - Fork 12k
Support import()
syntax for loadChildren in ViewEngine
#13948
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
edbace1
8eacb50
ca4cea9
429fd62
6166c8d
c9b3071
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,6 +46,7 @@ import { | |
exportLazyModuleMap, | ||
exportNgFactory, | ||
findResources, | ||
importFactory, | ||
registerLocaleData, | ||
removeDecorators, | ||
replaceBootstrap, | ||
|
@@ -103,6 +104,12 @@ export interface AngularCompilerPluginOptions { | |
nameLazyFiles?: boolean; | ||
logger?: logging.Logger; | ||
directTemplateLoading?: boolean; | ||
// When using the loadChildren string syntax, @ngtools/webpack must query @angular/compiler-cli | ||
// via a private API to know which lazy routes exist. This increases build and rebuild time. | ||
// When using Ivy, the string syntax is not supported at all. Thus we shouldn't attempt that. | ||
// This option is also used for when the compilation doesn't need this sort of processing at all. | ||
discoverLazyRoutes?: boolean; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you kindly explain the reasoning behind this option? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When using the When using Ivy, the string syntax is not supported at all. Thus we shouldn't attempt that. This commit also exists in #13700 for slightly different reasons. There, it is meant to disable this sort of processing for compilations that do not need it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the clarification, it makes more sense now :) As to disable lazy route discovery under ivy we can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we warn (or probably error) in the transformer (or somewhere else) if the string form is found when this option is off? Users may not know why their applications are not working. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think not because that means we need to always check, which incurs a performance penalty on every AOT build. The "right way" of doing that probably a Ivy migration when it's stable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's try to generally answer questions like this in the form of a comment in the code - no one later will find this thread There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
importFactories?: boolean; | ||
|
||
// added to the list of lazy routes | ||
additionalLazyModules?: { [module: string]: string }; | ||
|
@@ -134,6 +141,8 @@ export class AngularCompilerPlugin { | |
private _compilerHost: WebpackCompilerHost & CompilerHost; | ||
private _moduleResolutionCache: ts.ModuleResolutionCache; | ||
private _resourceLoader?: WebpackResourceLoader; | ||
private _discoverLazyRoutes = true; | ||
private _importFactories = false; | ||
// Contains `moduleImportPath#exportName` => `fullModulePath`. | ||
private _lazyRoutes: LazyRouteMap = {}; | ||
private _tsConfigPath: string; | ||
|
@@ -292,6 +301,36 @@ export class AngularCompilerPlugin { | |
this._platformTransformers = options.platformTransformers; | ||
} | ||
|
||
// Determine if lazy route discovery via Compiler CLI private API should be attempted. | ||
if (this._compilerOptions.enableIvy) { | ||
// Never try to discover lazy routes with Ivy. | ||
this._discoverLazyRoutes = false; | ||
} else if (options.discoverLazyRoutes !== undefined) { | ||
// The default is to discover routes, but it can be overriden. | ||
this._discoverLazyRoutes = options.discoverLazyRoutes; | ||
} | ||
|
||
if (this._discoverLazyRoutes === false && this.options.additionalLazyModuleResources | ||
&& this.options.additionalLazyModuleResources.length > 0) { | ||
this._warnings.push( | ||
new Error(`Lazy route discovery is disabled but additional Lazy Module Resources were` | ||
+ ` provided. These will be ignored.`), | ||
); | ||
} | ||
|
||
if (this._discoverLazyRoutes === false && this.options.additionalLazyModules | ||
&& Object.keys(this.options.additionalLazyModules).length > 0) { | ||
this._warnings.push( | ||
new Error(`Lazy route discovery is disabled but additional lazy modules were provided.` | ||
+ `These will be ignored.`), | ||
); | ||
} | ||
|
||
if (!this._compilerOptions.enableIvy && options.importFactories === true) { | ||
// Only transform imports to use factories with View Engine. | ||
this._importFactories = true; | ||
} | ||
|
||
// Default ContextElementDependency to the one we can import from here. | ||
// Failing to use the right ContextElementDependency will throw the error below: | ||
// "No module factory available for dependency type: ContextElementDependency" | ||
|
@@ -411,7 +450,7 @@ export class AngularCompilerPlugin { | |
this._entryModule = resolveEntryModuleFromMain( | ||
this._mainPath, this._compilerHost, this._getTsProgram() as ts.Program); | ||
|
||
if (!this.entryModule && !this._compilerOptions.enableIvy) { | ||
if (this._discoverLazyRoutes && !this.entryModule && !this._compilerOptions.enableIvy) { | ||
this._warnings.push('Lazy routes discovery is not enabled. ' | ||
+ 'Because there is neither an entryModule nor a ' | ||
+ 'statically analyzable bootstrap code in the main file.', | ||
|
@@ -697,64 +736,63 @@ export class AngularCompilerPlugin { | |
); | ||
}); | ||
|
||
// Add lazy modules to the context module for @angular/core | ||
compiler.hooks.contextModuleFactory.tap('angular-compiler', cmf => { | ||
const angularCorePackagePath = require.resolve('@angular/core/package.json'); | ||
|
||
// APFv6 does not have single FESM anymore. Instead of verifying if we're pointing to | ||
// FESMs, we resolve the `@angular/core` path and verify that the path for the | ||
// module starts with it. | ||
// This may be slower but it will be compatible with both APF5, 6 and potential future | ||
// versions (until the dynamic import appears outside of core I suppose). | ||
// We resolve any symbolic links in order to get the real path that would be used in webpack. | ||
const angularCoreResourceRoot = fs.realpathSync(path.dirname(angularCorePackagePath)); | ||
|
||
cmf.hooks.afterResolve.tapPromise('angular-compiler', async result => { | ||
// Alter only existing request from Angular or one of the additional lazy module resources. | ||
const isLazyModuleResource = (resource: string) => | ||
resource.startsWith(angularCoreResourceRoot) || | ||
( this.options.additionalLazyModuleResources && | ||
this.options.additionalLazyModuleResources.includes(resource)); | ||
|
||
if (!result || !this.done || !isLazyModuleResource(result.resource)) { | ||
return result; | ||
} | ||
if (this._discoverLazyRoutes) { | ||
// Add lazy modules to the context module for @angular/core | ||
compiler.hooks.contextModuleFactory.tap('angular-compiler', cmf => { | ||
const angularCorePackagePath = require.resolve('@angular/core/package.json'); | ||
|
||
// APFv6 does not have single FESM anymore. Instead of verifying if we're pointing to | ||
// FESMs, we resolve the `@angular/core` path and verify that the path for the | ||
// module starts with it. | ||
// This may be slower but it will be compatible with both APF5, 6 and potential future | ||
// versions (until the dynamic import appears outside of core I suppose). | ||
// We resolve symbolic links in order to get the real path that would be used in webpack. | ||
const angularCoreResourceRoot = fs.realpathSync(path.dirname(angularCorePackagePath)); | ||
|
||
cmf.hooks.afterResolve.tapPromise('angular-compiler', async result => { | ||
// Alter only existing request from Angular or the additional lazy module resources. | ||
const isLazyModuleResource = (resource: string) => | ||
resource.startsWith(angularCoreResourceRoot) || | ||
(this.options.additionalLazyModuleResources && | ||
this.options.additionalLazyModuleResources.includes(resource)); | ||
|
||
if (!result || !this.done || !isLazyModuleResource(result.resource)) { | ||
return result; | ||
} | ||
|
||
return this.done.then( | ||
() => { | ||
// This folder does not exist, but we need to give webpack a resource. | ||
// TODO: check if we can't just leave it as is (angularCoreModuleDir). | ||
result.resource = path.join(this._basePath, '$$_lazy_route_resource'); | ||
// tslint:disable-next-line:no-any | ||
result.dependencies.forEach((d: any) => d.critical = false); | ||
// tslint:disable-next-line:no-any | ||
result.resolveDependencies = (_fs: any, options: any, callback: Callback) => { | ||
const dependencies = Object.keys(this._lazyRoutes) | ||
.map((key) => { | ||
const modulePath = this._lazyRoutes[key]; | ||
if (modulePath !== null) { | ||
const name = key.split('#')[0]; | ||
|
||
return new this._contextElementDependencyConstructor(modulePath, name); | ||
} else { | ||
return null; | ||
} | ||
}) | ||
.filter(x => !!x); | ||
|
||
if (this._options.nameLazyFiles) { | ||
options.chunkName = '[request]'; | ||
} | ||
await this.done; | ||
|
||
// This folder does not exist, but we need to give webpack a resource. | ||
// TODO: check if we can't just leave it as is (angularCoreModuleDir). | ||
result.resource = path.join(this._basePath, '$$_lazy_route_resource'); | ||
// tslint:disable-next-line:no-any | ||
result.dependencies.forEach((d: any) => d.critical = false); | ||
// tslint:disable-next-line:no-any | ||
result.resolveDependencies = (_fs: any, options: any, callback: Callback) => { | ||
const dependencies = Object.keys(this._lazyRoutes) | ||
.map((key) => { | ||
const modulePath = this._lazyRoutes[key]; | ||
if (modulePath !== null) { | ||
const name = key.split('#')[0]; | ||
|
||
return new this._contextElementDependencyConstructor(modulePath, name); | ||
} else { | ||
return null; | ||
} | ||
}) | ||
.filter(x => !!x); | ||
|
||
if (this._options.nameLazyFiles) { | ||
options.chunkName = '[request]'; | ||
} | ||
|
||
callback(null, dependencies); | ||
}; | ||
callback(null, dependencies); | ||
}; | ||
|
||
return result; | ||
}, | ||
() => undefined, | ||
); | ||
return result; | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
// Create and destroy forked type checker on watch mode. | ||
compiler.hooks.watchRun.tap('angular-compiler', () => { | ||
|
@@ -868,6 +906,10 @@ export class AngularCompilerPlugin { | |
} else { | ||
// Remove unneeded angular decorators. | ||
this._transformers.push(removeDecorators(isAppPath, getTypeChecker)); | ||
// Import ngfactory in loadChildren import syntax | ||
if (this._importFactories) { | ||
this._transformers.push(importFactory(msg => this._warnings.push(msg))); | ||
} | ||
} | ||
|
||
if (this._platformTransformers !== null) { | ||
|
@@ -922,27 +964,29 @@ export class AngularCompilerPlugin { | |
// Make a new program and load the Angular structure. | ||
await this._createOrUpdateProgram(); | ||
|
||
// 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. | ||
// Lazy routes modules will be read with compilerHost and added to the changed files. | ||
let lazyRouteMap: LazyRouteMap = {}; | ||
if (!this._JitMode || this._firstRun) { | ||
lazyRouteMap = this._listLazyRoutesFromProgram(); | ||
} else { | ||
const changedTsFiles = this._getChangedTsFiles(); | ||
if (changedTsFiles.length > 0) { | ||
lazyRouteMap = this._findLazyRoutesInAst(changedTsFiles); | ||
if (this._discoverLazyRoutes) { | ||
// 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. | ||
// Lazy routes modules will be read with compilerHost and added to the changed files. | ||
let lazyRouteMap: LazyRouteMap = {}; | ||
if (!this._JitMode || this._firstRun) { | ||
lazyRouteMap = this._listLazyRoutesFromProgram(); | ||
} else { | ||
const changedTsFiles = this._getChangedTsFiles(); | ||
if (changedTsFiles.length > 0) { | ||
lazyRouteMap = this._findLazyRoutesInAst(changedTsFiles); | ||
} | ||
} | ||
} | ||
|
||
// Find lazy routes | ||
lazyRouteMap = { | ||
...lazyRouteMap, | ||
...this._options.additionalLazyModules, | ||
}; | ||
// Find lazy routes | ||
lazyRouteMap = { | ||
...lazyRouteMap, | ||
...this._options.additionalLazyModules, | ||
clydin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
this._processLazyRoutes(lazyRouteMap); | ||
this._processLazyRoutes(lazyRouteMap); | ||
} | ||
|
||
// Emit files. | ||
time('AngularCompilerPlugin._update._emit'); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: why switch the order of these? makes the delta harder to review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To keep similar e2e jobs (
e2e-cli
ande2e-cli-ivy
) together.The old
e2e-cli-ng-snapshots
was aftere2e-cli-ivy-snapshots
because they shared the snapshot flag and only ran on some builds. Nowe2e-cli-ivy-snapshots
is insteade2e-cli-ivy
and should instead be next toe2e-cli
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.