refactor(compiler): set element attributes via one call

This makes the cost of using directives that have host attributes
smaller.

Part of #11683
This commit is contained in:
Tobias Bosch 2016-10-19 09:17:36 -07:00 committed by vsavkin
parent bc3f4bc816
commit faa3478514
14 changed files with 269 additions and 87 deletions

View File

@ -0,0 +1,36 @@
/**
* @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 {CompileTokenMetadata} from '../compile_metadata';
import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
if (isPresent(token.value)) {
return o.literal(token.value);
} else if (token.identifierIsInstance) {
return o.importExpr(token.identifier)
.instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const]));
} else {
return o.importExpr(token.identifier);
}
}
export function createFastArray(values: o.Expression[]): o.Expression {
if (values.length === 0) {
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_FAST_ARRAY));
}
const index = Math.ceil(values.length / 2) - 1;
const identifierSpec = index < Identifiers.fastArrays.length ? Identifiers.fastArrays[index] :
Identifiers.FastArrayDynamic;
const identifier = resolveIdentifier(identifierSpec);
return o.importExpr(identifier).instantiate([
<o.Expression>o.literal(values.length)
].concat(values));
}

View File

@ -10,7 +10,6 @@ import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionS
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core';
import {assetUrl} from './util';
var APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view');
var VIEW_UTILS_MODULE_URL = assetUrl('core', 'linker/view_utils');
@ -190,8 +189,17 @@ export class Identifiers {
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.EMPTY_MAP
};
static pureProxies = [
static createRenderElement: IdentifierSpec = {
name: 'createRenderElement',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.createRenderElement
};
static selectOrCreateRenderHostElement: IdentifierSpec = {
name: 'selectOrCreateRenderHostElement',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.selectOrCreateRenderHostElement
};
static pureProxies: IdentifierSpec[] = [
null,
{name: 'pureProxy1', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy1},
{name: 'pureProxy2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy2},
@ -284,6 +292,34 @@ export class Identifiers {
moduleUrl: assetUrl('core', 'animation/animation_transition'),
runtime: AnimationTransition
};
// This is just the interface!
static FastArray:
IdentifierSpec = {name: 'FastArray', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: null};
static fastArrays: IdentifierSpec[] = [
{name: 'FastArray2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.FastArray2},
{name: 'FastArray4', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.FastArray4},
{name: 'FastArray8', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.FastArray8},
{name: 'FastArray16', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.FastArray16},
];
static EMPTY_FAST_ARRAY: IdentifierSpec = {
name: 'EMPTY_FAST_ARRAY',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.EMPTY_FAST_ARRAY
};
static FastArrayDynamic: IdentifierSpec = {
name: 'FastArrayDynamic',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.FastArrayDynamic
};
}
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
if (path == null) {
return `asset:@angular/lib/${pkg}/index`;
} else {
return `asset:@angular/lib/${pkg}/src/${path}`;
}
}
export function resolveIdentifier(identifier: IdentifierSpec) {

View File

@ -9,6 +9,7 @@
import {Injectable} from '@angular/core';
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata';
import {createDiTokenExpression} from './compiler_util/identifier_util';
import {isPresent} from './facade/lang';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
import * as o from './output/output_ast';
@ -17,7 +18,6 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {LifecycleHooks} from './private_import_core';
import {NgModuleProviderAnalyzer} from './provider_analyzer';
import {ProviderAst} from './template_parser/template_ast';
import {createDiTokenExpression} from './util';
export class ComponentFactoryDependency {
constructor(

View File

@ -6,9 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompileTokenMetadata} from './compile_metadata';
import {isBlank, isPresent, isPrimitive, isStrictStringMap} from './facade/lang';
import * as o from './output/output_ast';
import {isBlank, isPrimitive, isStrictStringMap} from './facade/lang';
export const MODULE_SUFFIX = '';
@ -72,25 +70,6 @@ export class ValueTransformer implements ValueVisitor {
visitOther(value: any, context: any): any { return value; }
}
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
if (path == null) {
return `asset:@angular/lib/${pkg}/index`;
} else {
return `asset:@angular/lib/${pkg}/src/${path}`;
}
}
export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
if (isPresent(token.value)) {
return o.literal(token.value);
} else if (token.identifierIsInstance) {
return o.importExpr(token.identifier)
.instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const]));
} else {
return o.importExpr(token.identifier);
}
}
export class SyncAsyncResult<T> {
constructor(public syncResult: T, public asyncResult: Promise<T> = null) {
if (!asyncResult) {

View File

@ -8,6 +8,7 @@
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata';
import {createDiTokenExpression} from '../compiler_util/identifier_util';
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {MapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang';
@ -15,7 +16,6 @@ import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken}
import * as o from '../output/output_ast';
import {convertValueToOutputAst} from '../output/value_util';
import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../template_parser/template_ast';
import {createDiTokenExpression} from '../util';
import {CompileMethod} from './compile_method';
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';

View File

@ -8,10 +8,10 @@
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
import {createDiTokenExpression} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
import {createDiTokenExpression} from '../util';
import {CompileView} from './compile_view';

View File

@ -9,12 +9,12 @@
import {ViewEncapsulation} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
import {createDiTokenExpression, createFastArray} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang';
import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
import {createDiTokenExpression} from '../util';
import {CompileElement, CompileNode} from './compile_element';
import {CompileView} from './compile_view';
@ -156,19 +156,29 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
visitElement(ast: ElementAst, parent: CompileElement): any {
var nodeIndex = this.view.nodes.length;
var createRenderNodeExpr: o.InvokeMethodExpr;
var createRenderNodeExpr: o.Expression;
var debugContextExpr = this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast);
if (nodeIndex === 0 && this.view.viewType === ViewType.HOST) {
createRenderNodeExpr = o.THIS_EXPR.callMethod(
'selectOrCreateHostElement', [o.literal(ast.name), rootSelectorVar, debugContextExpr]);
var directives = ast.directives.map(directiveAst => directiveAst.directive);
var component = directives.find(directive => directive.isComponent);
if (ast.name === NG_CONTAINER_TAG) {
createRenderNodeExpr = ViewProperties.renderer.callMethod(
'createTemplateAnchor', [this._getParentRenderNode(parent), debugContextExpr]);
} else {
if (ast.name === NG_CONTAINER_TAG) {
createRenderNodeExpr = ViewProperties.renderer.callMethod(
'createTemplateAnchor', [this._getParentRenderNode(parent), debugContextExpr]);
const htmlAttrs = _readHtmlAttrs(ast.attrs);
const attrNameAndValues = createFastArray(
_mergeHtmlAndDirectiveAttrs(htmlAttrs, directives).map(v => o.literal(v)));
if (nodeIndex === 0 && this.view.viewType === ViewType.HOST) {
createRenderNodeExpr =
o.importExpr(resolveIdentifier(Identifiers.selectOrCreateRenderHostElement)).callFn([
ViewProperties.renderer, o.literal(ast.name), attrNameAndValues, rootSelectorVar,
debugContextExpr
]);
} else {
createRenderNodeExpr = ViewProperties.renderer.callMethod(
'createElement',
[this._getParentRenderNode(parent), o.literal(ast.name), debugContextExpr]);
createRenderNodeExpr =
o.importExpr(resolveIdentifier(Identifiers.createRenderElement)).callFn([
ViewProperties.renderer, this._getParentRenderNode(parent), o.literal(ast.name),
attrNameAndValues, debugContextExpr
]);
}
}
var fieldName = `_el_${nodeIndex}`;
@ -178,22 +188,6 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
var renderNode = o.THIS_EXPR.prop(fieldName);
var directives = ast.directives.map(directiveAst => directiveAst.directive);
var component = directives.find(directive => directive.isComponent);
var htmlAttrs = _readHtmlAttrs(ast.attrs);
var attrNameAndValues = _mergeHtmlAndDirectiveAttrs(htmlAttrs, directives);
for (var i = 0; i < attrNameAndValues.length; i++) {
const attrName = attrNameAndValues[i][0];
if (ast.name !== NG_CONTAINER_TAG) {
// <ng-container> are not rendered in the DOM
const attrValue = attrNameAndValues[i][1];
this.view.createMethod.addStmt(
ViewProperties.renderer
.callMethod(
'setElementAttribute', [renderNode, o.literal(attrName), o.literal(attrValue)])
.toStmt());
}
}
var compileElement = new CompileElement(
parent, this.view, nodeIndex, renderNode, ast, component, directives, ast.providers,
ast.hasViewContainer, false, ast.references, this.targetDependencies);
@ -328,18 +322,22 @@ function _isNgContainer(node: CompileNode, view: CompileView): boolean {
function _mergeHtmlAndDirectiveAttrs(
declaredHtmlAttrs: {[key: string]: string},
directives: CompileDirectiveMetadata[]): string[][] {
var result: {[key: string]: string} = {};
Object.keys(declaredHtmlAttrs).forEach(key => { result[key] = declaredHtmlAttrs[key]; });
declaredHtmlAttrs: {[key: string]: string}, directives: CompileDirectiveMetadata[]): string[] {
const mapResult: {[key: string]: string} = {};
Object.keys(declaredHtmlAttrs).forEach(key => { mapResult[key] = declaredHtmlAttrs[key]; });
directives.forEach(directiveMeta => {
Object.keys(directiveMeta.hostAttributes).forEach(name => {
const value = directiveMeta.hostAttributes[name];
var prevValue = result[name];
result[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
const prevValue = mapResult[name];
mapResult[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
});
});
return mapToKeyValueArray(result);
const arrResult: string[] = [];
// Note: We need to sort to get a defined output order
// for tests and for caching generated artifacts...
Object.keys(mapResult).sort().forEach(
(attrName) => { arrResult.push(attrName, mapResult[attrName]); });
return arrResult;
}
function _readHtmlAttrs(attrs: AttrAst[]): {[key: string]: string} {
@ -356,14 +354,6 @@ function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: s
}
}
function mapToKeyValueArray(data: {[key: string]: string}): string[][] {
var entryArray: string[][] = [];
Object.keys(data).forEach(name => { entryArray.push([name, data[name]]); });
// We need to sort to get a defined output order
// for tests and for caching generated artifacts...
return entryArray.sort();
}
function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statement[]) {
var nodeDebugInfosVar: o.Expression = o.NULL_EXPR;
if (view.genConfig.genDebugInfo) {

View File

@ -11,7 +11,7 @@
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
import {print} from '../../src/facade/lang';
import {assetUrl} from '../../src/util';
import {assetUrl} from '../../src/identifiers';
function unimplemented(): any {
throw new Error('unimplemented');

View File

@ -10,7 +10,7 @@
import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter';
import {print} from '../../src/facade/lang';
import {assetUrl} from '../../src/util';
import {assetUrl} from '../../src/identifiers';
import {SimpleJsImportGenerator, codegenExportsVars, codegenStmts} from './output_emitter_util';

View File

@ -7,9 +7,9 @@
*/
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
import {assetUrl} from '@angular/compiler/src/identifiers';
import * as o from '@angular/compiler/src/output/output_ast';
import {ImportGenerator} from '@angular/compiler/src/output/path_util';
import {assetUrl} from '@angular/compiler/src/util';
import {EventEmitter} from '@angular/core';
import {BaseError} from '@angular/core/src/facade/errors';
import {ViewType} from '@angular/core/src/linker/view_type';

View File

@ -113,17 +113,6 @@ export abstract class AppView<T> {
}
}
selectOrCreateHostElement(
elementName: string, rootSelectorOrNode: string|any, debugInfo: RenderDebugInfo): any {
var hostElement: any;
if (isPresent(rootSelectorOrNode)) {
hostElement = this.renderer.selectRootElement(rootSelectorOrNode, debugInfo);
} else {
hostElement = this.renderer.createElement(null, elementName, debugInfo);
}
return hostElement;
}
injectorGet(token: any, nodeIndex: number, notFoundResult: any): any {
return this.injectorGetInternal(token, nodeIndex, notFoundResult);
}

View File

@ -12,7 +12,7 @@ import {UNINITIALIZED} from '../change_detection/change_detection_util';
import {Inject, Injectable} from '../di';
import {isPresent, looseIdentical} from '../facade/lang';
import {ViewEncapsulation} from '../metadata/view';
import {RenderComponentType, Renderer, RootRenderer} from '../render/api';
import {RenderComponentType, RenderDebugInfo, Renderer, RootRenderer} from '../render/api';
import {Sanitizer} from '../security';
import {AppElement} from './element';
@ -377,3 +377,153 @@ const CAMEL_CASE_REGEXP = /([A-Z])/g;
function camelCaseToDashCase(input: string): string {
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
}
export function createRenderElement(
renderer: Renderer, parentElement: any, name: string, attrs: FastArray<string>,
debugInfo?: RenderDebugInfo): any {
const el = renderer.createElement(parentElement, name, debugInfo);
for (var i = 0; i < attrs.length; i += 2) {
renderer.setElementAttribute(el, attrs.get(i), attrs.get(i + 1));
}
return el;
}
export function selectOrCreateRenderHostElement(
renderer: Renderer, elementName: string, attrs: FastArray<string>,
rootSelectorOrNode: string | any, debugInfo: RenderDebugInfo): any {
var hostElement: any;
if (isPresent(rootSelectorOrNode)) {
hostElement = renderer.selectRootElement(rootSelectorOrNode, debugInfo);
} else {
hostElement = createRenderElement(renderer, null, elementName, attrs, debugInfo);
}
return hostElement;
}
export interface FastArray<T> {
length: number;
get(index: number): T;
}
class FastArray0 implements FastArray<any> {
length = 0;
get(index: number): any { return undefined; }
}
export class FastArray2<T> implements FastArray<T> {
constructor(public length: number, private _v0: T, private _v1: T) {}
get(index: number) {
switch (index) {
case 0:
return this._v0;
case 1:
return this._v1;
default:
return undefined;
}
}
}
export class FastArray4<T> implements FastArray<T> {
constructor(
public length: number, private _v0: T, private _v1: T, private _v2: T, private _v3: T) {}
get(index: number) {
switch (index) {
case 0:
return this._v0;
case 1:
return this._v1;
case 2:
return this._v2;
case 3:
return this._v3;
default:
return undefined;
}
}
}
export class FastArray8<T> implements FastArray<T> {
constructor(
public length: number, private _v0: T, private _v1: T, private _v2: T, private _v3: T,
private _v4: T, private _v5: T, private _v6: T, private _v7: T) {}
get(index: number) {
switch (index) {
case 0:
return this._v0;
case 1:
return this._v1;
case 2:
return this._v2;
case 3:
return this._v3;
case 4:
return this._v4;
case 5:
return this._v5;
case 6:
return this._v6;
case 7:
return this._v7;
default:
return undefined;
}
}
}
export class FastArray16<T> implements FastArray<T> {
constructor(
public length: number, private _v0: T, private _v1: T, private _v2: T, private _v3: T,
private _v4: T, private _v5: T, private _v6: T, private _v7: T, private _v8: T,
private _v9: T, private _v10: T, private _v11: T, private _v12: T, private _v13: T,
private _v14: T, private _v15: T) {}
get(index: number) {
switch (index) {
case 0:
return this._v0;
case 1:
return this._v1;
case 2:
return this._v2;
case 3:
return this._v3;
case 4:
return this._v4;
case 5:
return this._v5;
case 6:
return this._v6;
case 7:
return this._v7;
case 8:
return this._v8;
case 9:
return this._v9;
case 10:
return this._v10;
case 11:
return this._v11;
case 12:
return this._v12;
case 13:
return this._v13;
case 14:
return this._v14;
case 15:
return this._v15;
default:
return undefined;
}
}
}
export class FastArrayDynamic<T> implements FastArray<T> {
private _values: any[];
// Note: We still take the length argument so this class can be created
// in the same ways as the other classes!
constructor(public length: number, ...values: any[]) { this._values = values; }
get(index: number) { return this._values[index]; }
}
export const EMPTY_FAST_ARRAY: FastArray<any> = new FastArray0();

View File

@ -33,7 +33,8 @@ class _View_TreeComponent_Host0 extends import1.AppView<any> {
parentInjector, declarationEl, import7.ChangeDetectorStatus.CheckAlways);
}
createInternal(rootSelector: string): import2.AppElement {
this._el_0 = this.selectOrCreateHostElement('tree', rootSelector, (null as any));
this._el_0 = import4.selectOrCreateRenderHostElement(
this.renderer, 'tree', import4.EMPTY_FAST_ARRAY, rootSelector, (null as any));
this._vc_0 = new import2.AppElement(0, (null as any), this, this._el_0);
this._TreeComponent_0_4 = new _View_TreeComponent0(this._el_0);
this._vc_0.initComponent(this._TreeComponent_0_4.context, [], <any>this._TreeComponent_0_4);

View File

@ -35,7 +35,8 @@ class _View_TreeRootComponent_Host0 extends import1.AppView<any> {
viewUtils, parentInjector, declarationEl, import7.ChangeDetectorStatus.CheckAlways);
}
createInternal(rootSelector: string): import2.AppElement {
this._el_0 = this.selectOrCreateHostElement('tree', rootSelector, (null as any));
this._el_0 = import4.selectOrCreateRenderHostElement(
this.renderer, 'tree', import4.EMPTY_FAST_ARRAY, rootSelector, (null as any));
this._appEl_0 = new import2.AppElement(0, (null as any), this, this._el_0);
var compView_0: any =
viewFactory_TreeRootComponent0(this.viewUtils, this.injector(0), this._appEl_0);