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:
parent
9429032da1
commit
2a0e55ffb5
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user