From 4d164b6ca49036906c2fb7a18117f49bf30bbaa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Fri, 12 Oct 2018 15:49:42 -0700 Subject: [PATCH] fix(ivy): make defineComponent tree shakable by Closure Compiler (#26425) PR Close #26425 --- packages/core/src/render3/definition.ts | 64 +++++++++++-------- .../core/src/render3/interfaces/definition.ts | 12 +++- packages/core/src/type.ts | 4 ++ packages/core/src/util.ts | 13 ++++ .../bundle.golden_symbols.json | 3 + .../hello_world/bundle.golden_symbols.json | 3 + .../bundling/todo/bundle.golden_symbols.json | 3 + .../todo_r2/bundle.golden_symbols.json | 3 + 8 files changed, 74 insertions(+), 31 deletions(-) diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 8615645e0c..cd48ffda9a 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -12,7 +12,8 @@ import {ChangeDetectionStrategy} from '../change_detection/constants'; import {Provider} from '../di/provider'; import {NgModuleDef} from '../metadata/ng_module'; import {ViewEncapsulation} from '../metadata/view'; -import {Type} from '../type'; +import {Mutable, Type} from '../type'; +import {noSideEffects} from '../util'; import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields'; import {BaseDef, ComponentDef, ComponentDefFeature, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition'; @@ -265,13 +266,9 @@ export function defineComponent(componentDefinition: { pipes?: PipeTypesOrFactory | null; }): never { const type = componentDefinition.type; - const pipeTypes = componentDefinition.pipes !; - const directiveTypes = componentDefinition.directives !; + const typePrototype = type.prototype; const declaredInputs: {[key: string]: string} = {} as any; - const encapsulation = componentDefinition.encapsulation || ViewEncapsulation.Emulated; - const styles: string[] = componentDefinition.styles || EMPTY_ARRAY; - const data = componentDefinition.data || {}; - const def: ComponentDef = { + const def: Mutable, keyof ComponentDef> = { type: type, diPublic: null, consts: componentDefinition.consts, @@ -283,38 +280,49 @@ export function defineComponent(componentDefinition: { contentQueries: componentDefinition.contentQueries || null, contentQueriesRefresh: componentDefinition.contentQueriesRefresh || null, attributes: componentDefinition.attributes || null, - inputs: invertObject(componentDefinition.inputs, declaredInputs), declaredInputs: declaredInputs, - outputs: invertObject(componentDefinition.outputs), + inputs: null !, // assigned in noSideEffects + outputs: null !, // assigned in noSideEffects exportAs: componentDefinition.exportAs || null, - onInit: type.prototype.ngOnInit || null, - doCheck: type.prototype.ngDoCheck || null, - afterContentInit: type.prototype.ngAfterContentInit || null, - afterContentChecked: type.prototype.ngAfterContentChecked || null, - afterViewInit: type.prototype.ngAfterViewInit || null, - afterViewChecked: type.prototype.ngAfterViewChecked || null, - onDestroy: type.prototype.ngOnDestroy || null, + onInit: typePrototype.ngOnInit || null, + doCheck: typePrototype.ngDoCheck || null, + afterContentInit: typePrototype.ngAfterContentInit || null, + afterContentChecked: typePrototype.ngAfterContentChecked || null, + afterViewInit: typePrototype.ngAfterViewInit || null, + afterViewChecked: typePrototype.ngAfterViewChecked || null, + onDestroy: typePrototype.ngOnDestroy || null, onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush, - directiveDefs: directiveTypes ? - () => (typeof directiveTypes === 'function' ? directiveTypes() : directiveTypes) - .map(extractDirectiveDef) : - null, - pipeDefs: pipeTypes ? - () => (typeof pipeTypes === 'function' ? pipeTypes() : pipeTypes).map(extractPipeDef) : - null, + directiveDefs: null !, // assigned in noSideEffects + pipeDefs: null !, // assigned in noSideEffects selectors: componentDefinition.selectors, viewQuery: componentDefinition.viewQuery || null, features: componentDefinition.features || null, - data, + data: componentDefinition.data || {}, // TODO(misko): convert ViewEncapsulation into const enum so that it can be used directly in the // next line. Also `None` should be 0 not 2. - encapsulation, + encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated, providers: EMPTY_ARRAY, viewProviders: EMPTY_ARRAY, - id: `c${_renderCompCount++}`, styles, + id: 'c', + styles: componentDefinition.styles || EMPTY_ARRAY, + _: null as never, }; - const feature = componentDefinition.features; - feature && feature.forEach((fn) => fn(def)); + def._ = noSideEffects(() => { + const directiveTypes = componentDefinition.directives !; + const feature = componentDefinition.features; + const pipeTypes = componentDefinition.pipes !; + def.id += _renderCompCount++; + def.inputs = invertObject(componentDefinition.inputs, declaredInputs), + def.outputs = invertObject(componentDefinition.outputs), + feature && feature.forEach((fn) => fn(def)); + def.directiveDefs = directiveTypes ? + () => (typeof directiveTypes === 'function' ? directiveTypes() : directiveTypes) + .map(extractDirectiveDef) : + null; + def.pipeDefs = pipeTypes ? + () => (typeof pipeTypes === 'function' ? pipeTypes() : pipeTypes).map(extractPipeDef) : + null; + }) as never; return def as never; } diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index a061291397..b94b4af1ca 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -190,7 +190,7 @@ export interface ComponentDef extends DirectiveDef { /** * Runtime unique component ID. */ - id: string; + readonly id: string; /** * The View template of the component. @@ -209,7 +209,7 @@ export interface ComponentDef extends DirectiveDef { * can pre-fill the array and set the binding start index. */ // TODO(kara): remove queries from this count - consts: number; + readonly consts: number; /** * The number of bindings in this component template (including pure fn bindings). @@ -217,7 +217,7 @@ export interface ComponentDef extends DirectiveDef { * Used to calculate the length of the component's LViewData array, so we * can pre-fill the array and set the host binding start index. */ - vars: number; + readonly vars: number; /** * Query-related instructions for a component. @@ -271,6 +271,12 @@ export interface ComponentDef extends DirectiveDef { * `PipeDefs`s. The function is necessary to be able to support forward declarations. */ pipeDefs: PipeDefListOrFactory|null; + + /** + * Used to store the result of `noSideEffects` function so that it is not removed by closure + * compiler. The property should never be read. + */ + readonly _?: never; } /** diff --git a/packages/core/src/type.ts b/packages/core/src/type.ts index 42f072385b..eed33add7d 100644 --- a/packages/core/src/type.ts +++ b/packages/core/src/type.ts @@ -23,3 +23,7 @@ export function isType(v: any): v is Type { } export interface Type extends Function { new (...args: any[]): T; } + +export type Mutable = { + [P in K]: T[P]; +}; diff --git a/packages/core/src/util.ts b/packages/core/src/util.ts index 4c0011dbb4..07a25685f8 100644 --- a/packages/core/src/util.ts +++ b/packages/core/src/util.ts @@ -96,3 +96,16 @@ export function stringify(token: any): string { const newLineIndex = res.indexOf('\n'); return newLineIndex === -1 ? res : res.substring(0, newLineIndex); } + +/** + * Convince closure compiler that the wrapped function has no side-effects. + * + * Closure compiler always assumes that `toString` has no side-effects. We use this quirk to + * allow us to execute a function but have closure compiler mark the call as no-side-effects. + * It is important that the return value for the `noSideEffects` function be assigned + * to something which is retained otherwise the call to `noSideEffects` will be removed by closure + * compiler. + */ +export function noSideEffects(fn: () => void): string { + return '' + {toString: fn}; +} \ No newline at end of file diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index 03b7c14b2b..b56e86b8af 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -827,6 +827,9 @@ { "name": "nextNgElementId" }, + { + "name": "noSideEffects" + }, { "name": "pointers" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 6c7cdf4cd0..4508d3edc7 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -308,6 +308,9 @@ { "name": "nextNgElementId" }, + { + "name": "noSideEffects" + }, { "name": "prefillHostVars" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 57d7f4e833..67a3688d3d 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -854,6 +854,9 @@ { "name": "nextNgElementId" }, + { + "name": "noSideEffects" + }, { "name": "pointers" }, diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 17d929abcb..311d1be5df 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -2120,6 +2120,9 @@ { "name": "noComponentFactoryError" }, + { + "name": "noSideEffects" + }, { "name": "noop$1" },