refactor(ivy): split up directiveCreate for tree shaking (#22838)

PR Close #22838
This commit is contained in:
Kara Erickson 2018-03-16 20:31:24 -07:00 committed by Misko Hevery
parent 1612985e48
commit e27cfd6236
5 changed files with 67 additions and 43 deletions

View File

@ -12,8 +12,8 @@ import {Injector} from '../di/injector';
import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory'; import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
import {assertNotNull} from './assert'; import {assertNotNull} from './assert';
import {queueLifecycleHooks} from './hooks'; import {queueInitHooks, queueLifecycleHooks} from './hooks';
import {CLEAN_PROMISE, _getComponentHostLElementNode, createLView, createTView, directiveCreate, enterView, getDirectiveInstance, getRootView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderComponentOrTemplate} from './instructions'; import {CLEAN_PROMISE, _getComponentHostLElementNode, baseDirectiveCreate, createLView, createTView, enterView, getRootView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderComponentOrTemplate} from './instructions';
import {ComponentDef, ComponentType} from './interfaces/definition'; import {ComponentDef, ComponentType} from './interfaces/definition';
import {LElementNode} from './interfaces/node'; import {LElementNode} from './interfaces/node';
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
@ -22,6 +22,7 @@ import {stringify} from './util';
import {createViewRef} from './view_ref'; import {createViewRef} from './view_ref';
/** Options that control how the component should be bootstrapped. */ /** Options that control how the component should be bootstrapped. */
export interface CreateComponentOptions { export interface CreateComponentOptions {
/** Which renderer factory to use. */ /** Which renderer factory to use. */
@ -135,7 +136,7 @@ export function renderComponent<T>(
// Create element node at index 0 in data array // Create element node at index 0 in data array
elementNode = hostElement(hostNode, componentDef); elementNode = hostElement(hostNode, componentDef);
// Create directive instance with n() and store at index 1 in data array (el is 0) // Create directive instance with n() and store at index 1 in data array (el is 0)
component = rootContext.component = directiveCreate(1, componentDef.factory(), componentDef) as T; component = rootContext.component = baseDirectiveCreate(1, componentDef.factory(), componentDef) as T;
initChangeDetectorIfExisting(elementNode.nodeInjector, component); initChangeDetectorIfExisting(elementNode.nodeInjector, component);
} finally { } finally {
// We must not use leaveView here because it will set creationMode to false too early, // We must not use leaveView here because it will set creationMode to false too early,
@ -164,6 +165,9 @@ export function renderComponent<T>(
*/ */
export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void { export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void {
const elementNode = _getComponentHostLElementNode(component); const elementNode = _getComponentHostLElementNode(component);
// Root component is always created at dir index 1, after host element at 0
queueInitHooks(1, def.onInit, def.doCheck, elementNode.view.tView);
queueLifecycleHooks(elementNode.flags, elementNode.view); queueLifecycleHooks(elementNode.flags, elementNode.view);
} }

View File

@ -1096,6 +1096,34 @@ export function textBinding<T>(index: number, value: T | NO_CHANGE): void {
export function directiveCreate<T>( export function directiveCreate<T>(
index: number, directive: T, directiveDef: DirectiveDef<T>, index: number, directive: T, directiveDef: DirectiveDef<T>,
localNames?: (string | number)[] | null): T { localNames?: (string | number)[] | null): T {
const instance = baseDirectiveCreate(index, directive, directiveDef);
ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
const tNode: TNode|null = previousOrParentNode.tNode !;
if (currentView.tView.firstTemplatePass && localNames) {
tNode.localNames = tNode.localNames ? tNode.localNames.concat(localNames) : localNames;
}
if (tNode && tNode.attrs) {
setInputsFromAttrs<T>(instance, directiveDef !.inputs, tNode);
}
// Init hooks are queued now so ngOnInit is called in host components before
// any projected components.
queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView);
return instance;
}
/**
* A lighter version of directiveCreate() that is used for the root component
*
* This version does not contain features that we don't already support at root in
* current Angular. Example: local refs and inputs on root component.
*/
export function baseDirectiveCreate<T>(
index: number, directive: T, directiveDef: DirectiveDef<T>): T {
let instance; let instance;
ngDevMode && ngDevMode &&
assertNull(currentView.bindingStartIndex, 'directives should be created before any bindings'); assertNull(currentView.bindingStartIndex, 'directives should be created before any bindings');
@ -1117,11 +1145,6 @@ export function directiveCreate<T>(
if (index >= tData.length) { if (index >= tData.length) {
tData[index] = directiveDef !; tData[index] = directiveDef !;
if (localNames) {
ngDevMode && assertNotNull(previousOrParentNode.tNode, 'previousOrParentNode.tNode');
const tNode = previousOrParentNode !.tNode !;
tNode.localNames = tNode.localNames ? tNode.localNames.concat(localNames) : localNames;
}
} }
const diPublic = directiveDef !.diPublic; const diPublic = directiveDef !.diPublic;
@ -1135,15 +1158,6 @@ export function directiveCreate<T>(
(previousOrParentNode as LElementNode).native, directiveDef !.attributes as string[]); (previousOrParentNode as LElementNode).native, directiveDef !.attributes as string[]);
} }
const tNode: TNode|null = previousOrParentNode.tNode !;
if (tNode && tNode.attrs) {
setInputsFromAttrs<T>(instance, directiveDef !.inputs, tNode);
}
// Init hooks are queued now so ngOnInit is called in host components before
// any projected components.
queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView);
return instance; return instance;
} }

View File

@ -41,6 +41,9 @@
{ {
"name": "appendChild" "name": "appendChild"
}, },
{
"name": "baseDirectiveCreate"
},
{ {
"name": "bindingUpdated" "name": "bindingUpdated"
}, },
@ -74,9 +77,6 @@
{ {
"name": "detectChangesInternal" "name": "detectChangesInternal"
}, },
{
"name": "directiveCreate"
},
{ {
"name": "domRendererFactory3" "name": "domRendererFactory3"
}, },
@ -98,9 +98,6 @@
{ {
"name": "findNextRNodeSibling" "name": "findNextRNodeSibling"
}, },
{
"name": "generateInitialInputs"
},
{ {
"name": "getDirectiveInstance" "name": "getDirectiveInstance"
}, },
@ -146,9 +143,6 @@
{ {
"name": "locateHostElement" "name": "locateHostElement"
}, },
{
"name": "queueInitHooks"
},
{ {
"name": "refreshChildComponents" "name": "refreshChildComponents"
}, },
@ -179,9 +173,6 @@
{ {
"name": "setHostBindings" "name": "setHostBindings"
}, },
{
"name": "setInputsFromAttrs"
},
{ {
"name": "setUpAttributes" "name": "setUpAttributes"
}, },

View File

@ -10,7 +10,7 @@ import {withBody} from '@angular/core/testing';
import {ChangeDetectionStrategy, ChangeDetectorRef, DoCheck} from '../../src/core'; import {ChangeDetectionStrategy, ChangeDetectorRef, DoCheck} from '../../src/core';
import {getRenderedText, whenRendered} from '../../src/render3/component'; import {getRenderedText, whenRendered} from '../../src/render3/component';
import {defineComponent, defineDirective, injectChangeDetectorRef} from '../../src/render3/index'; import {LifecycleHooksFeature, defineComponent, defineDirective, injectChangeDetectorRef} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, listener, markDirty, text, textBinding, tick} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, listener, markDirty, text, textBinding, tick} from '../../src/render3/instructions';
import {containerEl, renderComponent, requestAnimationFrame} from './render_util'; import {containerEl, renderComponent, requestAnimationFrame} from './render_util';
@ -39,7 +39,7 @@ describe('change detection', () => {
} }
it('should mark a component dirty and schedule change detection', withBody('my-comp', () => { it('should mark a component dirty and schedule change detection', withBody('my-comp', () => {
const myComp = renderComponent(MyComponent); const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]});
expect(getRenderedText(myComp)).toEqual('works'); expect(getRenderedText(myComp)).toEqual('works');
myComp.value = 'updated'; myComp.value = 'updated';
markDirty(myComp); markDirty(myComp);
@ -49,7 +49,7 @@ describe('change detection', () => {
})); }));
it('should detectChanges on a component', withBody('my-comp', () => { it('should detectChanges on a component', withBody('my-comp', () => {
const myComp = renderComponent(MyComponent); const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]});
expect(getRenderedText(myComp)).toEqual('works'); expect(getRenderedText(myComp)).toEqual('works');
myComp.value = 'updated'; myComp.value = 'updated';
detectChanges(myComp); detectChanges(myComp);
@ -58,7 +58,7 @@ describe('change detection', () => {
it('should detectChanges only once if markDirty is called multiple times', it('should detectChanges only once if markDirty is called multiple times',
withBody('my-comp', () => { withBody('my-comp', () => {
const myComp = renderComponent(MyComponent); const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]});
expect(getRenderedText(myComp)).toEqual('works'); expect(getRenderedText(myComp)).toEqual('works');
expect(myComp.doCheckCount).toBe(1); expect(myComp.doCheckCount).toBe(1);
myComp.value = 'ignore'; myComp.value = 'ignore';
@ -72,7 +72,7 @@ describe('change detection', () => {
})); }));
it('should notify whenRendered', withBody('my-comp', async() => { it('should notify whenRendered', withBody('my-comp', async() => {
const myComp = renderComponent(MyComponent); const myComp = renderComponent(MyComponent, {hostFeatures: [LifecycleHooksFeature]});
await whenRendered(myComp); await whenRendered(myComp);
myComp.value = 'updated'; myComp.value = 'updated';
markDirty(myComp); markDirty(myComp);
@ -347,7 +347,7 @@ describe('change detection', () => {
it('should check the component view when called by component (even when OnPush && clean)', it('should check the component view when called by component (even when OnPush && clean)',
() => { () => {
const comp = renderComponent(MyComp); const comp = renderComponent(MyComp, {hostFeatures: [LifecycleHooksFeature]});
expect(getRenderedText(comp)).toEqual('Nancy'); expect(getRenderedText(comp)).toEqual('Nancy');
comp.name = 'Bess'; // as this is not an Input, the component stays clean comp.name = 'Bess'; // as this is not an Input, the component stays clean
@ -356,7 +356,7 @@ describe('change detection', () => {
}); });
it('should NOT call component doCheck when called by a component', () => { it('should NOT call component doCheck when called by a component', () => {
const comp = renderComponent(MyComp); const comp = renderComponent(MyComp, {hostFeatures: [LifecycleHooksFeature]});
expect(comp.doCheckCount).toEqual(1); expect(comp.doCheckCount).toEqual(1);
// NOTE: in current Angular, detectChanges does not itself trigger doCheck, but you // NOTE: in current Angular, detectChanges does not itself trigger doCheck, but you
@ -367,7 +367,7 @@ describe('change detection', () => {
}); });
it('should NOT check the component parent when called by a child component', () => { it('should NOT check the component parent when called by a child component', () => {
const parentComp = renderComponent(ParentComp); const parentComp = renderComponent(ParentComp, {hostFeatures: [LifecycleHooksFeature]});
expect(getRenderedText(parentComp)).toEqual('1 - Nancy'); expect(getRenderedText(parentComp)).toEqual('1 - Nancy');
parentComp.doCheckCount = 100; parentComp.doCheckCount = 100;
@ -378,7 +378,7 @@ describe('change detection', () => {
it('should check component children when called by component if dirty or check-always', it('should check component children when called by component if dirty or check-always',
() => { () => {
const parentComp = renderComponent(ParentComp); const parentComp = renderComponent(ParentComp, {hostFeatures: [LifecycleHooksFeature]});
expect(parentComp.doCheckCount).toEqual(1); expect(parentComp.doCheckCount).toEqual(1);
myComp.name = 'Bess'; myComp.name = 'Bess';
@ -390,7 +390,7 @@ describe('change detection', () => {
}); });
it('should not group detectChanges calls (call every time)', () => { it('should not group detectChanges calls (call every time)', () => {
const parentComp = renderComponent(ParentComp); const parentComp = renderComponent(ParentComp, {hostFeatures: [LifecycleHooksFeature]});
expect(myComp.doCheckCount).toEqual(1); expect(myComp.doCheckCount).toEqual(1);
parentComp.cdr.detectChanges(); parentComp.cdr.detectChanges();
@ -524,7 +524,7 @@ describe('change detection', () => {
}); });
} }
const comp = renderComponent(DetectChangesComp); const comp = renderComponent(DetectChangesComp, {hostFeatures: [LifecycleHooksFeature]});
expect(getRenderedText(comp)).toEqual('1'); expect(getRenderedText(comp)).toEqual('1');
}); });
@ -553,7 +553,7 @@ describe('change detection', () => {
}); });
} }
const comp = renderComponent(DetectChangesComp); const comp = renderComponent(DetectChangesComp, {hostFeatures: [LifecycleHooksFeature]});
expect(getRenderedText(comp)).toEqual('1'); expect(getRenderedText(comp)).toEqual('1');
}); });
@ -940,7 +940,7 @@ describe('change detection', () => {
} }
it('should throw if bindings in current view have changed', () => { it('should throw if bindings in current view have changed', () => {
const comp = renderComponent(NoChangesComp); const comp = renderComponent(NoChangesComp, {hostFeatures: [LifecycleHooksFeature]});
expect(() => comp.cdr.checkNoChanges()).not.toThrow(); expect(() => comp.cdr.checkNoChanges()).not.toThrow();

View File

@ -9,7 +9,7 @@
import {defineComponent, defineDirective} from '../../src/render3/index'; import {defineComponent, defineDirective} from '../../src/render3/index';
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleNamed, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleNamed, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
import {containerEl, renderToHtml} from './render_util'; import {ComponentFixture, containerEl, renderToHtml} from './render_util';
describe('render3 integration test', () => { describe('render3 integration test', () => {
@ -269,6 +269,21 @@ describe('render3 integration test', () => {
expect(renderToHtml(Template, {})).toEqual('<todo title="two">two</todo>'); expect(renderToHtml(Template, {})).toEqual('<todo title="two">two</todo>');
}); });
it('should support root component with host attribute', () => {
class HostAttributeComp {
static ngComponentDef = defineComponent({
type: HostAttributeComp,
tag: 'host-attr-comp',
factory: () => new HostAttributeComp(),
template: (ctx: HostAttributeComp, cm: boolean) => {},
attributes: ['role', 'button']
});
}
const fixture = new ComponentFixture(HostAttributeComp);
expect(fixture.hostElement.getAttribute('role')).toEqual('button');
});
it('should support component with bindings in template', () => { it('should support component with bindings in template', () => {
/** <p> {{ name }} </p>*/ /** <p> {{ name }} </p>*/
class MyComp { class MyComp {