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:
committed by
Matias Niemelä
parent
5da96c75a2
commit
8d45fefc31
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user