fix(core): allow tree shaking of component factories and styles (#15214)

Closure compiler is very sensitive to top level function calls.
This commit makes the function calls `createComponentFactory`
and `createRendererTypeV2` logic-less.

Fixes #15181

PR Close #15214
This commit is contained in:
Tobias Bosch 2017-03-16 17:32:14 -07:00 committed by Miško Hevery
parent 9429032da1
commit 2a0e55ffb5
4 changed files with 69 additions and 48 deletions

View File

@ -136,30 +136,23 @@ export class CompileMetadataResolver {
} }
} }
private getComponentFactory(selector: string, dirType: any): StaticSymbol|ComponentFactory<any> { private getComponentFactory(
selector: string, dirType: any, inputs: {[key: string]: string},
outputs: {[key: string]: string}): StaticSymbol|ComponentFactory<any> {
if (dirType instanceof StaticSymbol) { if (dirType instanceof StaticSymbol) {
return this._staticSymbolCache.get( return this._staticSymbolCache.get(
ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType)); ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType));
} else { } else {
const hostView = this.getHostComponentViewClass(dirType); const hostView = this.getHostComponentViewClass(dirType);
// Note: inputs / outputs / ngContentSelectors will be filled later once the template is // Note: ngContentSelectors will be filled later once the template is
// loaded. // loaded.
return createComponentFactory(selector, dirType, <any>hostView, {}, {}, []); return createComponentFactory(selector, dirType, <any>hostView, inputs, outputs, []);
} }
} }
private initComponentFactory( private initComponentFactory(
factory: StaticSymbol|ComponentFactory<any>, inputs: {[key: string]: string}, factory: StaticSymbol|ComponentFactory<any>, ngContentSelectors: string[]) {
outputs: {[key: string]: string}, ngContentSelectors: string[]) {
if (!(factory instanceof StaticSymbol)) { if (!(factory instanceof StaticSymbol)) {
for (let propName in inputs) {
const templateName = inputs[propName];
factory.inputs.push({propName, templateName});
}
for (let propName in outputs) {
const templateName = outputs[propName];
factory.outputs.push({propName, templateName});
}
factory.ngContentSelectors.push(...ngContentSelectors); factory.ngContentSelectors.push(...ngContentSelectors);
} }
} }
@ -205,9 +198,7 @@ export class CompileMetadataResolver {
template: templateMetadata template: templateMetadata
}); });
if (templateMetadata) { if (templateMetadata) {
this.initComponentFactory( this.initComponentFactory(metadata.componentFactory, templateMetadata.ngContentSelectors);
metadata.componentFactory, metadata.inputs, metadata.outputs,
templateMetadata.ngContentSelectors);
} }
this._directiveCache.set(directiveType, normalizedDirMeta); this._directiveCache.set(directiveType, normalizedDirMeta);
this._summaryCache.set(directiveType, normalizedDirMeta.toSummary()); this._summaryCache.set(directiveType, normalizedDirMeta.toSummary());
@ -343,10 +334,12 @@ export class CompileMetadataResolver {
componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) : componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) :
undefined, undefined,
rendererType: nonNormalizedTemplateMetadata ? this.getRendererType(directiveType) : undefined, rendererType: nonNormalizedTemplateMetadata ? this.getRendererType(directiveType) : undefined,
componentFactory: nonNormalizedTemplateMetadata ? componentFactory: undefined
this.getComponentFactory(selector, directiveType) :
undefined
}); });
if (nonNormalizedTemplateMetadata) {
metadata.componentFactory =
this.getComponentFactory(selector, directiveType, metadata.inputs, metadata.outputs);
}
cacheEntry = {metadata, annotation: dirMeta}; cacheEntry = {metadata, annotation: dirMeta};
this._nonNormalizedDirectiveCache.set(directiveType, cacheEntry); this._nonNormalizedDirectiveCache.set(directiveType, cacheEntry);
return cacheEntry; return cacheEntry;

View File

@ -6,11 +6,12 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ViewEncapsulation} from '../metadata/view';
import {Renderer2, RendererType2} from '../render/api'; import {Renderer2, RendererType2} from '../render/api';
import {SecurityContext} from '../security'; import {SecurityContext} from '../security';
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementHandleEventFn, NodeData, NodeDef, NodeFlags, OutputDef, OutputType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData, asProviderData} from './types'; import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementHandleEventFn, NodeData, NodeDef, NodeFlags, OutputDef, OutputType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData, asProviderData} from './types';
import {NOOP, checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, splitMatchedQueriesDsl, splitNamespace} from './util'; import {NOOP, checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveRendererType2, resolveViewDefinition, splitMatchedQueriesDsl, splitNamespace} from './util';
export function anchorDef( export function anchorDef(
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][], flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
@ -112,11 +113,7 @@ export function elementDef(
const [ns, name] = splitNamespace(namespaceAndName); const [ns, name] = splitNamespace(namespaceAndName);
return [ns, name, value]; return [ns, name, value];
}); });
// This is needed as the jit compiler always uses an empty hash as default RendererType2, componentRendererType = resolveRendererType2(componentRendererType);
// which is not filled for host views.
if (componentRendererType && componentRendererType.encapsulation == null) {
componentRendererType = null;
}
if (componentView) { if (componentView) {
flags |= NodeFlags.ComponentView; flags |= NodeFlags.ComponentView;
} }

View File

@ -25,22 +25,14 @@ import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView, renderDetachVi
const EMPTY_CONTEXT = new Object(); const EMPTY_CONTEXT = new Object();
// Attention: this function is called as top level function.
// Putting any logic in here will destroy closure tree shaking!
export function createComponentFactory( export function createComponentFactory(
selector: string, componentType: Type<any>, viewDefFactory: ViewDefinitionFactory, selector: string, componentType: Type<any>, viewDefFactory: ViewDefinitionFactory,
inputs: {[propName: string]: string}, outputs: {[propName: string]: string}, inputs: {[propName: string]: string}, outputs: {[propName: string]: string},
ngContentSelectors: string[]): ComponentFactory<any> { ngContentSelectors: string[]): ComponentFactory<any> {
const inputsArr: {propName: string, templateName: string}[] = [];
for (let propName in inputs) {
const templateName = inputs[propName];
inputsArr.push({propName, templateName});
}
const outputsArr: {propName: string, templateName: string}[] = [];
for (let propName in outputs) {
const templateName = outputs[propName];
outputsArr.push({propName, templateName});
}
return new ComponentFactory_( return new ComponentFactory_(
selector, componentType, viewDefFactory, inputsArr, outputsArr, ngContentSelectors); selector, componentType, viewDefFactory, inputs, outputs, ngContentSelectors);
} }
export function getComponentViewDefinitionFactory(componentFactory: ComponentFactory<any>): export function getComponentViewDefinitionFactory(componentFactory: ComponentFactory<any>):
@ -56,14 +48,32 @@ class ComponentFactory_ extends ComponentFactory<any> {
constructor( constructor(
public selector: string, public componentType: Type<any>, public selector: string, public componentType: Type<any>,
viewDefFactory: ViewDefinitionFactory, viewDefFactory: ViewDefinitionFactory, private _inputs: {[propName: string]: string},
public inputs: {propName: string, templateName: string}[], private _outputs: {[propName: string]: string}, public ngContentSelectors: string[]) {
public outputs: {propName: string, templateName: string}[], // Attention: this ctor is called as top level function.
public ngContentSelectors: string[]) { // Putting any logic in here will destroy closure tree shaking!
super(); super();
this.viewDefFactory = viewDefFactory; this.viewDefFactory = viewDefFactory;
} }
get inputs() {
const inputsArr: {propName: string, templateName: string}[] = [];
for (let propName in this._inputs) {
const templateName = this._inputs[propName];
inputsArr.push({propName, templateName});
}
return inputsArr;
}
get outputs() {
const outputsArr: {propName: string, templateName: string}[] = [];
for (let propName in this._outputs) {
const templateName = this._outputs[propName];
outputsArr.push({propName, templateName});
}
return outputsArr;
}
/** /**
* Creates a new component. * Creates a new component.
*/ */

View File

@ -42,21 +42,42 @@ export function unwrapValue(value: any): any {
return value; return value;
} }
let _renderCompCount = 0; const UNDEFINED_RENDERER_TYPE_ID = '$$undefined';
const EMPTY_RENDERER_TYPE_ID = '$$empty';
// Attention: this function is called as top level function.
// Putting any logic in here will destroy closure tree shaking!
export function createRendererType2(values: { export function createRendererType2(values: {
styles: (string | any[])[], styles: (string | any[])[],
encapsulation: ViewEncapsulation, encapsulation: ViewEncapsulation,
data: {[kind: string]: any[]} data: {[kind: string]: any[]}
}): RendererType2 { }): RendererType2 {
const isFilled = values && (values.encapsulation !== ViewEncapsulation.None || return {
values.styles.length || Object.keys(values.data).length); id: UNDEFINED_RENDERER_TYPE_ID,
styles: values.styles,
encapsulation: values.encapsulation,
data: values.data
};
}
let _renderCompCount = 0;
export function resolveRendererType2(type: RendererType2): RendererType2 {
if (type && type.id === UNDEFINED_RENDERER_TYPE_ID) {
// first time we see this RendererType2. Initialize it...
const isFilled =
((type.encapsulation != null && type.encapsulation !== ViewEncapsulation.None) ||
type.styles.length || Object.keys(type.data).length);
if (isFilled) { if (isFilled) {
const id = `c${_renderCompCount++}`; type.id = `c${_renderCompCount++}`;
return {id: id, styles: values.styles, encapsulation: values.encapsulation, data: values.data};
} else { } else {
return null; type.id = EMPTY_RENDERER_TYPE_ID;
} }
}
if (type && type.id === EMPTY_RENDERER_TYPE_ID) {
type = null;
}
return type;
} }
export function checkBinding( export function checkBinding(