Skip to content

Commit 7407b03

Browse files
committed
feat(ivy): implement listing lazy routes for specific entry point in ngtsc
Related: angular/angular-cli#13532 Jira issue: FW-860
1 parent b16b24c commit 7407b03

File tree

4 files changed

+482
-94
lines changed

4 files changed

+482
-94
lines changed

packages/compiler-cli/src/ngtsc/program.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,7 @@ export class NgtscProgram implements api.Program {
192192

193193
listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
194194
this.ensureAnalyzed();
195-
// Listing specific routes is unsupported for now, so we erroneously return
196-
// all lazy routes instead (which should be okay for the CLI's usage).
197-
return this.routeAnalyzer !.listLazyRoutes();
195+
return this.routeAnalyzer !.listLazyRoutes(entryRoute);
198196
}
199197

200198
getLibrarySummaries(): Map<string, api.LibrarySummary> {

packages/compiler-cli/src/ngtsc/routing/src/analyzer.ts

+38-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as ts from 'typescript';
1111
import {ModuleResolver} from '../../imports';
1212
import {PartialEvaluator} from '../../partial_evaluator';
1313

14-
import {scanForRouteEntryPoints} from './lazy';
14+
import {scanForCandidateTransitiveModules, scanForRouteEntryPoints} from './lazy';
1515
import {RouterEntryPointManager, entryPointKeyFor} from './route';
1616

1717
export interface NgModuleRawRouteData {
@@ -48,18 +48,53 @@ export class NgModuleRouteAnalyzer {
4848
});
4949
}
5050

51-
listLazyRoutes(): LazyRoute[] {
51+
listLazyRoutes(entryModuleKey?: string|undefined): LazyRoute[] {
52+
if ((entryModuleKey !== undefined) && !this.modules.has(entryModuleKey)) {
53+
throw new Error(`Failed to list lazy routes: Unknown module '${entryModuleKey}'.`);
54+
}
55+
5256
const routes: LazyRoute[] = [];
53-
for (const key of Array.from(this.modules.keys())) {
57+
const scannedModuleKeys = new Set<string>();
58+
const pendingModuleKeys = entryModuleKey ? [entryModuleKey] : Array.from(this.modules.keys());
59+
60+
// When listing lazy routes for a specific entry module, we need to recursively extract
61+
// "transitive" routes from imported/exported modules. This is not necessary when listing all
62+
// lazy routes, because all analyzed modules will be scanned anyway.
63+
const scanRecursively = entryModuleKey !== undefined;
64+
65+
while (pendingModuleKeys.length > 0) {
66+
const key = pendingModuleKeys.pop() !;
67+
68+
if (scannedModuleKeys.has(key)) {
69+
continue;
70+
} else {
71+
scannedModuleKeys.add(key);
72+
}
73+
5474
const data = this.modules.get(key) !;
5575
const entryPoints = scanForRouteEntryPoints(
5676
data.sourceFile, data.moduleName, data, this.entryPointManager, this.evaluator);
77+
5778
routes.push(...entryPoints.map(entryPoint => ({
5879
route: entryPoint.loadChildren,
5980
module: entryPoint.from,
6081
referencedModule: entryPoint.resolvedTo,
6182
})));
83+
84+
if (scanRecursively) {
85+
pendingModuleKeys.push(
86+
...[
87+
// Scan the retrieved lazy route entry points.
88+
...entryPoints.map(
89+
({resolvedTo}) => entryPointKeyFor(resolvedTo.filePath, resolvedTo.moduleName)),
90+
// Scan the current module's imported modules.
91+
...scanForCandidateTransitiveModules(data.imports, this.evaluator),
92+
// Scan the current module's exported modules.
93+
...scanForCandidateTransitiveModules(data.exports, this.evaluator),
94+
].filter(key => this.modules.has(key)));
95+
}
6296
}
97+
6398
return routes;
6499
}
65100
}

packages/compiler-cli/src/ngtsc/routing/src/lazy.ts

+35-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {AbsoluteReference, NodeReference, Reference} from '../../imports';
1212
import {ForeignFunctionResolver, PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
1313

1414
import {NgModuleRawRouteData} from './analyzer';
15-
import {RouterEntryPoint, RouterEntryPointManager} from './route';
15+
import {RouterEntryPoint, RouterEntryPointManager, entryPointKeyFor} from './route';
1616

1717
const ROUTES_MARKER = '__ngRoutesMarker__';
1818

@@ -22,6 +22,35 @@ export interface LazyRouteEntry {
2222
resolvedTo: RouterEntryPoint;
2323
}
2424

25+
export function scanForCandidateTransitiveModules(
26+
expr: ts.Expression | null, evaluator: PartialEvaluator): string[] {
27+
if (expr === null) {
28+
return [];
29+
}
30+
31+
const candidateModuleKeys: string[] = [];
32+
const entries = evaluator.evaluate(expr);
33+
34+
function recursivelyAddModules(entry: ResolvedValue) {
35+
if (Array.isArray(entry)) {
36+
for (const e of entry) {
37+
recursivelyAddModules(e);
38+
}
39+
} else if (entry instanceof Map) {
40+
if (entry.has('ngModule')) {
41+
recursivelyAddModules(entry.get('ngModule') !);
42+
}
43+
} else if ((entry instanceof Reference) && hasIdentifier(entry.node)) {
44+
const filePath = entry.node.getSourceFile().fileName;
45+
const moduleName = entry.node.name.text;
46+
candidateModuleKeys.push(entryPointKeyFor(filePath, moduleName));
47+
}
48+
}
49+
50+
recursivelyAddModules(entries);
51+
return candidateModuleKeys;
52+
}
53+
2554
export function scanForRouteEntryPoints(
2655
ngModule: ts.SourceFile, moduleName: string, data: NgModuleRawRouteData,
2756
entryPointManager: RouterEntryPointManager, evaluator: PartialEvaluator): LazyRouteEntry[] {
@@ -152,6 +181,11 @@ const routerModuleFFR: ForeignFunctionResolver =
152181
]);
153182
};
154183

184+
function hasIdentifier(node: ts.Node): node is ts.Node&{name: ts.Identifier} {
185+
const node_ = node as ts.NamedDeclaration;
186+
return (node_.name !== undefined) && ts.isIdentifier(node_.name);
187+
}
188+
155189
function isMethodNodeReference(
156190
ref: Reference<ts.FunctionDeclaration|ts.MethodDeclaration|ts.FunctionExpression>):
157191
ref is NodeReference<ts.MethodDeclaration> {

0 commit comments

Comments
 (0)