Skip to content

Commit 0dc7327

Browse files
alan-agius4filipesilva
authored andcommitted
feat(@ngtools/webpack): drop support for string based lazy loading
BREAKING CHANGE: With this change we drop support for string based lazy loading `./lazy.module#LazyModule` use dynamic imports instead. The following options which were used to support the above syntax were removed without replacement. - discoverLazyRoutes - additionalLazyModules - additionalLazyModuleResources - contextElementDependencyConstructor
1 parent 4fce9bf commit 0dc7327

8 files changed

+5
-618
lines changed

packages/ngtools/webpack/src/angular_compiler_plugin.ts

+4-255
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,12 @@ import { DiagnosticMode, gatherDiagnostics, hasErrors, reportDiagnostics } from
4141
import { resolveEntryModuleFromMain } from './entry_resolver';
4242
import {
4343
AngularCompilerPluginOptions,
44-
ContextElementDependencyConstructor,
4544
PLATFORM,
4645
} from './interfaces';
47-
import { LazyRouteMap, findLazyRoutes } from './lazy_routes';
4846
import { NgccProcessor } from './ngcc_processor';
4947
import { TypeScriptPathsPlugin } from './paths-plugin';
5048
import { WebpackResourceLoader } from './resource_loader';
5149
import {
52-
exportLazyModuleMap,
5350
exportNgFactory,
5451
findResources,
5552
importFactory,
@@ -90,10 +87,7 @@ export class AngularCompilerPlugin {
9087
private _compilerHost!: WebpackCompilerHost & CompilerHost;
9188
private _moduleResolutionCache!: ts.ModuleResolutionCache;
9289
private _resourceLoader?: WebpackResourceLoader;
93-
private _discoverLazyRoutes = true;
9490
private _useFactories = false;
95-
// Contains `moduleImportPath#exportName` => `fullModulePath`.
96-
private _lazyRoutes: LazyRouteMap = {};
9791
private _tsConfigPath!: string;
9892
private _entryModule: string | null = null;
9993
private _mainPath: string | undefined;
@@ -117,7 +111,6 @@ export class AngularCompilerPlugin {
117111
private _normalizedLocale: string | null = null;
118112
private _warnings: string[] = [];
119113
private _errors: string[] = [];
120-
private _contextElementDependencyConstructor!: ContextElementDependencyConstructor;
121114

122115
// TypeChecker process.
123116
private _forkTypeChecker = true;
@@ -259,12 +252,6 @@ export class AngularCompilerPlugin {
259252
this._platformTransformers = options.platformTransformers;
260253
}
261254

262-
// Determine if lazy route discovery via Compiler CLI private API should be attempted.
263-
// The default is to discover routes, but it can be overriden.
264-
if (options.discoverLazyRoutes !== undefined) {
265-
this._discoverLazyRoutes = options.discoverLazyRoutes;
266-
}
267-
268255
if (
269256
!this.options.suppressZoneJsIncompatibilityWarning &&
270257
this._compilerOptions.target !== undefined &&
@@ -277,42 +264,17 @@ export class AngularCompilerPlugin {
277264
);
278265
}
279266

280-
if (this._discoverLazyRoutes === false && this.options.additionalLazyModuleResources
281-
&& this.options.additionalLazyModuleResources.length > 0) {
282-
this._warnings.push(
283-
`Lazy route discovery is disabled but additional Lazy Module Resources were` +
284-
` provided. These will be ignored.`,
285-
);
286-
}
287-
288267
if (this._compilerOptions.strictMetadataEmit) {
289268
this._warnings.push(
290269
`Using Angular compiler option 'strictMetadataEmit' for applications might cause undefined behavior.`,
291270
);
292271
}
293272

294-
if (this._discoverLazyRoutes === false && this.options.additionalLazyModules
295-
&& Object.keys(this.options.additionalLazyModules).length > 0) {
296-
this._warnings.push(
297-
`Lazy route discovery is disabled but additional lazy modules were provided.` +
298-
`These will be ignored.`,
299-
);
300-
}
301-
302273
if (!this._JitMode && !this._compilerOptions.enableIvy) {
303274
// Only attempt to use factories when AOT and not Ivy.
304275
this._useFactories = true;
305276
}
306277

307-
// Default ContextElementDependency to the one we can import from here.
308-
// Failing to use the right ContextElementDependency will throw the error below:
309-
// "No module factory available for dependency type: ContextElementDependency"
310-
// Hoisting together with peer dependencies can make it so the imported
311-
// ContextElementDependency does not come from the same Webpack instance that is used
312-
// in the compilation. In that case, we can pass the right one as an option to the plugin.
313-
this._contextElementDependencyConstructor = options.contextElementDependencyConstructor
314-
|| require('webpack/lib/dependencies/ContextElementDependency');
315-
316278
// Use entryModule if available in options, otherwise resolve it from mainPath after program
317279
// creation.
318280
if (this._options.entryModule) {
@@ -446,124 +408,10 @@ export class AngularCompilerPlugin {
446408
time('AngularCompilerPlugin._make.resolveEntryModuleFromMain');
447409
this._entryModule = resolveEntryModuleFromMain(
448410
this._mainPath, this._compilerHost, this._getTsProgram() as ts.Program);
449-
450-
if (this._discoverLazyRoutes && !this.entryModule && !this._compilerOptions.enableIvy) {
451-
this._warnings.push('Lazy routes discovery is not enabled. '
452-
+ 'Because there is neither an entryModule nor a '
453-
+ 'statically analyzable bootstrap code in the main file.',
454-
);
455-
}
456411
timeEnd('AngularCompilerPlugin._make.resolveEntryModuleFromMain');
457412
}
458413
}
459414

460-
private _findLazyRoutesInAst(changedFilePaths: string[]): LazyRouteMap {
461-
time('AngularCompilerPlugin._findLazyRoutesInAst');
462-
const result: LazyRouteMap = {};
463-
for (const filePath of changedFilePaths) {
464-
const fileLazyRoutes = findLazyRoutes(filePath, this._compilerHost, undefined,
465-
this._compilerOptions);
466-
for (const routeKey of Object.keys(fileLazyRoutes)) {
467-
const route = fileLazyRoutes[routeKey];
468-
result[routeKey] = route;
469-
}
470-
}
471-
timeEnd('AngularCompilerPlugin._findLazyRoutesInAst');
472-
473-
return result;
474-
}
475-
476-
private _listLazyRoutesFromProgram(): LazyRouteMap {
477-
let entryRoute: string | undefined;
478-
let ngProgram: Program;
479-
480-
if (this._JitMode) {
481-
if (!this.entryModule) {
482-
return {};
483-
}
484-
485-
time('AngularCompilerPlugin._listLazyRoutesFromProgram.createProgram');
486-
ngProgram = createProgram({
487-
rootNames: this._rootNames,
488-
options: { ...this._compilerOptions, genDir: '', collectAllErrors: true, enableIvy: false },
489-
host: this._compilerHost,
490-
});
491-
timeEnd('AngularCompilerPlugin._listLazyRoutesFromProgram.createProgram');
492-
493-
entryRoute = workaroundResolve(this.entryModule.path) + '#' + this.entryModule.className;
494-
} else {
495-
ngProgram = this._program as Program;
496-
}
497-
498-
time('AngularCompilerPlugin._listLazyRoutesFromProgram.listLazyRoutes');
499-
// entryRoute will only be defined in JIT.
500-
// In AOT all routes within the program are returned.
501-
const lazyRoutes = ngProgram.listLazyRoutes(entryRoute);
502-
timeEnd('AngularCompilerPlugin._listLazyRoutesFromProgram.listLazyRoutes');
503-
504-
return lazyRoutes.reduce(
505-
(acc, curr) => {
506-
const ref = curr.route;
507-
if (ref in acc && acc[ref] !== curr.referencedModule.filePath) {
508-
throw new Error(
509-
+ `Duplicated path in loadChildren detected: "${ref}" is used in 2 loadChildren, `
510-
+ `but they point to different modules "(${acc[ref]} and `
511-
+ `"${curr.referencedModule.filePath}"). Webpack cannot distinguish on context and `
512-
+ 'would fail to load the proper one.',
513-
);
514-
}
515-
acc[ref] = curr.referencedModule.filePath;
516-
517-
return acc;
518-
},
519-
{} as LazyRouteMap,
520-
);
521-
}
522-
523-
// Process the lazy routes discovered, adding then to _lazyRoutes.
524-
// TODO: find a way to remove lazy routes that don't exist anymore.
525-
// This will require a registry of known references to a lazy route, removing it when no
526-
// module references it anymore.
527-
private _processLazyRoutes(discoveredLazyRoutes: LazyRouteMap) {
528-
Object.keys(discoveredLazyRoutes)
529-
.forEach(lazyRouteKey => {
530-
const [lazyRouteModule, moduleName] = lazyRouteKey.split('#');
531-
532-
if (!lazyRouteModule) {
533-
return;
534-
}
535-
536-
const lazyRouteTSFile = forwardSlashPath(discoveredLazyRoutes[lazyRouteKey]);
537-
let modulePath: string, moduleKey: string;
538-
539-
if (this._useFactories) {
540-
modulePath = lazyRouteTSFile.replace(/(\.d)?\.tsx?$/, '');
541-
modulePath += '.ngfactory.js';
542-
const factoryModuleName = moduleName ? `#${moduleName}NgFactory` : '';
543-
moduleKey = `${lazyRouteModule}.ngfactory${factoryModuleName}`;
544-
} else {
545-
modulePath = lazyRouteTSFile;
546-
moduleKey = `${lazyRouteModule}${moduleName ? '#' + moduleName : ''}`;
547-
}
548-
549-
modulePath = workaroundResolve(modulePath);
550-
551-
if (moduleKey in this._lazyRoutes) {
552-
if (this._lazyRoutes[moduleKey] !== modulePath) {
553-
// Found a duplicate, this is an error.
554-
this._warnings.push(
555-
`Duplicated path in loadChildren detected during a rebuild. ` +
556-
`We will take the latest version detected and override it to save rebuild time. ` +
557-
`You should perform a full build to validate that your routes don't overlap.`,
558-
);
559-
}
560-
} else {
561-
// Found a new route, add it to the map.
562-
this._lazyRoutes[moduleKey] = modulePath;
563-
}
564-
});
565-
}
566-
567415
private _createForkedTypeChecker() {
568416
const typeCheckerFile = './type_checker_worker.js';
569417

@@ -839,66 +687,6 @@ export class AngularCompilerPlugin {
839687
);
840688
});
841689

842-
if (this._discoverLazyRoutes) {
843-
// Add lazy modules to the context module for @angular/core
844-
compiler.hooks.contextModuleFactory.tap('angular-compiler', cmf => {
845-
const angularCorePackagePath = require.resolve('@angular/core/package.json', {
846-
paths: [this._basePath],
847-
});
848-
849-
// APFv6 does not have single FESM anymore. Instead of verifying if we're pointing to
850-
// FESMs, we resolve the `@angular/core` path and verify that the path for the
851-
// module starts with it.
852-
// This may be slower but it will be compatible with both APF5, 6 and potential future
853-
// versions (until the dynamic import appears outside of core I suppose).
854-
// We resolve symbolic links in order to get the real path that would be used in webpack.
855-
const angularCoreResourceRoot = fs.realpathSync(path.dirname(angularCorePackagePath));
856-
857-
cmf.hooks.afterResolve.tapPromise('angular-compiler', async result => {
858-
// Alter only existing request from Angular or the additional lazy module resources.
859-
const isLazyModuleResource = (resource: string) =>
860-
resource.startsWith(angularCoreResourceRoot) ||
861-
(this.options.additionalLazyModuleResources &&
862-
this.options.additionalLazyModuleResources.includes(resource));
863-
864-
if (!result || !this.done || !isLazyModuleResource(result.resource)) {
865-
return result;
866-
}
867-
868-
await this.done;
869-
870-
// This folder does not exist, but we need to give webpack a resource.
871-
// TODO: check if we can't just leave it as is (angularCoreModuleDir).
872-
result.resource = path.join(this._basePath, '$$_lazy_route_resource');
873-
// tslint:disable-next-line:no-any
874-
result.dependencies.forEach((d: any) => d.critical = false);
875-
// tslint:disable-next-line:no-any
876-
result.resolveDependencies = (_fs: any, options: any, callback: any) => {
877-
const dependencies = Object.keys(this._lazyRoutes)
878-
.map((key) => {
879-
const modulePath = this._lazyRoutes[key];
880-
if (modulePath !== null) {
881-
const name = key.split('#')[0];
882-
883-
return new this._contextElementDependencyConstructor(modulePath, name);
884-
} else {
885-
return null;
886-
}
887-
})
888-
.filter(x => !!x);
889-
890-
if (this._options.nameLazyFiles) {
891-
options.chunkName = '[request]';
892-
}
893-
894-
callback(null, dependencies);
895-
};
896-
897-
return result;
898-
});
899-
});
900-
}
901-
902690
// Create and destroy forked type checker on watch mode.
903691
compiler.hooks.watchRun.tap('angular-compiler', () => {
904692
if (this._forkTypeChecker && !this._typeCheckerProcess) {
@@ -1041,7 +829,6 @@ export class AngularCompilerPlugin {
1041829
const getEntryModule = () => this.entryModule
1042830
? { path: workaroundResolve(this.entryModule.path), className: this.entryModule.className }
1043831
: this.entryModule;
1044-
const getLazyRoutes = () => this._lazyRoutes;
1045832
const getTypeChecker = () => (this._getTsProgram() as ts.Program).getTypeChecker();
1046833

1047834
if (this._JitMode) {
@@ -1100,28 +887,14 @@ export class AngularCompilerPlugin {
1100887
this._useFactories,
1101888
));
1102889
}
1103-
} else if (this._platform === PLATFORM.Server) {
1104-
// The export lazy module map is required only for string based lazy loading
1105-
// which is not supported in Ivy
1106-
if (!this._compilerOptions.enableIvy) {
1107-
this._transformers.push(exportLazyModuleMap(isMainPath, getLazyRoutes));
1108-
}
1109-
1110-
if (this._useFactories) {
1111-
this._transformers.push(
1112-
exportNgFactory(isMainPath, getEntryModule),
1113-
replaceServerBootstrap(isMainPath, getEntryModule, getTypeChecker));
1114-
}
890+
} else if (this._platform === PLATFORM.Server && this._useFactories) {
891+
this._transformers.push(
892+
exportNgFactory(isMainPath, getEntryModule),
893+
replaceServerBootstrap(isMainPath, getEntryModule, getTypeChecker));
1115894
}
1116895
}
1117896
}
1118897

1119-
private _getChangedTsFiles() {
1120-
return this._getChangedCompilationFiles()
1121-
.filter(k => (k.endsWith('.ts') || k.endsWith('.tsx')) && !k.endsWith('.d.ts'))
1122-
.filter(k => this._compilerHost.fileExists(k));
1123-
}
1124-
1125898
private async _update() {
1126899
time('AngularCompilerPlugin._update');
1127900
// We only want to update on TS and template changes, but all kinds of files are on this
@@ -1136,30 +909,6 @@ export class AngularCompilerPlugin {
1136909
// Make a new program and load the Angular structure.
1137910
await this._createOrUpdateProgram();
1138911

1139-
if (this._discoverLazyRoutes) {
1140-
// Try to find lazy routes if we have an entry module.
1141-
// We need to run the `listLazyRoutes` the first time because it also navigates libraries
1142-
// and other things that we might miss using the (faster) findLazyRoutesInAst.
1143-
// Lazy routes modules will be read with compilerHost and added to the changed files.
1144-
let lazyRouteMap: LazyRouteMap = {};
1145-
if (!this._JitMode || this._firstRun) {
1146-
lazyRouteMap = this._listLazyRoutesFromProgram();
1147-
} else {
1148-
const changedTsFiles = this._getChangedTsFiles();
1149-
if (changedTsFiles.length > 0) {
1150-
lazyRouteMap = this._findLazyRoutesInAst(changedTsFiles);
1151-
}
1152-
}
1153-
1154-
// Find lazy routes
1155-
lazyRouteMap = {
1156-
...lazyRouteMap,
1157-
...this._options.additionalLazyModules,
1158-
};
1159-
1160-
this._processLazyRoutes(lazyRouteMap);
1161-
}
1162-
1163912
// Emit files.
1164913
time('AngularCompilerPlugin._update._emit');
1165914
const { emitResult, diagnostics } = this._emit();

packages/ngtools/webpack/src/interfaces.ts

-28
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,6 @@ export enum PLATFORM {
1616
Server,
1717
}
1818

19-
export interface ContextElementDependency { }
20-
21-
export interface ContextElementDependencyConstructor {
22-
new(modulePath: string, name: string): ContextElementDependency;
23-
}
24-
2519
/**
2620
* Option Constants
2721
*/
@@ -56,28 +50,6 @@ export interface AngularCompilerPluginOptions {
5650
emitClassMetadata?: boolean;
5751
emitNgModuleScope?: boolean;
5852

59-
/**
60-
* When using the loadChildren string syntax, @ngtools/webpack must query @angular/compiler-cli
61-
* via a private API to know which lazy routes exist. This increases build and rebuild time.
62-
* When using Ivy, the string syntax is not supported at all. Thus we shouldn't attempt that
63-
* This option is also used for when the compilation doesn't need this sort of processing at all.
64-
* @deprecated SystemJsNgModuleLoader is deprecated, and this is part of its usage.
65-
*/
66-
discoverLazyRoutes?: boolean;
67-
68-
/** added to the list of lazy routes
69-
* @deprecated SystemJsNgModuleLoader is deprecated, and this is part of its usage.
70-
*/
71-
additionalLazyModules?: { [module: string]: string };
72-
/**
73-
* @deprecated SystemJsNgModuleLoader is deprecated, and this is part of its usage.
74-
*/
75-
additionalLazyModuleResources?: string[];
76-
77-
// The ContextElementDependency of correct Webpack compilation.
78-
// This is needed when there are multiple Webpack installs.
79-
contextElementDependencyConstructor?: ContextElementDependencyConstructor;
80-
8153
// Use tsconfig to include path globs.
8254
compilerOptions?: CompilerOptions;
8355

0 commit comments

Comments
 (0)