diff --git a/packages/compiler/src/jit/compiler.ts b/packages/compiler/src/jit/compiler.ts index 18ddbe5686..b503dcf773 100644 --- a/packages/compiler/src/jit/compiler.ts +++ b/packages/compiler/src/jit/compiler.ts @@ -42,6 +42,7 @@ export class JitCompiler { private _compiledDirectiveWrapperCache = new Map(); private _compiledNgModuleCache = new Map(); private _sharedStylesheetCount = 0; + private _addedAotSummaries = new Set<() => any[]>(); constructor( private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser, @@ -74,10 +75,25 @@ export class JitCompiler { loadAotSummaries(summaries: () => any[]) { this.clearCache(); - flattenSummaries(summaries).forEach((summary) => { - this._summaryResolver.addSummary( - {symbol: summary.type.reference, metadata: null, type: summary}); - }); + this._addAotSummaries(summaries); + } + + private _addAotSummaries(fn: () => any[]) { + if (this._addedAotSummaries.has(fn)) { + return; + } + this._addedAotSummaries.add(fn); + const summaries = fn(); + for (let i = 0; i < summaries.length; i++) { + const entry = summaries[i]; + if (typeof entry === 'function') { + this._addAotSummaries(entry); + } else { + const summary = entry as CompileTypeSummary; + this._summaryResolver.addSummary( + {symbol: summary.type.reference, metadata: null, type: summary}) + } + } } hasAotSummary(ref: Type) { return !!this._summaryResolver.resolveSummary(ref); } @@ -200,6 +216,7 @@ export class JitCompiler { } clearCache(): void { + // Note: don't clear the _addedAotSummaries, as they don't change! this._metadataResolver.clearCache(); this._compiledTemplateCache.clear(); this._compiledHostTemplateCache.clear(); @@ -335,25 +352,6 @@ function assertComponent(meta: CompileDirectiveMetadata) { } } -function flattenSummaries( - fn: () => any[], out: CompileTypeSummary[] = [], - seen = new Set<() => any[]>()): CompileTypeSummary[] { - if (seen.has(fn)) { - return out; - } - seen.add(fn); - const summaries = fn(); - for (let i = 0; i < summaries.length; i++) { - const entry = summaries[i]; - if (typeof entry === 'function') { - flattenSummaries(entry, out, seen); - } else { - out.push(entry); - } - } - return out; -} - function createOutputContext(): OutputContext { const importExpr = (symbol: any) => ir.importExpr({name: identifierName(symbol), moduleName: null, runtime: symbol}); diff --git a/packages/core/test/linker/jit_summaries_integration_spec.ts b/packages/core/test/linker/jit_summaries_integration_spec.ts index 149df8d4e0..71c306246d 100644 --- a/packages/core/test/linker/jit_summaries_integration_spec.ts +++ b/packages/core/test/linker/jit_summaries_integration_spec.ts @@ -15,6 +15,7 @@ import {TestBed, async, getTestBed} from '@angular/core/testing'; export function main() { describe('Jit Summaries', () => { let instances: Map; + let summaries: () => any[]; class SomeDep {} @@ -69,7 +70,7 @@ export function main() { TestBed.configureCompiler({providers: [{provide: ResourceLoader, useValue: resourceLoader}]}); TestBed.configureTestingModule({imports: [SomeModule], providers: [SomeDep]}); - TestBed.compileComponents().then(() => { + let summariesPromise = TestBed.compileComponents().then(() => { const metadataResolver = TestBed.get(CompileMetadataResolver) as CompileMetadataResolver; const summaries = [ metadataResolver.getNgModuleSummary(SomeModule), @@ -83,10 +84,12 @@ export function main() { metadataResolver.getInjectableSummary(SomeService) ]; clearMetadata(); - resetTestEnvironmentWithSummaries(() => summaries); + TestBed.resetTestingModule(); + return () => summaries; }); resourceLoader.flush(); + return summariesPromise; } function setMetadata(resourceLoader: MockResourceLoader) { @@ -123,12 +126,14 @@ export function main() { beforeEach(async(() => { instances = new Map(); - createSummaries(); + createSummaries().then(s => summaries = s); })); afterEach(() => { resetTestEnvironmentWithSummaries(); }); it('should use directive metadata from summaries', () => { + resetTestEnvironmentWithSummaries(summaries); + @Component({template: '
'}) class TestComp { } @@ -140,6 +145,8 @@ export function main() { }); it('should use pipe metadata from summaries', () => { + resetTestEnvironmentWithSummaries(summaries); + @Component({template: '{{1 | somePipe}}'}) class TestComp { } @@ -150,6 +157,8 @@ export function main() { }); it('should use Service metadata from summaries', () => { + resetTestEnvironmentWithSummaries(summaries); + TestBed.configureTestingModule({ providers: [SomeService, SomeDep], }); @@ -158,6 +167,8 @@ export function main() { }); it('should use NgModule metadata from summaries', () => { + resetTestEnvironmentWithSummaries(summaries); + TestBed .configureTestingModule( {providers: [SomeDep], declarations: [TestComp3], imports: [SomeModule]}) @@ -170,12 +181,16 @@ export function main() { }); it('should allow to create private components from imported NgModule summaries', () => { + resetTestEnvironmentWithSummaries(summaries); + TestBed.configureTestingModule({providers: [SomeDep], imports: [SomeModule]}) .createComponent(SomePrivateComponent); expectInstanceCreated(SomePrivateComponent); }); it('should throw when trying to mock a type with a summary', () => { + resetTestEnvironmentWithSummaries(summaries); + TestBed.resetTestingModule(); expect(() => TestBed.overrideComponent(SomePrivateComponent, {add: {}}).compileComponents()) .toThrowError( @@ -190,5 +205,22 @@ export function main() { expect(() => TestBed.overrideModule(SomeModule, {add: {}}).compileComponents()) .toThrowError('SomeModule was AOT compiled, so its metadata cannot be changed.'); }); + + it('should allow to add summaries via configureTestingModule', () => { + resetTestEnvironmentWithSummaries(); + + @Component({template: '
'}) + class TestComp { + } + + TestBed + .configureTestingModule({ + providers: [SomeDep], + declarations: [TestComp, SomeDirective], + aotSummaries: summaries + }) + .createComponent(TestComp); + expectInstanceCreated(SomeDirective); + }); }); } diff --git a/packages/core/testing/src/test_bed.ts b/packages/core/testing/src/test_bed.ts index 5eaa822063..104c71227d 100644 --- a/packages/core/testing/src/test_bed.ts +++ b/packages/core/testing/src/test_bed.ts @@ -45,6 +45,7 @@ export type TestModuleMetadata = { declarations?: any[], imports?: any[], schemas?: Array, + aotSummaries?: () => any[], }; /** @@ -205,7 +206,8 @@ export class TestBed implements Injector { private _schemas: Array = []; private _activeFixtures: ComponentFixture[] = []; - private _aotSummaries: () => any[] = () => []; + private _testEnvAotSummaries: () => any[] = () => []; + private _aotSummaries: Array<() => any[]> = []; platform: PlatformRef = null !; @@ -232,7 +234,7 @@ export class TestBed implements Injector { this.platform = platform; this.ngModule = ngModule; if (aotSummaries) { - this._aotSummaries = aotSummaries; + this._testEnvAotSummaries = aotSummaries; } } @@ -245,11 +247,12 @@ export class TestBed implements Injector { this.resetTestingModule(); this.platform = null !; this.ngModule = null !; - this._aotSummaries = () => []; + this._testEnvAotSummaries = () => []; } resetTestingModule() { clearProviderOverrides(); + this._aotSummaries = []; this._compiler = null !; this._moduleOverrides = []; this._componentOverrides = []; @@ -293,6 +296,9 @@ export class TestBed implements Injector { if (moduleDef.schemas) { this._schemas.push(...moduleDef.schemas); } + if (moduleDef.aotSummaries) { + this._aotSummaries.push(moduleDef.aotSummaries); + } } compileComponents(): Promise { @@ -350,7 +356,9 @@ export class TestBed implements Injector { const compilerFactory: TestingCompilerFactory = this.platform.injector.get(TestingCompilerFactory); this._compiler = compilerFactory.createTestingCompiler(this._compilerOptions); - this._compiler.loadAotSummaries(this._aotSummaries); + for (const summary of [this._testEnvAotSummaries, ...this._aotSummaries]) { + this._compiler.loadAotSummaries(summary); + } this._moduleOverrides.forEach((entry) => this._compiler.overrideModule(entry[0], entry[1])); this._componentOverrides.forEach( (entry) => this._compiler.overrideComponent(entry[0], entry[1]));