From 6be9c0466cfe60d6555b0430285c1ae983f977b3 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Tue, 9 Jan 2018 18:38:17 -0800 Subject: [PATCH] refactor(core): split up interface files in render3 (#21433) PR Close #21433 --- .../src/largetable/render3/table.ts | 2 +- modules/benchmarks/src/tree/render3/tree.ts | 2 +- packages/core/src/render3/component.ts | 6 +- packages/core/src/render3/definition.ts | 2 +- packages/core/src/render3/di.ts | 5 +- packages/core/src/render3/index.ts | 2 +- packages/core/src/render3/instructions.ts | 12 +- packages/core/src/render3/interfaces.ts | 555 ------------------ .../core/src/render3/interfaces/container.ts | 73 +++ .../definition.ts} | 12 +- .../core/src/render3/interfaces/injector.ts | 73 +++ packages/core/src/render3/interfaces/node.ts | 348 +++++++++++ .../core/src/render3/interfaces/projection.ts | 52 ++ packages/core/src/render3/interfaces/query.ts | 63 ++ .../src/render3/{ => interfaces}/renderer.ts | 8 +- packages/core/src/render3/interfaces/view.ts | 157 +++++ packages/core/src/render3/node_assert.ts | 2 +- .../core/src/render3/node_manipulation.ts | 7 +- .../core/src/render3/node_selector_matcher.ts | 4 +- packages/core/src/render3/perf_notes.md | 19 + packages/core/src/render3/query.ts | 8 +- packages/core/src/render3/t_node.ts | 153 ----- packages/core/test/render3/di_spec.ts | 3 +- .../render3/node_selector_matcher_spec.ts | 4 +- packages/core/test/render3/query_spec.ts | 2 +- packages/core/test/render3/render_util.ts | 4 +- 26 files changed, 835 insertions(+), 743 deletions(-) delete mode 100644 packages/core/src/render3/interfaces.ts create mode 100644 packages/core/src/render3/interfaces/container.ts rename packages/core/src/render3/{definition_interfaces.ts => interfaces/definition.ts} (94%) create mode 100644 packages/core/src/render3/interfaces/injector.ts create mode 100644 packages/core/src/render3/interfaces/node.ts create mode 100644 packages/core/src/render3/interfaces/projection.ts create mode 100644 packages/core/src/render3/interfaces/query.ts rename packages/core/src/render3/{ => interfaces}/renderer.ts (94%) create mode 100644 packages/core/src/render3/interfaces/view.ts create mode 100644 packages/core/src/render3/perf_notes.md delete mode 100644 packages/core/src/render3/t_node.ts diff --git a/modules/benchmarks/src/largetable/render3/table.ts b/modules/benchmarks/src/largetable/render3/table.ts index 35553891c5..f7efd08437 100644 --- a/modules/benchmarks/src/largetable/render3/table.ts +++ b/modules/benchmarks/src/largetable/render3/table.ts @@ -7,7 +7,7 @@ */ import {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵc as c, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as detectChanges, ɵe as e, ɵs as s, ɵt as t, ɵv as v} from '@angular/core'; -import {ComponentDef} from '@angular/core/src/render3/definition_interfaces'; +import {ComponentDef} from '@angular/core/src/render3/interfaces/definition'; import {TableCell, buildTable, emptyTable} from '../util'; diff --git a/modules/benchmarks/src/tree/render3/tree.ts b/modules/benchmarks/src/tree/render3/tree.ts index cc48991fb2..71cead278a 100644 --- a/modules/benchmarks/src/tree/render3/tree.ts +++ b/modules/benchmarks/src/tree/render3/tree.ts @@ -7,7 +7,7 @@ */ import {ɵC as C, ɵD as D, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵb1 as b1, ɵc as c, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵp as p, ɵs as s, ɵt as t, ɵv as v} from '@angular/core'; -import {ComponentDef} from '@angular/core/src/render3/definition_interfaces'; +import {ComponentDef} from '@angular/core/src/render3/interfaces/definition'; import {TreeNode, buildTree, emptyTree} from '../util'; diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index ccdd9664bd..e9a1b85a9a 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -13,10 +13,10 @@ import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_facto import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref'; import {assertNotNull} from './assert'; -import {ComponentDef, ComponentType} from './definition_interfaces'; import {NG_HOST_SYMBOL, createError, createLView, directive, enterView, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions'; -import {LElementNode} from './interfaces'; -import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './renderer'; +import {ComponentDef, ComponentType} from './interfaces/definition'; +import {LElementNode} from './interfaces/node'; +import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {notImplemented, stringify} from './util'; diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 9d0c38de47..74563c93da 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -10,8 +10,8 @@ import {RendererType2} from '../render/api'; import {Type} from '../type'; import {resolveRendererType2} from '../view/util'; -import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs} from './definition_interfaces'; import {componentRefresh, diPublic} from './instructions'; +import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs} from './interfaces/definition'; diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 343f5476f6..d233c10231 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -17,8 +17,9 @@ import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_co import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref'; import {Type} from '../type'; -import {ComponentTemplate, DirectiveDef} from './definition_interfaces'; -import {LContainerNode, LElementNode, LInjector, LNodeFlags} from './interfaces'; +import {ComponentTemplate, DirectiveDef} from './interfaces/definition'; +import {LInjector} from './interfaces/injector'; +import {LContainerNode, LElementNode, LNodeFlags} from './interfaces/node'; import {assertNodeType} from './node_assert'; import {notImplemented, stringify} from './util'; diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index ee426baab0..bec2d6bef0 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -8,7 +8,7 @@ import {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent} from './component'; import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective} from './definition'; -import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './definition_interfaces'; +import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition'; // Naming scheme: // - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View), diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 5ca3800ed4..f425d00d27 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -14,16 +14,20 @@ import {ViewContainerRef} from '../linker/view_container_ref'; import {Type} from '../type'; import {assertEqual, assertLessThan, assertNotEqual, assertNotNull} from './assert'; -import {CssSelector, LContainer, LContainerNode, LElementNode, LInjector, LNode, LNodeFlags, LProjection, LProjectionNode, LQuery, LTextNode, LView, LViewNode, QueryReadType} from './interfaces'; +import {LContainer} from './interfaces/container'; +import {LInjector} from './interfaces/injector'; +import {CssSelector, LProjection} from './interfaces/projection'; +import {LQuery, QueryReadType} from './interfaces/query'; +import {LView} from './interfaces/view'; -import {NgStaticData, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './t_node'; +import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, NgStaticData, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; import {assertNodeType} from './node_assert'; import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation'; import {isNodeMatchingSelector} from './node_selector_matcher'; -import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef} from './definition_interfaces'; +import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef} from './interfaces/definition'; import {InjectFlags, diPublicInInjector, getOrCreateNodeInjectorForNode, getOrCreateElementRef, getOrCreateTemplateRef, getOrCreateContainerRef, getOrCreateInjectable} from './di'; import {QueryList, LQuery_} from './query'; -import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3} from './renderer'; +import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3} from './interfaces/renderer'; import {isDifferent, stringify} from './util'; export {queryRefresh} from './query'; diff --git a/packages/core/src/render3/interfaces.ts b/packages/core/src/render3/interfaces.ts deleted file mode 100644 index c4be0d8289..0000000000 --- a/packages/core/src/render3/interfaces.ts +++ /dev/null @@ -1,555 +0,0 @@ -/** - * @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 {Injector} from '../di/injector'; -import {ElementRef} from '../linker/element_ref'; -import {QueryList} from '../linker/query_list'; -import {TemplateRef} from '../linker/template_ref'; -import {ViewContainerRef} from '../linker/view_container_ref'; -import {Type} from '../type'; - -import {ComponentTemplate, DirectiveDef} from './definition_interfaces'; -import {RComment, RElement, RText, Renderer3} from './renderer'; -import {TNode} from './t_node'; - - - -/** - * LNodeFlags corresponds to the LNode.flags property. It contains information - * on how to map a particular set of bits in LNode.flags to the node type, directive - * count, or directive starting index. - * - * For example, if you wanted to check the type of a certain node, you would mask - * node.flags with TYPE_MASK and compare it to the value for a certain node type. e.g: - * - *```ts - * if ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element) {...} - *``` - */ -export const enum LNodeFlags { - Container = 0b00, - Projection = 0b01, - View = 0b10, - Element = 0b11, - ViewOrElement = 0b10, - SIZE_SKIP = 0b100, - SIZE_SHIFT = 2, - INDX_SHIFT = 12, - TYPE_MASK = 0b00000000000000000000000000000011, - SIZE_MASK = 0b00000000000000000000111111111100, - INDX_MASK = 0b11111111111111111111000000000000 -} - -/** - * LNode is an internal data structure which is used for the incremental DOM algorithm. - * The "L" stands for "Logical" to differentiate between `RNodes` (actual rendered DOM - * node) and our logical representation of DOM nodes, `LNodes`. - * - * The data structure is optimized for speed and size. - * - * In order to be fast, all subtypes of `LNode` should have the same shape. - * Because size of the `LNode` matters, many fields have multiple roles depending - * on the `LNode` subtype. - * - * See: https://en.wikipedia.org/wiki/Inline_caching#Monomorphic_inline_caching - * - * NOTE: This is a private data structure and should not be exported by any of the - * instructions. - */ -export interface LNode { - /** - * This number stores three values using its bits: - * - * - the type of the node (first 2 bits) - * - the number of directives on that node (next 10 bits) - * - the starting index of the node's directives in the directives array (last 20 bits). - * - * The latter two values are necessary so DI can effectively search the directives associated - * with a node without searching the whole directives array. - */ - flags: LNodeFlags; - - /** - * The associated DOM node. Storing this allows us to: - * - append children to their element parents in the DOM (e.g. `parent.native.appendChild(...)`) - * - retrieve the sibling elements of text nodes whose creation / insertion has been delayed - * - mark locations where child views should be inserted (for containers) - */ - readonly native: RElement|RText|RComment|null; - - /** - * We need a reference to a node's parent so we can append the node to its parent's native - * element at the appropriate time. - */ - readonly parent: LNode|null; - - /** - * First child of the current node. - */ - child: LNode|null; - - /** - * The next sibling node. Necessary so we can propagate through the root nodes of a view - * to insert them or remove them from the DOM. - */ - next: LNode|null; - - /** - * If regular LElementNode, then `data` will be null. - * If LElementNode with component, then `data` contains LView. - * If LViewNode, then `data` contains the LView. - * If LContainerNode, then `data` contains LContainer. - * If LProjectionNode, then `data` contains LProjection. - */ - readonly data: LView|LContainer|LProjection|null; - - - /** - * Each node belongs to a view. - * - * When the injector is walking up a tree, it needs access to the `directives` (part of view). - */ - readonly view: LView; - - /** The injector associated with this node. Necessary for DI. */ - nodeInjector: LInjector|null; - - /** - * Optional `QueryState` used for tracking queries. - * - * If present the node creation/updates are reported to the `QueryState`. - */ - query: LQuery|null; - - /** - * Pointer to the corresponding TNode object, which stores static - * data about this node. - */ - tNode: TNode|null; -} - - -/** LNode representing an element. */ -export interface LElementNode extends LNode { - /** The DOM element associated with this node. */ - readonly native: RElement; - - child: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; - next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; - - /** If Component then data has LView (light DOM) */ - readonly data: LView|null; - - /** LElementNodes can be inside other LElementNodes or inside LViewNodes. */ - readonly parent: LElementNode|LViewNode; -} - -/** LNode representing a #text node. */ -export interface LTextNode extends LNode { - /** The text node associated with this node. */ - native: RText; - child: null; - next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; - - /** LTextNodes can be inside LElementNodes or inside LViewNodes. */ - readonly parent: LElementNode|LViewNode; - readonly data: null; -} - -/** Abstract node which contains root nodes of a view. */ -export interface LViewNode extends LNode { - readonly native: null; - child: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; - next: LViewNode|null; - - /** LViewNodes can only be added to LContainerNodes. */ - readonly parent: LContainerNode|null; - readonly data: LView; -} - -/** Abstract node container which contains other views. */ -export interface LContainerNode extends LNode { - /** - * This comment node is appended to the container's parent element to mark where - * in the DOM the container's child views should be added. - * - * If the container is a root node of a view, this comment will not be appended - * until the parent view is processed. - */ - readonly native: RComment; - readonly data: LContainer; - child: null; - next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; - - /** Containers can be added to elements or views. */ - readonly parent: LElementNode|LViewNode|null; -} - - -export interface LProjectionNode extends LNode { - readonly native: null; - child: null; - next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; - - readonly data: LProjection; - - /** Projections can be added to elements or views. */ - readonly parent: LElementNode|LViewNode; -} - -/** - * NOTES: - * - * Each Array costs 70 bytes and is composed of `Array` and `(array)` object - * - `Array` javascript visible object: 32 bytes - * - `(array)` VM object where the array is actually stored in: 38 bytes - * - * Each Object cost is 24 bytes plus 8 bytes per property. - * - * For small arrays, it is more efficient to store the data as a linked list - * of items rather than small arrays. However, the array access is faster as - * shown here: https://jsperf.com/small-arrays-vs-linked-objects - */ - -export interface LInjector { - /** - * We need to store a reference to the injector's parent so DI can keep looking up - * the injector tree until it finds the dependency it's looking for. - */ - readonly parent: LInjector|null; - - /** - * Allows access to the directives array in that node's static data and to - * the node's flags (for starting directive index and directive size). Necessary - * for DI to retrieve a directive from the data array if injector indicates - * it is there. - */ - readonly node: LElementNode|LContainerNode; - - /** - * The following bloom filter determines whether a directive is available - * on the associated node or not. This prevents us from searching the directives - * array at this level unless it's probable the directive is in it. - * - * - bf0: Check directive IDs 0-31 (IDs are % 128) - * - bf1: Check directive IDs 33-63 - * - bf2: Check directive IDs 64-95 - * - bf3: Check directive IDs 96-127 - * - * See: https://en.wikipedia.org/wiki/Bloom_filter for more about bloom filters. - */ - bf0: number; - bf1: number; - bf2: number; - bf3: number; - - /** - * cbf0 - cbf3 properties determine whether a directive is available through a - * parent injector. They refer to the merged values of parent bloom filters. This - * allows us to skip looking up the chain unless it's probable that directive exists - * up the chain. - */ - cbf0: number; - cbf1: number; - cbf2: number; - cbf3: number; - injector: Injector|null; - - /** Stores the TemplateRef so subsequent injections of the TemplateRef get the same instance. */ - templateRef: TemplateRef|null; - - /** Stores the ViewContainerRef so subsequent injections of the ViewContainerRef get the same - * instance. */ - viewContainerRef: ViewContainerRef|null; - - /** Stores the ElementRef so subsequent injections of the ElementRef get the same instance. */ - elementRef: ElementRef|null; -} - -/** - * `LView` stores all of the information needed to process the instructions as - * they are invoked from the template. Each embedded view and component view has its - * own `LView`. When processing a particular view, we set the `currentView` to that - * `LView`. When that view is done processing, the `currentView` is set back to - * whatever the original `currentView` was before(the parent `LView`). - * - * Keeping separate state for each view facilities view insertion / deletion, so we - * don't have to edit the data array based on which views are present. - */ -export interface LView { - /** - * Whether or not the view is in creationMode. - * - * This must be stored in the view rather than using `data` as a marker so that - * we can properly support embedded views. Otherwise, when exiting a child view - * back into the parent view, `data` will be defined and `creationMode` will be - * improperly reported as false. - */ - creationMode: boolean; - - /** The index in the data array at which view hooks begin to be stored. */ - viewHookStartIndex: number|null; - - /** - * The parent view is needed when we exit the view and must restore the previous - * `LView`. Without this, the render method would have to keep a stack of - * views as it is recursively rendering templates. - */ - readonly parent: LView|null; - - /** - * Pointer to the `LViewNode` or `LElementNode` which represents the root of the view. - * - * If `LViewNode`, this is an embedded view of a container. We need this to be able to - * efficiently find the `LViewNode` when inserting the view into an anchor. - * - * If `LElementNode`, this is the LView of a component. - */ - readonly node: LViewNode|LElementNode; - - /** - * ID to determine whether this view is the same as the previous view - * in this position. If it's not, we know this view needs to be inserted - * and the one that exists needs to be removed (e.g. if/else statements) - */ - readonly id: number; - - /** Renderer to be used for this view. */ - readonly renderer: Renderer3; - - /** - * The binding start index is the index at which the nodes array - * starts to store bindings only. Saving this value ensures that we - * will begin reading bindings at the correct point in the array when - * we are in update mode. - */ - bindingStartIndex: number|null; - - /** - * When a view is destroyed, listeners need to be released and onDestroy callbacks - * need to be called. This cleanup array stores both listener data (in chunks of 4) - * and onDestroy data (in chunks of 2) for a particular view. Combining the arrays - * saves on memory (70 bytes per array) and on a few bytes of code size (for two - * separate for loops). - * - * If it's a listener being stored: - * 1st index is: event name to remove - * 2nd index is: native element - * 3rd index is: listener function - * 4th index is: useCapture boolean - * - * If it's an onDestroy function: - * 1st index is: onDestroy function - * 2nd index is; context for function - */ - cleanup: any[]|null; - - /** - * The first LView or LContainer beneath this LView in the hierarchy. - * - * Necessary to store this so views can traverse through their nested views - * to remove listeners and call onDestroy callbacks. - * - * For embedded views, we store the LContainer rather than the first ViewState - * to avoid managing splicing when views are added/removed. - */ - child: LView|LContainer|null; - - /** - * The last LView or LContainer beneath this LView in the hierarchy. - * - * The tail allows us to quickly add a new state to the end of the view list - * without having to propagate starting from the first child. - */ - tail: LView|LContainer|null; - - /** - * The next sibling LView or LContainer. - * - * Allows us to propagate between sibling view states that aren't in the same - * container. Embedded views already have a node.next, but it is only set for - * views in the same container. We need a way to link component views and views - * across containers as well. - */ - next: LView|LContainer|null; - - /** - * This array stores all element/text/container nodes created inside this view - * and their bindings. Stored as an array rather than a linked list so we can - * look up nodes directly in the case of forward declaration or bindings - * (e.g. E(1)).. - * - * All bindings for a given view are stored in the order in which they - * appear in the template, starting with `bindingStartIndex`. - * We use `bindingIndex` to internally keep track of which binding - * is currently active. - * - * NOTE: We also use data == null as a marker for creationMode. We - * do this by creating ViewState in incomplete state with nodes == null - * and we initialize it on first run. - */ - readonly data: any[]; - - /** - * The static data array for the current view. We need a reference to this so we - * can easily walk up the node tree in DI and get the ngStaticData array associated - * with a node (where the directive defs are stored). - */ - ngStaticData: (TNode|DirectiveDef|null)[]; -} - - -/** The state associated with an LContainer */ -export interface LContainer { - /** - * The next active index in the views array to read or write to. This helps us - * keep track of where we are in the views array. - */ - nextIndex: number; - - /** - * This allows us to jump from a container to a sibling container or - * component view with the same parent, so we can remove listeners efficiently. - */ - next: LView|LContainer|null; - - /** - * Access to the parent view is necessary so we can propagate back - * up from inside a container to parent.next. - */ - parent: LView|null; - - /** - * A list of the container's currently active child views. Views will be inserted - * here as they are added and spliced from here when they are removed. We need - * to keep a record of current views so we know which views are already in the DOM - * (and don't need to be re-added) and so we can remove views from the DOM when they - * are no longer required. - */ - readonly views: LViewNode[]; - - /** - * Parent Element which will contain the location where all of the Views will be - * inserted into to. - * - * If `renderParent` is `null` it is headless. This means that it is contained - * in another `LViewNode` which in turn is contained in another `LContainerNode` and - * therefore it does not yet have its own parent. - * - * If `renderParent` is not `null` then it may be: - * - same as `LContainerNode.parent` in which case it is just a normal container. - * - different from `LContainerNode.parent` in which case it has been re-projected. - * In other words `LContainerNode.parent` is logical parent where as - * `LContainer.projectedParent` is render parent. - * - * When views are inserted into `LContainerNode` then `renderParent` is: - * - `null`, we are in `LViewNode` keep going up a hierarchy until actual - * `renderParent` is found. - * - not `null`, then use the `projectedParent.native` as the `RElement` to insert - * `LViewNode`s into. - */ - renderParent: LElementNode|null; - - /** - * The template extracted from the location of the Container. - */ - readonly template: ComponentTemplate|null; -} - - -/** Interface necessary to work with view tree traversal */ -export interface LViewOrLContainer { - next: LView|LContainer|null; - child?: LView|LContainer|null; - views?: LViewNode[]; - parent: LView|null; -} - -/** - * An LProjection is just an array of projected nodes. - * - * It would be nice if we could not need an array, but since a projected node can be - * re-projected, the same node can be part of more than one LProjectionNode which makes - * list approach not possible. - */ -export type LProjection = Array; - -/** An enum representing possible values of the "read" option for queries. */ -export const enum QueryReadType { - ElementRef = 0, - ViewContainerRef = 1, - TemplateRef = 2, -} - -/** Used for tracking queries (e.g. ViewChild, ContentChild). */ -export interface LQuery { - /** - * Used to ask query if it should be cloned to the child element. - * - * For example in the case of deep queries the `child()` returns - * query for the child node. In case of shallow queries it returns - * `null`. - */ - child(): LQuery|null; - - /** - * Notify `LQuery` that a `LNode` has been created. - */ - addNode(node: LNode): void; - - /** - * Notify `LQuery` that an `LViewNode` has been added to `LContainerNode`. - */ - insertView(container: LContainerNode, view: LViewNode, insertIndex: number): void; - - /** - * Notify `LQuery` that an `LViewNode` has been removed from `LContainerNode`. - */ - removeView(container: LContainerNode, view: LViewNode, removeIndex: number): void; - - /** - * Add additional `QueryList` to track. - * - * @param queryList `QueryList` to update with changes. - * @param predicate Either `Type` or selector array of [key, value] predicates. - * @param descend If true the query will recursively apply to the children. - * @param read Indicates which token should be read from DI for this query. - */ - track( - queryList: QueryList, predicate: Type|string[], descend?: boolean, - read?: QueryReadType|Type): void; -} - -/** - * Parsed selector in the following format: - * [tagName, attr1Name, attr1Val, ..., attrnName, attrnValue, 'class', className1, className2, ..., - * classNameN] - * - * * For example, given the following selector: - * `div.foo.bar[attr1=val1][attr2]` a parsed format would be: - * `['div', 'attr1', 'val1', 'attr2', '', 'class', 'foo', 'bar']`. - * - * Things to notice: - * - tag name is always at the position 0 - * - the `class` attribute is always the last attribute in a pre-parsed array - * - class names in a selector are at the end of an array (after the attribute with the name - * 'class'). - */ -export type SimpleCssSelector = string[]; - -/** - * A complex selector expressed as an Array where: - * - element at index 0 is a selector (SimpleCSSSelector) to match - * - elements at index 1..n is a selector (SimpleCSSSelector) that should NOT match - */ -export type CssSelectorWithNegations = [SimpleCssSelector | null, SimpleCssSelector[] | null]; - -/** - * A collection of complex selectors (CSSSelectorWithNegations) in a parsed form - */ -export type CssSelector = CssSelectorWithNegations[]; diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts new file mode 100644 index 0000000000..0270133b80 --- /dev/null +++ b/packages/core/src/render3/interfaces/container.ts @@ -0,0 +1,73 @@ +/** + * @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 {ComponentTemplate} from './definition'; +import {LElementNode, LViewNode} from './node'; +import {LView} from './view'; + + +/** The state associated with an LContainer */ +export interface LContainer { + /** + * The next active index in the views array to read or write to. This helps us + * keep track of where we are in the views array. + */ + nextIndex: number; + + /** + * This allows us to jump from a container to a sibling container or + * component view with the same parent, so we can remove listeners efficiently. + */ + next: LView|LContainer|null; + + /** + * Access to the parent view is necessary so we can propagate back + * up from inside a container to parent.next. + */ + parent: LView|null; + + /** + * A list of the container's currently active child views. Views will be inserted + * here as they are added and spliced from here when they are removed. We need + * to keep a record of current views so we know which views are already in the DOM + * (and don't need to be re-added) and so we can remove views from the DOM when they + * are no longer required. + */ + readonly views: LViewNode[]; + + /** + * Parent Element which will contain the location where all of the Views will be + * inserted into to. + * + * If `renderParent` is `null` it is headless. This means that it is contained + * in another `LViewNode` which in turn is contained in another `LContainerNode` and + * therefore it does not yet have its own parent. + * + * If `renderParent` is not `null` then it may be: + * - same as `LContainerNode.parent` in which case it is just a normal container. + * - different from `LContainerNode.parent` in which case it has been re-projected. + * In other words `LContainerNode.parent` is logical parent where as + * `LContainer.projectedParent` is render parent. + * + * When views are inserted into `LContainerNode` then `renderParent` is: + * - `null`, we are in `LViewNode` keep going up a hierarchy until actual + * `renderParent` is found. + * - not `null`, then use the `projectedParent.native` as the `RElement` to insert + * `LViewNode`s into. + */ + renderParent: LElementNode|null; + + /** + * The template extracted from the location of the Container. + */ + readonly template: ComponentTemplate|null; +} + +// Note: This hack is necessary so we don't erroneously get a circular dependency +// failure based on types. +export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/definition_interfaces.ts b/packages/core/src/render3/interfaces/definition.ts similarity index 94% rename from packages/core/src/render3/definition_interfaces.ts rename to packages/core/src/render3/interfaces/definition.ts index 80a868c6e9..296d444a5c 100644 --- a/packages/core/src/render3/definition_interfaces.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -6,11 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {RendererType2} from '../render/api'; -import {Type} from '../type'; -import {resolveRendererType2} from '../view/util'; - - +import {RendererType2} from '../../render/api'; +import {Type} from '../../type'; +import {resolveRendererType2} from '../../view/util'; /** * Definition of what a template rendering function should look like. @@ -146,3 +144,7 @@ export interface ComponentDefArgs extends DirectiveDefArgs { export type DirectiveDefFeature = (directiveDef: DirectiveDef) => void; export type ComponentDefFeature = (directiveDef: DirectiveDef) => void; + +// Note: This hack is necessary so we don't erroneously get a circular dependency +// failure based on types. +export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/interfaces/injector.ts b/packages/core/src/render3/interfaces/injector.ts new file mode 100644 index 0000000000..ca533a339e --- /dev/null +++ b/packages/core/src/render3/interfaces/injector.ts @@ -0,0 +1,73 @@ +/** + * @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 {Injector} from '../../di/injector'; +import {ElementRef} from '../../linker/element_ref'; +import {TemplateRef} from '../../linker/template_ref'; +import {ViewContainerRef} from '../../linker/view_container_ref'; + +import {LContainerNode, LElementNode} from './node'; + +export interface LInjector { + /** + * We need to store a reference to the injector's parent so DI can keep looking up + * the injector tree until it finds the dependency it's looking for. + */ + readonly parent: LInjector|null; + + /** + * Allows access to the directives array in that node's static data and to + * the node's flags (for starting directive index and directive size). Necessary + * for DI to retrieve a directive from the data array if injector indicates + * it is there. + */ + readonly node: LElementNode|LContainerNode; + + /** + * The following bloom filter determines whether a directive is available + * on the associated node or not. This prevents us from searching the directives + * array at this level unless it's probable the directive is in it. + * + * - bf0: Check directive IDs 0-31 (IDs are % 128) + * - bf1: Check directive IDs 33-63 + * - bf2: Check directive IDs 64-95 + * - bf3: Check directive IDs 96-127 + * + * See: https://en.wikipedia.org/wiki/Bloom_filter for more about bloom filters. + */ + bf0: number; + bf1: number; + bf2: number; + bf3: number; + + /** + * cbf0 - cbf3 properties determine whether a directive is available through a + * parent injector. They refer to the merged values of parent bloom filters. This + * allows us to skip looking up the chain unless it's probable that directive exists + * up the chain. + */ + cbf0: number; + cbf1: number; + cbf2: number; + cbf3: number; + injector: Injector|null; + + /** Stores the TemplateRef so subsequent injections of the TemplateRef get the same instance. */ + templateRef: TemplateRef|null; + + /** Stores the ViewContainerRef so subsequent injections of the ViewContainerRef get the same + * instance. */ + viewContainerRef: ViewContainerRef|null; + + /** Stores the ElementRef so subsequent injections of the ElementRef get the same instance. */ + elementRef: ElementRef|null; +} + +// Note: This hack is necessary so we don't erroneously get a circular dependency +// failure based on types. +export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts new file mode 100644 index 0000000000..27fe3a917e --- /dev/null +++ b/packages/core/src/render3/interfaces/node.ts @@ -0,0 +1,348 @@ +/** + * @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 {LContainer} from './container'; +import {DirectiveDef} from './definition'; +import {LInjector} from './injector'; +import {LProjection} from './projection'; +import {LQuery} from './query'; +import {RComment, RElement, RText} from './renderer'; +import {LView} from './view'; + + +/** + * LNodeFlags corresponds to the LNode.flags property. It contains information + * on how to map a particular set of bits in LNode.flags to the node type, directive + * count, or directive starting index. + * + * For example, if you wanted to check the type of a certain node, you would mask + * node.flags with TYPE_MASK and compare it to the value for a certain node type. e.g: + * + *```ts + * if ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element) {...} + *``` + */ +export const enum LNodeFlags { + Container = 0b00, + Projection = 0b01, + View = 0b10, + Element = 0b11, + ViewOrElement = 0b10, + SIZE_SKIP = 0b100, + SIZE_SHIFT = 2, + INDX_SHIFT = 12, + TYPE_MASK = 0b00000000000000000000000000000011, + SIZE_MASK = 0b00000000000000000000111111111100, + INDX_MASK = 0b11111111111111111111000000000000 +} + +/** + * LNode is an internal data structure which is used for the incremental DOM algorithm. + * The "L" stands for "Logical" to differentiate between `RNodes` (actual rendered DOM + * node) and our logical representation of DOM nodes, `LNodes`. + * + * The data structure is optimized for speed and size. + * + * In order to be fast, all subtypes of `LNode` should have the same shape. + * Because size of the `LNode` matters, many fields have multiple roles depending + * on the `LNode` subtype. + * + * See: https://en.wikipedia.org/wiki/Inline_caching#Monomorphic_inline_caching + * + * NOTE: This is a private data structure and should not be exported by any of the + * instructions. + */ +export interface LNode { + /** + * This number stores three values using its bits: + * + * - the type of the node (first 2 bits) + * - the number of directives on that node (next 10 bits) + * - the starting index of the node's directives in the directives array (last 20 bits). + * + * The latter two values are necessary so DI can effectively search the directives associated + * with a node without searching the whole directives array. + */ + flags: LNodeFlags; + + /** + * The associated DOM node. Storing this allows us to: + * - append children to their element parents in the DOM (e.g. `parent.native.appendChild(...)`) + * - retrieve the sibling elements of text nodes whose creation / insertion has been delayed + * - mark locations where child views should be inserted (for containers) + */ + readonly native: RElement|RText|RComment|null; + + /** + * We need a reference to a node's parent so we can append the node to its parent's native + * element at the appropriate time. + */ + readonly parent: LNode|null; + + /** + * First child of the current node. + */ + child: LNode|null; + + /** + * The next sibling node. Necessary so we can propagate through the root nodes of a view + * to insert them or remove them from the DOM. + */ + next: LNode|null; + + /** + * If regular LElementNode, then `data` will be null. + * If LElementNode with component, then `data` contains LView. + * If LViewNode, then `data` contains the LView. + * If LContainerNode, then `data` contains LContainer. + * If LProjectionNode, then `data` contains LProjection. + */ + readonly data: LView|LContainer|LProjection|null; + + + /** + * Each node belongs to a view. + * + * When the injector is walking up a tree, it needs access to the `directives` (part of view). + */ + readonly view: LView; + + /** The injector associated with this node. Necessary for DI. */ + nodeInjector: LInjector|null; + + /** + * Optional `QueryState` used for tracking queries. + * + * If present the node creation/updates are reported to the `QueryState`. + */ + query: LQuery|null; + + /** + * Pointer to the corresponding TNode object, which stores static + * data about this node. + */ + tNode: TNode|null; +} + + +/** LNode representing an element. */ +export interface LElementNode extends LNode { + /** The DOM element associated with this node. */ + readonly native: RElement; + + child: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; + next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; + + /** If Component then data has LView (light DOM) */ + readonly data: LView|null; + + /** LElementNodes can be inside other LElementNodes or inside LViewNodes. */ + readonly parent: LElementNode|LViewNode; +} + +/** LNode representing a #text node. */ +export interface LTextNode extends LNode { + /** The text node associated with this node. */ + native: RText; + child: null; + next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; + + /** LTextNodes can be inside LElementNodes or inside LViewNodes. */ + readonly parent: LElementNode|LViewNode; + readonly data: null; +} + +/** Abstract node which contains root nodes of a view. */ +export interface LViewNode extends LNode { + readonly native: null; + child: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; + next: LViewNode|null; + + /** LViewNodes can only be added to LContainerNodes. */ + readonly parent: LContainerNode|null; + readonly data: LView; +} + +/** Abstract node container which contains other views. */ +export interface LContainerNode extends LNode { + /** + * This comment node is appended to the container's parent element to mark where + * in the DOM the container's child views should be added. + * + * If the container is a root node of a view, this comment will not be appended + * until the parent view is processed. + */ + readonly native: RComment; + readonly data: LContainer; + child: null; + next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; + + /** Containers can be added to elements or views. */ + readonly parent: LElementNode|LViewNode|null; +} + + +export interface LProjectionNode extends LNode { + readonly native: null; + child: null; + next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; + + readonly data: LProjection; + + /** Projections can be added to elements or views. */ + readonly parent: LElementNode|LViewNode; +} + + +/** The type of the global ngStaticData array. */ +export type NgStaticData = (TNode | DirectiveDef| null)[]; + +/** + * LNode binding data (flyweight) for a particular node that is shared between all templates + * of a specific type. + * + * If a property is: + * - PropertyAliases: that property's data was generated and this is it + * - Null: that property's data was already generated and nothing was found. + * - Undefined: that property's data has not yet been generated + * + * see: https://en.wikipedia.org/wiki/Flyweight_pattern for more on the Flyweight pattern + */ +export interface TNode { + /** The tag name associated with this node. */ + tagName: string|null; + + /** + * Static attributes associated with an element. We need to store + * static attributes to support content projection with selectors. + * Attributes are stored statically because reading them from the DOM + * would be way too slow for content projection and queries. + * + * Since attrs will always be calculated first, they will never need + * to be marked undefined by other instructions. + * + * The name of the attribute and its value alternate in the array. + * e.g. ['role', 'checkbox'] + */ + attrs: string[]|null; + + /** + * A set of local names under which a given element is exported in a template and + * visible to queries. An entry in this array can be created for different reasons: + * - an element itself is referenced, ex.: `
` + * - a component is referenced, ex.: `` + * - a directive is referenced, ex.: ``. + * + * A given element might have different local names and those names can be associated + * with a directive. We store local names at even indexes while odd indexes are reserved + * for directive index in a view (or `-1` if there is no associated directive). + * + * Some examples: + * - `
` => `["foo", -1]` + * - `` => `["foo", myCmptIdx]` + * - `` => `["foo", myCmptIdx, "bar", directiveIdx]` + * - `
` => `["foo", -1, "bar", directiveIdx]` + */ + localNames: (string|number)[]|null; + + /** + * This property contains information about input properties that + * need to be set once from attribute data. + */ + initialInputs: InitialInputData|null|undefined; + + /** Input data for all directives on this node. */ + inputs: PropertyAliases|null|undefined; + + /** Output data for all directives on this node. */ + outputs: PropertyAliases|null|undefined; + + /** + * If this TNode corresponds to an LContainerNode, the container will + * need to have nested static data for each of its embedded views. + * Otherwise, nodes in embedded views with the same index as nodes + * in their parent views will overwrite each other, as they are in + * the same template. + * + * Each index in this array corresponds to the static data for a certain + * view. So if you had V(0) and V(1) in a container, you might have: + * + * [ + * [{tagName: 'div', attrs: ...}, null], // V(0) ngData + * [{tagName: 'button', attrs ...}, null] // V(1) ngData + * ] + */ + containerStatic: (TNode|null)[][]|null; +} + +/** Static data for an LElementNode */ +export interface TElementNode extends TNode { containerStatic: null; } + +/** Static data for an LContainerNode */ +export interface TContainerNode extends TNode { containerStatic: (TNode|null)[][]; } + +/** + * This mapping is necessary so we can set input properties and output listeners + * properly at runtime when property names are minified or aliased. + * + * Key: unminified / public input or output name + * Value: array containing minified / internal name and related directive index + * + * The value must be an array to support inputs and outputs with the same name + * on the same node. + */ +export type PropertyAliases = { + // This uses an object map because using the Map type would be too slow + [key: string]: PropertyAliasValue +}; + +/** + * The value in PropertyAliases. + * + * In each array: + * Even indices: directive index + * Odd indices: minified / internal name + * + * e.g. [0, 'change-minified'] + */ +export type PropertyAliasValue = (number | string)[]; + + +/** + * This array contains information about input properties that + * need to be set once from attribute data. It's ordered by + * directive index (relative to element) so it's simple to + * look up a specific directive's initial input data. + * + * Within each sub-array: + * + * Even indices: minified/internal input name + * Odd indices: initial value + * + * If a directive on a node does not have any input properties + * that should be set from attributes, its index is set to null + * to avoid a sparse array. + * + * e.g. [null, ['role-min', 'button']] + */ +export type InitialInputData = (InitialInputs | null)[]; + +/** + * Used by InitialInputData to store input properties + * that should be set once from attributes. + * + * Even indices: minified/internal input name + * Odd indices: initial value + * + * e.g. ['role-min', 'button'] + */ +export type InitialInputs = string[]; + +// Note: This hack is necessary so we don't erroneously get a circular dependency +// failure based on types. +export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/interfaces/projection.ts b/packages/core/src/render3/interfaces/projection.ts new file mode 100644 index 0000000000..f438d0f9cf --- /dev/null +++ b/packages/core/src/render3/interfaces/projection.ts @@ -0,0 +1,52 @@ +/** + * @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 {LContainerNode, LElementNode, LTextNode} from './node'; + +/** + * An LProjection is just an array of projected nodes. + * + * It would be nice if we could not need an array, but since a projected node can be + * re-projected, the same node can be part of more than one LProjectionNode which makes + * list approach not possible. + */ +export type LProjection = Array; + + +/** + * Parsed selector in the following format: + * [tagName, attr1Name, attr1Val, ..., attrnName, attrnValue, 'class', className1, className2, ..., + * classNameN] + * + * * For example, given the following selector: + * `div.foo.bar[attr1=val1][attr2]` a parsed format would be: + * `['div', 'attr1', 'val1', 'attr2', '', 'class', 'foo', 'bar']`. + * + * Things to notice: + * - tag name is always at the position 0 + * - the `class` attribute is always the last attribute in a pre-parsed array + * - class names in a selector are at the end of an array (after the attribute with the name + * 'class'). + */ +export type SimpleCssSelector = string[]; + +/** + * A complex selector expressed as an Array where: + * - element at index 0 is a selector (SimpleCSSSelector) to match + * - elements at index 1..n is a selector (SimpleCSSSelector) that should NOT match + */ +export type CssSelectorWithNegations = [SimpleCssSelector | null, SimpleCssSelector[] | null]; + +/** + * A collection of complex selectors (CSSSelectorWithNegations) in a parsed form + */ +export type CssSelector = CssSelectorWithNegations[]; + +// Note: This hack is necessary so we don't erroneously get a circular dependency +// failure based on types. +export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/interfaces/query.ts b/packages/core/src/render3/interfaces/query.ts new file mode 100644 index 0000000000..2ed3fe1d94 --- /dev/null +++ b/packages/core/src/render3/interfaces/query.ts @@ -0,0 +1,63 @@ +/** + * @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 {QueryList} from '../../linker'; +import {Type} from '../../type'; + +import {LContainerNode, LNode, LViewNode} from './node'; + + +/** Used for tracking queries (e.g. ViewChild, ContentChild). */ +export interface LQuery { + /** + * Used to ask query if it should be cloned to the child element. + * + * For example in the case of deep queries the `child()` returns + * query for the child node. In case of shallow queries it returns + * `null`. + */ + child(): LQuery|null; + + /** + * Notify `LQuery` that a `LNode` has been created. + */ + addNode(node: LNode): void; + + /** + * Notify `LQuery` that an `LViewNode` has been added to `LContainerNode`. + */ + insertView(container: LContainerNode, view: LViewNode, insertIndex: number): void; + + /** + * Notify `LQuery` that an `LViewNode` has been removed from `LContainerNode`. + */ + removeView(container: LContainerNode, view: LViewNode, removeIndex: number): void; + + /** + * Add additional `QueryList` to track. + * + * @param queryList `QueryList` to update with changes. + * @param predicate Either `Type` or selector array of [key, value] predicates. + * @param descend If true the query will recursively apply to the children. + * @param read Indicates which token should be read from DI for this query. + */ + track( + queryList: QueryList, predicate: Type|string[], descend?: boolean, + read?: QueryReadType|Type): void; +} + +/** An enum representing possible values of the "read" option for queries. */ +export const enum QueryReadType { + ElementRef = 0, + ViewContainerRef = 1, + TemplateRef = 2, +} + +// Note: This hack is necessary so we don't erroneously get a circular dependency +// failure based on types. +export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/renderer.ts b/packages/core/src/render3/interfaces/renderer.ts similarity index 94% rename from packages/core/src/render3/renderer.ts rename to packages/core/src/render3/interfaces/renderer.ts index c2ea24a221..0f88662fbe 100644 --- a/packages/core/src/render3/renderer.ts +++ b/packages/core/src/render3/interfaces/renderer.ts @@ -15,8 +15,8 @@ * it will be easy to implement such API. */ -import {ViewEncapsulation} from '../metadata/view'; -import {RendererStyleFlags2, RendererType2} from '../render/api'; +import {ViewEncapsulation} from '../../metadata/view'; +import {RendererStyleFlags2, RendererType2} from '../../render/api'; // TODO: cleanup once the code is merged in angular/angular @@ -139,3 +139,7 @@ export interface RDomTokenList { export interface RText extends RNode { textContent: string|null; } export interface RComment extends RNode {} + +// Note: This hack is necessary so we don't erroneously get a circular dependency +// failure based on types. +export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts new file mode 100644 index 0000000000..72652c8e77 --- /dev/null +++ b/packages/core/src/render3/interfaces/view.ts @@ -0,0 +1,157 @@ +/** + * @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 {LContainer} from './container'; +import {DirectiveDef} from './definition'; +import {LElementNode, LViewNode, TNode} from './node'; +import {Renderer3} from './renderer'; + + +/** + * `LView` stores all of the information needed to process the instructions as + * they are invoked from the template. Each embedded view and component view has its + * own `LView`. When processing a particular view, we set the `currentView` to that + * `LView`. When that view is done processing, the `currentView` is set back to + * whatever the original `currentView` was before(the parent `LView`). + * + * Keeping separate state for each view facilities view insertion / deletion, so we + * don't have to edit the data array based on which views are present. + */ +export interface LView { + /** + * Whether or not the view is in creationMode. + * + * This must be stored in the view rather than using `data` as a marker so that + * we can properly support embedded views. Otherwise, when exiting a child view + * back into the parent view, `data` will be defined and `creationMode` will be + * improperly reported as false. + */ + creationMode: boolean; + + /** The index in the data array at which view hooks begin to be stored. */ + viewHookStartIndex: number|null; + + /** + * The parent view is needed when we exit the view and must restore the previous + * `LView`. Without this, the render method would have to keep a stack of + * views as it is recursively rendering templates. + */ + readonly parent: LView|null; + + /** + * Pointer to the `LViewNode` or `LElementNode` which represents the root of the view. + * + * If `LViewNode`, this is an embedded view of a container. We need this to be able to + * efficiently find the `LViewNode` when inserting the view into an anchor. + * + * If `LElementNode`, this is the LView of a component. + */ + readonly node: LViewNode|LElementNode; + + /** + * ID to determine whether this view is the same as the previous view + * in this position. If it's not, we know this view needs to be inserted + * and the one that exists needs to be removed (e.g. if/else statements) + */ + readonly id: number; + + /** Renderer to be used for this view. */ + readonly renderer: Renderer3; + + /** + * The binding start index is the index at which the nodes array + * starts to store bindings only. Saving this value ensures that we + * will begin reading bindings at the correct point in the array when + * we are in update mode. + */ + bindingStartIndex: number|null; + + /** + * When a view is destroyed, listeners need to be released and onDestroy callbacks + * need to be called. This cleanup array stores both listener data (in chunks of 4) + * and onDestroy data (in chunks of 2) for a particular view. Combining the arrays + * saves on memory (70 bytes per array) and on a few bytes of code size (for two + * separate for loops). + * + * If it's a listener being stored: + * 1st index is: event name to remove + * 2nd index is: native element + * 3rd index is: listener function + * 4th index is: useCapture boolean + * + * If it's an onDestroy function: + * 1st index is: onDestroy function + * 2nd index is; context for function + */ + cleanup: any[]|null; + + /** + * The first LView or LContainer beneath this LView in the hierarchy. + * + * Necessary to store this so views can traverse through their nested views + * to remove listeners and call onDestroy callbacks. + * + * For embedded views, we store the LContainer rather than the first ViewState + * to avoid managing splicing when views are added/removed. + */ + child: LView|LContainer|null; + + /** + * The last LView or LContainer beneath this LView in the hierarchy. + * + * The tail allows us to quickly add a new state to the end of the view list + * without having to propagate starting from the first child. + */ + tail: LView|LContainer|null; + + /** + * The next sibling LView or LContainer. + * + * Allows us to propagate between sibling view states that aren't in the same + * container. Embedded views already have a node.next, but it is only set for + * views in the same container. We need a way to link component views and views + * across containers as well. + */ + next: LView|LContainer|null; + + /** + * This array stores all element/text/container nodes created inside this view + * and their bindings. Stored as an array rather than a linked list so we can + * look up nodes directly in the case of forward declaration or bindings + * (e.g. E(1)).. + * + * All bindings for a given view are stored in the order in which they + * appear in the template, starting with `bindingStartIndex`. + * We use `bindingIndex` to internally keep track of which binding + * is currently active. + * + * NOTE: We also use data == null as a marker for creationMode. We + * do this by creating ViewState in incomplete state with nodes == null + * and we initialize it on first run. + */ + readonly data: any[]; + + /** + * The static data array for the current view. We need a reference to this so we + * can easily walk up the node tree in DI and get the ngStaticData array associated + * with a node (where the directive defs are stored). + */ + ngStaticData: (TNode|DirectiveDef|null)[]; +} + +/** Interface necessary to work with view tree traversal */ +export interface LViewOrLContainer { + next: LView|LContainer|null; + child?: LView|LContainer|null; + views?: LViewNode[]; + parent: LView|null; +} + +// Note: This hack is necessary so we don't erroneously get a circular dependency +// failure based on types. +export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/node_assert.ts b/packages/core/src/render3/node_assert.ts index c998b65c3d..15b82f9297 100644 --- a/packages/core/src/render3/node_assert.ts +++ b/packages/core/src/render3/node_assert.ts @@ -7,7 +7,7 @@ */ import {assertEqual, assertNotEqual} from './assert'; -import {LNode, LNodeFlags} from './interfaces'; +import {LNode, LNodeFlags} from './interfaces/node'; export function assertNodeType(node: LNode, type: LNodeFlags) { assertNotEqual(node, null, 'node'); diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 715d5b7e52..449fbcd39e 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -7,9 +7,12 @@ */ import {assertNotNull} from './assert'; -import {LContainer, LContainerNode, LElementNode, LNode, LNodeFlags, LProjection, LProjectionNode, LTextNode, LView, LViewNode, LViewOrLContainer} from './interfaces'; +import {LContainer} from './interfaces/container'; +import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode} from './interfaces/node'; +import {LProjection} from './interfaces/projection'; +import {ProceduralRenderer3, RComment, RElement, RNode, RText} from './interfaces/renderer'; +import {LView, LViewOrLContainer} from './interfaces/view'; import {assertNodeType} from './node_assert'; -import {ProceduralRenderer3, RComment, RElement, RNode, RText} from './renderer'; diff --git a/packages/core/src/render3/node_selector_matcher.ts b/packages/core/src/render3/node_selector_matcher.ts index 8f77807e5a..b934b50a46 100644 --- a/packages/core/src/render3/node_selector_matcher.ts +++ b/packages/core/src/render3/node_selector_matcher.ts @@ -9,8 +9,8 @@ import './ng_dev_mode'; import {assertNotNull} from './assert'; -import {CssSelector, CssSelectorWithNegations, SimpleCssSelector} from './interfaces'; -import {TNode} from './t_node'; +import {TNode} from './interfaces/node'; +import {CssSelector, CssSelectorWithNegations, SimpleCssSelector} from './interfaces/projection'; function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string): boolean { const nodeClassesLen = nodeClassAttrVal.length; diff --git a/packages/core/src/render3/perf_notes.md b/packages/core/src/render3/perf_notes.md new file mode 100644 index 0000000000..327e4379f5 --- /dev/null +++ b/packages/core/src/render3/perf_notes.md @@ -0,0 +1,19 @@ + +## General Notes + +Each Array costs 70 bytes and is composed of `Array` and `(array)` object + * `Array` javascript visible object: 32 bytes + * `(array)` VM object where the array is actually stored in: 38 bytes + +Each Object cost is 24 bytes plus 8 bytes per property. + +For small arrays, it is more efficient to store the data as a linked list +of items rather than small arrays. However, the array access is faster as +shown here: https://jsperf.com/small-arrays-vs-linked-objects + +## Monomorphic vs Megamorphic code + +1) Monomophic prop access is 100 times faster then megamorphic. +2) Monomorphic call is 4 times faster the megamorphic call. + + See benchmark [here](https://jsperf.com/mono-vs-megamorphic-property-access). \ No newline at end of file diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 7c09c3fcd8..2afc949dd6 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -16,12 +16,12 @@ import {TemplateRef as viewEngine_TemplateRef} from '../linker/template_ref'; import {Type} from '../type'; import {assertNotNull} from './assert'; -import {DirectiveDef} from './definition_interfaces'; import {getOrCreateContainerRef, getOrCreateElementRef, getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from './di'; -import {LContainerNode, LElementNode, LInjector, LNode, LNodeFlags, LQuery, LViewNode, QueryReadType} from './interfaces'; +import {DirectiveDef} from './interfaces/definition'; +import {LInjector} from './interfaces/injector'; +import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode, TNode} from './interfaces/node'; +import {LQuery, QueryReadType} from './interfaces/query'; import {assertNodeOfPossibleTypes} from './node_assert'; -import {TNode} from './t_node'; - /** diff --git a/packages/core/src/render3/t_node.ts b/packages/core/src/render3/t_node.ts deleted file mode 100644 index fa107f0252..0000000000 --- a/packages/core/src/render3/t_node.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @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 {DirectiveDef} from './definition_interfaces'; - -/** The type of the global ngStaticData array. */ -export type NgStaticData = (TNode | DirectiveDef| null)[]; - -/** - * LNode binding data (flyweight) for a particular node that is shared between all templates - * of a specific type. - * - * If a property is: - * - PropertyAliases: that property's data was generated and this is it - * - Null: that property's data was already generated and nothing was found. - * - Undefined: that property's data has not yet been generated - * - * see: https://en.wikipedia.org/wiki/Flyweight_pattern for more on the Flyweight pattern - */ -export interface TNode { - /** The tag name associated with this node. */ - tagName: string|null; - - /** - * Static attributes associated with an element. We need to store - * static attributes to support content projection with selectors. - * Attributes are stored statically because reading them from the DOM - * would be way too slow for content projection and queries. - * - * Since attrs will always be calculated first, they will never need - * to be marked undefined by other instructions. - * - * The name of the attribute and its value alternate in the array. - * e.g. ['role', 'checkbox'] - */ - attrs: string[]|null; - - /** - * A set of local names under which a given element is exported in a template and - * visible to queries. An entry in this array can be created for different reasons: - * - an element itself is referenced, ex.: `
` - * - a component is referenced, ex.: `` - * - a directive is referenced, ex.: ``. - * - * A given element might have different local names and those names can be associated - * with a directive. We store local names at even indexes while odd indexes are reserved - * for directive index in a view (or `-1` if there is no associated directive). - * - * Some examples: - * - `
` => `["foo", -1]` - * - `` => `["foo", myCmptIdx]` - * - `` => `["foo", myCmptIdx, "bar", directiveIdx]` - * - `
` => `["foo", -1, "bar", directiveIdx]` - */ - localNames: (string|number)[]|null; - - /** - * This property contains information about input properties that - * need to be set once from attribute data. - */ - initialInputs: InitialInputData|null|undefined; - - /** Input data for all directives on this node. */ - inputs: PropertyAliases|null|undefined; - - /** Output data for all directives on this node. */ - outputs: PropertyAliases|null|undefined; - - /** - * If this TNode corresponds to an LContainerNode, the container will - * need to have nested static data for each of its embedded views. - * Otherwise, nodes in embedded views with the same index as nodes - * in their parent views will overwrite each other, as they are in - * the same template. - * - * Each index in this array corresponds to the static data for a certain - * view. So if you had V(0) and V(1) in a container, you might have: - * - * [ - * [{tagName: 'div', attrs: ...}, null], // V(0) ngData - * [{tagName: 'button', attrs ...}, null] // V(1) ngData - * ] - */ - containerStatic: (TNode|null)[][]|null; -} - -/** Static data for an LElementNode */ -export interface TElementNode extends TNode { containerStatic: null; } - -/** Static data for an LContainerNode */ -export interface TContainerNode extends TNode { containerStatic: (TNode|null)[][]; } - -/** - * This mapping is necessary so we can set input properties and output listeners - * properly at runtime when property names are minified or aliased. - * - * Key: unminified / public input or output name - * Value: array containing minified / internal name and related directive index - * - * The value must be an array to support inputs and outputs with the same name - * on the same node. - */ -export type PropertyAliases = { - // This uses an object map because using the Map type would be too slow - [key: string]: PropertyAliasValue -}; - -/** - * The value in PropertyAliases. - * - * In each array: - * Even indices: directive index - * Odd indices: minified / internal name - * - * e.g. [0, 'change-minified'] - */ -export type PropertyAliasValue = (number | string)[]; - - -/** - * This array contains information about input properties that - * need to be set once from attribute data. It's ordered by - * directive index (relative to element) so it's simple to - * look up a specific directive's initial input data. - * - * Within each sub-array: - * - * Even indices: minified/internal input name - * Odd indices: initial value - * - * If a directive on a node does not have any input properties - * that should be set from attributes, its index is set to null - * to avoid a sparse array. - * - * e.g. [null, ['role-min', 'button']] - */ -export type InitialInputData = (InitialInputs | null)[]; - -/** - * Used by InitialInputData to store input properties - * that should be set once from attributes. - * - * Even indices: minified/internal input name - * Odd indices: initial value - * - * e.g. ['role-min', 'button'] - */ -export type InitialInputs = string[]; diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 1f141ad48a..22e41bc356 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -11,7 +11,8 @@ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {bloomAdd, bloomFindPossibleInjector} from '../../src/render3/di'; import {C, D, E, PublicFeature, T, V, b, b2, c, cR, cr, defineDirective, e, inject, injectElementRef, injectTemplateRef, injectViewContainerRef, t, v} from '../../src/render3/index'; import {createLNode, createLView, enterView, getOrCreateNodeInjector, leaveView} from '../../src/render3/instructions'; -import {LInjector, LNodeFlags} from '../../src/render3/interfaces'; +import {LInjector} from '../../src/render3/interfaces/injector'; +import {LNodeFlags} from '../../src/render3/interfaces/node'; import {renderToHtml} from './render_util'; diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index ee6e5392a4..bf5ee2a913 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {CssSelector, CssSelectorWithNegations, SimpleCssSelector} from '../../src/render3/interfaces'; +import {TNode} from '../../src/render3/interfaces/node'; +import {CssSelector, CssSelectorWithNegations, SimpleCssSelector} from '../../src/render3/interfaces/projection'; import {isNodeMatchingSelector, isNodeMatchingSelectorWithNegations, isNodeMatchingSimpleSelector} from '../../src/render3/node_selector_matcher'; -import {TNode} from '../../src/render3/t_node'; function testLStaticData(tagName: string, attrs: string[] | null): TNode { return { diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index 3e991f22a0..8f3f601133 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {C, D, E, Q, QueryList, c, e, m, qR} from '../../src/render3/index'; -import {QueryReadType} from '../../src/render3/interfaces'; +import {QueryReadType} from '../../src/render3/interfaces/query'; import {createComponent, createDirective, renderComponent} from './render_util'; diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index 83eec622d2..9e93cbc02d 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -9,8 +9,8 @@ import {stringifyElement} from '@angular/platform-browser/testing/src/browser_util'; import {ComponentTemplate, ComponentType, DirectiveType, PublicFeature, defineComponent, defineDirective, renderComponent as _renderComponent} from '../../src/render3/index'; import {NG_HOST_SYMBOL, createLNode, createLView, renderTemplate} from '../../src/render3/instructions'; -import {LElementNode, LNodeFlags} from '../../src/render3/interfaces'; -import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/renderer'; +import {LElementNode, LNodeFlags} from '../../src/render3/interfaces/node'; +import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer'; import {getRendererFactory2} from './imported_renderer2'; export const document = ((global || window) as any).document;