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;
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;
// added to the list of lazy routes
additionalLazyModules?: { [module: string]: string };
@ -134,6 +139,7 @@ export class AngularCompilerPlugin {
private _compilerHost: WebpackCompilerHost & CompilerHost;
private _moduleResolutionCache: ts.ModuleResolutionCache;
private _resourceLoader?: WebpackResourceLoader;
private _discoverLazyRoutes = true;
// Contains `moduleImportPath#exportName` => `fullModulePath`.
private _lazyRoutes: LazyRouteMap = {};
private _tsConfigPath: string;
@ -292,6 +298,26 @@ export class AngularCompilerPlugin {
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.
// Failing to use the right ContextElementDependency will throw the error below:
// "No module factory available for dependency type: ContextElementDependency"
@ -411,7 +437,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 +723,66 @@ 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');
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 any symbolic links in order to get the real path that would be used in webpack.
const angularCoreResourceRoot = fs.realpathSync(path.dirname(angularCorePackagePath));
// 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 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;
}
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);
};
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;
},
() => 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.
compiler.hooks.watchRun.tap('angular-compiler', () => {
@ -922,28 +950,30 @@ 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,
};
this._processLazyRoutes(lazyRouteMap);
}
// Find lazy routes
lazyRouteMap = {
...lazyRouteMap,
...this._options.additionalLazyModules,
};
this._processLazyRoutes(lazyRouteMap);
// Emit files.
time('AngularCompilerPlugin._update._emit');
const { emitResult, diagnostics } = this._emit();