fix(compiler): walk third party modules (#12453)

fixes #11889
fixes #12428
This commit is contained in:
Victor Berchet
2016-10-24 13:28:23 -07:00
committed by Igor Minar
parent bfc97ff2cd
commit a838aba756
24 changed files with 463 additions and 239 deletions

View File

@ -0,0 +1,18 @@
/**
* @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 {Component} from '@angular/core';
@Component({
selector: 'use-third-party',
template: '<third-party-comp [thirdParty]="title"></third-party-comp>' +
'<another-third-party-comp></another-third-party-comp>',
})
export class ComponentUsingThirdParty {
title: string = 'from 3rd party';
}

View File

@ -12,6 +12,10 @@
<source>Welcome</source>
<target>tervetuloa</target>
</trans-unit>
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
<source>other-3rdP-component</source>
<target>other-3rdP-component</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -9,4 +9,5 @@
<translationbundle>
<translation id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4">käännä teksti</translation>
<translation id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">tervetuloa</translation>
<translation id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</translation>
</translationbundle>

View File

@ -11,9 +11,12 @@ import {FormsModule} from '@angular/forms';
import {BrowserModule} from '@angular/platform-browser';
import {MdButtonModule} from '@angular2-material/button';
import {ThirdpartyModule} from '../third_party_src/module';
import {MultipleComponentsMyComp, NextComp} from './a/multiple_components';
import {AnimateCmp} from './animate';
import {BasicComp} from './basic';
import {ComponentUsingThirdParty} from './comp_using_3rdp';
import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components';
import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features';
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomePipeInRootModule, SomeService, someLibModuleWithProviders} from './module_fixtures';
@ -22,35 +25,47 @@ import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, Directive
@NgModule({
declarations: [
SomeDirectiveInRootModule,
SomePipeInRootModule,
AnimateCmp,
BasicComp,
CompConsumingEvents,
CompForChildQuery,
CompWithEntryComponents,
CompUsingPipes,
CompUsingRootModuleDirectiveAndPipe,
CompWithAnalyzeEntryComponentsProvider,
ProjectingComp,
CompWithChildQuery,
CompWithDirectiveChild,
CompWithEntryComponents,
CompWithNgContent,
CompUsingRootModuleDirectiveAndPipe,
CompWithProviders,
CompWithReferences,
CompUsingPipes,
CompConsumingEvents,
DirectiveForQuery,
DirPublishingEvents,
MultipleComponentsMyComp,
DirectiveForQuery,
NextComp,
ProjectingComp,
SomeDirectiveInRootModule,
SomePipeInRootModule,
ComponentUsingThirdParty,
],
imports: [
BrowserModule, FormsModule, someLibModuleWithProviders(), ModuleUsingCustomElements,
MdButtonModule
BrowserModule,
FormsModule,
MdButtonModule,
ModuleUsingCustomElements,
someLibModuleWithProviders(),
ThirdpartyModule,
],
providers: [SomeService],
entryComponents: [
AnimateCmp, BasicComp, CompWithEntryComponents, CompWithAnalyzeEntryComponentsProvider,
ProjectingComp, CompWithChildQuery, CompUsingRootModuleDirectiveAndPipe, CompWithReferences
AnimateCmp,
BasicComp,
CompUsingRootModuleDirectiveAndPipe,
CompWithAnalyzeEntryComponentsProvider,
CompWithChildQuery,
CompWithEntryComponents,
CompWithReferences,
ProjectingComp,
ComponentUsingThirdParty,
]
})
export class MainModule {

View File

@ -43,13 +43,13 @@ describe('template codegen output', () => {
});
it('should be able to create the basic component', () => {
var compFixture = createComponent(BasicComp);
const compFixture = createComponent(BasicComp);
expect(compFixture.componentInstance).toBeTruthy();
});
it('should support ngIf', () => {
var compFixture = createComponent(BasicComp);
var debugElement = compFixture.debugElement;
const compFixture = createComponent(BasicComp);
const debugElement = compFixture.debugElement;
expect(debugElement.children.length).toBe(3);
compFixture.componentInstance.ctxBool = true;
@ -59,8 +59,8 @@ describe('template codegen output', () => {
});
it('should support ngFor', () => {
var compFixture = createComponent(BasicComp);
var debugElement = compFixture.debugElement;
const compFixture = createComponent(BasicComp);
const debugElement = compFixture.debugElement;
expect(debugElement.children.length).toBe(3);
// test NgFor
@ -83,11 +83,9 @@ describe('template codegen output', () => {
});
it('should support i18n for content tags', () => {
const compFixture = createComponent(BasicComp);
const debugElement = compFixture.debugElement;
const containerElement = <any>debugElement.nativeElement;
const pElement = <any>containerElement.children.find((c: any) => c.name == 'p');
const pText = <string>pElement.children.map((c: any) => c.data).join('').trim();
const containerElement = createComponent(BasicComp).nativeElement;
const pElement = containerElement.children.find((c: any) => c.name == 'p');
const pText = pElement.children.map((c: any) => c.data).join('').trim();
expect(pText).toBe('tervetuloa');
});
});

View File

@ -34,6 +34,7 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT ex (#PCDATA)>
]>
<messagebundle>
<msg id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</msg>
<msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg>
<msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg>
</messagebundle>
@ -43,6 +44,10 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
<source>other-3rdP-component</source>
<target/>
</trans-unit>
<trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html">
<source>translate me</source>
<target/>

View File

@ -7,6 +7,7 @@
*/
import './init';
import {ComponentUsingThirdParty} from '../src/comp_using_3rdp';
import {MainModule} from '../src/module';
import {CompUsingLibModuleDirectiveAndPipe, CompUsingRootModuleDirectiveAndPipe, SOME_TOKEN, ServiceUsingLibModule, SomeLibModule, SomeService} from '../src/module_fixtures';
@ -15,9 +16,9 @@ import {createComponent, createModule} from './util';
describe('NgModule', () => {
it('should support providers', () => {
const moduleRef = createModule();
expect(moduleRef.instance instanceof MainModule).toBe(true);
expect(moduleRef.injector.get(MainModule) instanceof MainModule).toBe(true);
expect(moduleRef.injector.get(SomeService) instanceof SomeService).toBe(true);
expect(moduleRef.instance instanceof MainModule).toEqual(true);
expect(moduleRef.injector.get(MainModule) instanceof MainModule).toEqual(true);
expect(moduleRef.injector.get(SomeService) instanceof SomeService).toEqual(true);
});
it('should support entryComponents components', () => {
@ -26,7 +27,7 @@ describe('NgModule', () => {
CompUsingRootModuleDirectiveAndPipe);
expect(cf.componentType).toBe(CompUsingRootModuleDirectiveAndPipe);
const compRef = cf.create(moduleRef.injector);
expect(compRef.instance instanceof CompUsingRootModuleDirectiveAndPipe).toBe(true);
expect(compRef.instance instanceof CompUsingRootModuleDirectiveAndPipe).toEqual(true);
});
it('should support entryComponents via the ANALYZE_FOR_ENTRY_COMPONENTS provider and function providers in components',
@ -42,12 +43,30 @@ describe('NgModule', () => {
]);
});
describe('third-party modules', () => {
// https://github.com/angular/angular/issues/11889
it('should support third party entryComponents components', () => {
const fixture = createComponent(ComponentUsingThirdParty);
const thirdPComps = fixture.nativeElement.children;
expect(thirdPComps[0].children[0].children[0].data).toEqual('3rdP-component');
expect(thirdPComps[1].children[0].children[0].data).toEqual('other-3rdP-component');
});
// https://github.com/angular/angular/issues/12428
it('should support third party directives', () => {
const fixture = createComponent(ComponentUsingThirdParty);
const debugElement = fixture.debugElement;
fixture.detectChanges();
expect(debugElement.children[0].properties['title']).toEqual('from 3rd party');
});
});
it('should support module directives and pipes', () => {
const compFixture = createComponent(CompUsingRootModuleDirectiveAndPipe);
compFixture.detectChanges();
const debugElement = compFixture.debugElement;
expect(debugElement.children[0].properties['title']).toBe('transformed someValue');
expect(debugElement.children[0].properties['title']).toEqual('transformed someValue');
});
it('should support module directives and pipes on lib modules', () => {
@ -55,10 +74,10 @@ describe('NgModule', () => {
compFixture.detectChanges();
const debugElement = compFixture.debugElement;
expect(debugElement.children[0].properties['title']).toBe('transformed someValue');
expect(debugElement.children[0].properties['title']).toEqual('transformed someValue');
expect(debugElement.injector.get(SomeLibModule) instanceof SomeLibModule).toBe(true);
expect(debugElement.injector.get(SomeLibModule) instanceof SomeLibModule).toEqual(true);
expect(debugElement.injector.get(ServiceUsingLibModule) instanceof ServiceUsingLibModule)
.toBe(true);
.toEqual(true);
});
});

View File

@ -0,0 +1,8 @@
This folder emulates consuming precompiled modules and components.
It is compiled separately from the other sources under `src`
to only generate `*.js` / `*.d.ts` / `*.metadata.json` files,
but no `*.ngfactory.ts` files.
** WARNING **
Do not import components/directives from here directly as we want to test that ngc still compiles
them when they are not imported.

View File

@ -0,0 +1,16 @@
/**
* @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 {Component} from '@angular/core';
@Component({
selector: 'third-party-comp',
template: '<div>3rdP-component</div>',
})
export class ThirdPartyComponent {
}

View File

@ -0,0 +1,17 @@
/**
* @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 {Directive, Input} from '@angular/core';
@Directive({
selector: '[thirdParty]',
host: {'[title]': 'thirdParty'},
})
export class ThirdPartyDirective {
@Input() thirdParty: string;
}

View File

@ -0,0 +1,28 @@
/**
* @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 {NgModule} from '@angular/core';
import {ThirdPartyComponent} from './comp';
import {ThirdPartyDirective} from './directive';
import {AnotherThirdPartyModule} from './other_module';
@NgModule({
declarations: [
ThirdPartyComponent,
ThirdPartyDirective,
],
exports: [
AnotherThirdPartyModule,
ThirdPartyComponent,
ThirdPartyDirective,
],
imports: [AnotherThirdPartyModule]
})
export class ThirdpartyModule {
}

View File

@ -0,0 +1,16 @@
/**
* @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 {Component} from '@angular/core';
@Component({
selector: 'another-third-party-comp',
template: '<div i18n>other-3rdP-component</div>',
})
export class AnotherThirdpartyComponent {
}

View File

@ -0,0 +1,17 @@
/**
* @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 {NgModule} from '@angular/core';
import {AnotherThirdpartyComponent} from './other_comp';
@NgModule({
declarations: [AnotherThirdpartyComponent],
exports: [AnotherThirdpartyComponent],
})
export class AnotherThirdPartyModule {
}

View File

@ -0,0 +1,20 @@
{
"angularCompilerOptions": {
// For TypeScript 1.8, we have to lay out generated files
// in the same source directory with your code.
"genDir": ".",
"debug": true
},
"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"noImplicitAny": true,
"moduleResolution": "node",
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": ".",
"outDir": "../node_modules/third_party"
}
}

View File

@ -0,0 +1,29 @@
{
"angularCompilerOptions": {
// For TypeScript 1.8, we have to lay out generated files
// in the same source directory with your code.
"genDir": ".",
"debug": true
},
"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"noImplicitAny": true,
"moduleResolution": "node",
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": "."
},
"files": [
"src/module",
"src/bootstrap",
"test/all_spec",
"benchmarks/src/tree/ng2/index_aot.ts",
"benchmarks/src/tree/ng2_switch/index_aot.ts",
"benchmarks/src/largetable/ng2/index_aot.ts",
"benchmarks/src/largetable/ng2_switch/index_aot.ts"
]
}

View File

@ -1,29 +0,0 @@
{
"angularCompilerOptions": {
// For TypeScript 1.8, we have to lay out generated files
// in the same source directory with your code.
"genDir": ".",
"debug": true
},
"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"noImplicitAny": true,
"moduleResolution": "node",
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": "."
},
"files": [
"src/module",
"src/bootstrap",
"test/all_spec",
"benchmarks/src/tree/ng2/index_aot.ts",
"benchmarks/src/tree/ng2_switch/index_aot.ts",
"benchmarks/src/largetable/ng2/index_aot.ts",
"benchmarks/src/largetable/ng2_switch/index_aot.ts"
]
}

View File

@ -40,50 +40,47 @@ export class CodeGeneratorModuleCollector {
private staticReflector: StaticReflector, private reflectorHost: StaticReflectorHost,
private program: ts.Program, private options: AngularCompilerOptions) {}
getModuleSymbols(program: ts.Program): {fileMetas: FileMetadata[], ngModules: StaticSymbol[]} {
getModuleSymbols(): StaticSymbol[] {
// Compare with false since the default should be true
const skipFileNames = (this.options.generateCodeForLibraries === false) ?
GENERATED_OR_DTS_FILES :
GENERATED_FILES;
let filePaths = this.program.getSourceFiles()
.filter(sf => !skipFileNames.test(sf.fileName))
.map(sf => this.reflectorHost.getCanonicalFileName(sf.fileName));
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
ngModules.push(...fileMeta.ngModules);
return ngModules;
}, <StaticSymbol[]>[]);
return {fileMetas, ngModules};
}
const skipFileNames =
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
private readFileMetadata(absSourcePath: string): FileMetadata {
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
const result: FileMetadata = {directives: [], ngModules: [], fileUrl: absSourcePath};
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${absSourcePath}`);
return result;
}
const metadata = moduleMetadata['metadata'];
const symbols = metadata && Object.keys(metadata);
if (!symbols || !symbols.length) {
return result;
}
for (const symbol of symbols) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
}
const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath);
const annotations = this.staticReflector.annotations(staticType);
annotations.forEach((annotation) => {
if (annotation instanceof NgModule) {
result.ngModules.push(staticType);
} else if (annotation instanceof Directive) {
result.directives.push(staticType);
}
});
}
return result;
const ngModules: StaticSymbol[] = [];
this.program.getSourceFiles()
.filter(sourceFile => !skipFileNames.test(sourceFile.fileName))
.forEach(sourceFile => {
const absSrcPath = this.reflectorHost.getCanonicalFileName(sourceFile.fileName);
const moduleMetadata = this.staticReflector.getModuleMetadata(absSrcPath);
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${absSrcPath}`);
return;
}
const metadata = moduleMetadata['metadata'];
if (!metadata) {
return;
}
for (const symbol of Object.keys(metadata)) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
}
const staticType = this.reflectorHost.findDeclaration(absSrcPath, symbol, absSrcPath);
const annotations = this.staticReflector.annotations(staticType);
annotations.some((annotation) => {
if (annotation instanceof NgModule) {
ngModules.push(staticType);
return true;
}
});
}
});
return ngModules;
}
}
@ -101,7 +98,7 @@ export class CodeGenerator {
// Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string): string {
let root = this.options.basePath;
for (let eachRootDir of this.options.rootDirs || []) {
for (const eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
}
@ -111,31 +108,27 @@ export class CodeGenerator {
}
// transplant the codegen path to be inside the `genDir`
var relativePath: string = path.relative(root, filePath);
let relativePath: string = path.relative(root, filePath);
while (relativePath.startsWith('..' + path.sep)) {
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
// into `genDir`.
relativePath = relativePath.substr(3);
}
return path.join(this.options.genDir, relativePath);
}
codegen(): Promise<any> {
const {fileMetas, ngModules} = this.moduleCollector.getModuleSymbols(this.program);
const analyzedNgModules = this.compiler.analyzeModules(ngModules);
return Promise.all(fileMetas.map(
(fileMeta) =>
this.compiler
.compile(
fileMeta.fileUrl, analyzedNgModules, fileMeta.directives, fileMeta.ngModules)
.then((generatedModules) => {
generatedModules.forEach((generatedModule) => {
const sourceFile = this.program.getSourceFile(fileMeta.fileUrl);
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
this.host.writeFile(
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
});
})));
const ngModules = this.moduleCollector.getModuleSymbols();
return this.compiler.compileModules(ngModules).then(generatedModules => {
generatedModules.forEach(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
this.host.writeFile(
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
});
});
}
static create(
@ -201,9 +194,3 @@ export class CodeGenerator {
options, program, compilerHost, staticReflector, offlineCompiler, reflectorHost);
}
}
export interface FileMetadata {
fileUrl: string;
directives: StaticSymbol[];
ngModules: StaticSymbol[];
}

View File

@ -10,8 +10,6 @@
/**
* Extract i18n messages from source code
*
* TODO(vicb): factorize code with the CodeGenerator
*/
// Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata';

View File

@ -18,16 +18,13 @@ import 'reflect-metadata';
import * as compiler from '@angular/compiler';
import {Component, NgModule, ViewEncapsulation} from '@angular/core';
import * as tsc from '@angular/tsc-wrapped';
import * as path from 'path';
import * as ts from 'typescript';
import {Console} from './private_import_core';
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector, StaticSymbol} from './static_reflector';
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
export class Extractor {
constructor(
@ -37,82 +34,82 @@ export class Extractor {
private metadataResolver: compiler.CompileMetadataResolver,
private directiveNormalizer: compiler.DirectiveNormalizer) {}
private readFileMetadata(absSourcePath: string): FileMetadata {
private readModuleSymbols(absSourcePath: string): StaticSymbol[] {
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
const result: FileMetadata = {components: [], ngModules: [], fileUrl: absSourcePath};
const modSymbols: StaticSymbol[] = [];
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${absSourcePath}`);
return result;
return modSymbols;
}
const metadata = moduleMetadata['metadata'];
const symbols = metadata && Object.keys(metadata);
if (!symbols || !symbols.length) {
return result;
return modSymbols;
}
for (const symbol of symbols) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
}
const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath);
const annotations = this.staticReflector.annotations(staticType);
annotations.forEach((annotation) => {
if (annotation instanceof NgModule) {
result.ngModules.push(staticType);
} else if (annotation instanceof Component) {
result.components.push(staticType);
annotations.some(a => {
if (a instanceof NgModule) {
modSymbols.push(staticType);
return true;
}
});
}
return result;
return modSymbols;
}
extract(): Promise<compiler.MessageBundle> {
const skipFileNames = (this.options.generateCodeForLibraries === false) ?
GENERATED_OR_DTS_FILES :
GENERATED_FILES;
const filePaths =
this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !skipFileNames.test(f));
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
ngModules.push(...fileMeta.ngModules);
return ngModules;
}, <StaticSymbol[]>[]);
const analyzedNgModules = compiler.analyzeModules(ngModules, this.metadataResolver);
const errors: compiler.ParseError[] = [];
this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !GENERATED_FILES.test(f));
const ngModules: StaticSymbol[] = [];
filePaths.forEach((filePath) => ngModules.push(...this.readModuleSymbols(filePath)));
const files = compiler.analyzeNgModules(ngModules, this.metadataResolver).files;
const errors: compiler.ParseError[] = [];
const filePromises: Promise<any>[] = [];
files.forEach(file => {
const cmpPromises: Promise<compiler.CompileDirectiveMetadata>[] = [];
file.directives.forEach(directiveType => {
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
if (dirMeta.isComponent) {
cmpPromises.push(this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult);
}
});
if (cmpPromises.length) {
const done =
Promise.all(cmpPromises).then((compMetas: compiler.CompileDirectiveMetadata[]) => {
compMetas.forEach(compMeta => {
const html = compMeta.template.template;
const interpolationConfig =
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
errors.push(...this.messageBundle.updateFromTemplate(
html, file.srcUrl, interpolationConfig));
});
});
filePromises.push(done);
}
});
let bundlePromise =
Promise
.all(fileMetas.map((fileMeta) => {
const url = fileMeta.fileUrl;
return Promise.all(fileMeta.components.map(compType => {
const compMeta = this.metadataResolver.getDirectiveMetadata(<any>compType);
const ngModule = analyzedNgModules.ngModuleByDirective.get(compType);
if (!ngModule) {
throw new Error(
`Cannot determine the module for component ${compMeta.type.name}!`);
}
return Promise
.all([compMeta, ...ngModule.transitiveModule.directives].map(
dirMeta =>
this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult))
.then((normalizedCompWithDirectives) => {
const compMeta = normalizedCompWithDirectives[0];
const html = compMeta.template.template;
const interpolationConfig =
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
errors.push(
...this.messageBundle.updateFromTemplate(html, url, interpolationConfig));
});
}));
}))
.then(_ => this.messageBundle);
if (errors.length) {
throw new Error(errors.map(e => e.toString()).join('\n'));
}
return bundlePromise;
return Promise.all(filePromises).then(_ => this.messageBundle);
}
static create(
@ -148,10 +145,4 @@ export class Extractor {
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver,
normalizer);
}
}
interface FileMetadata {
fileUrl: string;
components: StaticSymbol[];
ngModules: StaticSymbol[];
}
}

View File

@ -250,6 +250,12 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
} else {
const sf = this.program.getSourceFile(filePath);
if (!sf) {
if (this.context.fileExists(filePath)) {
const sourceText = this.context.readFile(filePath);
return this.metadataCollector.getMetadata(
ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true));
}
throw new Error(`Source file ${filePath} not present in program.`);
}
return this.metadataCollector.getMetadata(sf);