Skip to content

Commit 6fe3a31

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 aa8f538 commit 6fe3a31

File tree

6 files changed

+443
-98
lines changed

6 files changed

+443
-98
lines changed

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

+1-5
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,8 @@ export class NgtscProgram implements api.Program {
191191
}
192192

193193
listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
194-
if (entryRoute !== undefined) {
195-
throw new Error(
196-
`Listing specific routes is unsupported for now (got query for ${entryRoute})`);
197-
}
198194
this.ensureAnalyzed();
199-
return this.routeAnalyzer !.listLazyRoutes();
195+
return this.routeAnalyzer !.listLazyRoutes(entryRoute);
200196
}
201197

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

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

+40-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} from './route';
1616

1717
export interface NgModuleRawRouteData {
@@ -48,18 +48,55 @@ 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+
// Scan the retrieved lazy route entry points.
87+
...entryPoints.map(
88+
({resolvedTo}) => this.entryPointManager.keyFor(
89+
resolvedTo.filePath, resolvedTo.moduleName)),
90+
// Scan the current module's imported modules.
91+
...scanForCandidateTransitiveModules(
92+
data.imports, this.entryPointManager, this.evaluator),
93+
// Scan the current module's exported modules.
94+
...scanForCandidateTransitiveModules(
95+
data.exports, this.entryPointManager, this.evaluator),
96+
].filter(key => this.modules.has(key)));
97+
}
6298
}
99+
63100
return routes;
64101
}
65102
}

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

+35
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,36 @@ export interface LazyRouteEntry {
2222
resolvedTo: RouterEntryPoint;
2323
}
2424

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

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

packages/compiler-cli/test/BUILD.bazel

-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ ts_library(
106106
":test_utils",
107107
"//packages/compiler",
108108
"//packages/compiler-cli",
109-
"//packages/private/testing",
110109
"@ngdeps//typescript",
111110
],
112111
)

packages/compiler-cli/test/ngtools_api_spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88

99
import {__NGTOOLS_PRIVATE_API_2 as NgTools_InternalApi_NG_2} from '@angular/compiler-cli';
10-
import {fixmeIvy} from '@angular/private/testing';
1110
import * as path from 'path';
1211
import * as ts from 'typescript';
1312

@@ -60,7 +59,7 @@ describe('ngtools_api (deprecated)', () => {
6059
});
6160
}
6261

63-
fixmeIvy('FW-629: ngtsc lists lazy routes').it('should list lazy routes recursively', () => {
62+
fit('should list lazy routes recursively', () => {
6463
writeSomeRoutes();
6564
const {program, host, options} = createProgram(['src/main.ts']);
6665
const routes = NgTools_InternalApi_NG_2.listLazyRoutes({

0 commit comments

Comments
 (0)