perf(ivy): move attributes array into component def (#32798)

Currently Ivy stores the element attributes into an array above the component def and passes it into the relevant instructions, however the problem is that upon minification the array will get a unique name which won't compress very well. These changes move the attributes array into the component def and pass in the index into the instructions instead.

Before:
```
const _c0 = ['foo', 'bar'];

SomeComp.ngComponentDef = defineComponent({
  template: function() {
    element(0, 'div', _c0);
  }
});
```

After:
```
SomeComp.ngComponentDef = defineComponent({
  consts: [['foo', 'bar']],
  template: function() {
    element(0, 'div', 0);
  }
});
```

A couple of cases that this PR doesn't handle:
* Template references are still in a separate array.
* i18n attributes are still in a separate array.

PR Close #32798
This commit is contained in:
crisbeto
2019-09-23 20:08:51 +02:00
committed by Alex Rickabaugh
parent b2b917d2d8
commit d5b87d32b0
52 changed files with 912 additions and 885 deletions

View File

@ -14,7 +14,7 @@ The layout is as such:
| Section | `LView` | `TView.data`
| ---------- | ------------------------------------------------------------ | --------------------------------------------------
| `HEADER` | contextual data | mostly `null`
| `CONSTS` | DOM, pipe, and local ref instances |
| `DECLS` | DOM, pipe, and local ref instances |
| `VARS` | binding values | property names
| `EXPANDO` | host bindings; directive instances; providers; dynamic nodes | host prop names; directive tokens; provider tokens; `null`
@ -25,10 +25,10 @@ The layout is as such:
Mostly information such as parent `LView`, `Sanitizer`, `TView`, and many more bits of information needed for template rendering.
## `CONSTS`
## `DECLS`
`CONSTS` contain the DOM elements, pipe instances, and local refs.
The size of the `CONSTS` section is declared in the property `consts` of the component definition.
`DECLS` contain the DOM elements, pipe instances, and local refs.
The size of the `DECLS` section is declared in the property `decls` of the component definition.
```typescript
@Component({
@ -38,7 +38,7 @@ class MyApp {
static ngComponentDef = ɵɵdefineComponent({
...,
consts: 5,
decls: 5,
template: function(rf: RenderFlags, ctx: MyApp) {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'div');
@ -89,7 +89,7 @@ class MyApp {
static ngComponentDef = ɵɵdefineComponent({
...,
consts: 2, // Two DOM Elements.
decls: 2, // Two DOM Elements.
vars: 2, // Two bindings.
template: function(rf: RenderFlags, ctx: MyApp) {
if (rf & RenderFlags.Create) {
@ -142,7 +142,7 @@ class MyApp {
static ngComponentDef = ɵɵdefineComponent({
...,
consts: 1,
decls: 1,
template: function(rf: RenderFlags, ctx: MyApp) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'child', ['tooltip', null]);
@ -276,7 +276,7 @@ class MyApp {
static ngComponentDef = ɵɵdefineComponent({
...,
consts: 1,
decls: 1,
template: function(rf: RenderFlags, ctx: MyApp) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'child');

View File

@ -128,7 +128,7 @@ export function renderComponent<T>(
const rootContext = createRootContext(opts.scheduler, opts.playerHandler);
const renderer = rendererFactory.createRenderer(hostRNode, componentDef);
const rootTView = createTView(-1, null, 1, 0, null, null, null, null);
const rootTView = createTView(-1, null, 1, 0, null, null, null, null, null);
const rootView: LView = createLView(
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, renderer, undefined,
opts.injector || null);

View File

@ -161,7 +161,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
}
// Create the root view. Uses empty TView and ContentTemplate.
const rootTView = createTView(-1, null, 1, 0, null, null, null, null);
const rootTView = createTView(-1, null, 1, 0, null, null, null, null, null);
const rootLView = createLView(
null, rootTView, rootContext, rootFlags, null, null, rendererFactory, renderer, sanitizer,
rootViewInjector);

View File

@ -18,6 +18,7 @@ import {stringify} from '../util/stringify';
import {EMPTY_ARRAY, EMPTY_OBJ} from './empty';
import {NG_BASE_DEF, NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_FACTORY_DEF, NG_LOCALE_ID_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields';
import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, FactoryFn, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory, ViewQueriesFunction, ɵɵBaseDef} from './interfaces/definition';
import {TAttributes} from './interfaces/node';
// while SelectorFlags is unused here, it's required so that types don't get resolved lazily
// see: https://github.com/Microsoft/web-build-tools/issues/1050
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
@ -56,7 +57,7 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
* can pre-fill the array and set the binding start index.
*/
// TODO(kara): remove queries from this count
consts: number;
decls: number;
/**
* The number of bindings in this component template (including pure fn bindings).
@ -171,6 +172,9 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
*/
template: ComponentTemplate<T>;
/** Constants for the nodes in the component's view. */
consts?: TAttributes[];
/**
* An array of `ngContent[selector]` values that were found in the template.
*/
@ -248,10 +252,11 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
const def: Mutable<ComponentDef<any>, keyof ComponentDef<any>> = {
type: type,
providersResolver: null,
consts: componentDefinition.consts,
decls: componentDefinition.decls,
vars: componentDefinition.vars,
factory: null,
template: componentDefinition.template || null !,
consts: componentDefinition.consts || null,
ngContentSelectors: componentDefinition.ngContentSelectors,
hostBindings: componentDefinition.hostBindings || null,
contentQueries: componentDefinition.contentQueries || null,

View File

@ -22,7 +22,7 @@ import {DirectiveDef} from '../interfaces/definition';
* type: ComponentWithProviders,
* selectors: [['component-with-providers']],
* factory: () => new ComponentWithProviders(directiveInject(GreeterDE as any)),
* consts: 1,
* decls: 1,
* vars: 1,
* template: function(fs: RenderFlags, ctx: ComponentWithProviders) {
* if (fs & RenderFlags.Create) {

View File

@ -53,10 +53,10 @@ export function ɵɵcontainer(index: number): void {
*
* @param index The index of the container in the data array
* @param templateFn Inline template
* @param consts The number of nodes, local refs, and pipes for this template
* @param decls The number of nodes, local refs, and pipes for this template
* @param vars The number of bindings for this template
* @param tagName The name of the container element, if applicable
* @param attrs The attrs attached to the container, if applicable
* @param constsIndex Index of template in the `consts` array.
* @param localRefs A set of local reference bindings on the element.
* @param localRefExtractor A function which extracts local-refs values from the template.
* Defaults to the current element associated with the local-ref.
@ -64,22 +64,25 @@ export function ɵɵcontainer(index: number): void {
* @codeGenApi
*/
export function ɵɵtemplate(
index: number, templateFn: ComponentTemplate<any>| null, consts: number, vars: number,
tagName?: string | null, attrs?: TAttributes | null, localRefs?: string[] | null,
index: number, templateFn: ComponentTemplate<any>| null, decls: number, vars: number,
tagName?: string | null, constsIndex?: number | null, localRefs?: string[] | null,
localRefExtractor?: LocalRefExtractor) {
const lView = getLView();
const tView = lView[TVIEW];
const tViewConsts = tView.consts;
// TODO: consider a separate node type for templates
const tContainerNode = containerInternal(lView, index, tagName || null, attrs || null);
const tContainerNode = containerInternal(
lView, index, tagName || null,
tViewConsts === null || constsIndex == null ? null : tViewConsts[constsIndex]);
if (tView.firstTemplatePass) {
ngDevMode && ngDevMode.firstTemplatePass++;
resolveDirectives(tView, lView, tContainerNode, localRefs || null);
registerPostOrderHooks(tView, tContainerNode);
const embeddedTView = tContainerNode.tViews = createTView(
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null,
tView.schemas);
-1, templateFn, decls, vars, tView.directiveRegistry, tView.pipeRegistry, null,
tView.schemas, tViewConsts);
const embeddedTViewNode = createTNode(tView, null, TNodeType.View, -1, null, null) as TViewNode;
embeddedTViewNode.injectorIndex = tContainerNode.injectorIndex;
embeddedTView.node = embeddedTViewNode;

View File

@ -32,8 +32,7 @@ import {registerInitialStylingOnTNode} from './styling';
*
* @param index Index of the element in the LView array
* @param name Name of the DOM Node
* @param attrs Statically bound set of attributes, classes, and styles to be written into the DOM
* element on creation. Use [AttributeMarker] to denote the meaning of this array.
* @param constsIndex Index of the element in the `consts` array.
* @param localRefs A set of local reference bindings on the element.
*
* Attributes and localRefs are passed as an array of strings where elements with an even index
@ -43,24 +42,25 @@ import {registerInitialStylingOnTNode} from './styling';
* @codeGenApi
*/
export function ɵɵelementStart(
index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): void {
index: number, name: string, constsIndex?: number | null, localRefs?: string[] | null): void {
const lView = getLView();
const tView = lView[TVIEW];
const tViewConsts = tView.consts;
const consts = tViewConsts === null || constsIndex == null ? null : tViewConsts[constsIndex];
ngDevMode && assertEqual(
lView[BINDING_INDEX], tView.bindingStartIndex,
'elements should be created before any bindings ');
'elements should be created before any bindings');
ngDevMode && ngDevMode.rendererCreateElement++;
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
const renderer = lView[RENDERER];
const native = lView[index + HEADER_OFFSET] = elementCreate(name, renderer, getNamespace());
const tNode =
getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, name, attrs || null);
const tNode = getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, name, consts);
if (attrs != null) {
const lastAttrIndex = setUpAttributes(renderer, native, attrs);
if (consts != null) {
const lastAttrIndex = setUpAttributes(renderer, native, consts);
if (tView.firstTemplatePass) {
registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);
registerInitialStylingOnTNode(tNode, consts, lastAttrIndex);
}
}
@ -146,15 +146,14 @@ export function ɵɵelementEnd(): void {
*
* @param index Index of the element in the data array
* @param name Name of the DOM Node
* @param attrs Statically bound set of attributes, classes, and styles to be written into the DOM
* element on creation. Use [AttributeMarker] to denote the meaning of this array.
* @param constsIndex Index of the element in the `consts` array.
* @param localRefs A set of local reference bindings on the element.
*
* @codeGenApi
*/
export function ɵɵelement(
index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): void {
ɵɵelementStart(index, name, attrs, localRefs);
index: number, name: string, constsIndex?: number | null, localRefs?: string[] | null): void {
ɵɵelementStart(index, name, constsIndex, localRefs);
ɵɵelementEnd();
}

View File

@ -26,7 +26,7 @@ import {registerInitialStylingOnTNode} from './styling';
* The instruction must later be followed by `elementContainerEnd()` call.
*
* @param index Index of the element in the LView array
* @param attrs Set of attributes to be used when matching directives.
* @param constsIndex Index of the container in the `consts` array.
* @param localRefs A set of local reference bindings on the element.
*
* Even if this instruction accepts a set of attributes no actual attribute values are propagated to
@ -36,11 +36,13 @@ import {registerInitialStylingOnTNode} from './styling';
* @codeGenApi
*/
export function ɵɵelementContainerStart(
index: number, attrs?: TAttributes | null, localRefs?: string[] | null): void {
index: number, constsIndex?: number | null, localRefs?: string[] | null): void {
const lView = getLView();
const tView = lView[TVIEW];
const renderer = lView[RENDERER];
const tagName = 'ng-container';
const tViewConsts = tView.consts;
const consts = tViewConsts === null || constsIndex == null ? null : tViewConsts[constsIndex];
ngDevMode && assertEqual(
lView[BINDING_INDEX], tView.bindingStartIndex,
'element containers should be created before any bindings');
@ -50,14 +52,13 @@ export function ɵɵelementContainerStart(
const native = lView[index + HEADER_OFFSET] = renderer.createComment(ngDevMode ? tagName : '');
ngDevMode && assertDataInRange(lView, index - 1);
const tNode = getOrCreateTNode(
tView, lView[T_HOST], index, TNodeType.ElementContainer, tagName, attrs || null);
const tNode =
getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.ElementContainer, tagName, consts);
if (attrs && tView.firstTemplatePass) {
if (consts && tView.firstTemplatePass) {
// While ng-container doesn't necessarily support styling, we use the style context to identify
// and execute directives on the ng-container.
registerInitialStylingOnTNode(tNode, attrs as TAttributes, 0);
registerInitialStylingOnTNode(tNode, consts as TAttributes, 0);
}
appendChild(native, tNode, lView);
@ -113,13 +114,13 @@ export function ɵɵelementContainerEnd(): void {
* and {@link elementContainerEnd}
*
* @param index Index of the element in the LView array
* @param attrs Set of attributes to be used when matching directives.
* @param constsIndex Index of the container in the `consts` array.
* @param localRefs A set of local reference bindings on the element.
*
* @codeGenApi
*/
export function ɵɵelementContainer(
index: number, attrs?: TAttributes | null, localRefs?: string[] | null): void {
ɵɵelementContainerStart(index, attrs, localRefs);
index: number, constsIndex?: number | null, localRefs?: string[] | null): void {
ɵɵelementContainerStart(index, constsIndex, localRefs);
ɵɵelementContainerEnd();
}

View File

@ -28,8 +28,7 @@ import {assignTViewNodeToLView, createLView, createTView, refreshView, renderVie
*
* @codeGenApi
*/
export function ɵɵembeddedViewStart(
viewBlockId: number, consts: number, vars: number): RenderFlags {
export function ɵɵembeddedViewStart(viewBlockId: number, decls: number, vars: number): RenderFlags {
const lView = getLView();
const previousOrParentTNode = getPreviousOrParentTNode();
// The previous node can be a view node if we are processing an inline for loop
@ -47,9 +46,8 @@ export function ɵɵembeddedViewStart(
} else {
// When we create a new LView, we always reset the state of the instructions.
viewToRender = createLView(
lView,
getOrCreateEmbeddedTView(viewBlockId, consts, vars, containerTNode as TContainerNode), null,
LViewFlags.CheckAlways, null, null);
lView, getOrCreateEmbeddedTView(viewBlockId, decls, vars, containerTNode as TContainerNode),
null, LViewFlags.CheckAlways, null, null);
const tParentNode = getIsParent() ? previousOrParentTNode :
previousOrParentTNode && previousOrParentTNode.parent;
@ -75,13 +73,13 @@ export function ɵɵembeddedViewStart(
* it with the same index (since it's in the same template).
*
* @param viewIndex The index of the TView in TNode.tViews
* @param consts The number of nodes, local refs, and pipes in this template
* @param decls The number of nodes, local refs, and pipes in this template
* @param vars The number of bindings and pure function bindings in this template
* @param container The parent container in which to look for the view's static data
* @returns TView
*/
function getOrCreateEmbeddedTView(
viewIndex: number, consts: number, vars: number, parent: TContainerNode): TView {
viewIndex: number, decls: number, vars: number, parent: TContainerNode): TView {
const tView = getLView()[TVIEW];
ngDevMode && assertNodeType(parent, TNodeType.Container);
const containerTViews = parent.tViews as TView[];
@ -89,7 +87,8 @@ function getOrCreateEmbeddedTView(
ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array');
if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) {
containerTViews[viewIndex] = createTView(
viewIndex, null, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null, null);
viewIndex, null, decls, vars, tView.directiveRegistry, tView.pipeRegistry, null, null,
tView.consts);
}
return containerTViews[viewIndex];
}

View File

@ -14,7 +14,7 @@ import {initNgDevMode} from '../../util/ng_dev_mode';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
import {PropertyAliases, TContainerNode, TElementNode, TNode as ITNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
import {PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode as ITNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
import {SelectorFlags} from '../interfaces/projection';
import {TQueries} from '../interfaces/query';
import {RComment, RElement, RNode} from '../interfaces/renderer';
@ -102,6 +102,7 @@ export const TViewConstructor = class TView implements ITView {
public pipeRegistry: PipeDefList|null, //
public firstChild: TNode|null, //
public schemas: SchemaMetadata[]|null, //
public consts: TAttributes[]|null, //
) {}
get template_(): string {

View File

@ -569,8 +569,8 @@ export function saveResolvedLocalsInData(
*/
export function getOrCreateTView(def: ComponentDef<any>): TView {
return def.tView || (def.tView = createTView(
-1, def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs,
def.viewQuery, def.schemas));
-1, def.template, def.decls, def.vars, def.directiveDefs, def.pipeDefs,
def.viewQuery, def.schemas, def.consts));
}
@ -579,18 +579,20 @@ export function getOrCreateTView(def: ComponentDef<any>): TView {
*
* @param viewIndex The viewBlockId for inline views, or -1 if it's a component/dynamic
* @param templateFn Template function
* @param consts The number of nodes, local refs, and pipes in this template
* @param decls The number of nodes, local refs, and pipes in this template
* @param directives Registry of directives for this view
* @param pipes Registry of pipes for this view
* @param viewQuery View queries for this view
* @param schemas Schemas for this view
* @param consts Constants for this view
*/
export function createTView(
viewIndex: number, templateFn: ComponentTemplate<any>| null, consts: number, vars: number,
viewIndex: number, templateFn: ComponentTemplate<any>| null, decls: number, vars: number,
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
viewQuery: ViewQueriesFunction<any>| null, schemas: SchemaMetadata[] | null): TView {
viewQuery: ViewQueriesFunction<any>| null, schemas: SchemaMetadata[] | null,
consts: TAttributes[] | null): TView {
ngDevMode && ngDevMode.tView++;
const bindingStartIndex = HEADER_OFFSET + consts;
const bindingStartIndex = HEADER_OFFSET + decls;
// This length does not yet contain host bindings from child directives because at this point,
// we don't know which directives are active on this template. As soon as a directive is matched
// that has a host binding, we will update the blueprint with that def's hostVars count.
@ -627,7 +629,7 @@ export function createTView(
typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null,
null, // firstChild: TNode|null,
schemas, // schemas: SchemaMetadata[]|null,
) :
consts) : // consts: TAttributes[]
{
id: viewIndex,
blueprint: blueprint,
@ -656,6 +658,7 @@ export function createTView(
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
firstChild: null,
schemas: schemas,
consts: consts,
};
}

View File

@ -9,6 +9,8 @@
import {SchemaMetadata, ViewEncapsulation} from '../../core';
import {ProcessProvidersFunction} from '../../di/interface/provider';
import {Type} from '../../interface/type';
import {TAttributes} from './node';
import {CssSelectorList} from './projection';
import {TView} from './view';
@ -241,6 +243,9 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
*/
readonly template: ComponentTemplate<T>;
/** Constants associated with the component's view. */
readonly consts: TAttributes[]|null;
/**
* An array of `ngContent[selector]` values that were found in the template.
*/
@ -258,7 +263,7 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
* can pre-fill the array and set the binding start index.
*/
// TODO(kara): remove queries from this count
readonly consts: number;
readonly decls: number;
/**
* The number of bindings in this component template (including pure fn bindings).

View File

@ -15,7 +15,7 @@ import {Sanitizer} from '../../sanitization/sanitizer';
import {LContainer} from './container';
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList, ViewQueriesFunction} from './definition';
import {I18nUpdateOpCodes, TI18n} from './i18n';
import {TElementNode, TNode, TViewNode} from './node';
import {TAttributes, TElementNode, TNode, TViewNode} from './node';
import {PlayerHandler} from './player';
import {LQueries, TQueries} from './query';
import {RElement, Renderer3, RendererFactory3} from './renderer';
@ -387,7 +387,7 @@ export interface TView {
/**
* The index where the "expando" section of `LView` begins. The expando
* section contains injectors, directive instances, and host binding values.
* Unlike the "consts" and "vars" sections of `LView`, the length of this
* Unlike the "decls" and "vars" sections of `LView`, the length of this
* section cannot be calculated at compile-time because directives are matched
* at runtime to preserve locality.
*
@ -561,6 +561,12 @@ export interface TView {
* Set of schemas that declare elements to be allowed inside the view.
*/
schemas: SchemaMetadata[]|null;
/**
* Array of attributes for all of the elements in the view. Used
* for directive matching and attribute bindings.
*/
consts: TAttributes[]|null;
}
export const enum RootContextFlags {Empty = 0b00, DetectChanges = 0b01, FlushPlayers = 0b10}

View File

@ -14,7 +14,7 @@ import {isCreationMode} from './util/view_utils';
/**
* Bindings for pure functions are stored after regular bindings.
*
* |------consts------|---------vars---------| |----- hostVars (dir1) ------|
* |-------decls------|---------vars---------| |----- hostVars (dir1) ------|
* ------------------------------------------------------------------------------------------
* | nodes/refs/pipes | bindings | fn slots | injector | dir1 | host bindings | host slots |
* ------------------------------------------------------------------------------------------