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';