perf(ivy): initialise TNode inputs / outputs on the first creation pass (#32608)
This perf-focused refactoring moves the TNode's input / output initialization logic to the first template pass - close to the place where directives are matched and resolved. This code change makes it possible to update-mode checks for both property bindings and listeners registration. PR Close #32608
This commit is contained in:
parent
adeee0fa7f
commit
ad178c55fd
|
@ -23,7 +23,7 @@ import {getInitialStylingValue, hasClassInput, hasStyleInput} from '../styling_n
|
||||||
import {setUpAttributes} from '../util/attrs_utils';
|
import {setUpAttributes} from '../util/attrs_utils';
|
||||||
import {getNativeByTNode, getTNode} from '../util/view_utils';
|
import {getNativeByTNode, getTNode} from '../util/view_utils';
|
||||||
|
|
||||||
import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, initializeTNodeInputs, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared';
|
import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,13 +84,14 @@ export function ɵɵelementStart(
|
||||||
ngDevMode && ngDevMode.firstTemplatePass++;
|
ngDevMode && ngDevMode.firstTemplatePass++;
|
||||||
resolveDirectives(tView, lView, tNode, localRefs || null);
|
resolveDirectives(tView, lView, tNode, localRefs || null);
|
||||||
|
|
||||||
const inputData = initializeTNodeInputs(tView, tNode);
|
const inputData = tNode.inputs;
|
||||||
if (inputData && inputData.hasOwnProperty('class')) {
|
if (inputData != null) {
|
||||||
tNode.flags |= TNodeFlags.hasClassInput;
|
if (inputData.hasOwnProperty('class')) {
|
||||||
}
|
tNode.flags |= TNodeFlags.hasClassInput;
|
||||||
|
}
|
||||||
if (inputData && inputData.hasOwnProperty('style')) {
|
if (inputData.hasOwnProperty('style')) {
|
||||||
tNode.flags |= TNodeFlags.hasStyleInput;
|
tNode.flags |= TNodeFlags.hasStyleInput;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tView.queries !== null) {
|
if (tView.queries !== null) {
|
||||||
|
|
|
@ -17,8 +17,7 @@ import {CLEANUP, FLAGS, LView, LViewFlags, RENDERER, TVIEW} from '../interfaces/
|
||||||
import {assertNodeOfPossibleTypes} from '../node_assert';
|
import {assertNodeOfPossibleTypes} from '../node_assert';
|
||||||
import {getLView, getPreviousOrParentTNode} from '../state';
|
import {getLView, getPreviousOrParentTNode} from '../state';
|
||||||
import {getComponentViewByIndex, getNativeByTNode, unwrapRNode} from '../util/view_utils';
|
import {getComponentViewByIndex, getNativeByTNode, unwrapRNode} from '../util/view_utils';
|
||||||
|
import {getCleanup, handleError, loadComponentRenderer, markViewDirty} from './shared';
|
||||||
import {BindingDirection, generatePropertyAliases, getCleanup, handleError, loadComponentRenderer, markViewDirty} from './shared';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an event listener to the current node.
|
* Adds an event listener to the current node.
|
||||||
|
@ -186,36 +185,28 @@ function listenerInternal(
|
||||||
}
|
}
|
||||||
|
|
||||||
// subscribe to directive outputs
|
// subscribe to directive outputs
|
||||||
if (isTNodeDirectiveHost && processOutputs) {
|
const outputs = tNode.outputs;
|
||||||
let outputs = tNode.outputs;
|
let props: PropertyAliasValue|undefined;
|
||||||
if (outputs === undefined) {
|
if (processOutputs && outputs != null && (props = outputs[eventName])) {
|
||||||
// if we create TNode here, inputs must be undefined so we know they still need to be
|
const propsLength = props.length;
|
||||||
// checked
|
if (propsLength) {
|
||||||
outputs = tNode.outputs = generatePropertyAliases(tView, tNode, BindingDirection.Output);
|
const lCleanup = getCleanup(lView);
|
||||||
}
|
for (let i = 0; i < propsLength; i += 3) {
|
||||||
|
const index = props[i] as number;
|
||||||
|
ngDevMode && assertDataInRange(lView, index);
|
||||||
|
const minifiedName = props[i + 2];
|
||||||
|
const directiveInstance = lView[index];
|
||||||
|
const output = directiveInstance[minifiedName];
|
||||||
|
|
||||||
let props: PropertyAliasValue|undefined;
|
if (ngDevMode && !isObservable(output)) {
|
||||||
if (outputs !== null && (props = outputs[eventName])) {
|
throw new Error(
|
||||||
const propsLength = props.length;
|
`@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`);
|
||||||
if (propsLength) {
|
|
||||||
const lCleanup = getCleanup(lView);
|
|
||||||
for (let i = 0; i < propsLength; i += 3) {
|
|
||||||
const index = props[i] as number;
|
|
||||||
ngDevMode && assertDataInRange(lView, index);
|
|
||||||
const minifiedName = props[i + 2];
|
|
||||||
const directiveInstance = lView[index];
|
|
||||||
const output = directiveInstance[minifiedName];
|
|
||||||
|
|
||||||
if (ngDevMode && !isObservable(output)) {
|
|
||||||
throw new Error(
|
|
||||||
`@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscription = output.subscribe(listenerFn);
|
|
||||||
const idx = lCleanup.length;
|
|
||||||
lCleanup.push(listenerFn, subscription);
|
|
||||||
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subscription = output.subscribe(listenerFn);
|
||||||
|
const idx = lCleanup.length;
|
||||||
|
lCleanup.push(listenerFn, subscription);
|
||||||
|
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} f
|
||||||
import {throwMultipleComponentError} from '../errors';
|
import {throwMultipleComponentError} from '../errors';
|
||||||
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPreOrderHooks} from '../hooks';
|
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPreOrderHooks} from '../hooks';
|
||||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
|
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
|
||||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, FactoryFn, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
||||||
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
|
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
|
||||||
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
|
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
|
||||||
import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
|
import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
|
||||||
|
@ -49,11 +49,6 @@ import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNod
|
||||||
*/
|
*/
|
||||||
const _CLEAN_PROMISE = (() => Promise.resolve(null))();
|
const _CLEAN_PROMISE = (() => Promise.resolve(null))();
|
||||||
|
|
||||||
export const enum BindingDirection {
|
|
||||||
Input,
|
|
||||||
Output,
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets the host bindings for the current view. */
|
/** Sets the host bindings for the current view. */
|
||||||
export function setHostBindings(tView: TView, viewData: LView): void {
|
export function setHostBindings(tView: TView, viewData: LView): void {
|
||||||
const selectedIndex = getSelectedIndex();
|
const selectedIndex = getSelectedIndex();
|
||||||
|
@ -803,41 +798,47 @@ export function createTNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
function generatePropertyAliases(
|
||||||
* Consolidates all inputs or outputs of all directives on this logical node.
|
inputAliasMap: {[publicName: string]: string}, directiveDefIdx: number,
|
||||||
*
|
propStore: PropertyAliases | null): PropertyAliases|null {
|
||||||
* @param tNode
|
for (let publicName in inputAliasMap) {
|
||||||
* @param direction whether to consider inputs or outputs
|
if (inputAliasMap.hasOwnProperty(publicName)) {
|
||||||
* @returns PropertyAliases|null aggregate of all properties if any, `null` otherwise
|
propStore = propStore === null ? {} : propStore;
|
||||||
*/
|
const internalName = inputAliasMap[publicName];
|
||||||
export function generatePropertyAliases(
|
|
||||||
tView: TView, tNode: TNode, direction: BindingDirection): PropertyAliases|null {
|
|
||||||
let propStore: PropertyAliases|null = null;
|
|
||||||
const start = tNode.directiveStart;
|
|
||||||
const end = tNode.directiveEnd;
|
|
||||||
|
|
||||||
if (end > start) {
|
if (propStore.hasOwnProperty(publicName)) {
|
||||||
const isInput = direction === BindingDirection.Input;
|
propStore[publicName].push(directiveDefIdx, publicName, internalName);
|
||||||
const defs = tView.data;
|
} else {
|
||||||
|
(propStore[publicName] = [directiveDefIdx, publicName, internalName]);
|
||||||
for (let i = start; i < end; i++) {
|
|
||||||
const directiveDef = defs[i] as DirectiveDef<any>;
|
|
||||||
const propertyAliasMap: {[publicName: string]: string} =
|
|
||||||
isInput ? directiveDef.inputs : directiveDef.outputs;
|
|
||||||
for (let publicName in propertyAliasMap) {
|
|
||||||
if (propertyAliasMap.hasOwnProperty(publicName)) {
|
|
||||||
propStore = propStore || {};
|
|
||||||
const internalName = propertyAliasMap[publicName];
|
|
||||||
const hasProperty = propStore.hasOwnProperty(publicName);
|
|
||||||
hasProperty ? propStore[publicName].push(i, publicName, internalName) :
|
|
||||||
(propStore[publicName] = [i, publicName, internalName]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return propStore;
|
return propStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes data structures required to work with directive outputs and outputs.
|
||||||
|
* Initialization is done for all directives matched on a given TNode.
|
||||||
|
*/
|
||||||
|
function initializeInputAndOutputAliases(tView: TView, tNode: TNode): void {
|
||||||
|
ngDevMode && assertFirstTemplatePass(tView);
|
||||||
|
|
||||||
|
const start = tNode.directiveStart;
|
||||||
|
const end = tNode.directiveEnd;
|
||||||
|
const defs = tView.data;
|
||||||
|
|
||||||
|
let inputsStore: PropertyAliases|null = null;
|
||||||
|
let outputsStore: PropertyAliases|null = null;
|
||||||
|
for (let i = start; i < end; i++) {
|
||||||
|
const directiveDef = defs[i] as DirectiveDef<any>;
|
||||||
|
inputsStore = generatePropertyAliases(directiveDef.inputs, i, inputsStore);
|
||||||
|
outputsStore = generatePropertyAliases(directiveDef.outputs, i, outputsStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
tNode.inputs = inputsStore;
|
||||||
|
tNode.outputs = outputsStore;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping between attributes names that don't correspond to their element property names.
|
* Mapping between attributes names that don't correspond to their element property names.
|
||||||
*
|
*
|
||||||
|
@ -863,13 +864,11 @@ export function elementPropertyInternal<T>(
|
||||||
loadRendererFn?: ((tNode: TNode, lView: LView) => Renderer3) | null): void {
|
loadRendererFn?: ((tNode: TNode, lView: LView) => Renderer3) | null): void {
|
||||||
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.');
|
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.');
|
||||||
const lView = getLView();
|
const lView = getLView();
|
||||||
const tView = lView[TVIEW];
|
|
||||||
const element = getNativeByIndex(index, lView) as RElement | RComment;
|
const element = getNativeByIndex(index, lView) as RElement | RComment;
|
||||||
const tNode = getTNode(index, lView);
|
const tNode = getTNode(index, lView);
|
||||||
let inputData: PropertyAliases|null|undefined;
|
let inputData = tNode.inputs;
|
||||||
let dataValue: PropertyAliasValue|undefined;
|
let dataValue: PropertyAliasValue|undefined;
|
||||||
if (!nativeOnly && (inputData = initializeTNodeInputs(tView, tNode)) &&
|
if (!nativeOnly && inputData != null && (dataValue = inputData[propName])) {
|
||||||
(dataValue = inputData[propName])) {
|
|
||||||
setInputsForProperty(lView, dataValue, value);
|
setInputsForProperty(lView, dataValue, value);
|
||||||
if (isComponentHost(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
|
if (isComponentHost(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET);
|
||||||
if (ngDevMode) {
|
if (ngDevMode) {
|
||||||
|
@ -1055,6 +1054,8 @@ export function resolveDirectives(
|
||||||
directiveDefIdx, def, tView, nodeIndex, initialPreOrderHooksLength,
|
directiveDefIdx, def, tView, nodeIndex, initialPreOrderHooksLength,
|
||||||
initialPreOrderCheckHooksLength);
|
initialPreOrderCheckHooksLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeInputAndOutputAliases(tView, tNode);
|
||||||
}
|
}
|
||||||
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
|
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
|
||||||
}
|
}
|
||||||
|
@ -1773,16 +1774,6 @@ export function storePropertyBindingMetadata(
|
||||||
|
|
||||||
export const CLEAN_PROMISE = _CLEAN_PROMISE;
|
export const CLEAN_PROMISE = _CLEAN_PROMISE;
|
||||||
|
|
||||||
export function initializeTNodeInputs(tView: TView, tNode: TNode): PropertyAliases|null {
|
|
||||||
// If tNode.inputs is undefined, a listener has created outputs, but inputs haven't
|
|
||||||
// yet been checked.
|
|
||||||
if (tNode.inputs === undefined) {
|
|
||||||
// mark inputs as checked
|
|
||||||
tNode.inputs = generatePropertyAliases(tView, tNode, BindingDirection.Input);
|
|
||||||
}
|
|
||||||
return tNode.inputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCleanup(view: LView): any[] {
|
export function getCleanup(view: LView): any[] {
|
||||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||||
return view[CLEANUP] || (view[CLEANUP] = ngDevMode ? new LCleanup() : []);
|
return view[CLEANUP] || (view[CLEANUP] = ngDevMode ? new LCleanup() : []);
|
||||||
|
|
|
@ -423,7 +423,7 @@
|
||||||
"name": "initNodeFlags"
|
"name": "initNodeFlags"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "initializeTNodeInputs"
|
"name": "initializeInputAndOutputAliases"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "insertBloom"
|
"name": "insertBloom"
|
||||||
|
|
|
@ -936,7 +936,7 @@
|
||||||
"name": "initNodeFlags"
|
"name": "initNodeFlags"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "initializeTNodeInputs"
|
"name": "initializeInputAndOutputAliases"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "injectElementRef"
|
"name": "injectElementRef"
|
||||||
|
|
Loading…
Reference in New Issue