Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions aio/content/guide/architecture-modules.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Introduction to modules

Angular apps are modular and Angular has its own modularity system called *NgModules*.
Angular apps are modular and Angular has its own modularity system called *NgModules*.
NgModules are containers for a cohesive block of code dedicated to an application domain, a workflow, or a closely related set of capabilities. They can contain components, service providers, and other code files whose scope is defined by the containing NgModule. They can import functionality that is exported from other NgModules, and export selected functionality for use by other NgModules.

Every Angular app has at least one NgModule class, [the *root module*](guide/bootstrapping), which is conventionally named `AppModule` and resides in a file named `app.module.ts`. You launch your app by *bootstrapping* the root NgModule.
Expand All @@ -27,7 +27,7 @@ Here's a simple root NgModule definition.

<div class="alert is-helpful">

The `export` property of `AppComponent` is included here for illustration; it isn't actually necessary in this example. A root NgModule has no reason to *export* anything because other modules don't need to *import* the root NgModule.
`AppComponent` is included in the `exports` list here for illustration; it isn't actually necessary in this example. A root NgModule has no reason to *export* anything because other modules don't need to *import* the root NgModule.

</div>

Expand Down Expand Up @@ -89,7 +89,7 @@ For example, import Angular's `Component` decorator from the `@angular/core` lib

<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example>

You also import NgModules from Angular *libraries* using JavaScript import statements.
You also import NgModules from Angular *libraries* using JavaScript import statements.
For example, the following code imports the `BrowserModule` NgModule from the `platform-browser` library.

<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>
Expand Down
6 changes: 1 addition & 5 deletions packages/compiler-cli/src/ngtsc/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,8 @@ export class NgtscProgram implements api.Program {
}

listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
if (entryRoute !== undefined) {
throw new Error(
`Listing specific routes is unsupported for now (got query for ${entryRoute})`);
}
this.ensureAnalyzed();
return this.routeAnalyzer !.listLazyRoutes();
return this.routeAnalyzer !.listLazyRoutes(entryRoute);
}

getLibrarySummaries(): Map<string, api.LibrarySummary> {
Expand Down
47 changes: 42 additions & 5 deletions packages/compiler-cli/src/ngtsc/routing/src/analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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} from './route';

export interface NgModuleRawRouteData {
Expand All @@ -38,28 +38,65 @@ 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 = this.entryPointManager.keyFor(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[] {
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}) => this.entryPointManager.keyFor(
resolvedTo.filePath, resolvedTo.moduleName)),
// Scan the current module's imported modules.
...scanForCandidateTransitiveModules(
data.imports, this.entryPointManager, this.evaluator),
// Scan the current module's exported modules.
...scanForCandidateTransitiveModules(
data.exports, this.entryPointManager, this.evaluator),
].filter(key => this.modules.has(key)));
}
}

return routes;
}
}
35 changes: 35 additions & 0 deletions packages/compiler-cli/src/ngtsc/routing/src/lazy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,36 @@ export interface LazyRouteEntry {
resolvedTo: RouterEntryPoint;
}

export function scanForCandidateTransitiveModules(
expr: ts.Expression | null, entryPointManager: RouterEntryPointManager,
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(entryPointManager.keyFor(filePath, moduleName));
}
}

recursivelyAddModules(entries);
return candidateModuleKeys;
}

export function scanForRouteEntryPoints(
ngModule: ts.SourceFile, moduleName: string, data: NgModuleRawRouteData,
entryPointManager: RouterEntryPointManager, evaluator: PartialEvaluator): LazyRouteEntry[] {
Expand Down Expand Up @@ -152,6 +182,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> {
Expand Down
16 changes: 8 additions & 8 deletions packages/compiler-cli/src/ngtsc/routing/src/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,26 @@ 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}`; }
}

export class RouterEntryPointManager {
private map = new Map<string, RouterEntryPoint>();

constructor(private moduleResolver: ModuleResolver) {}

keyFor(filePath: string, moduleName: string): string {
// Drop the extension to be compatible with how cli calls `listLazyRoutes(entryRoute)`.
return `${filePath.replace(/\.tsx?$/i, '')}#${moduleName}`;
}

resolveLoadChildrenIdentifier(loadChildrenIdentifier: string, context: ts.SourceFile):
RouterEntryPoint|null {
const [relativeFile, moduleName] = loadChildrenIdentifier.split('#');
Expand All @@ -48,10 +49,9 @@ export class RouterEntryPointManager {
}

fromNgModule(sf: ts.SourceFile, moduleName: string): RouterEntryPoint {
const absoluteFile = sf.fileName;
const key = `${absoluteFile}#${moduleName}`;
const key = this.keyFor(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) !;
}
Expand Down
1 change: 0 additions & 1 deletion packages/compiler-cli/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ ts_library(
":test_utils",
"//packages/compiler",
"//packages/compiler-cli",
"//packages/private/testing",
"@ngdeps//typescript",
],
)
Expand Down
3 changes: 1 addition & 2 deletions packages/compiler-cli/test/ngtools_api_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
*/

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

Expand Down Expand Up @@ -60,7 +59,7 @@ describe('ngtools_api (deprecated)', () => {
});
}

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