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
44 changes: 21 additions & 23 deletions packages/compiler/src/jit/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class JitCompiler {
private _compiledDirectiveWrapperCache = new Map<Type, Type>();
private _compiledNgModuleCache = new Map<Type, object>();
private _sharedStylesheetCount = 0;
private _addedAotSummaries = new Set<() => any[]>();

constructor(
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
Expand Down Expand Up @@ -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); }
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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});
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/core_private_export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ export {DirectRenderer as ɵDirectRenderer, RenderDebugInfo as ɵRenderDebugInfo
export {global as ɵglobal, looseIdentical as ɵlooseIdentical, stringify as ɵstringify} from './util';
export {makeDecorator as ɵmakeDecorator} from './util/decorators';
export {isObservable as ɵisObservable, isPromise as ɵisPromise} from './util/lang';
export {clearProviderOverrides as ɵclearProviderOverrides, overrideProvider as ɵoverrideProvider} from './view/index';
export {clearOverrides as ɵclearOverrides, overrideComponentView as ɵoverrideComponentView, overrideProvider as ɵoverrideProvider} from './view/index';
export {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from './view/provider';
12 changes: 9 additions & 3 deletions packages/core/src/view/entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,27 @@
*/

import {Injector} from '../di/injector';
import {ComponentFactory} from '../linker/component_factory';
import {NgModuleFactory, NgModuleRef} from '../linker/ng_module_factory';
import {Type} from '../type';

import {initServicesIfNeeded} from './services';
import {NgModuleDefinitionFactory, ProviderOverride, Services} from './types';
import {NgModuleDefinitionFactory, ProviderOverride, Services, ViewDefinition} from './types';
import {resolveDefinition} from './util';

export function overrideProvider(override: ProviderOverride) {
initServicesIfNeeded();
return Services.overrideProvider(override);
}

export function clearProviderOverrides() {
export function overrideComponentView(comp: Type<any>, componentFactory: ComponentFactory<any>) {
initServicesIfNeeded();
return Services.clearProviderOverrides();
return Services.overrideComponentView(comp, componentFactory);
}

export function clearOverrides() {
initServicesIfNeeded();
return Services.clearOverrides();
}

// Attention: this function is called as top level function.
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

export {anchorDef, elementDef} from './element';
export {clearProviderOverrides, createNgModuleFactory, overrideProvider} from './entrypoint';
export {clearOverrides, createNgModuleFactory, overrideComponentView, overrideProvider} from './entrypoint';
export {ngContentDef} from './ng_content';
export {moduleDef, moduleProvideDef} from './ng_module';
export {directiveDef, pipeDef, providerDef} from './provider';
Expand Down
35 changes: 26 additions & 9 deletions packages/core/src/view/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {isDevMode} from '../application_ref';
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
import {Injector} from '../di';
import {ErrorHandler} from '../error_handler';
import {ComponentFactory} from '../linker/component_factory';
import {NgModuleRef} from '../linker/ng_module_factory';
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
import {Sanitizer} from '../security';
Expand All @@ -18,9 +19,9 @@ import {Type} from '../type';
import {isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
import {resolveDep} from './provider';
import {dirtyParentQueries, getQueryValue} from './query';
import {createInjector, createNgModuleRef} from './refs';
import {createInjector, createNgModuleRef, getComponentViewDefinitionFactory} from './refs';
import {ArgumentType, BindingFlags, CheckType, DebugContext, DepDef, ElementData, NgModuleDefinition, NgModuleProviderDef, NodeDef, NodeFlags, NodeLogger, ProviderOverride, RootData, Services, ViewData, ViewDefinition, ViewState, asElementData, asPureExpressionData} from './types';
import {NOOP, isComponentView, renderNode, splitDepsDsl, viewParentEl} from './util';
import {NOOP, isComponentView, renderNode, resolveDefinition, splitDepsDsl, viewParentEl} from './util';
import {checkAndUpdateNode, checkAndUpdateView, checkNoChangesNode, checkNoChangesView, createComponentView, createEmbeddedView, createRootView, destroyView} from './view';


Expand All @@ -38,7 +39,8 @@ export function initServicesIfNeeded() {
Services.createComponentView = services.createComponentView;
Services.createNgModuleRef = services.createNgModuleRef;
Services.overrideProvider = services.overrideProvider;
Services.clearProviderOverrides = services.clearProviderOverrides;
Services.overrideComponentView = services.overrideComponentView;
Services.clearOverrides = services.clearOverrides;
Services.checkAndUpdateView = services.checkAndUpdateView;
Services.checkNoChangesView = services.checkNoChangesView;
Services.destroyView = services.destroyView;
Expand All @@ -58,7 +60,8 @@ function createProdServices() {
createComponentView: createComponentView,
createNgModuleRef: createNgModuleRef,
overrideProvider: NOOP,
clearProviderOverrides: NOOP,
overrideComponentView: NOOP,
clearOverrides: NOOP,
checkAndUpdateView: checkAndUpdateView,
checkNoChangesView: checkNoChangesView,
destroyView: destroyView,
Expand All @@ -84,7 +87,8 @@ function createDebugServices() {
createComponentView: debugCreateComponentView,
createNgModuleRef: debugCreateNgModuleRef,
overrideProvider: debugOverrideProvider,
clearProviderOverrides: debugClearProviderOverrides,
overrideComponentView: debugOverrideComponentView,
clearOverrides: debugClearOverrides,
checkAndUpdateView: debugCheckAndUpdateView,
checkNoChangesView: debugCheckNoChangesView,
destroyView: debugDestroyView,
Expand Down Expand Up @@ -139,10 +143,15 @@ function debugCreateEmbeddedView(

function debugCreateComponentView(
parentView: ViewData, nodeDef: NodeDef, viewDef: ViewDefinition, hostElement: any): ViewData {
const defWithOverride = applyProviderOverridesToView(viewDef);
const overrideComponentView =
viewDefOverrides.get(nodeDef.element !.componentProvider !.provider !.token);
if (overrideComponentView) {
viewDef = overrideComponentView;
} else {
viewDef = applyProviderOverridesToView(viewDef);
}
return callWithDebugContext(
DebugAction.create, createComponentView, null,
[parentView, nodeDef, defWithOverride, hostElement]);
DebugAction.create, createComponentView, null, [parentView, nodeDef, viewDef, hostElement]);
}

function debugCreateNgModuleRef(
Expand All @@ -153,13 +162,21 @@ function debugCreateNgModuleRef(
}

const providerOverrides = new Map<any, ProviderOverride>();
const viewDefOverrides = new Map<any, ViewDefinition>();

function debugOverrideProvider(override: ProviderOverride) {
providerOverrides.set(override.token, override);
}

function debugClearProviderOverrides() {
function debugOverrideComponentView(comp: any, compFactory: ComponentFactory<any>) {
const hostViewDef = resolveDefinition(getComponentViewDefinitionFactory(compFactory));
const compViewDef = resolveDefinition(hostViewDef.nodes[0].element !.componentView !);
viewDefOverrides.set(comp, compViewDef);
}

function debugClearOverrides() {
providerOverrides.clear();
viewDefOverrides.clear();
}

// Notes about the algorithm:
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/view/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {Injector} from '../di';
import {ErrorHandler} from '../error_handler';
import {ComponentFactory} from '../linker/component_factory';
import {NgModuleRef} from '../linker/ng_module_factory';
import {QueryList} from '../linker/query_list';
import {TemplateRef} from '../linker/template_ref';
Expand All @@ -16,6 +17,7 @@ import {Renderer2, RendererFactory2, RendererType2} from '../render/api';
import {Sanitizer, SecurityContext} from '../security';
import {Type} from '../type';


// -------------------------------------
// Defs
// -------------------------------------
Expand Down Expand Up @@ -522,7 +524,8 @@ export interface Services {
moduleType: Type<any>, parent: Injector, bootstrapComponents: Type<any>[],
def: NgModuleDefinition): NgModuleRef<any>;
overrideProvider(override: ProviderOverride): void;
clearProviderOverrides(): void;
overrideComponentView(compType: Type<any>, compFactory: ComponentFactory<any>): void;
clearOverrides(): void;
checkAndUpdateView(view: ViewData): void;
checkNoChangesView(view: ViewData): void;
destroyView(view: ViewData): void;
Expand All @@ -547,7 +550,8 @@ export const Services: Services = {
createComponentView: undefined !,
createNgModuleRef: undefined !,
overrideProvider: undefined !,
clearProviderOverrides: undefined !,
overrideComponentView: undefined !,
clearOverrides: undefined !,
checkAndUpdateView: undefined !,
checkNoChangesView: undefined !,
destroyView: undefined !,
Expand Down
64 changes: 61 additions & 3 deletions packages/core/test/linker/jit_summaries_integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import {CompileMetadataResolver} from '@angular/compiler/src/metadata_resolver';
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
import {Component, Directive, Injectable, NgModule, Pipe, Type} from '@angular/core';
import {TestBed, async, getTestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';

export function main() {
describe('Jit Summaries', () => {
let instances: Map<any, Base>;
let summaries: () => any[];

class SomeDep {}

Expand Down Expand Up @@ -69,7 +71,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),
Expand All @@ -83,10 +85,12 @@ export function main() {
metadataResolver.getInjectableSummary(SomeService)
];
clearMetadata();
resetTestEnvironmentWithSummaries(() => summaries);
TestBed.resetTestingModule();
return () => summaries;
});

resourceLoader.flush();
return summariesPromise;
}

function setMetadata(resourceLoader: MockResourceLoader) {
Expand Down Expand Up @@ -123,12 +127,14 @@ export function main() {

beforeEach(async(() => {
instances = new Map<any, any>();
createSummaries();
createSummaries().then(s => summaries = s);
}));

afterEach(() => { resetTestEnvironmentWithSummaries(); });

it('should use directive metadata from summaries', () => {
resetTestEnvironmentWithSummaries(summaries);

@Component({template: '<div someDir></div>'})
class TestComp {
}
Expand All @@ -140,6 +146,8 @@ export function main() {
});

it('should use pipe metadata from summaries', () => {
resetTestEnvironmentWithSummaries(summaries);

@Component({template: '{{1 | somePipe}}'})
class TestComp {
}
Expand All @@ -150,6 +158,8 @@ export function main() {
});

it('should use Service metadata from summaries', () => {
resetTestEnvironmentWithSummaries(summaries);

TestBed.configureTestingModule({
providers: [SomeService, SomeDep],
});
Expand All @@ -158,6 +168,8 @@ export function main() {
});

it('should use NgModule metadata from summaries', () => {
resetTestEnvironmentWithSummaries(summaries);

TestBed
.configureTestingModule(
{providers: [SomeDep], declarations: [TestComp3], imports: [SomeModule]})
Expand All @@ -170,12 +182,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(
Expand All @@ -190,5 +206,47 @@ 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: '<div someDir></div>'})
class TestComp {
}

TestBed
.configureTestingModule({
providers: [SomeDep],
declarations: [TestComp, SomeDirective],
aotSummaries: summaries
})
.createComponent(TestComp);
expectInstanceCreated(SomeDirective);
});

it('should allow to override a provider', () => {
resetTestEnvironmentWithSummaries(summaries);

const overwrittenValue = {};

const fixture =
TestBed.overrideProvider(SomeDep, {useFactory: () => overwrittenValue, deps: []})
.configureTestingModule({providers: [SomeDep], imports: [SomeModule]})
.createComponent(SomePublicComponent);

expect(fixture.componentInstance.dep).toBe(overwrittenValue);
});

it('should allow to override a template', () => {
resetTestEnvironmentWithSummaries(summaries);

TestBed.overrideTemplateUsingTestingModule(SomePublicComponent, 'overwritten');

const fixture = TestBed.configureTestingModule({providers: [SomeDep], imports: [SomeModule]})
.createComponent(SomePublicComponent);
expectInstanceCreated(SomePublicComponent);

expect(fixture.nativeElement).toHaveText('overwritten');
});
});
}
Loading