fix(ivy): make defineComponent tree shakable by Closure Compiler (#26425)

PR Close #26425
This commit is contained in:
Miško Hevery 2018-10-12 15:49:42 -07:00 committed by Misko Hevery
parent 371ffef4ce
commit 4d164b6ca4
8 changed files with 74 additions and 31 deletions

View File

@ -12,7 +12,8 @@ import {ChangeDetectionStrategy} from '../change_detection/constants';
import {Provider} from '../di/provider'; import {Provider} from '../di/provider';
import {NgModuleDef} from '../metadata/ng_module'; import {NgModuleDef} from '../metadata/ng_module';
import {ViewEncapsulation} from '../metadata/view'; 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 {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'; import {BaseDef, ComponentDef, ComponentDefFeature, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition';
@ -265,13 +266,9 @@ export function defineComponent<T>(componentDefinition: {
pipes?: PipeTypesOrFactory | null; pipes?: PipeTypesOrFactory | null;
}): never { }): never {
const type = componentDefinition.type; const type = componentDefinition.type;
const pipeTypes = componentDefinition.pipes !; const typePrototype = type.prototype;
const directiveTypes = componentDefinition.directives !;
const declaredInputs: {[key: string]: string} = {} as any; const declaredInputs: {[key: string]: string} = {} as any;
const encapsulation = componentDefinition.encapsulation || ViewEncapsulation.Emulated; const def: Mutable<ComponentDef<any>, keyof ComponentDef<any>> = {
const styles: string[] = componentDefinition.styles || EMPTY_ARRAY;
const data = componentDefinition.data || {};
const def: ComponentDef<any> = {
type: type, type: type,
diPublic: null, diPublic: null,
consts: componentDefinition.consts, consts: componentDefinition.consts,
@ -283,38 +280,49 @@ export function defineComponent<T>(componentDefinition: {
contentQueries: componentDefinition.contentQueries || null, contentQueries: componentDefinition.contentQueries || null,
contentQueriesRefresh: componentDefinition.contentQueriesRefresh || null, contentQueriesRefresh: componentDefinition.contentQueriesRefresh || null,
attributes: componentDefinition.attributes || null, attributes: componentDefinition.attributes || null,
inputs: invertObject(componentDefinition.inputs, declaredInputs),
declaredInputs: declaredInputs, declaredInputs: declaredInputs,
outputs: invertObject(componentDefinition.outputs), inputs: null !, // assigned in noSideEffects
outputs: null !, // assigned in noSideEffects
exportAs: componentDefinition.exportAs || null, exportAs: componentDefinition.exportAs || null,
onInit: type.prototype.ngOnInit || null, onInit: typePrototype.ngOnInit || null,
doCheck: type.prototype.ngDoCheck || null, doCheck: typePrototype.ngDoCheck || null,
afterContentInit: type.prototype.ngAfterContentInit || null, afterContentInit: typePrototype.ngAfterContentInit || null,
afterContentChecked: type.prototype.ngAfterContentChecked || null, afterContentChecked: typePrototype.ngAfterContentChecked || null,
afterViewInit: type.prototype.ngAfterViewInit || null, afterViewInit: typePrototype.ngAfterViewInit || null,
afterViewChecked: type.prototype.ngAfterViewChecked || null, afterViewChecked: typePrototype.ngAfterViewChecked || null,
onDestroy: type.prototype.ngOnDestroy || null, onDestroy: typePrototype.ngOnDestroy || null,
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush, onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
directiveDefs: directiveTypes ? directiveDefs: null !, // assigned in noSideEffects
() => (typeof directiveTypes === 'function' ? directiveTypes() : directiveTypes) pipeDefs: null !, // assigned in noSideEffects
.map(extractDirectiveDef) :
null,
pipeDefs: pipeTypes ?
() => (typeof pipeTypes === 'function' ? pipeTypes() : pipeTypes).map(extractPipeDef) :
null,
selectors: componentDefinition.selectors, selectors: componentDefinition.selectors,
viewQuery: componentDefinition.viewQuery || null, viewQuery: componentDefinition.viewQuery || null,
features: componentDefinition.features || 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 // 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. // next line. Also `None` should be 0 not 2.
encapsulation, encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated,
providers: EMPTY_ARRAY, providers: EMPTY_ARRAY,
viewProviders: EMPTY_ARRAY, viewProviders: EMPTY_ARRAY,
id: `c${_renderCompCount++}`, styles, id: 'c',
styles: componentDefinition.styles || EMPTY_ARRAY,
_: null as never,
}; };
def._ = noSideEffects(() => {
const directiveTypes = componentDefinition.directives !;
const feature = componentDefinition.features; 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)); 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; return def as never;
} }

View File

@ -190,7 +190,7 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
/** /**
* Runtime unique component ID. * Runtime unique component ID.
*/ */
id: string; readonly id: string;
/** /**
* The View template of the component. * The View template of the component.
@ -209,7 +209,7 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
* can pre-fill the array and set the binding start index. * can pre-fill the array and set the binding start index.
*/ */
// TODO(kara): remove queries from this count // TODO(kara): remove queries from this count
consts: number; readonly consts: number;
/** /**
* The number of bindings in this component template (including pure fn bindings). * The number of bindings in this component template (including pure fn bindings).
@ -217,7 +217,7 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
* Used to calculate the length of the component's LViewData array, so we * 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. * can pre-fill the array and set the host binding start index.
*/ */
vars: number; readonly vars: number;
/** /**
* Query-related instructions for a component. * Query-related instructions for a component.
@ -271,6 +271,12 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
* `PipeDefs`s. The function is necessary to be able to support forward declarations. * `PipeDefs`s. The function is necessary to be able to support forward declarations.
*/ */
pipeDefs: PipeDefListOrFactory|null; 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;
} }
/** /**

View File

@ -23,3 +23,7 @@ export function isType(v: any): v is Type<any> {
} }
export interface Type<T> extends Function { new (...args: any[]): T; } export interface Type<T> extends Function { new (...args: any[]): T; }
export type Mutable<T extends{[x: string]: any}, K extends string> = {
[P in K]: T[P];
};

View File

@ -96,3 +96,16 @@ export function stringify(token: any): string {
const newLineIndex = res.indexOf('\n'); const newLineIndex = res.indexOf('\n');
return newLineIndex === -1 ? res : res.substring(0, newLineIndex); 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};
}

View File

@ -827,6 +827,9 @@
{ {
"name": "nextNgElementId" "name": "nextNgElementId"
}, },
{
"name": "noSideEffects"
},
{ {
"name": "pointers" "name": "pointers"
}, },

View File

@ -308,6 +308,9 @@
{ {
"name": "nextNgElementId" "name": "nextNgElementId"
}, },
{
"name": "noSideEffects"
},
{ {
"name": "prefillHostVars" "name": "prefillHostVars"
}, },

View File

@ -854,6 +854,9 @@
{ {
"name": "nextNgElementId" "name": "nextNgElementId"
}, },
{
"name": "noSideEffects"
},
{ {
"name": "pointers" "name": "pointers"
}, },

View File

@ -2120,6 +2120,9 @@
{ {
"name": "noComponentFactoryError" "name": "noComponentFactoryError"
}, },
{
"name": "noSideEffects"
},
{ {
"name": "noop$1" "name": "noop$1"
}, },