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:
parent
f358188ec1
commit
e6c51b3e06
|
@ -192,9 +192,7 @@ export class NgtscProgram implements api.Program {
|
||||||
|
|
||||||
listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
|
listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
|
||||||
this.ensureAnalyzed();
|
this.ensureAnalyzed();
|
||||||
// Listing specific routes is unsupported for now, so we erroneously return
|
return this.routeAnalyzer !.listLazyRoutes(entryRoute);
|
||||||
// all lazy routes instead (which should be okay for the CLI's usage).
|
|
||||||
return this.routeAnalyzer !.listLazyRoutes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getLibrarySummaries(): Map<string, api.LibrarySummary> {
|
getLibrarySummaries(): Map<string, api.LibrarySummary> {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
||||||
import {ModuleResolver} from '../../imports';
|
import {ModuleResolver} from '../../imports';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
|
|
||||||
import {scanForRouteEntryPoints} from './lazy';
|
import {scanForCandidateTransitiveModules, scanForRouteEntryPoints} from './lazy';
|
||||||
import {RouterEntryPointManager, entryPointKeyFor} from './route';
|
import {RouterEntryPointManager, entryPointKeyFor} from './route';
|
||||||
|
|
||||||
export interface NgModuleRawRouteData {
|
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[] = [];
|
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 data = this.modules.get(key) !;
|
||||||
const entryPoints = scanForRouteEntryPoints(
|
const entryPoints = scanForRouteEntryPoints(
|
||||||
data.sourceFile, data.moduleName, data, this.entryPointManager, this.evaluator);
|
data.sourceFile, data.moduleName, data, this.entryPointManager, this.evaluator);
|
||||||
|
|
||||||
routes.push(...entryPoints.map(entryPoint => ({
|
routes.push(...entryPoints.map(entryPoint => ({
|
||||||
route: entryPoint.loadChildren,
|
route: entryPoint.loadChildren,
|
||||||
module: entryPoint.from,
|
module: entryPoint.from,
|
||||||
referencedModule: entryPoint.resolvedTo,
|
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;
|
return routes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {AbsoluteReference, NodeReference, Reference} from '../../imports';
|
||||||
import {ForeignFunctionResolver, PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
import {ForeignFunctionResolver, PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
||||||
|
|
||||||
import {NgModuleRawRouteData} from './analyzer';
|
import {NgModuleRawRouteData} from './analyzer';
|
||||||
import {RouterEntryPoint, RouterEntryPointManager} from './route';
|
import {RouterEntryPoint, RouterEntryPointManager, entryPointKeyFor} from './route';
|
||||||
|
|
||||||
const ROUTES_MARKER = '__ngRoutesMarker__';
|
const ROUTES_MARKER = '__ngRoutesMarker__';
|
||||||
|
|
||||||
|
@ -22,6 +22,35 @@ export interface LazyRouteEntry {
|
||||||
resolvedTo: RouterEntryPoint;
|
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(
|
export function scanForRouteEntryPoints(
|
||||||
ngModule: ts.SourceFile, moduleName: string, data: NgModuleRawRouteData,
|
ngModule: ts.SourceFile, moduleName: string, data: NgModuleRawRouteData,
|
||||||
entryPointManager: RouterEntryPointManager, evaluator: PartialEvaluator): LazyRouteEntry[] {
|
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(
|
function isMethodNodeReference(
|
||||||
ref: Reference<ts.FunctionDeclaration|ts.MethodDeclaration|ts.FunctionExpression>):
|
ref: Reference<ts.FunctionDeclaration|ts.MethodDeclaration|ts.FunctionExpression>):
|
||||||
ref is NodeReference<ts.MethodDeclaration> {
|
ref is NodeReference<ts.MethodDeclaration> {
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {LazyRoute} from '@angular/compiler-cli/src/ngtsc/routing';
|
||||||
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {NgtscTestEnvironment} from './env';
|
import {NgtscTestEnvironment} from './env';
|
||||||
|
@ -1439,13 +1441,13 @@ describe('ngtsc behavioral tests', () => {
|
||||||
env.write('test.ts', `
|
env.write('test.ts', `
|
||||||
import {Component, NgModule} from '@angular/core';
|
import {Component, NgModule} from '@angular/core';
|
||||||
import {NormalComponent} from './cyclic';
|
import {NormalComponent} from './cyclic';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'cyclic-component',
|
selector: 'cyclic-component',
|
||||||
template: 'Importing this causes a cycle',
|
template: 'Importing this causes a cycle',
|
||||||
})
|
})
|
||||||
export class CyclicComponent {}
|
export class CyclicComponent {}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [NormalComponent, CyclicComponent],
|
declarations: [NormalComponent, CyclicComponent],
|
||||||
})
|
})
|
||||||
|
@ -1454,7 +1456,7 @@ describe('ngtsc behavioral tests', () => {
|
||||||
|
|
||||||
env.write('cyclic.ts', `
|
env.write('cyclic.ts', `
|
||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'normal-component',
|
selector: 'normal-component',
|
||||||
template: '<cyclic-component></cyclic-component>',
|
template: '<cyclic-component></cyclic-component>',
|
||||||
|
@ -1992,91 +1994,497 @@ describe('ngtsc behavioral tests', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect all lazy routes', () => {
|
describe('listLazyRoutes()', () => {
|
||||||
env.tsconfig();
|
// clang-format off
|
||||||
env.write('test.ts', `
|
const lazyRouteMatching = (
|
||||||
import {NgModule} from '@angular/core';
|
route: string, fromModulePath: RegExp, fromModuleName: string, toModulePath: RegExp,
|
||||||
import {RouterModule} from '@angular/router';
|
toModuleName: string) => {
|
||||||
|
return {
|
||||||
|
route,
|
||||||
|
module: jasmine.objectContaining({
|
||||||
|
name: fromModuleName,
|
||||||
|
filePath: jasmine.stringMatching(fromModulePath),
|
||||||
|
}),
|
||||||
|
referencedModule: jasmine.objectContaining({
|
||||||
|
name: toModuleName,
|
||||||
|
filePath: jasmine.stringMatching(toModulePath),
|
||||||
|
}),
|
||||||
|
} as unknown as LazyRoute;
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
@NgModule({
|
beforeEach(() => {
|
||||||
imports: [
|
env.tsconfig();
|
||||||
RouterModule.forChild([
|
env.write('node_modules/@angular/router/index.d.ts', `
|
||||||
{path: '', loadChildren: './lazy#LazyModule'},
|
import {ModuleWithProviders} from '@angular/core';
|
||||||
]),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class TestModule {}
|
|
||||||
`);
|
|
||||||
env.write('lazy.ts', `
|
|
||||||
import {NgModule} from '@angular/core';
|
|
||||||
import {RouterModule} from '@angular/router';
|
|
||||||
|
|
||||||
@NgModule({})
|
export declare var ROUTES;
|
||||||
export class LazyModule {}
|
export declare class RouterModule {
|
||||||
`);
|
static forRoot(arg1: any, arg2: any): ModuleWithProviders<RouterModule>;
|
||||||
env.write('node_modules/@angular/router/index.d.ts', `
|
static forChild(arg1: any): ModuleWithProviders<RouterModule>;
|
||||||
import {ModuleWithProviders} from '@angular/core';
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
export declare var ROUTES;
|
describe('when called without arguments', () => {
|
||||||
export declare class RouterModule {
|
it('should list all routes', () => {
|
||||||
static forRoot(arg1: any, arg2: any): ModuleWithProviders<RouterModule>;
|
env.write('test.ts', `
|
||||||
static forChild(arg1: any): ModuleWithProviders<RouterModule>;
|
import {NgModule} from '@angular/core';
|
||||||
}
|
import {RouterModule} from '@angular/router';
|
||||||
`);
|
|
||||||
|
|
||||||
const routes = env.driveRoutes();
|
@NgModule({
|
||||||
expect(routes.length).toBe(1);
|
imports: [
|
||||||
expect(routes[0].route).toEqual('./lazy#LazyModule');
|
RouterModule.forRoot([
|
||||||
expect(routes[0].module.filePath.endsWith('/test.ts')).toBe(true);
|
{path: '1', loadChildren: './lazy/lazy-1#Lazy1Module'},
|
||||||
expect(routes[0].referencedModule.filePath.endsWith('/lazy.ts')).toBe(true);
|
{path: '2', loadChildren: './lazy/lazy-2#Lazy2Module'},
|
||||||
});
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class TestModule {}
|
||||||
|
`);
|
||||||
|
env.write('lazy/lazy-1.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
it('should detect lazy routes in simple children routes', () => {
|
@NgModule({})
|
||||||
env.tsconfig();
|
export class Lazy1Module {}
|
||||||
env.write('test.ts', `
|
`);
|
||||||
import {NgModule} from '@angular/core';
|
env.write('lazy/lazy-2.ts', `
|
||||||
import {RouterModule} from '@angular/router';
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
@Component({
|
|
||||||
selector: 'foo',
|
|
||||||
template: '<div>Foo</div>'
|
|
||||||
})
|
|
||||||
class FooCmp {}
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forRoot([
|
RouterModule.forChild([
|
||||||
{path: '', children: [
|
{path: '3', loadChildren: './lazy-3#Lazy3Module'},
|
||||||
{path: 'foo', component: FooCmp},
|
]),
|
||||||
{path: 'lazy', loadChildren: './lazy#LazyModule'}
|
],
|
||||||
]},
|
})
|
||||||
]),
|
export class Lazy2Module {}
|
||||||
],
|
`);
|
||||||
})
|
env.write('lazy/lazy-3.ts', `
|
||||||
export class TestModule {}
|
import {NgModule} from '@angular/core';
|
||||||
`);
|
|
||||||
env.write('lazy.ts', `
|
|
||||||
import {NgModule} from '@angular/core';
|
|
||||||
import {RouterModule} from '@angular/router';
|
|
||||||
|
|
||||||
@NgModule({})
|
@NgModule({})
|
||||||
export class LazyModule {}
|
export class Lazy3Module {}
|
||||||
`);
|
`);
|
||||||
env.write('node_modules/@angular/router/index.d.ts', `
|
|
||||||
import {ModuleWithProviders} from '@angular/core';
|
|
||||||
|
|
||||||
export declare var ROUTES;
|
const routes = env.driveRoutes();
|
||||||
export declare class RouterModule {
|
expect(routes).toEqual([
|
||||||
static forRoot(arg1: any, arg2: any): ModuleWithProviders<RouterModule>;
|
lazyRouteMatching(
|
||||||
static forChild(arg1: any): ModuleWithProviders<RouterModule>;
|
'./lazy-3#Lazy3Module', /\/lazy\/lazy-2\.ts$/, 'Lazy2Module', /\/lazy\/lazy-3\.ts$/,
|
||||||
}
|
'Lazy3Module'),
|
||||||
`);
|
lazyRouteMatching(
|
||||||
|
'./lazy/lazy-1#Lazy1Module', /\/test\.ts$/, 'TestModule', /\/lazy\/lazy-1\.ts$/,
|
||||||
|
'Lazy1Module'),
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy/lazy-2#Lazy2Module', /\/test\.ts$/, 'TestModule', /\/lazy\/lazy-2\.ts$/,
|
||||||
|
'Lazy2Module'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
const routes = env.driveRoutes();
|
it('should detect lazy routes in simple children routes', () => {
|
||||||
expect(routes.length).toBe(1);
|
env.write('test.ts', `
|
||||||
expect(routes[0].route).toEqual('./lazy#LazyModule');
|
import {NgModule} from '@angular/core';
|
||||||
expect(routes[0].module.filePath.endsWith('/test.ts')).toBe(true);
|
import {RouterModule} from '@angular/router';
|
||||||
expect(routes[0].referencedModule.filePath.endsWith('/lazy.ts')).toBe(true);
|
|
||||||
|
@Component({
|
||||||
|
selector: 'foo',
|
||||||
|
template: '<div>Foo</div>'
|
||||||
|
})
|
||||||
|
class FooCmp {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot([
|
||||||
|
{path: '', children: [
|
||||||
|
{path: 'foo', component: FooCmp},
|
||||||
|
{path: 'lazy', loadChildren: './lazy#LazyModule'}
|
||||||
|
]},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class TestModule {}
|
||||||
|
`);
|
||||||
|
env.write('lazy.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class LazyModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const routes = env.driveRoutes();
|
||||||
|
expect(routes).toEqual([
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy#LazyModule', /\/test\.ts$/, 'TestModule', /\/lazy\.ts$/, 'LazyModule'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with entry module', () => {
|
||||||
|
it('should throw if the entry module hasn\'t been analyzed', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{path: '', loadChildren: './lazy#LazyModule'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class TestModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const entryModule1 = path.join(env.basePath, 'test#TestModule');
|
||||||
|
const entryModule2 = path.join(env.basePath, 'not-test#TestModule');
|
||||||
|
const entryModule3 = path.join(env.basePath, 'test#NotTestModule');
|
||||||
|
|
||||||
|
expect(() => env.driveRoutes(entryModule1)).not.toThrow();
|
||||||
|
expect(() => env.driveRoutes(entryModule2))
|
||||||
|
.toThrowError(`Failed to list lazy routes: Unknown module '${entryModule2}'.`);
|
||||||
|
expect(() => env.driveRoutes(entryModule3))
|
||||||
|
.toThrowError(`Failed to list lazy routes: Unknown module '${entryModule3}'.`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list all transitive lazy routes', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
import {Test1Module as Test1ModuleRenamed} from './test-1';
|
||||||
|
import {Test2Module} from './test-2';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
exports: [
|
||||||
|
Test1ModuleRenamed,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
Test2Module,
|
||||||
|
RouterModule.forRoot([
|
||||||
|
{path: '', loadChildren: './lazy/lazy#LazyModule'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class TestModule {}
|
||||||
|
`);
|
||||||
|
env.write('test-1.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{path: 'one', loadChildren: './lazy-1/lazy-1#Lazy1Module'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class Test1Module {}
|
||||||
|
`);
|
||||||
|
env.write('test-2.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
exports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{path: 'two', loadChildren: './lazy-2/lazy-2#Lazy2Module'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class Test2Module {}
|
||||||
|
`);
|
||||||
|
env.write('lazy/lazy.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class LazyModule {}
|
||||||
|
`);
|
||||||
|
env.write('lazy-1/lazy-1.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class Lazy1Module {}
|
||||||
|
`);
|
||||||
|
env.write('lazy-2/lazy-2.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class Lazy2Module {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const routes = env.driveRoutes(path.join(env.basePath, 'test#TestModule'));
|
||||||
|
|
||||||
|
expect(routes).toEqual([
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy/lazy#LazyModule', /\/test\.ts$/, 'TestModule', /\/lazy\/lazy\.ts$/,
|
||||||
|
'LazyModule'),
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy-1/lazy-1#Lazy1Module', /\/test-1\.ts$/, 'Test1Module',
|
||||||
|
/\/lazy-1\/lazy-1\.ts$/, 'Lazy1Module'),
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy-2/lazy-2#Lazy2Module', /\/test-2\.ts$/, 'Test2Module',
|
||||||
|
/\/lazy-2\/lazy-2\.ts$/, 'Lazy2Module'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore exports that do not refer to an `NgModule`', () => {
|
||||||
|
env.write('test-1.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
import {Test2Component, Test2Module} from './test-2';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
exports: [
|
||||||
|
Test2Component,
|
||||||
|
Test2Module,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot([
|
||||||
|
{path: '', loadChildren: './lazy-1/lazy-1#Lazy1Module'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class Test1Module {}
|
||||||
|
`);
|
||||||
|
env.write('test-2.ts', `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'test-2',
|
||||||
|
template: '',
|
||||||
|
})
|
||||||
|
export class Test2Component {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
Test2Component,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
Test2Component,
|
||||||
|
RouterModule.forChild([
|
||||||
|
{path: 'two', loadChildren: './lazy-2/lazy-2#Lazy2Module'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class Test2Module {}
|
||||||
|
`);
|
||||||
|
env.write('lazy-1/lazy-1.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class Lazy1Module {}
|
||||||
|
`);
|
||||||
|
env.write('lazy-2/lazy-2.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class Lazy2Module {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const routes = env.driveRoutes(path.join(env.basePath, 'test-1#Test1Module'));
|
||||||
|
|
||||||
|
expect(routes).toEqual([
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy-1/lazy-1#Lazy1Module', /\/test-1\.ts$/, 'Test1Module',
|
||||||
|
/\/lazy-1\/lazy-1\.ts$/, 'Lazy1Module'),
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy-2/lazy-2#Lazy2Module', /\/test-2\.ts$/, 'Test2Module',
|
||||||
|
/\/lazy-2\/lazy-2\.ts$/, 'Lazy2Module'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support `ModuleWithProviders`', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {ModuleWithProviders, NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{path: '', loadChildren: './lazy-2/lazy-2#Lazy2Module'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class TestRoutingModule {
|
||||||
|
static forRoot(): ModuleWithProviders<TestRoutingModule> {
|
||||||
|
return {
|
||||||
|
ngModule: TestRoutingModule,
|
||||||
|
providers: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
TestRoutingModule.forRoot(),
|
||||||
|
RouterModule.forRoot([
|
||||||
|
{path: '', loadChildren: './lazy-1/lazy-1#Lazy1Module'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class TestModule {}
|
||||||
|
`);
|
||||||
|
env.write('lazy-1/lazy-1.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class Lazy1Module {}
|
||||||
|
`);
|
||||||
|
env.write('lazy-2/lazy-2.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class Lazy2Module {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const routes = env.driveRoutes(path.join(env.basePath, 'test#TestModule'));
|
||||||
|
|
||||||
|
expect(routes).toEqual([
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy-1/lazy-1#Lazy1Module', /\/test\.ts$/, 'TestModule', /\/lazy-1\/lazy-1\.ts$/,
|
||||||
|
'Lazy1Module'),
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy-2/lazy-2#Lazy2Module', /\/test\.ts$/, 'TestRoutingModule',
|
||||||
|
/\/lazy-2\/lazy-2\.ts$/, 'Lazy2Module'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only process each module once', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{path: '', loadChildren: './lazy/lazy#LazyModule'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class SharedModule {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
SharedModule,
|
||||||
|
RouterModule.forRoot([
|
||||||
|
{path: '', loadChildren: './lazy/lazy#LazyModule'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class TestModule {}
|
||||||
|
`);
|
||||||
|
env.write('lazy/lazy.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{path: '', loadChildren: '../lazier/lazier#LazierModule'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class LazyModule {}
|
||||||
|
`);
|
||||||
|
env.write('lazier/lazier.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class LazierModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const routes = env.driveRoutes(path.join(env.basePath, 'test#TestModule'));
|
||||||
|
|
||||||
|
// `LazyModule` is referenced in both `SharedModule` and `TestModule`,
|
||||||
|
// but it is only processed once (hence one `LazierModule` entry).
|
||||||
|
expect(routes).toEqual([
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy/lazy#LazyModule', /\/test\.ts$/, 'TestModule', /\/lazy\/lazy\.ts$/,
|
||||||
|
'LazyModule'),
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy/lazy#LazyModule', /\/test\.ts$/, 'SharedModule', /\/lazy\/lazy\.ts$/,
|
||||||
|
'LazyModule'),
|
||||||
|
lazyRouteMatching(
|
||||||
|
'../lazier/lazier#LazierModule', /\/lazy\/lazy\.ts$/, 'LazyModule',
|
||||||
|
/\/lazier\/lazier\.ts$/, 'LazierModule'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore modules not (transitively) referenced by the entry module', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot([
|
||||||
|
{path: '', loadChildren: './lazy/lazy#Lazy1Module'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class Test1Module {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot([
|
||||||
|
{path: '', loadChildren: './lazy/lazy#Lazy2Module'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class Test2Module {}
|
||||||
|
`);
|
||||||
|
env.write('lazy/lazy.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class Lazy1Module {}
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class Lazy2Module {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const routes = env.driveRoutes(path.join(env.basePath, 'test#Test1Module'));
|
||||||
|
|
||||||
|
expect(routes).toEqual([
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy/lazy#Lazy1Module', /\/test\.ts$/, 'Test1Module', /\/lazy\/lazy\.ts$/,
|
||||||
|
'Lazy1Module'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore routes to unknown modules', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot([
|
||||||
|
{path: '', loadChildren: './unknown/unknown#UnknownModule'},
|
||||||
|
{path: '', loadChildren: './lazy/lazy#LazyModule'},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class TestModule {}
|
||||||
|
`);
|
||||||
|
env.write('lazy/lazy.ts', `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@NgModule({})
|
||||||
|
export class LazyModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const routes = env.driveRoutes(path.join(env.basePath, 'test#TestModule'));
|
||||||
|
|
||||||
|
expect(routes).toEqual([
|
||||||
|
lazyRouteMatching(
|
||||||
|
'./lazy/lazy#LazyModule', /\/test\.ts$/, 'TestModule', /\/lazy\/lazy\.ts$/,
|
||||||
|
'LazyModule'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue