diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index 614fd48e36..00de568b1a 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime": 1440, - "main": 13415, + "main": 14228, "polyfills": 45340 } } @@ -21,7 +21,7 @@ "master": { "uncompressed": { "runtime": 1440, - "main": 123904, + "main": 125674, "polyfills": 45340 } } diff --git a/packages/core/src/render3/instructions/styling.ts b/packages/core/src/render3/instructions/styling.ts index ef2b50c894..5906f1597f 100644 --- a/packages/core/src/render3/instructions/styling.ts +++ b/packages/core/src/render3/instructions/styling.ts @@ -184,7 +184,7 @@ function stylingProp( // it's important we remove the current style sanitizer once the // element exits, otherwise it will be used by the next styling // instructions for the next element. - setElementExitFn(resetCurrentStyleSanitizer); + setElementExitFn(stylingApply); } } else { // Context Resolution (or first update) Case: save the value @@ -354,7 +354,7 @@ function _stylingMap( // it's important we remove the current style sanitizer once the // element exits, otherwise it will be used by the next styling // instructions for the next element. - setElementExitFn(resetCurrentStyleSanitizer); + setElementExitFn(stylingApply); } } else { updated = valueHasChanged; diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 0c3897ee00..a47e8172b3 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -7,67 +7,185 @@ */ import {StyleSanitizeFn} from '../sanitization/style_sanitizer'; -import {assertDefined} from '../util/assert'; +import {assertDefined, assertEqual} from '../util/assert'; import {assertLViewOrUndefined} from './assert'; import {ComponentDef, DirectiveDef} from './interfaces/definition'; import {TElementNode, TNode, TViewNode} from './interfaces/node'; import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState} from './interfaces/view'; - /** - * Store the element depth count. This is used to identify the root elements of the template - * so that we can than attach `LView` to only those elements. + * All implicit instruction state is stored here. + * + * It is useful to have a single object where all of the state is stored as a mental model + * (rather it being spread across many different variables.) + * + * PERF NOTE: Turns out that writing to a true global variable is slower than + * having an intermediate object with properties. */ -let elementDepthCount !: number; +interface InstructionState { + /** + * State of the current view being processed. + * + * An array of nodes (text, element, container, etc), pipes, their bindings, and + * any local variables that need to be stored between invocations. + */ + lView: LView; + + /** + * Used to set the parent property when nodes are created and track query results. + * + * This is used in conjection with `isParent`. + */ + previousOrParentTNode: TNode; + + /** + * If `isParent` is: + * - `true`: then `previousOrParentTNode` points to a parent node. + * - `false`: then `previousOrParentTNode` points to previous node (sibling). + */ + isParent: boolean; + + /** + * Index of currently selected element in LView. + * + * Used by binding instructions. Updated as part of advance instruction. + */ + selectedIndex: number; + + /** + * The last viewData retrieved by nextContext(). + * Allows building nextContext() and reference() calls. + * + * e.g. const inner = x().$implicit; const outer = x().$implicit; + */ + contextLView: LView; + + /** + * In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error. + * + * Necessary to support ChangeDetectorRef.checkNoChanges(). + */ + checkNoChangesMode: boolean; + + /** + * Store the element depth count. This is used to identify the root elements of the template + * so that we can then attach `LView` to only those elements. + */ + elementDepthCount: number; + + /** + * Stores whether directives should be matched to elements. + * + * When template contains `ngNonBindable` then we need to prevent the runtime form matching + * directives on children of that element. + * + * Example: + * ``` + * + * Should match component / directive. + * + *
+ * + * Should not match component / directive because we are in ngNonBindable. + * + *
+ * ``` + */ + bindingsEnabled: boolean; + + /** + * Current namespace to be used when creating elements + */ + currentNamespace: string|null; + + /** + * Current sanitizer + */ + currentSanitizer: StyleSanitizeFn|null; + + + /** + * Used when processing host bindings. + */ + currentDirectiveDef: DirectiveDef|ComponentDef|null; + + /** + * Used as the starting directive id value. + * + * All subsequent directives are incremented from this value onwards. + * The reason why this value is `1` instead of `0` is because the `0` + * value is reserved for the template. + */ + activeDirectiveId: number; + + /** + * The root index from which pure function instructions should calculate their binding + * indices. In component views, this is TView.bindingStartIndex. In a host binding + * context, this is the TView.expandoStartIndex + any dirs/hostVars before the given dir. + */ + bindingRootIndex: number; + + /** + * Current index of a View or Content Query which needs to be processed next. + * We iterate over the list of Queries and increment current query index at every step. + */ + currentQueryIndex: number; + + + /** + * Function to be called when the element is exited. + * + * NOTE: The function is here for tree shakable purposes since it is only needed by styling. + */ + elementExitFn: (() => void)|null; +} + +export const instructionState: InstructionState = { + previousOrParentTNode: null !, + isParent: null !, + lView: null !, + // tslint:disable-next-line: no-toplevel-property-access + selectedIndex: -1 << ActiveElementFlags.Size, + contextLView: null !, + checkNoChangesMode: false, + elementDepthCount: 0, + bindingsEnabled: true, + currentNamespace: null, + currentSanitizer: null, + currentDirectiveDef: null, + activeDirectiveId: 0, + bindingRootIndex: -1, + currentQueryIndex: 0, + elementExitFn: null, +}; + export function getElementDepthCount() { // top level variables should not be exported for performance reasons (PERF_NOTES.md) - return elementDepthCount; + return instructionState.elementDepthCount; } export function increaseElementDepthCount() { - elementDepthCount++; + instructionState.elementDepthCount++; } export function decreaseElementDepthCount() { - elementDepthCount--; + instructionState.elementDepthCount--; } -let currentDirectiveDef: DirectiveDef|ComponentDef|null = null; - export function getCurrentDirectiveDef(): DirectiveDef|ComponentDef|null { // top level variables should not be exported for performance reasons (PERF_NOTES.md) - return currentDirectiveDef; + return instructionState.currentDirectiveDef; } export function setCurrentDirectiveDef(def: DirectiveDef| ComponentDef| null): void { - currentDirectiveDef = def; + instructionState.currentDirectiveDef = def; } -/** - * Stores whether directives should be matched to elements. - * - * When template contains `ngNonBindable` than we need to prevent the runtime form matching - * directives on children of that element. - * - * Example: - * ``` - * - * Should match component / directive. - * - *
- * - * Should not match component / directive because we are in ngNonBindable. - * - *
- * ``` - */ -let bindingsEnabled !: boolean; - export function getBindingsEnabled(): boolean { // top level variables should not be exported for performance reasons (PERF_NOTES.md) - return bindingsEnabled; + return instructionState.bindingsEnabled; } @@ -91,7 +209,7 @@ export function getBindingsEnabled(): boolean { * @codeGenApi */ export function ɵɵenableBindings(): void { - bindingsEnabled = true; + instructionState.bindingsEnabled = true; } /** @@ -114,22 +232,13 @@ export function ɵɵenableBindings(): void { * @codeGenApi */ export function ɵɵdisableBindings(): void { - bindingsEnabled = false; + instructionState.bindingsEnabled = false; } export function getLView(): LView { - return lView; + return instructionState.lView; } -/** - * Used as the starting directive id value. - * - * All subsequent directives are incremented from this value onwards. - * The reason why this value is `1` instead of `0` is because the `0` - * value is reserved for the template. - */ -let activeDirectiveId = 0; - /** * Flags used for an active element during change detection. * @@ -150,14 +259,14 @@ export const enum ActiveElementFlags { * Determines whether or not a flag is currently set for the active element. */ export function hasActiveElementFlag(flag: ActiveElementFlags) { - return (_selectedIndex & flag) === flag; + return (instructionState.selectedIndex & flag) === flag; } /** * Sets a flag is for the active element. */ export function setActiveElementFlag(flag: ActiveElementFlags) { - _selectedIndex |= flag; + instructionState.selectedIndex |= flag; } /** @@ -173,16 +282,15 @@ export function setActiveHostElement(elementIndex: number | null = null) { executeElementExitFn(); } setSelectedIndex(elementIndex === null ? -1 : elementIndex); - activeDirectiveId = 0; + instructionState.activeDirectiveId = 0; } } -let _elementExitFn: Function|null = null; export function executeElementExitFn() { - _elementExitFn !(); + instructionState.elementExitFn !(); // TODO (matsko|misko): remove this unassignment once the state management of // global variables are better managed. - _selectedIndex &= ~ActiveElementFlags.RunExitFn; + instructionState.selectedIndex &= ~ActiveElementFlags.RunExitFn; } /** @@ -198,9 +306,13 @@ export function executeElementExitFn() { * * @param fn */ -export function setElementExitFn(fn: Function): void { +export function setElementExitFn(fn: () => void): void { setActiveElementFlag(ActiveElementFlags.RunExitFn); - _elementExitFn = fn; + if (instructionState.elementExitFn == null) { + instructionState.elementExitFn = fn; + } + ngDevMode && + assertEqual(instructionState.elementExitFn, fn, 'Expecting to always get the same function'); } /** @@ -219,7 +331,7 @@ export function setElementExitFn(fn: Function): void { * different set of directives). */ export function getActiveDirectiveId() { - return activeDirectiveId; + return instructionState.activeDirectiveId; } /** @@ -248,7 +360,7 @@ export function incrementActiveDirectiveId() { // directive uniqueId is not set anywhere--it is just incremented between // each hostBindings call and is useful for helping instruction code // uniquely determine which directive is currently active when executed. - activeDirectiveId += 1; + instructionState.activeDirectiveId += 1; } /** @@ -263,113 +375,67 @@ export function incrementActiveDirectiveId() { * @codeGenApi */ export function ɵɵrestoreView(viewToRestore: OpaqueViewState) { - contextLView = viewToRestore as any as LView; + instructionState.contextLView = viewToRestore as any as LView; } -/** Used to set the parent property when nodes are created and track query results. */ -let previousOrParentTNode: TNode; - export function getPreviousOrParentTNode(): TNode { // top level variables should not be exported for performance reasons (PERF_NOTES.md) - return previousOrParentTNode; + return instructionState.previousOrParentTNode; } export function setPreviousOrParentTNode(tNode: TNode, _isParent: boolean) { - previousOrParentTNode = tNode; - isParent = _isParent; + instructionState.previousOrParentTNode = tNode; + instructionState.isParent = _isParent; } export function setTNodeAndViewData(tNode: TNode, view: LView) { ngDevMode && assertLViewOrUndefined(view); - previousOrParentTNode = tNode; - lView = view; + instructionState.previousOrParentTNode = tNode; + instructionState.lView = view; } -/** - * If `isParent` is: - * - `true`: then `previousOrParentTNode` points to a parent node. - * - `false`: then `previousOrParentTNode` points to previous node (sibling). - */ -let isParent: boolean; - export function getIsParent(): boolean { // top level variables should not be exported for performance reasons (PERF_NOTES.md) - return isParent; + return instructionState.isParent; } export function setIsNotParent(): void { - isParent = false; + instructionState.isParent = false; } export function setIsParent(): void { - isParent = true; + instructionState.isParent = true; } -/** - * State of the current view being processed. - * - * An array of nodes (text, element, container, etc), pipes, their bindings, and - * any local variables that need to be stored between invocations. - */ -let lView: LView; - -/** - * The last viewData retrieved by nextContext(). - * Allows building nextContext() and reference() calls. - * - * e.g. const inner = x().$implicit; const outer = x().$implicit; - */ -let contextLView: LView = null !; - export function getContextLView(): LView { // top level variables should not be exported for performance reasons (PERF_NOTES.md) - return contextLView; + return instructionState.contextLView; } -/** - * In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error. - * - * Necessary to support ChangeDetectorRef.checkNoChanges(). - */ -let checkNoChangesMode = false; - export function getCheckNoChangesMode(): boolean { // top level variables should not be exported for performance reasons (PERF_NOTES.md) - return checkNoChangesMode; + return instructionState.checkNoChangesMode; } export function setCheckNoChangesMode(mode: boolean): void { - checkNoChangesMode = mode; + instructionState.checkNoChangesMode = mode; } -/** - * The root index from which pure function instructions should calculate their binding - * indices. In component views, this is TView.bindingStartIndex. In a host binding - * context, this is the TView.expandoStartIndex + any dirs/hostVars before the given dir. - */ -let bindingRootIndex: number = -1; - // top level variables should not be exported for performance reasons (PERF_NOTES.md) export function getBindingRoot() { - return bindingRootIndex; + return instructionState.bindingRootIndex; } export function setBindingRoot(value: number) { - bindingRootIndex = value; + instructionState.bindingRootIndex = value; } -/** - * Current index of a View or Content Query which needs to be processed next. - * We iterate over the list of Queries and increment current query index at every step. - */ -let currentQueryIndex: number = 0; - export function getCurrentQueryIndex(): number { // top level variables should not be exported for performance reasons (PERF_NOTES.md) - return currentQueryIndex; + return instructionState.currentQueryIndex; } export function setCurrentQueryIndex(value: number): void { - currentQueryIndex = value; + instructionState.currentQueryIndex = value; } /** @@ -390,18 +456,18 @@ export function selectView(newView: LView, hostTNode: TElementNode | TViewNode | } ngDevMode && assertLViewOrUndefined(newView); - const oldView = lView; + const oldView = instructionState.lView; - previousOrParentTNode = hostTNode !; - isParent = true; + instructionState.previousOrParentTNode = hostTNode !; + instructionState.isParent = true; - lView = contextLView = newView; + instructionState.lView = instructionState.contextLView = newView; return oldView; } export function nextContextImpl(level: number = 1): T { - contextLView = walkUpViews(level, contextLView !); - return contextLView[CONTEXT] as T; + instructionState.contextLView = walkUpViews(level, instructionState.contextLView !); + return instructionState.contextLView[CONTEXT] as T; } function walkUpViews(nestingLevel: number, currentView: LView): LView { @@ -419,16 +485,13 @@ function walkUpViews(nestingLevel: number, currentView: LView): LView { * Resets the application state. */ export function resetComponentState() { - isParent = false; - previousOrParentTNode = null !; - elementDepthCount = 0; - bindingsEnabled = true; + instructionState.isParent = false; + instructionState.previousOrParentTNode = null !; + instructionState.elementDepthCount = 0; + instructionState.bindingsEnabled = true; setCurrentStyleSanitizer(null); } -/* tslint:disable */ -let _selectedIndex = -1 << ActiveElementFlags.Size; - /** * Gets the most recent index passed to {@link select} * @@ -436,7 +499,7 @@ let _selectedIndex = -1 << ActiveElementFlags.Size; * current `LView` to act on. */ export function getSelectedIndex() { - return _selectedIndex >> ActiveElementFlags.Size; + return instructionState.selectedIndex >> ActiveElementFlags.Size; } /** @@ -449,19 +512,17 @@ export function getSelectedIndex() { * run if and when the provided `index` value is different from the current selected index value.) */ export function setSelectedIndex(index: number) { - _selectedIndex = index << ActiveElementFlags.Size; + instructionState.selectedIndex = index << ActiveElementFlags.Size; } -let _currentNamespace: string|null = null; - /** * Sets the namespace used to create elements to `'http://www.w3.org/2000/svg'` in global state. * * @codeGenApi */ export function ɵɵnamespaceSVG() { - _currentNamespace = 'http://www.w3.org/2000/svg'; + instructionState.currentNamespace = 'http://www.w3.org/2000/svg'; } /** @@ -470,7 +531,7 @@ export function ɵɵnamespaceSVG() { * @codeGenApi */ export function ɵɵnamespaceMathML() { - _currentNamespace = 'http://www.w3.org/1998/MathML/'; + instructionState.currentNamespace = 'http://www.w3.org/1998/MathML/'; } /** @@ -488,16 +549,15 @@ export function ɵɵnamespaceHTML() { * `createElement` rather than `createElementNS`. */ export function namespaceHTMLInternal() { - _currentNamespace = null; + instructionState.currentNamespace = null; } export function getNamespace(): string|null { - return _currentNamespace; + return instructionState.currentNamespace; } -let _currentSanitizer: StyleSanitizeFn|null; export function setCurrentStyleSanitizer(sanitizer: StyleSanitizeFn | null) { - _currentSanitizer = sanitizer; + instructionState.currentSanitizer = sanitizer; } export function resetCurrentStyleSanitizer() { @@ -505,5 +565,5 @@ export function resetCurrentStyleSanitizer() { } export function getCurrentStyleSanitizer() { - return _currentSanitizer; + return instructionState.currentSanitizer; } diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index ec7b4c2d66..b2ff9ceaa6 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -140,21 +140,12 @@ { "name": "__window" }, - { - "name": "_currentNamespace" - }, - { - "name": "_elementExitFn" - }, { "name": "_global" }, { "name": "_renderCompCount" }, - { - "name": "_selectedIndex" - }, { "name": "addComponentLogic" }, @@ -188,9 +179,6 @@ { "name": "callHooks" }, - { - "name": "checkNoChangesMode" - }, { "name": "concatString" }, @@ -431,6 +419,9 @@ { "name": "instantiateRootComponent" }, + { + "name": "instructionState" + }, { "name": "invertObject" }, 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 4ec5256011..5da2c4dc39 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -125,18 +125,12 @@ { "name": "__window" }, - { - "name": "_elementExitFn" - }, { "name": "_global" }, { "name": "_renderCompCount" }, - { - "name": "_selectedIndex" - }, { "name": "addToViewTree" }, @@ -158,9 +152,6 @@ { "name": "callHooks" }, - { - "name": "checkNoChangesMode" - }, { "name": "createLView" }, @@ -332,6 +323,9 @@ { "name": "instantiateRootComponent" }, + { + "name": "instructionState" + }, { "name": "invertObject" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 2a8f0e48d6..b0c792bc1b 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -326,33 +326,21 @@ { "name": "_currentInjector" }, - { - "name": "_currentNamespace" - }, { "name": "_devMode" }, - { - "name": "_elementExitFn" - }, { "name": "_global" }, { "name": "_renderCompCount" }, - { - "name": "_selectedIndex" - }, { "name": "_state" }, { "name": "_symbolIterator" }, - { - "name": "activeDirectiveId" - }, { "name": "addBindingIntoContext" }, @@ -446,9 +434,6 @@ { "name": "checkNoChangesInternal" }, - { - "name": "checkNoChangesMode" - }, { "name": "cleanUpView" }, @@ -461,9 +446,6 @@ { "name": "containerInternal" }, - { - "name": "contextLView" - }, { "name": "createContainerRef" }, @@ -899,6 +881,9 @@ { "name": "instantiateRootComponent" }, + { + "name": "instructionState" + }, { "name": "interpolation1" },