feat(@ngtools/webpack): add option to control lazy route discovery

This commit is contained in:
Filipe Silva 2019-02-28 13:58:23 +00:00 committed by Minko Gechev
parent 7e9297b64d
commit b54a9379a6

View File

@ -103,6 +103,11 @@ export interface AngularCompilerPluginOptions {
nameLazyFiles?: boolean; nameLazyFiles?: boolean;
logger?: logging.Logger; logger?: logging.Logger;
directTemplateLoading?: boolean; 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;
// added to the list of lazy routes // added to the list of lazy routes
additionalLazyModules?: { [module: string]: string }; additionalLazyModules?: { [module: string]: string };
@ -134,6 +139,7 @@ export class AngularCompilerPlugin {
private _compilerHost: WebpackCompilerHost & CompilerHost; private _compilerHost: WebpackCompilerHost & CompilerHost;
private _moduleResolutionCache: ts.ModuleResolutionCache; private _moduleResolutionCache: ts.ModuleResolutionCache;
private _resourceLoader?: WebpackResourceLoader; private _resourceLoader?: WebpackResourceLoader;
private _discoverLazyRoutes = true;
// Contains `moduleImportPath#exportName` => `fullModulePath`. // Contains `moduleImportPath#exportName` => `fullModulePath`.
private _lazyRoutes: LazyRouteMap = {}; private _lazyRoutes: LazyRouteMap = {};
private _tsConfigPath: string; private _tsConfigPath: string;
@ -292,6 +298,26 @@ export class AngularCompilerPlugin {
this._platformTransformers = options.platformTransformers; this._platformTransformers = options.platformTransformers;
} }
if (options.discoverLazyRoutes !== undefined) {
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.`),
);
}
// Default ContextElementDependency to the one we can import from here. // Default ContextElementDependency to the one we can import from here.
// Failing to use the right ContextElementDependency will throw the error below: // Failing to use the right ContextElementDependency will throw the error below:
// "No module factory available for dependency type: ContextElementDependency" // "No module factory available for dependency type: ContextElementDependency"
@ -411,7 +437,7 @@ export class AngularCompilerPlugin {
this._entryModule = resolveEntryModuleFromMain( this._entryModule = resolveEntryModuleFromMain(
this._mainPath, this._compilerHost, this._getTsProgram() as ts.Program); 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. ' this._warnings.push('Lazy routes discovery is not enabled. '
+ 'Because there is neither an entryModule nor a ' + 'Because there is neither an entryModule nor a '
+ 'statically analyzable bootstrap code in the main file.', + 'statically analyzable bootstrap code in the main file.',
@ -697,64 +723,66 @@ export class AngularCompilerPlugin {
); );
}); });
// Add lazy modules to the context module for @angular/core if (this._discoverLazyRoutes) {
compiler.hooks.contextModuleFactory.tap('angular-compiler', cmf => { // Add lazy modules to the context module for @angular/core
const angularCorePackagePath = require.resolve('@angular/core/package.json'); 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 // 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 // FESMs, we resolve the `@angular/core` path and verify that the path for the
// module starts with it. // module starts with it.
// This may be slower but it will be compatible with both APF5, 6 and potential future // 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). // 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. // We resolve symbolic links in order to get the real path that would be used in webpack.
const angularCoreResourceRoot = fs.realpathSync(path.dirname(angularCorePackagePath)); const angularCoreResourceRoot = fs.realpathSync(path.dirname(angularCorePackagePath));
cmf.hooks.afterResolve.tapPromise('angular-compiler', async result => { cmf.hooks.afterResolve.tapPromise('angular-compiler', async result => {
// Alter only existing request from Angular or one of the additional lazy module resources. // Alter only existing request from Angular or the additional lazy module resources.
const isLazyModuleResource = (resource: string) => const isLazyModuleResource = (resource: string) =>
resource.startsWith(angularCoreResourceRoot) || resource.startsWith(angularCoreResourceRoot) ||
( this.options.additionalLazyModuleResources && (this.options.additionalLazyModuleResources &&
this.options.additionalLazyModuleResources.includes(resource)); 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]';
}
callback(null, dependencies);
};
if (!result || !this.done || !isLazyModuleResource(result.resource)) {
return result; return result;
}, }
() => undefined,
); 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]';
}
callback(null, dependencies);
};
return result;
},
() => undefined,
);
});
}); });
}); }
// Create and destroy forked type checker on watch mode. // Create and destroy forked type checker on watch mode.
compiler.hooks.watchRun.tap('angular-compiler', () => { compiler.hooks.watchRun.tap('angular-compiler', () => {
@ -922,28 +950,30 @@ export class AngularCompilerPlugin {
// Make a new program and load the Angular structure. // Make a new program and load the Angular structure.
await this._createOrUpdateProgram(); await this._createOrUpdateProgram();
// Try to find lazy routes if we have an entry module. if (this._discoverLazyRoutes) {
// We need to run the `listLazyRoutes` the first time because it also navigates libraries // Try to find lazy routes if we have an entry module.
// and other things that we might miss using the (faster) findLazyRoutesInAst. // We need to run the `listLazyRoutes` the first time because it also navigates libraries
// Lazy routes modules will be read with compilerHost and added to the changed files. // and other things that we might miss using the (faster) findLazyRoutesInAst.
let lazyRouteMap: LazyRouteMap = {}; // Lazy routes modules will be read with compilerHost and added to the changed files.
if (!this._JitMode || this._firstRun) { let lazyRouteMap: LazyRouteMap = {};
lazyRouteMap = this._listLazyRoutesFromProgram(); if (!this._JitMode || this._firstRun) {
} else { lazyRouteMap = this._listLazyRoutesFromProgram();
const changedTsFiles = this._getChangedTsFiles(); } else {
if (changedTsFiles.length > 0) { const changedTsFiles = this._getChangedTsFiles();
lazyRouteMap = this._findLazyRoutesInAst(changedTsFiles); if (changedTsFiles.length > 0) {
lazyRouteMap = this._findLazyRoutesInAst(changedTsFiles);
}
} }
// Find lazy routes
lazyRouteMap = {
...lazyRouteMap,
...this._options.additionalLazyModules,
};
this._processLazyRoutes(lazyRouteMap);
} }
// Find lazy routes
lazyRouteMap = {
...lazyRouteMap,
...this._options.additionalLazyModules,
};
this._processLazyRoutes(lazyRouteMap);
// Emit files. // Emit files.
time('AngularCompilerPlugin._update._emit'); time('AngularCompilerPlugin._update._emit');
const { emitResult, diagnostics } = this._emit(); const { emitResult, diagnostics } = this._emit();