Repository navigation
feat(ivy): implement listing lazy routes for specific entry point in ngtsc
#28542
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9e99b0d
b16b24c
7407b03
b96f00c
b3af7ea
be15fc4
2ae662c
1ffcbb1
2dee503
242976c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,8 +11,8 @@ import * as ts from 'typescript'; | |
| import {ModuleResolver} from '../../imports'; | ||
| import {PartialEvaluator} from '../../partial_evaluator'; | ||
|
|
||
| import {scanForRouteEntryPoints} from './lazy'; | ||
| import {RouterEntryPointManager} from './route'; | ||
| import {scanForCandidateTransitiveModules, scanForRouteEntryPoints} from './lazy'; | ||
| import {RouterEntryPointManager, entryPointKeyFor} from './route'; | ||
|
|
||
| export interface NgModuleRawRouteData { | ||
| sourceFile: ts.SourceFile; | ||
|
|
@@ -38,28 +38,63 @@ export class NgModuleRouteAnalyzer { | |
|
|
||
| add(sourceFile: ts.SourceFile, moduleName: string, imports: ts.Expression|null, | ||
| exports: ts.Expression|null, providers: ts.Expression|null): void { | ||
| const key = `${sourceFile.fileName}#${moduleName}`; | ||
| const key = entryPointKeyFor(sourceFile.fileName, moduleName); | ||
| if (this.modules.has(key)) { | ||
| throw new Error(`Double route analyzing ${key}`); | ||
| throw new Error(`Double route analyzing for '${key}'.`); | ||
| } | ||
| this.modules.set( | ||
| key, { | ||
| sourceFile, moduleName, imports, exports, providers, | ||
| }); | ||
| } | ||
|
|
||
| listLazyRoutes(): LazyRoute[] { | ||
| listLazyRoutes(entryModuleKey?: string|undefined): LazyRoute[] { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't it annoying that putting a
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically, they mean different things tho.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I know but the end result is the same for the type in the body of the function... if it has a
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAICT, it doesn't make a different for TS, but I was keeping consistent with program.ts (just in case I miss something). |
||
| 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; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,18 +15,17 @@ export abstract class RouterEntryPoint { | |
|
|
||
| abstract readonly moduleName: string; | ||
|
|
||
| // Alias of moduleName. | ||
| // Alias of moduleName for compatibility with what `ngtools_api` returned. | ||
| abstract readonly name: string; | ||
|
|
||
| abstract toString(): string; | ||
| } | ||
|
|
||
| class RouterEntryPointImpl implements RouterEntryPoint { | ||
| constructor(readonly filePath: string, readonly moduleName: string) {} | ||
|
|
||
| get name(): string { return this.moduleName; } | ||
|
|
||
| toString(): string { return `${this.filePath}#${this.moduleName}`; } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Happy to bring that back then 😃 |
||
| // For debugging purposes. | ||
| toString(): string { return `RouterEntryPoint(name: ${this.name}, filePath: ${this.filePath})`; } | ||
| } | ||
|
|
||
| export class RouterEntryPointManager { | ||
|
|
@@ -48,11 +47,15 @@ export class RouterEntryPointManager { | |
| } | ||
|
|
||
| fromNgModule(sf: ts.SourceFile, moduleName: string): RouterEntryPoint { | ||
| const absoluteFile = sf.fileName; | ||
| const key = `${absoluteFile}#${moduleName}`; | ||
| const key = entryPointKeyFor(sf.fileName, moduleName); | ||
| if (!this.map.has(key)) { | ||
| this.map.set(key, new RouterEntryPointImpl(absoluteFile, moduleName)); | ||
| this.map.set(key, new RouterEntryPointImpl(sf.fileName, moduleName)); | ||
| } | ||
| return this.map.get(key) !; | ||
| } | ||
| } | ||
|
|
||
| export function entryPointKeyFor(filePath: string, moduleName: string): string { | ||
| // Drop the extension to be compatible with how cli calls `listLazyRoutes(entryRoute)`. | ||
| return `${filePath.replace(/\.tsx?$/i, '')}#${moduleName}`; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ | |
| */ | ||
|
|
||
| import {__NGTOOLS_PRIVATE_API_2 as NgTools_InternalApi_NG_2} from '@angular/compiler-cli'; | ||
| import {fixmeIvy} from '@angular/private/testing'; | ||
| import {fixmeIvy, ivyEnabled} from '@angular/private/testing'; | ||
| import * as path from 'path'; | ||
| import * as ts from 'typescript'; | ||
|
|
||
|
|
@@ -19,7 +19,7 @@ describe('ngtools_api (deprecated)', () => { | |
| beforeEach(() => { testSupport = setup(); }); | ||
|
|
||
| function createProgram(rootNames: string[]) { | ||
| const options = testSupport.createCompilerOptions(); | ||
| const options = testSupport.createCompilerOptions({enableIvy: ivyEnabled && 'ngtsc'}); | ||
| const host = ts.createCompilerHost(options, true); | ||
| const program = | ||
| ts.createProgram(rootNames.map(p => path.resolve(testSupport.basePath, p)), options, host); | ||
|
|
@@ -37,7 +37,7 @@ describe('ngtools_api (deprecated)', () => { | |
| export class ErrorComp2 {} | ||
|
|
||
| @NgModule({ | ||
| declarations: [ErrorComp2, NonExistentComp], | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As well it should! |
||
| declarations: [ErrorComp2], | ||
| imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])] | ||
| }) | ||
| export class MainModule {} | ||
|
|
@@ -60,9 +60,10 @@ describe('ngtools_api (deprecated)', () => { | |
| }); | ||
| } | ||
|
|
||
| fixmeIvy('FW-629: ngtsc lists lazy routes').it('should list lazy routes recursively', () => { | ||
| it('should list lazy routes recursively', () => { | ||
| writeSomeRoutes(); | ||
| const {program, host, options} = createProgram(['src/main.ts']); | ||
| const {program, host, options} = | ||
| createProgram(['src/main.ts', 'src/child.ts', 'src/child2.ts']); | ||
| const routes = NgTools_InternalApi_NG_2.listLazyRoutes({ | ||
| program, | ||
| host, | ||
|
|
@@ -77,7 +78,8 @@ describe('ngtools_api (deprecated)', () => { | |
|
|
||
| it('should allow to emit the program after analyzing routes', () => { | ||
| writeSomeRoutes(); | ||
| const {program, host, options} = createProgram(['src/main.ts']); | ||
| const {program, host, options} = | ||
| createProgram(['src/main.ts', 'src/child.ts', 'src/child2.ts']); | ||
| NgTools_InternalApi_NG_2.listLazyRoutes({ | ||
| program, | ||
| host, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.