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) { | ||||
|       return this._staticSymbolCache.get( | ||||
|           ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType)); | ||||
|     } else { | ||||
|       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.
 | ||||
|       return createComponentFactory(selector, dirType, <any>hostView, {}, {}, []); | ||||
|       return createComponentFactory(selector, dirType, <any>hostView, inputs, outputs, []); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private initComponentFactory( | ||||
|       factory: StaticSymbol|ComponentFactory<any>, inputs: {[key: string]: string}, | ||||
|       outputs: {[key: string]: string}, ngContentSelectors: string[]) { | ||||
|       factory: StaticSymbol|ComponentFactory<any>, ngContentSelectors: string[]) { | ||||
|     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); | ||||
|     } | ||||
|   } | ||||
| @ -205,9 +198,7 @@ export class CompileMetadataResolver { | ||||
|         template: templateMetadata | ||||
|       }); | ||||
|       if (templateMetadata) { | ||||
|         this.initComponentFactory( | ||||
|             metadata.componentFactory, metadata.inputs, metadata.outputs, | ||||
|             templateMetadata.ngContentSelectors); | ||||
|         this.initComponentFactory(metadata.componentFactory, templateMetadata.ngContentSelectors); | ||||
|       } | ||||
|       this._directiveCache.set(directiveType, normalizedDirMeta); | ||||
|       this._summaryCache.set(directiveType, normalizedDirMeta.toSummary()); | ||||
| @ -343,10 +334,12 @@ export class CompileMetadataResolver { | ||||
|       componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) : | ||||
|                                                          undefined, | ||||
|       rendererType: nonNormalizedTemplateMetadata ? this.getRendererType(directiveType) : undefined, | ||||
|       componentFactory: nonNormalizedTemplateMetadata ? | ||||
|           this.getComponentFactory(selector, directiveType) : | ||||
|           undefined | ||||
|       componentFactory: undefined | ||||
|     }); | ||||
|     if (nonNormalizedTemplateMetadata) { | ||||
|       metadata.componentFactory = | ||||
|           this.getComponentFactory(selector, directiveType, metadata.inputs, metadata.outputs); | ||||
|     } | ||||
|     cacheEntry = {metadata, annotation: dirMeta}; | ||||
|     this._nonNormalizedDirectiveCache.set(directiveType, cacheEntry); | ||||
|     return cacheEntry; | ||||
|  | ||||
| @ -6,11 +6,12 @@ | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| 
 | ||||
| import {ViewEncapsulation} from '../metadata/view'; | ||||
| import {Renderer2, RendererType2} from '../render/api'; | ||||
| 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 {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( | ||||
|     flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][], | ||||
| @ -112,11 +113,7 @@ export function elementDef( | ||||
|     const [ns, name] = splitNamespace(namespaceAndName); | ||||
|     return [ns, name, value]; | ||||
|   }); | ||||
|   // This is needed as the jit compiler always uses an empty hash as default RendererType2,
 | ||||
|   // which is not filled for host views.
 | ||||
|   if (componentRendererType && componentRendererType.encapsulation == null) { | ||||
|     componentRendererType = null; | ||||
|   } | ||||
|   componentRendererType = resolveRendererType2(componentRendererType); | ||||
|   if (componentView) { | ||||
|     flags |= NodeFlags.ComponentView; | ||||
|   } | ||||
|  | ||||
| @ -25,22 +25,14 @@ import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView, renderDetachVi | ||||
| 
 | ||||
| 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( | ||||
|     selector: string, componentType: Type<any>, viewDefFactory: ViewDefinitionFactory, | ||||
|     inputs: {[propName: string]: string}, outputs: {[propName: string]: string}, | ||||
|     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_( | ||||
|       selector, componentType, viewDefFactory, inputsArr, outputsArr, ngContentSelectors); | ||||
|       selector, componentType, viewDefFactory, inputs, outputs, ngContentSelectors); | ||||
| } | ||||
| 
 | ||||
| export function getComponentViewDefinitionFactory(componentFactory: ComponentFactory<any>): | ||||
| @ -56,14 +48,32 @@ class ComponentFactory_ extends ComponentFactory<any> { | ||||
| 
 | ||||
|   constructor( | ||||
|       public selector: string, public componentType: Type<any>, | ||||
|       viewDefFactory: ViewDefinitionFactory, | ||||
|       public inputs: {propName: string, templateName: string}[], | ||||
|       public outputs: {propName: string, templateName: string}[], | ||||
|       public ngContentSelectors: string[]) { | ||||
|       viewDefFactory: ViewDefinitionFactory, private _inputs: {[propName: string]: string}, | ||||
|       private _outputs: {[propName: string]: string}, public ngContentSelectors: string[]) { | ||||
|     // Attention: this ctor is called as top level function.
 | ||||
|     // Putting any logic in here will destroy closure tree shaking!
 | ||||
|     super(); | ||||
|     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. | ||||
|    */ | ||||
|  | ||||
| @ -42,21 +42,42 @@ export function unwrapValue(value: any): any { | ||||
|   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: { | ||||
|   styles: (string | any[])[], | ||||
|   encapsulation: ViewEncapsulation, | ||||
|   data: {[kind: string]: any[]} | ||||
| }): RendererType2 { | ||||
|   const isFilled = values && (values.encapsulation !== ViewEncapsulation.None || | ||||
|                               values.styles.length || Object.keys(values.data).length); | ||||
|   return { | ||||
|     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) { | ||||
|     const id = `c${_renderCompCount++}`; | ||||
|     return {id: id, styles: values.styles, encapsulation: values.encapsulation, data: values.data}; | ||||
|       type.id = `c${_renderCompCount++}`; | ||||
|     } else { | ||||
|     return null; | ||||
|       type.id = EMPTY_RENDERER_TYPE_ID; | ||||
|     } | ||||
|   } | ||||
|   if (type && type.id === EMPTY_RENDERER_TYPE_ID) { | ||||
|     type = null; | ||||
|   } | ||||
|   return type; | ||||
| } | ||||
| 
 | ||||
| export function checkBinding( | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user