refactor(compiler): remove old ngtools api and add listLazyRoutes to new api (#19836)

Usages of `NgTools_InternalApi_NG_2` from `@angular/compiler-cli` will now
throw an error.

Adds `listLazyRoutes` to `@angular/compiler-cli/ngtools2.ts` for getting
the lazy routes of a `ng.Program`.
PR Close #19836
This commit is contained in:
Tobias Bosch
2017-10-20 09:46:41 -07:00
committed by Matias Niemelä
parent 5da96c75a2
commit 8d45fefc31
41 changed files with 1128 additions and 1837 deletions

View File

@ -1,310 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {METADATA_VERSION, ModuleMetadata} from '@angular/compiler-cli';
import * as ts from 'typescript';
import {CompilerHost} from '../src/compiler_host';
import {Directory, Entry, MockAotContext, MockCompilerHost} from './mocks';
describe('CompilerHost', () => {
let context: MockAotContext;
let program: ts.Program;
let hostNestedGenDir: CompilerHost;
let hostSiblingGenDir: CompilerHost;
beforeEach(() => {
context = new MockAotContext('/tmp/src', clone(FILES));
const host = new MockCompilerHost(context);
program = ts.createProgram(
['main.ts'], {
module: ts.ModuleKind.CommonJS,
},
host);
// Force a typecheck
const errors = program.getSemanticDiagnostics();
if (errors && errors.length) {
throw new Error('Expected no errors');
}
hostNestedGenDir = new CompilerHost(
program, {
genDir: '/tmp/project/src/gen/',
basePath: '/tmp/project/src',
skipMetadataEmit: false,
strictMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
hostSiblingGenDir = new CompilerHost(
program, {
genDir: '/tmp/project/gen',
basePath: '/tmp/project/src/',
skipMetadataEmit: false,
strictMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
});
describe('nestedGenDir', () => {
it('should import node_module from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts',
'/tmp/project/src/gen/my.ngfactory.ts', ))
.toEqual('@angular/core');
});
it('should import factory from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.css.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.css.ngstyle');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.shim.ngstyle.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other.shim.ngstyle');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.sass.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.sass.ngstyle');
});
it('should import application from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../my.other');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../../my.other');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/my.other');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/my.other.css');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/my.other.css.shim');
});
});
describe('siblingGenDir', () => {
it('should import node_module from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts',
'/tmp/project/src/gen/my.ngfactory.ts'))
.toEqual('@angular/core');
});
it('should import factory from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.css');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other.css.shim');
});
it('should import application from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other');
});
});
it('should be able to produce an import from main @angular/core', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts', '/tmp/project/src/main.ts'))
.toEqual('@angular/core');
});
it('should be able to produce an import to a shallow import', () => {
expect(hostNestedGenDir.fileNameToModuleName('@angular/core', '/tmp/project/src/main.ts'))
.toEqual('@angular/core');
expect(hostNestedGenDir.fileNameToModuleName(
'@angular/upgrade/static', '/tmp/project/src/main.ts'))
.toEqual('@angular/upgrade/static');
expect(hostNestedGenDir.fileNameToModuleName('myLibrary', '/tmp/project/src/main.ts'))
.toEqual('myLibrary');
expect(hostNestedGenDir.fileNameToModuleName('lib23-43', '/tmp/project/src/main.ts'))
.toEqual('lib23-43');
});
it('should be able to produce an import from main to a sub-directory', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'main.ts')).toEqual('./lib/utils');
});
it('should be able to produce an import from to a peer file', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/collections.ts', 'lib/utils.ts'))
.toEqual('./collections');
});
it('should be able to produce an import from to a sibling directory', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'lib2/utils2.ts'))
.toEqual('../lib/utils');
});
it('should be able to read a metadata file', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([
{__symbolic: 'module', version: METADATA_VERSION, metadata: {foo: {__symbolic: 'class'}}}
]);
});
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toEqual([
dummyMetadata
]);
});
it('should be able to read empty metadata ', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts')).toEqual([]);
});
it('should return undefined for missing modules', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
});
it(`should add missing v${METADATA_VERSION} metadata from v1 metadata and .d.ts files`, () => {
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1.d.ts')).toEqual([
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {
foo: {__symbolic: 'class'},
aType: {__symbolic: 'interface'},
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
},
exports: [{from: './lib/utils2', export: ['Export']}],
}
]);
});
it(`should upgrade a missing metadata file into v${METADATA_VERSION}`, () => {
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1_empty.d.ts')).toEqual([{
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {},
exports: [{from: './lib/utils'}]
}]);
});
it(`should upgrade v3 metadata into v${METADATA_VERSION}`, () => {
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v3.d.ts')).toEqual([
{__symbolic: 'module', version: 3, metadata: {foo: {__symbolic: 'class'}}}, {
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {
foo: {__symbolic: 'class'},
aType: {__symbolic: 'interface'},
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
}
// Note: exports is missing because it was elided in the original.
}
]);
});
});
const dummyModule = 'export let foo: any[];';
const dummyMetadata: ModuleMetadata = {
__symbolic: 'module',
version: METADATA_VERSION,
metadata:
{foo: {__symbolic: 'error', message: 'Variable not initialized', line: 0, character: 11}}
};
const FILES: Entry = {
'tmp': {
'src': {
'main.ts': `
import * as c from '@angular/core';
import * as r from '@angular/router';
import * as u from './lib/utils';
import * as cs from './lib/collections';
import * as u2 from './lib2/utils2';
`,
'lib': {
'utils.ts': dummyModule,
'collections.ts': dummyModule,
},
'lib2': {'utils2.ts': dummyModule},
'node_modules': {
'@angular': {
'core.d.ts': dummyModule,
'core.metadata.json':
`{"__symbolic":"module", "version": ${METADATA_VERSION}, "metadata": {"foo": {"__symbolic": "class"}}}`,
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
'unused.d.ts': dummyModule,
'empty.d.ts': 'export declare var a: string;',
'empty.metadata.json': '[]',
}
},
'metadata_versions': {
'v1.d.ts': `
import {ReExport} from './lib/utils2';
export {ReExport};
export {Export} from './lib/utils2';
export type aType = number;
export declare class Bar {
ngOnInit() {}
}
export declare class BarChild extends Bar {}
`,
'v1.metadata.json':
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
'v1_empty.d.ts': `
export * from './lib/utils';
`,
'v3.d.ts': `
import {ReExport} from './lib/utils2';
export {ReExport};
export {Export} from './lib/utils2';
export type aType = number;
export declare class Bar {
ngOnInit() {}
}
export declare class BarChild extends Bar {}
`,
'v3.metadata.json':
`{"__symbolic":"module", "version": 3, "metadata": {"foo": {"__symbolic": "class"}}}`,
}
}
}
};
function clone(entry: Entry): Entry {
if (typeof entry === 'string') {
return entry;
} else {
const result: Directory = {};
for (const name in entry) {
result[name] = clone(entry[name]);
}
return result;
}
}

View File

@ -8,6 +8,7 @@
import {StaticSymbol} from '@angular/compiler';
import {CompilerHost} from '@angular/compiler-cli';
import {ReflectorHost} from '@angular/language-service/src/reflector_host';
import * as ts from 'typescript';
import {getExpressionDiagnostics, getTemplateExpressionDiagnostics} from '../../src/diagnostics/expression_diagnostics';
@ -21,7 +22,6 @@ describe('expression diagnostics', () => {
let host: MockLanguageServiceHost;
let service: ts.LanguageService;
let context: DiagnosticContext;
let aotHost: CompilerHost;
let type: StaticSymbol;
beforeAll(() => {
@ -33,8 +33,8 @@ describe('expression diagnostics', () => {
const options: CompilerOptions = Object.create(host.getCompilationSettings());
options.genDir = '/dist';
options.basePath = '/src';
aotHost = new CompilerHost(program, options, host, {verboseInvalidExpression: true});
context = new DiagnosticContext(service, program, checker, aotHost);
const symbolResolverHost = new ReflectorHost(() => program, host, options);
context = new DiagnosticContext(service, program, checker, symbolResolverHost);
type = context.getStaticSymbol('app/app.component.ts', 'AppComponent');
});

View File

@ -6,9 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost, AotSummaryResolver, CompileMetadataResolver, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, InterpolationConfig, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, ParseTreeResult, Parser, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, SummaryResolver, TemplateParser, analyzeNgModules, createOfflineCompileUrlResolver} from '@angular/compiler';
import {AotCompilerHost, AotSummaryResolver, CompileMetadataResolver, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, InterpolationConfig, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, ParseTreeResult, Parser, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, SummaryResolver, TemplateParser, analyzeNgModules, createOfflineCompileUrlResolver} from '@angular/compiler';
import {ViewEncapsulation, ɵConsole as Console} from '@angular/core';
import {CompilerHostContext} from 'compiler-cli';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
@ -25,7 +24,7 @@ function calcRootPath() {
const realFiles = new Map<string, string>();
export class MockLanguageServiceHost implements ts.LanguageServiceHost, CompilerHostContext {
export class MockLanguageServiceHost implements ts.LanguageServiceHost {
private options: ts.CompilerOptions;
private context: MockAotContext;
private assumedExist = new Set<string>();
@ -122,7 +121,7 @@ export class DiagnosticContext {
constructor(
public service: ts.LanguageService, public program: ts.Program,
public checker: ts.TypeChecker, public host: AotCompilerHost) {}
public checker: ts.TypeChecker, public host: StaticSymbolResolverHost) {}
private collectError(e: any, path?: string) { this._errors.push({e, path}); }

View File

@ -9,6 +9,7 @@
import {StaticSymbol} from '@angular/compiler';
import {CompilerHost} from '@angular/compiler-cli';
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, arrayToMockMap, isSource, settings, setup, toMockFileArray} from '@angular/compiler/test/aot/test_util';
import {ReflectorHost} from '@angular/language-service/src/reflector_host';
import * as ts from 'typescript';
import {Symbol, SymbolQuery, SymbolTable} from '../../src/diagnostics/symbols';
@ -44,8 +45,8 @@ describe('symbol query', () => {
const options: CompilerOptions = Object.create(host.getCompilationSettings());
options.genDir = '/dist';
options.basePath = '/quickstart';
const aotHost = new CompilerHost(program, options, host, {verboseInvalidExpression: true});
context = new DiagnosticContext(service, program, checker, aotHost);
const symbolResolverHost = new ReflectorHost(() => program, host, options);
context = new DiagnosticContext(service, program, checker, symbolResolverHost);
query = getSymbolQuery(program, checker, sourceFile, emptyPipes);
});

View File

@ -6,14 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompilerHostContext} from '@angular/compiler-cli/src/compiler_host';
import * as ts from 'typescript';
export type Entry = string | Directory;
export interface Directory { [name: string]: Entry; }
export class MockAotContext implements CompilerHostContext {
export class MockAotContext {
constructor(public currentDirectory: string, private files: Entry) {}
fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }

View File

@ -0,0 +1,90 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as path from 'path';
import * as ts from 'typescript';
import {NgTools_InternalApi_NG_2} from '../src/ngtools_api';
import {TestSupport, setup} from './test_support';
describe('ngtools_api (deprecated)', () => {
let testSupport: TestSupport;
beforeEach(() => { testSupport = setup(); });
function createProgram(rootNames: string[]) {
const options = testSupport.createCompilerOptions();
const host = ts.createCompilerHost(options, true);
const program =
ts.createProgram(rootNames.map(p => path.resolve(testSupport.basePath, p)), options, host);
return {program, host, options};
}
function writeSomeRoutes() {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule, Component} from '@angular/core';
import {RouterModule} from '@angular/router';
// Component with metadata errors.
@Component(() => {if (1==1) return null as any;})
export class ErrorComp2 {}
@NgModule({
declarations: [ErrorComp2, NonExistentComp],
imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])]
})
export class MainModule {}
`,
'src/child.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [RouterModule.forChild([{loadChildren: './child2#ChildModule2'}])]
})
export class ChildModule {}
`,
'src/child2.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export class ChildModule2 {}
`,
});
}
it('should list lazy routes recursively', () => {
writeSomeRoutes();
const {program, host, options} = createProgram(['src/main.ts']);
const routes = NgTools_InternalApi_NG_2.listLazyRoutes({
program,
host,
angularCompilerOptions: options,
entryModule: 'src/main#MainModule',
});
expect(routes).toEqual({
'./child#ChildModule': path.resolve(testSupport.basePath, 'src/child.ts'),
'./child2#ChildModule2': path.resolve(testSupport.basePath, 'src/child2.ts'),
});
});
it('should allow to emit the program after analyzing routes', () => {
writeSomeRoutes();
const {program, host, options} = createProgram(['src/main.ts']);
NgTools_InternalApi_NG_2.listLazyRoutes({
program,
host,
angularCompilerOptions: options,
entryModule: 'src/main#MainModule',
});
program.emit();
testSupport.shouldExist('built/src/main.js');
});
});

View File

@ -0,0 +1,177 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {METADATA_VERSION, MetadataCollector, ModuleMetadata} from '../../src/metadata';
import {MetadataReaderHost, readMetadata} from '../../src/transformers/metadata_reader';
import {Directory, Entry, MockAotContext} from '../mocks';
describe('metadata reader', () => {
let host: MetadataReaderHost;
beforeEach(() => {
const context = new MockAotContext('/tmp/src', clone(FILES));
const metadataCollector = new MetadataCollector();
host = {
fileExists: (fileName) => context.fileExists(fileName),
readFile: (fileName) => context.readFile(fileName),
getSourceFileMetadata: (fileName) => {
const sourceText = context.readFile(fileName);
return sourceText != null ?
metadataCollector.getMetadata(
ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest)) :
undefined;
},
};
});
it('should be able to read a metadata file', () => {
expect(readMetadata('node_modules/@angular/core.d.ts', host)).toEqual([
{__symbolic: 'module', version: METADATA_VERSION, metadata: {foo: {__symbolic: 'class'}}}
]);
});
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
expect(readMetadata('node_modules/@angular/unused.d.ts', host)).toEqual([dummyMetadata]);
});
it('should be able to read empty metadata ',
() => { expect(readMetadata('node_modules/@angular/empty.d.ts', host)).toEqual([]); });
it('should return undefined for missing modules',
() => { expect(readMetadata('node_modules/@angular/missing.d.ts', host)).toBeUndefined(); });
it(`should add missing v${METADATA_VERSION} metadata from v1 metadata and .d.ts files`, () => {
expect(readMetadata('metadata_versions/v1.d.ts', host)).toEqual([
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {
foo: {__symbolic: 'class'},
aType: {__symbolic: 'interface'},
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
},
exports: [{from: './lib/utils2', export: ['Export']}],
}
]);
});
it(`should upgrade a missing metadata file into v${METADATA_VERSION}`, () => {
expect(readMetadata('metadata_versions/v1_empty.d.ts', host)).toEqual([{
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {},
exports: [{from: './lib/utils'}]
}]);
});
it(`should upgrade v3 metadata into v${METADATA_VERSION}`, () => {
expect(readMetadata('metadata_versions/v3.d.ts', host)).toEqual([
{__symbolic: 'module', version: 3, metadata: {foo: {__symbolic: 'class'}}}, {
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {
foo: {__symbolic: 'class'},
aType: {__symbolic: 'interface'},
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
}
// Note: exports is missing because it was elided in the original.
}
]);
});
});
const dummyModule = 'export let foo: any[];';
const dummyMetadata: ModuleMetadata = {
__symbolic: 'module',
version: METADATA_VERSION,
metadata:
{foo: {__symbolic: 'error', message: 'Variable not initialized', line: 0, character: 11}}
};
const FILES: Entry = {
'tmp': {
'src': {
'main.ts': `
import * as c from '@angular/core';
import * as r from '@angular/router';
import * as u from './lib/utils';
import * as cs from './lib/collections';
import * as u2 from './lib2/utils2';
`,
'lib': {
'utils.ts': dummyModule,
'collections.ts': dummyModule,
},
'lib2': {'utils2.ts': dummyModule},
'node_modules': {
'@angular': {
'core.d.ts': dummyModule,
'core.metadata.json':
`{"__symbolic":"module", "version": ${METADATA_VERSION}, "metadata": {"foo": {"__symbolic": "class"}}}`,
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
'unused.d.ts': dummyModule,
'empty.d.ts': 'export declare var a: string;',
'empty.metadata.json': '[]',
}
},
'metadata_versions': {
'v1.d.ts': `
import {ReExport} from './lib/utils2';
export {ReExport};
export {Export} from './lib/utils2';
export type aType = number;
export declare class Bar {
ngOnInit() {}
}
export declare class BarChild extends Bar {}
`,
'v1.metadata.json':
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
'v1_empty.d.ts': `
export * from './lib/utils';
`,
'v3.d.ts': `
import {ReExport} from './lib/utils2';
export {ReExport};
export {Export} from './lib/utils2';
export type aType = number;
export declare class Bar {
ngOnInit() {}
}
export declare class BarChild extends Bar {}
`,
'v3.metadata.json':
`{"__symbolic":"module", "version": 3, "metadata": {"foo": {"__symbolic": "class"}}}`,
}
}
}
};
function clone(entry: Entry): Entry {
if (typeof entry === 'string') {
return entry;
} else {
const result: Directory = {};
for (const name in entry) {
result[name] = clone(entry[name]);
}
return result;
}
}

View File

@ -11,7 +11,7 @@ import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {CompilerHost} from '../../src/transformers/api';
import {CompilerHost, LazyRoute} from '../../src/transformers/api';
import {createSrcToOutPathMapper} from '../../src/transformers/program';
import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from '../../src/transformers/util';
import {TestSupport, expectNoDiagnosticsInProgram, setup} from '../test_support';
@ -508,4 +508,287 @@ describe('ng program', () => {
expect(mapper('c:\\tmp\\b\\y.js')).toBe('c:\\tmp\\out\\b\\y.js');
});
});
describe('listLazyRoutes', () => {
function writeSomeRoutes() {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])]
})
export class MainModule {}
`,
'src/child.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [RouterModule.forChild([{loadChildren: './child2#ChildModule2'}])]
})
export class ChildModule {}
`,
'src/child2.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export class ChildModule2 {}
`,
});
}
function createProgram(rootNames: string[]) {
const options = testSupport.createCompilerOptions();
const host = ng.createCompilerHost({options});
const program = ng.createProgram(
{rootNames: rootNames.map(p => path.resolve(testSupport.basePath, p)), options, host});
return {program, options};
}
function normalizeRoutes(lazyRoutes: LazyRoute[]) {
return lazyRoutes.map(
r => ({
route: r.route,
module: {name: r.module.name, filePath: r.module.filePath},
referencedModule:
{name: r.referencedModule.name, filePath: r.referencedModule.filePath},
}));
}
it('should list all lazyRoutes', () => {
writeSomeRoutes();
const {program, options} = createProgram(['src/main.ts', 'src/child.ts', 'src/child2.ts']);
expectNoDiagnosticsInProgram(options, program);
expect(normalizeRoutes(program.listLazyRoutes())).toEqual([
{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child#ChildModule'
},
{
module:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
referencedModule:
{name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
route: './child2#ChildModule2'
},
]);
});
it('should list lazyRoutes given an entryRoute recursively', () => {
writeSomeRoutes();
const {program, options} = createProgram(['src/main.ts']);
expectNoDiagnosticsInProgram(options, program);
expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child#ChildModule'
},
{
module:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
referencedModule:
{name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
route: './child2#ChildModule2'
},
]);
expect(normalizeRoutes(program.listLazyRoutes('src/child#ChildModule'))).toEqual([
{
module:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
referencedModule:
{name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
route: './child2#ChildModule2'
},
]);
});
it('should list lazyRoutes pointing to a default export', () => {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [RouterModule.forRoot([{loadChildren: './child'}])]
})
export class MainModule {}
`,
'src/child.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export default class ChildModule {}
`,
});
const {program, options} = createProgram(['src/main.ts']);
expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: undefined, filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child'
},
]);
});
it('should list lazyRoutes from imported modules', () => {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {NestedMainModule} from './nested/main';
@NgModule({
imports: [
RouterModule.forRoot([{loadChildren: './child#ChildModule'}]),
NestedMainModule,
]
})
export class MainModule {}
`,
'src/child.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export class ChildModule {}
`,
'src/nested/main.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [RouterModule.forChild([{loadChildren: './child#NestedChildModule'}])]
})
export class NestedMainModule {}
`,
'src/nested/child.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export class NestedChildModule {}
`,
});
const {program, options} = createProgram(['src/main.ts']);
expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
{
module: {
name: 'NestedMainModule',
filePath: path.resolve(testSupport.basePath, 'src/nested/main.ts')
},
referencedModule: {
name: 'NestedChildModule',
filePath: path.resolve(testSupport.basePath, 'src/nested/child.ts')
},
route: './child#NestedChildModule'
},
{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child#ChildModule'
},
]);
});
it('should dedupe lazyRoutes given an entryRoute', () => {
writeSomeRoutes();
testSupport.writeFiles({
'src/index.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [
RouterModule.forRoot([{loadChildren: './main#MainModule'}]),
RouterModule.forRoot([{loadChildren: './child#ChildModule'}]),
]
})
export class MainModule {}
`,
});
const {program, options} = createProgram(['src/index.ts']);
expectNoDiagnosticsInProgram(options, program);
expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child#ChildModule'
},
{
module:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
referencedModule:
{name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
route: './child2#ChildModule2'
},
]);
});
it('should list lazyRoutes given an entryRoute even with static errors', () => {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule, Component} from '@angular/core';
import {RouterModule} from '@angular/router';
@Component({
selector: 'url-comp',
// Non existent external template
templateUrl: 'non-existent.html',
})
export class ErrorComp {}
@Component({
selector: 'err-comp',
// Error in template
template: '<input/>{{',
})
export class ErrorComp2 {}
// Component with metadata errors.
@Component(() => {if (1==1) return null as any;})
export class ErrorComp3 {}
// Unused component
@Component({
selector: 'unused-comp',
template: ''
})
export class UnusedComp {}
@NgModule({
declarations: [ErrorComp, ErrorComp2, ErrorComp3, NonExistentComp],
imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])]
})
export class MainModule {}
@NgModule({
// Component used in 2 NgModules
declarations: [ErrorComp],
})
export class Mod2 {}
`,
'src/child.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export class ChildModule {}
`,
});
const program = createProgram(['src/main.ts']).program;
expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child#ChildModule'
}]);
});
});
});