feat(ivy): in `ngDevMode` use named object literals and arrays for easier debugging/profiling (#30542)

PR Close #30542
This commit is contained in:
Miško Hevery 2019-05-17 09:52:31 -07:00 committed by Misko Hevery
parent c7850fff3b
commit 55a14e4866
6 changed files with 244 additions and 80 deletions

View File

@ -6,31 +6,31 @@
* found in the LICENSE file at https://angular.io/license
*/
import { getPluralCase } from '../i18n/localization';
import { getTemplateContent, SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS } from '../sanitization/html_sanitizer';
import { InertBodyHelper } from '../sanitization/inert_body';
import { sanitizeSrcset, _sanitizeUrl } from '../sanitization/url_sanitizer';
import { addAllToArray } from '../util/array_utils';
import { assertDataInRange, assertDefined, assertEqual, assertGreaterThan } from '../util/assert';
import '../util/ng_i18n_closure_mode';
import { attachPatchData } from './context_discovery';
import { attachI18nOpCodesDebug } from './debug';
import { elementAttributeInternal, ɵɵload, ɵɵtextBinding } from './instructions/all';
import { allocExpando, elementPropertyInternal, getOrCreateTNode, setInputsForProperty } from './instructions/shared';
import { LContainer, NATIVE } from './interfaces/container';
import { COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu } from './interfaces/i18n';
import { TElementNode, TIcuContainerNode, TNode, TNodeType } from './interfaces/node';
import { RComment, RElement, RText } from './interfaces/renderer';
import { SanitizerFn } from './interfaces/sanitization';
import { StylingContext } from './interfaces/styling';
import { BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, TView, T_HOST } from './interfaces/view';
import { appendChild, createTextNode, nativeRemoveNode } from './node_manipulation';
import { getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode } from './state';
import { NO_CHANGE } from './tokens';
import { renderStringify } from './util/misc_utils';
import { getNativeByIndex, getNativeByTNode, getTNode, isLContainer } from './util/view_utils';
import {getPluralCase} from '../i18n/localization';
import {SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS, getTemplateContent} from '../sanitization/html_sanitizer';
import {InertBodyHelper} from '../sanitization/inert_body';
import {_sanitizeUrl, sanitizeSrcset} from '../sanitization/url_sanitizer';
import {addAllToArray} from '../util/array_utils';
import {assertDataInRange, assertDefined, assertEqual, assertGreaterThan} from '../util/assert';
import {attachPatchData} from './context_discovery';
import {elementAttributeInternal, ɵɵload, ɵɵtextBinding} from './instructions/all';
import {attachI18nOpCodesDebug} from './instructions/lview_debug';
import {allocExpando, elementPropertyInternal, getOrCreateTNode, setInputsForProperty} from './instructions/shared';
import {LContainer, NATIVE} from './interfaces/container';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n';
import {TElementNode, TIcuContainerNode, TNode, TNodeType} from './interfaces/node';
import {RComment, RElement, RText} from './interfaces/renderer';
import {SanitizerFn} from './interfaces/sanitization';
import {StylingContext} from './interfaces/styling';
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, TView, T_HOST} from './interfaces/view';
import {appendChild, createTextNode, nativeRemoveNode} from './node_manipulation';
import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from './state';
import {NO_CHANGE} from './tokens';
import {renderStringify} from './util/misc_utils';
import {getNativeByIndex, getNativeByTNode, getTNode, isLContainer} from './util/view_utils';
const MARKER = `<EFBFBD>`;

View File

@ -6,20 +6,24 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ComponentTemplate} from '..';
import {SchemaMetadata} from '../../core';
import {assertDefined} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from '../interfaces/container';
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
import {TNode} from '../interfaces/node';
import {TElementNode, TNode, TViewNode} from '../interfaces/node';
import {LQueries} from '../interfaces/query';
import {RComment, RElement} from '../interfaces/renderer';
import {StylingContext} from '../interfaces/styling';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TVIEW, T_HOST} from '../interfaces/view';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, T_HOST} from '../interfaces/view';
import {runtimeIsNewStylingInUse} from '../styling_next/state';
import {DebugStyling as DebugNewStyling, NodeStylingDebug} from '../styling_next/styling_debug';
import {attachDebugObject} from '../util/debug_utils';
import {getTNode, isStylingContext, unwrapRNode} from '../util/view_utils';
/*
* This file contains conditionally attached classes which provide human readable (debug) level
* information for `LView`, `LContainer` and other internal data structures. These data structures
@ -50,6 +54,80 @@ import {getTNode, isStylingContext, unwrapRNode} from '../util/view_utils';
*/
export const LViewArray = ngDevMode && createNamedArrayType('LView');
let LVIEW_EMPTY: unknown[]; // can't initialize here or it will not be tree shaken, because `LView`
// constructor could have side-effects.
/**
* This function clones a blueprint and creates LView.
*
* Simple slice will keep the same type, and we need it to be LView
*/
export function cloneToLView(list: any[]): LView {
if (LVIEW_EMPTY === undefined) LVIEW_EMPTY = new LViewArray !();
return LVIEW_EMPTY.concat(list) as any;
}
/**
* This class is a debug version of Object literal so that we can have constructor name show up in
* debug tools in ngDevMode.
*/
export const TViewConstructor = class TView implements ITView {
constructor(
public id: number, //
public blueprint: LView, //
public template: ComponentTemplate<{}>|null, //
public viewQuery: ViewQueriesFunction<{}>|null, //
public node: TViewNode|TElementNode|null, //
public data: TData, //
public bindingStartIndex: number, //
public viewQueryStartIndex: number, //
public expandoStartIndex: number, //
public expandoInstructions: ExpandoInstructions|null, //
public firstTemplatePass: boolean, //
public staticViewQueries: boolean, //
public staticContentQueries: boolean, //
public preOrderHooks: HookData|null, //
public preOrderCheckHooks: HookData|null, //
public contentHooks: HookData|null, //
public contentCheckHooks: HookData|null, //
public viewHooks: HookData|null, //
public viewCheckHooks: HookData|null, //
public destroyHooks: HookData|null, //
public cleanup: any[]|null, //
public contentQueries: number[]|null, //
public components: number[]|null, //
public directiveRegistry: DirectiveDefList|null, //
public pipeRegistry: PipeDefList|null, //
public firstChild: TNode|null, //
public schemas: SchemaMetadata[]|null, //
) {}
};
const TViewData = ngDevMode && createNamedArrayType('TViewData');
let TVIEWDATA_EMPTY:
unknown[]; // can't initialize here or it will not be tree shaken, because `LView`
// constructor could have side-effects.
/**
* This function clones a blueprint and creates TData.
*
* Simple slice will keep the same type, and we need it to be TData
*/
export function cloneToTViewData(list: any[]): TData {
if (TVIEWDATA_EMPTY === undefined) TVIEWDATA_EMPTY = new TViewData !();
return TVIEWDATA_EMPTY.concat(list) as any;
}
export const LViewBlueprint = ngDevMode && createNamedArrayType('LViewBlueprint');
export const MatchesArray = ngDevMode && createNamedArrayType('MatchesArray');
export const TViewComponents = ngDevMode && createNamedArrayType('TViewComponents');
export const TNodeLocalNames = ngDevMode && createNamedArrayType('TNodeLocalNames');
export const TNodeInitialInputs = ngDevMode && createNamedArrayType('TNodeInitialInputs');
export const TNodeInitialData = ngDevMode && createNamedArrayType('TNodeInitialData');
export const LCleanup = ngDevMode && createNamedArrayType('LCleanup');
export const TCleanup = ngDevMode && createNamedArrayType('TCleanup');
export function attachLViewDebug(lView: LView) {
attachDebugObject(lView, new LViewDebug(lView));
}

View File

@ -12,6 +12,7 @@ import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../me
import {validateAgainstEventProperties} from '../../sanitization/sanitization';
import {Sanitizer} from '../../sanitization/security';
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertLessThan, assertNotEqual, assertNotSame} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
import {assertLView, assertPreviousIsParent} from '../assert';
import {attachPatchData, getComponentViewByInstance} from '../context_discovery';
@ -37,7 +38,8 @@ import {attrsStylingIndexOf} from '../util/attrs_utils';
import {INTERPOLATION_DELIMITER, stringifyForError} from '../util/misc_utils';
import {getLViewParent, getRootContext} from '../util/view_traversal_utils';
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isLContainer, isRootView, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
import {attachLContainerDebug, attachLViewDebug} from './lview_debug';
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeInitialData, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLView, cloneToTViewData} from './lview_debug';
@ -199,13 +201,12 @@ export function elementCreate(name: string, overriddenRenderer?: Renderer3): REl
}
return native;
}
export function createLView<T>(
parentLView: LView | null, tView: TView, context: T | null, flags: LViewFlags,
host: RElement | null, tHostNode: TViewNode | TElementNode | null,
rendererFactory?: RendererFactory3 | null, renderer?: Renderer3 | null,
sanitizer?: Sanitizer | null, injector?: Injector | null): LView {
const lView = tView.blueprint.slice() as LView;
const lView = ngDevMode ? cloneToLView(tView.blueprint) : tView.blueprint.slice() as LView;
lView[HOST] = host;
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass;
resetPreOrderHookFlags(lView);
@ -553,6 +554,7 @@ export function getOrCreateTView(def: ComponentDef<any>): TView {
def.viewQuery, def.schemas));
}
/**
* Creates a TView instance
*
@ -575,38 +577,71 @@ export function createTView(
// that has a host binding, we will update the blueprint with that def's hostVars count.
const initialViewLength = bindingStartIndex + vars;
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
return blueprint[TVIEW as any] = {
id: viewIndex,
blueprint: blueprint,
template: templateFn,
viewQuery: viewQuery,
node: null !,
data: blueprint.slice().fill(null, bindingStartIndex),
bindingStartIndex: bindingStartIndex,
viewQueryStartIndex: initialViewLength,
expandoStartIndex: initialViewLength,
expandoInstructions: null,
firstTemplatePass: true,
staticViewQueries: false,
staticContentQueries: false,
preOrderHooks: null,
preOrderCheckHooks: null,
contentHooks: null,
contentCheckHooks: null,
viewHooks: null,
viewCheckHooks: null,
destroyHooks: null,
cleanup: null,
contentQueries: null,
components: null,
directiveRegistry: typeof directives === 'function' ? directives() : directives,
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
firstChild: null,
schemas: schemas,
};
return blueprint[TVIEW as any] = ngDevMode ?
new TViewConstructor(
viewIndex, // id: number,
blueprint, // blueprint: LView,
templateFn, // template: ComponentTemplate<{}>|null,
viewQuery, // viewQuery: ViewQueriesFunction<{}>|null,
null !, // node: TViewNode|TElementNode|null,
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
bindingStartIndex, // bindingStartIndex: number,
initialViewLength, // viewQueryStartIndex: number,
initialViewLength, // expandoStartIndex: number,
null, // expandoInstructions: ExpandoInstructions|null,
true, // firstTemplatePass: boolean,
false, // staticViewQueries: boolean,
false, // staticContentQueries: boolean,
null, // preOrderHooks: HookData|null,
null, // preOrderCheckHooks: HookData|null,
null, // contentHooks: HookData|null,
null, // contentCheckHooks: HookData|null,
null, // viewHooks: HookData|null,
null, // viewCheckHooks: HookData|null,
null, // destroyHooks: HookData|null,
null, // cleanup: any[]|null,
null, // contentQueries: number[]|null,
null, // components: number[]|null,
typeof directives === 'function' ?
directives() :
directives, // directiveRegistry: DirectiveDefList|null,
typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null,
null, // firstChild: TNode|null,
schemas, // schemas: SchemaMetadata[]|null,
) :
{
id: viewIndex,
blueprint: blueprint,
template: templateFn,
viewQuery: viewQuery,
node: null !,
data: blueprint.slice().fill(null, bindingStartIndex),
bindingStartIndex: bindingStartIndex,
viewQueryStartIndex: initialViewLength,
expandoStartIndex: initialViewLength,
expandoInstructions: null,
firstTemplatePass: true,
staticViewQueries: false,
staticContentQueries: false,
preOrderHooks: null,
preOrderCheckHooks: null,
contentHooks: null,
contentCheckHooks: null,
viewHooks: null,
viewCheckHooks: null,
destroyHooks: null,
cleanup: null,
contentQueries: null,
components: null,
directiveRegistry: typeof directives === 'function' ? directives() : directives,
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
firstChild: null,
schemas: schemas,
};
}
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LView {
const blueprint = new Array(initialViewLength)
const blueprint = new (ngDevMode ? LViewBlueprint ! : Array)(initialViewLength)
.fill(null, 0, bindingStartIndex)
.fill(NO_CHANGE, bindingStartIndex) as LView;
blueprint[BINDING_INDEX] = bindingStartIndex;
@ -1132,7 +1167,6 @@ function postProcessBaseDirective<T>(
}
/**
* Matches the current node against all available selectors.
* If a component is matched (at most one), it is returned in first position in the array.
@ -1146,7 +1180,7 @@ function findDirectiveMatches(tView: TView, viewData: LView, tNode: TNode): Dire
for (let i = 0; i < registry.length; i++) {
const def = registry[i] as ComponentDef<any>| DirectiveDef<any>;
if (isNodeMatchingSelectorList(tNode, def.selectors !, /* isProjectionMode */ false)) {
matches || (matches = []);
matches || (matches = ngDevMode ? new MatchesArray !() : []);
diPublicInInjector(
getOrCreateNodeInjectorForNode(
getPreviousOrParentTNode() as TElementNode | TContainerNode | TElementContainerNode,
@ -1173,7 +1207,8 @@ export function queueComponentIndexForCheck(previousOrParentTNode: TNode): void
const tView = getLView()[TVIEW];
ngDevMode &&
assertEqual(tView.firstTemplatePass, true, 'Should only be called in first template pass.');
(tView.components || (tView.components = [])).push(previousOrParentTNode.index);
(tView.components || (tView.components = ngDevMode ? new TViewComponents !() : [
])).push(previousOrParentTNode.index);
}
@ -1181,7 +1216,8 @@ export function queueComponentIndexForCheck(previousOrParentTNode: TNode): void
function cacheMatchingLocalNames(
tNode: TNode, localRefs: string[] | null, exportsMap: {[key: string]: number}): void {
if (localRefs) {
const localNames: (string | number)[] = tNode.localNames = [];
const localNames: (string | number)[] = tNode.localNames =
ngDevMode ? new TNodeLocalNames !() : [];
// Local names must be stored in tNode in the same order that localRefs are defined
// in the template to ensure the data is loaded in the same slots as their refs
@ -1320,7 +1356,8 @@ function setInputsFromAttrs<T>(
*/
function generateInitialInputs(
directiveIndex: number, inputs: {[key: string]: string}, tNode: TNode): InitialInputData {
const initialInputData: InitialInputData = tNode.initialInputs || (tNode.initialInputs = []);
const initialInputData: InitialInputData =
tNode.initialInputs || (tNode.initialInputs = ngDevMode ? new TNodeInitialInputs !() : []);
// Ensure that we don't create sparse arrays
for (let i = initialInputData.length; i <= directiveIndex; i++) {
initialInputData.push(null);
@ -1347,8 +1384,8 @@ function generateInitialInputs(
const attrValue = attrs[i + 1];
if (minifiedInputName !== undefined) {
const inputsToStore: InitialInputs =
initialInputData[directiveIndex] || (initialInputData[directiveIndex] = []);
const inputsToStore: InitialInputs = initialInputData[directiveIndex] ||
(initialInputData[directiveIndex] = ngDevMode ? new TNodeInitialData !() : []);
inputsToStore.push(attrName as string, minifiedInputName, attrValue as string);
}
@ -1361,6 +1398,9 @@ function generateInitialInputs(
//// ViewContainer & View
//////////////////////////
// Not sure why I need to do `any` here but TS complains later.
const LContainerArray: any = ngDevMode && createNamedArrayType('LContainer');
/**
* Creates a LContainer, either from a container instruction, or for a ViewContainerRef.
*
@ -1376,16 +1416,17 @@ export function createLContainer(
tNode: TNode, isForViewContainerRef?: boolean): LContainer {
ngDevMode && assertDomNode(native);
ngDevMode && assertLView(currentView);
const lContainer: LContainer = [
hostNative, // host native
true, // Boolean `true` in this position signifies that this is an `LContainer`
isForViewContainerRef ? -1 : 0, // active index
currentView, // parent
null, // next
null, // queries
tNode, // t_host
native, // native
];
// https://jsperf.com/array-literal-vs-new-array-really
const lContainer: LContainer = new (ngDevMode ? LContainerArray : Array)(
hostNative, // host native
true, // Boolean `true` in this position signifies that this is an `LContainer`
isForViewContainerRef ? -1 : 0, // active index
currentView, // parent
null, // next
null, // queries
tNode, // t_host
native, // native
);
ngDevMode && attachLContainerDebug(lContainer);
return lContainer;
}
@ -1711,14 +1752,13 @@ export function initializeTNodeInputs(tNode: TNode): PropertyAliases|null {
return tNode.inputs;
}
export function getCleanup(view: LView): any[] {
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
return view[CLEANUP] || (view[CLEANUP] = []);
return view[CLEANUP] || (view[CLEANUP] = ngDevMode ? new LCleanup !() : []);
}
function getTViewCleanup(view: LView): any[] {
return view[TVIEW].cleanup || (view[TVIEW].cleanup = []);
return view[TVIEW].cleanup || (view[TVIEW].cleanup = ngDevMode ? new TCleanup !() : []);
}
/**

View File

@ -0,0 +1,42 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import './ng_dev_mode';
import {global} from './global';
/**
* THIS FILE CONTAINS CODE WHICH SHOULD BE TREE SHAKEN AND NEVER CALLED FROM PRODUCTION CODE!!!
*/
/**
* Creates an `Array` construction with a given name. This is useful when
* looking for memory consumption to see what time of array it is.
*
*
* @param name Name to give to the constructor
* @returns A subclass of `Array` if possible. This can only be done in
* environments which support `class` construct.
*/
export function createNamedArrayType(name: string): typeof Array {
// This should never be called in prod mode, so let's verify that is the case.
if (ngDevMode) {
try {
// We need to do it this way so that TypeScript does not down-level the below code.
const FunctionConstructor: any = createNamedArrayType.constructor;
return (new FunctionConstructor('Array', `return class ABC extends Array{}`))(Array);
} catch (e) {
// If it does not work just give up and fall back to regular Array.
return Array;
}
} else {
throw new Error(
'Looks like we are in \'prod mode\', but we are creating a named Array type, which is wrong! Check your code');
}
}

View File

@ -11,6 +11,7 @@ import {global} from './global';
declare global {
const ngDevMode: null|NgDevModePerfCounters;
interface NgDevModePerfCounters {
namedConstructors: boolean;
firstTemplatePass: number;
tNode: number;
tView: number;
@ -45,7 +46,9 @@ declare global {
}
export function ngDevModeResetPerfCounters(): NgDevModePerfCounters {
const locationString = typeof location !== 'undefined' ? location.toString() : '';
const newCounters: NgDevModePerfCounters = {
namedConstructors: locationString.indexOf('ngDevMode=namedConstructors') != -1,
firstTemplatePass: 0,
tNode: 0,
tView: 0,
@ -79,7 +82,8 @@ export function ngDevModeResetPerfCounters(): NgDevModePerfCounters {
};
// Make sure to refer to ngDevMode as ['ngDevMode'] for closure.
global['ngDevMode'] = newCounters;
const allowNgDevModeTrue = locationString.indexOf('ngDevMode=false') === -1;
global['ngDevMode'] = allowNgDevModeTrue && newCounters;
return newCounters;
}

View File

@ -9,7 +9,7 @@ import {CompilerStylingMode, compilerSetStylingMode} from '@angular/compiler/src
import {Component, Directive, HostBinding, Input, ViewChild} from '@angular/core';
import {SecurityContext} from '@angular/core/src/core';
import {getLContext} from '@angular/core/src/render3/context_discovery';
import {DebugNode, LViewDebug, toDebug} from '@angular/core/src/render3/debug';
import {DebugNode, LViewDebug, toDebug} from '@angular/core/src/render3/instructions/lview_debug';
import {SANITIZER} from '@angular/core/src/render3/interfaces/view';
import {RuntimeStylingMode, runtimeSetStylingMode, setCurrentStyleSanitizer} from '@angular/core/src/render3/styling_next/state';
import {loadLContextFromNode} from '@angular/core/src/render3/util/discovery_utils';