feat(ivy): implement listing lazy routes for specific entry point in ngtsc (#28542)

Related: angular/angular-cli#13532

Jira issue: FW-860

PR Close #28542
This commit is contained in:
George Kalpakas
2019-02-05 16:47:35 +02:00
committed by Miško Hevery
parent f358188ec1
commit e6c51b3e06
4 changed files with 561 additions and 86 deletions

View File

@ -192,9 +192,7 @@ export class NgtscProgram implements api.Program {
listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
this.ensureAnalyzed();
// Listing specific routes is unsupported for now, so we erroneously return
// all lazy routes instead (which should be okay for the CLI's usage).
return this.routeAnalyzer !.listLazyRoutes();
return this.routeAnalyzer !.listLazyRoutes(entryRoute);
}
getLibrarySummaries(): Map<string, api.LibrarySummary> {

View File

@ -11,7 +11,7 @@ import * as ts from 'typescript';
import {ModuleResolver} from '../../imports';
import {PartialEvaluator} from '../../partial_evaluator';
import {scanForRouteEntryPoints} from './lazy';
import {scanForCandidateTransitiveModules, scanForRouteEntryPoints} from './lazy';
import {RouterEntryPointManager, entryPointKeyFor} from './route';
export interface NgModuleRawRouteData {
@ -48,18 +48,53 @@ export class NgModuleRouteAnalyzer {
});
}
listLazyRoutes(): LazyRoute[] {
listLazyRoutes(entryModuleKey?: string|undefined): LazyRoute[] {
if ((entryModuleKey !== undefined) && !this.modules.has(entryModuleKey)) {
throw new Error(`Failed to list lazy routes: Unknown module '${entryModuleKey}'.`);
}
const routes: LazyRoute[] = [];
for (const key of Array.from(this.modules.keys())) {
const scannedModuleKeys = new Set<string>();
const pendingModuleKeys = entryModuleKey ? [entryModuleKey] : Array.from(this.modules.keys());
// When listing lazy routes for a specific entry module, we need to recursively extract
// "transitive" routes from imported/exported modules. This is not necessary when listing all
// lazy routes, because all analyzed modules will be scanned anyway.
const scanRecursively = entryModuleKey !== undefined;
while (pendingModuleKeys.length > 0) {
const key = pendingModuleKeys.pop() !;
if (scannedModuleKeys.has(key)) {
continue;
} else {
scannedModuleKeys.add(key);
}
const data = this.modules.get(key) !;
const entryPoints = scanForRouteEntryPoints(
data.sourceFile, data.moduleName, data, this.entryPointManager, this.evaluator);
routes.push(...entryPoints.map(entryPoint => ({
route: entryPoint.loadChildren,
module: entryPoint.from,
referencedModule: entryPoint.resolvedTo,
})));
if (scanRecursively) {
pendingModuleKeys.push(
...[
// Scan the retrieved lazy route entry points.
...entryPoints.map(
({resolvedTo}) => entryPointKeyFor(resolvedTo.filePath, resolvedTo.moduleName)),
// Scan the current module's imported modules.
...scanForCandidateTransitiveModules(data.imports, this.evaluator),
// Scan the current module's exported modules.
...scanForCandidateTransitiveModules(data.exports, this.evaluator),
].filter(key => this.modules.has(key)));
}
}
return routes;
}
}

View File

@ -12,7 +12,7 @@ import {AbsoluteReference, NodeReference, Reference} from '../../imports';
import {ForeignFunctionResolver, PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
import {NgModuleRawRouteData} from './analyzer';
import {RouterEntryPoint, RouterEntryPointManager} from './route';
import {RouterEntryPoint, RouterEntryPointManager, entryPointKeyFor} from './route';
const ROUTES_MARKER = '__ngRoutesMarker__';
@ -22,6 +22,35 @@ export interface LazyRouteEntry {
resolvedTo: RouterEntryPoint;
}
export function scanForCandidateTransitiveModules(
expr: ts.Expression | null, evaluator: PartialEvaluator): string[] {
if (expr === null) {
return [];
}
const candidateModuleKeys: string[] = [];
const entries = evaluator.evaluate(expr);
function recursivelyAddModules(entry: ResolvedValue) {
if (Array.isArray(entry)) {
for (const e of entry) {
recursivelyAddModules(e);
}
} else if (entry instanceof Map) {
if (entry.has('ngModule')) {
recursivelyAddModules(entry.get('ngModule') !);
}
} else if ((entry instanceof Reference) && hasIdentifier(entry.node)) {
const filePath = entry.node.getSourceFile().fileName;
const moduleName = entry.node.name.text;
candidateModuleKeys.push(entryPointKeyFor(filePath, moduleName));
}
}
recursivelyAddModules(entries);
return candidateModuleKeys;
}
export function scanForRouteEntryPoints(
ngModule: ts.SourceFile, moduleName: string, data: NgModuleRawRouteData,
entryPointManager: RouterEntryPointManager, evaluator: PartialEvaluator): LazyRouteEntry[] {
@ -152,6 +181,11 @@ const routerModuleFFR: ForeignFunctionResolver =
]);
};
function hasIdentifier(node: ts.Node): node is ts.Node&{name: ts.Identifier} {
const node_ = node as ts.NamedDeclaration;
return (node_.name !== undefined) && ts.isIdentifier(node_.name);
}
function isMethodNodeReference(
ref: Reference<ts.FunctionDeclaration|ts.MethodDeclaration|ts.FunctionExpression>):
ref is NodeReference<ts.MethodDeclaration> {