feat(core): speed up view creation via code gen for view factories.

BREAKING CHANGE:
- Platform pipes can only contain types and arrays of types,
  but no bindings any more.
- When using transformers, platform pipes need to be specified explicitly
  in the pubspec.yaml via the new config option
  `platform_pipes`.
- `Compiler.compileInHost` now returns a `HostViewFactoryRef`
- Component view is not yet created when component constructor is called.
  -> use `onInit` lifecycle callback to access the view of a component
- `ViewRef#setLocal` has been moved to new type `EmbeddedViewRef`
- `internalView` is gone, use `EmbeddedViewRef.rootNodes` to access
  the root nodes of an embedded view
- `renderer.setElementProperty`, `..setElementStyle`, `..setElementAttribute` now
  take a native element instead of an ElementRef
- `Renderer` interface now operates on plain native nodes,
  instead of `RenderElementRef`s or `RenderViewRef`s

Closes #5993
This commit is contained in:
Tobias Bosch
2015-12-02 10:35:51 -08:00
parent a08f50badd
commit 7ae23adaff
191 changed files with 6476 additions and 10232 deletions

View File

@ -1,4 +1,4 @@
import {ListWrapper} from 'angular2/src/facade/collection';
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {reflector} from 'angular2/src/core/reflection/reflection';
@ -43,7 +43,7 @@ export function createChangeDetectorDefinitions(
class ProtoViewVisitor implements TemplateAstVisitor {
viewIndex: number;
boundTextCount: number = 0;
nodeCount: number = 0;
boundElementCount: number = 0;
variableNames: string[] = [];
bindingRecords: BindingRecord[] = [];
@ -57,6 +57,7 @@ class ProtoViewVisitor implements TemplateAstVisitor {
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
this.nodeCount++;
this.boundElementCount++;
templateVisitAll(this, ast.outputs);
for (var i = 0; i < ast.directives.length; i++) {
@ -73,6 +74,7 @@ class ProtoViewVisitor implements TemplateAstVisitor {
}
visitElement(ast: ElementAst, context: any): any {
this.nodeCount++;
if (ast.isBound()) {
this.boundElementCount++;
}
@ -132,14 +134,20 @@ class ProtoViewVisitor implements TemplateAstVisitor {
}
visitAttr(ast: AttrAst, context: any): any { return null; }
visitBoundText(ast: BoundTextAst, context: any): any {
var boundTextIndex = this.boundTextCount++;
this.bindingRecords.push(BindingRecord.createForTextNode(ast.value, boundTextIndex));
var nodeIndex = this.nodeCount++;
this.bindingRecords.push(BindingRecord.createForTextNode(ast.value, nodeIndex));
return null;
}
visitText(ast: TextAst, context: any): any {
this.nodeCount++;
return null;
}
visitText(ast: TextAst, context: any): any { return null; }
visitDirective(ast: DirectiveAst, directiveIndexAsNumber: number): any {
var directiveIndex = new DirectiveIndex(this.boundElementCount - 1, directiveIndexAsNumber);
var directiveMetadata = ast.directive;
var outputsArray = [];
StringMapWrapper.forEach(ast.directive.outputs, (eventName, dirProperty) => outputsArray.push(
[dirProperty, eventName]));
var directiveRecord = new DirectiveRecord({
directiveIndex: directiveIndex,
callAfterContentInit:
@ -153,7 +161,9 @@ class ProtoViewVisitor implements TemplateAstVisitor {
callOnChanges: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1,
callDoCheck: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1,
callOnInit: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1,
changeDetection: directiveMetadata.changeDetection
callOnDestroy: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1,
changeDetection: directiveMetadata.changeDetection,
outputs: outputsArray
});
this.directiveRecords.push(directiveRecord);

View File

@ -3,6 +3,9 @@ import {SourceExpressions, moduleRef} from './source_module';
import {
ChangeDetectorJITGenerator
} from 'angular2/src/core/change_detection/change_detection_jit_generator';
import {AbstractChangeDetector} from 'angular2/src/core/change_detection/abstract_change_detector';
import {ChangeDetectionUtil} from 'angular2/src/core/change_detection/change_detection_util';
import {ChangeDetectorState} from 'angular2/src/core/change_detection/constants';
import {createChangeDetectorDefinitions} from './change_definition_factory';
import {IS_DART, isJsObject, CONST_EXPR} from 'angular2/src/facade/lang';
@ -23,6 +26,12 @@ const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
const UTIL = "ChangeDetectionUtil";
const CHANGE_DETECTOR_STATE = "ChangeDetectorState";
export const CHANGE_DETECTION_JIT_IMPORTS = CONST_EXPR({
'AbstractChangeDetector': AbstractChangeDetector,
'ChangeDetectionUtil': ChangeDetectionUtil,
'ChangeDetectorState': ChangeDetectorState
});
var ABSTRACT_CHANGE_DETECTOR_MODULE = moduleRef(
`package:angular2/src/core/change_detection/abstract_change_detector${MODULE_SUFFIX}`);
var UTIL_MODULE =
@ -45,14 +54,8 @@ export class ChangeDetectionCompiler {
}
private _createChangeDetectorFactory(definition: ChangeDetectorDefinition): Function {
if (IS_DART || !this._genConfig.useJit) {
var proto = new DynamicProtoChangeDetector(definition);
return (dispatcher) => proto.instantiate(dispatcher);
} else {
return new ChangeDetectorJITGenerator(definition, UTIL, ABSTRACT_CHANGE_DETECTOR,
CHANGE_DETECTOR_STATE)
.generate();
}
var proto = new DynamicProtoChangeDetector(definition);
return () => proto.instantiate();
}
compileComponentCodeGen(componentType: CompileTypeMetadata, strategy: ChangeDetectionStrategy,
@ -81,7 +84,7 @@ export class ChangeDetectionCompiler {
definition, `${UTIL_MODULE}${UTIL}`,
`${ABSTRACT_CHANGE_DETECTOR_MODULE}${ABSTRACT_CHANGE_DETECTOR}`,
`${CONSTANTS_MODULE}${CHANGE_DETECTOR_STATE}`);
factories.push(`function(dispatcher) { return new ${codegen.typeName}(dispatcher); }`);
factories.push(`function() { return new ${codegen.typeName}(); }`);
sourcePart = codegen.generateSource();
}
index++;

View File

@ -1,375 +0,0 @@
import {isPresent, isBlank, Type, isString, StringWrapper, IS_DART} from 'angular2/src/facade/lang';
import {SetWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {
TemplateCmd,
TextCmd,
NgContentCmd,
BeginElementCmd,
EndElementCmd,
BeginComponentCmd,
EndComponentCmd,
EmbeddedTemplateCmd,
CompiledComponentTemplate
} from 'angular2/src/core/linker/template_commands';
import {
TemplateAst,
TemplateAstVisitor,
NgContentAst,
EmbeddedTemplateAst,
ElementAst,
VariableAst,
BoundEventAst,
BoundElementPropertyAst,
AttrAst,
BoundTextAst,
TextAst,
DirectiveAst,
BoundDirectivePropertyAst,
templateVisitAll
} from './template_ast';
import {CompileTypeMetadata, CompileDirectiveMetadata} from './directive_metadata';
import {SourceExpressions, SourceExpression, moduleRef} from './source_module';
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
import {
escapeSingleQuoteString,
codeGenConstConstructorCall,
codeGenValueFn,
MODULE_SUFFIX
} from './util';
import {Injectable} from 'angular2/src/core/di';
export var TEMPLATE_COMMANDS_MODULE_REF =
moduleRef(`package:angular2/src/core/linker/template_commands${MODULE_SUFFIX}`);
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
const CLASS_ATTR = 'class';
const STYLE_ATTR = 'style';
@Injectable()
export class CommandCompiler {
compileComponentRuntime(component: CompileDirectiveMetadata, template: TemplateAst[],
changeDetectorFactories: Function[],
componentTemplateFactory: Function): TemplateCmd[] {
var visitor = new CommandBuilderVisitor(
new RuntimeCommandFactory(component, componentTemplateFactory, changeDetectorFactories), 0);
templateVisitAll(visitor, template);
return visitor.result;
}
compileComponentCodeGen(component: CompileDirectiveMetadata, template: TemplateAst[],
changeDetectorFactoryExpressions: string[],
componentTemplateFactory: Function): SourceExpression {
var visitor =
new CommandBuilderVisitor(new CodegenCommandFactory(component, componentTemplateFactory,
changeDetectorFactoryExpressions),
0);
templateVisitAll(visitor, template);
return new SourceExpression([], codeGenArray(visitor.result));
}
}
interface CommandFactory<R> {
createText(value: string, isBound: boolean, ngContentIndex: number): R;
createNgContent(index: number, ngContentIndex: number): R;
createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isBound: boolean, ngContentIndex: number): R;
createEndElement(): R;
createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
encapsulation: ViewEncapsulation, ngContentIndex: number): R;
createEndComponent(): R;
createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isMerged: boolean, ngContentIndex: number, children: R[]): R;
}
class RuntimeCommandFactory implements CommandFactory<TemplateCmd> {
constructor(private component: CompileDirectiveMetadata,
private componentTemplateFactory: Function,
private changeDetectorFactories: Function[]) {}
private _mapDirectives(directives: CompileDirectiveMetadata[]): Type[] {
return directives.map(directive => directive.type.runtime);
}
createText(value: string, isBound: boolean, ngContentIndex: number): TemplateCmd {
return new TextCmd(value, isBound, ngContentIndex);
}
createNgContent(index: number, ngContentIndex: number): TemplateCmd {
return new NgContentCmd(index, ngContentIndex);
}
createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isBound: boolean, ngContentIndex: number): TemplateCmd {
return new BeginElementCmd(name, attrNameAndValues, eventTargetAndNames, variableNameAndValues,
this._mapDirectives(directives), isBound, ngContentIndex);
}
createEndElement(): TemplateCmd { return new EndElementCmd(); }
createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
encapsulation: ViewEncapsulation, ngContentIndex: number): TemplateCmd {
var nestedTemplateAccessor = this.componentTemplateFactory(directives[0]);
return new BeginComponentCmd(name, attrNameAndValues, eventTargetAndNames,
variableNameAndValues, this._mapDirectives(directives),
encapsulation, ngContentIndex, nestedTemplateAccessor);
}
createEndComponent(): TemplateCmd { return new EndComponentCmd(); }
createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isMerged: boolean, ngContentIndex: number,
children: TemplateCmd[]): TemplateCmd {
return new EmbeddedTemplateCmd(attrNameAndValues, variableNameAndValues,
this._mapDirectives(directives), isMerged, ngContentIndex,
this.changeDetectorFactories[embeddedTemplateIndex], children);
}
}
class CodegenCommandFactory implements CommandFactory<Expression> {
constructor(private component: CompileDirectiveMetadata,
private componentTemplateFactory: Function,
private changeDetectorFactoryExpressions: string[]) {}
createText(value: string, isBound: boolean, ngContentIndex: number): Expression {
return new Expression(
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'TextCmd')}(${escapeSingleQuoteString(value)}, ${isBound}, ${ngContentIndex})`);
}
createNgContent(index: number, ngContentIndex: number): Expression {
return new Expression(
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'NgContentCmd')}(${index}, ${ngContentIndex})`);
}
createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isBound: boolean, ngContentIndex: number): Expression {
var attrsExpression = codeGenArray(attrNameAndValues);
return new Expression(
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'BeginElementCmd')}(${escapeSingleQuoteString(name)}, ${attrsExpression}, ` +
`${codeGenArray(eventTargetAndNames)}, ${codeGenArray(variableNameAndValues)}, ${codeGenDirectivesArray(directives)}, ${isBound}, ${ngContentIndex})`);
}
createEndElement(): Expression {
return new Expression(
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'EndElementCmd')}()`);
}
createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
encapsulation: ViewEncapsulation, ngContentIndex: number): Expression {
var attrsExpression = codeGenArray(attrNameAndValues);
return new Expression(
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'BeginComponentCmd')}(${escapeSingleQuoteString(name)}, ${attrsExpression}, ` +
`${codeGenArray(eventTargetAndNames)}, ${codeGenArray(variableNameAndValues)}, ${codeGenDirectivesArray(directives)}, ${codeGenViewEncapsulation(encapsulation)}, ${ngContentIndex}, ${this.componentTemplateFactory(directives[0])})`);
}
createEndComponent(): Expression {
return new Expression(
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'EndComponentCmd')}()`);
}
createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isMerged: boolean, ngContentIndex: number,
children: Expression[]): Expression {
return new Expression(
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'EmbeddedTemplateCmd')}(${codeGenArray(attrNameAndValues)}, ${codeGenArray(variableNameAndValues)}, ` +
`${codeGenDirectivesArray(directives)}, ${isMerged}, ${ngContentIndex}, ${this.changeDetectorFactoryExpressions[embeddedTemplateIndex]}, ${codeGenArray(children)})`);
}
}
function visitAndReturnContext(visitor: TemplateAstVisitor, asts: TemplateAst[],
context: any): any {
templateVisitAll(visitor, asts, context);
return context;
}
class CommandBuilderVisitor<R> implements TemplateAstVisitor {
result: R[] = [];
transitiveNgContentCount: number = 0;
constructor(public commandFactory: CommandFactory<R>, public embeddedTemplateIndex: number) {}
private _readAttrNameAndValues(directives: CompileDirectiveMetadata[],
attrAsts: TemplateAst[]): string[] {
var attrs = keyValueArrayToMap(visitAndReturnContext(this, attrAsts, []));
directives.forEach(directiveMeta => {
StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => {
var prevValue = attrs[name];
attrs[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
});
});
return mapToKeyValueArray(attrs);
}
visitNgContent(ast: NgContentAst, context: any): any {
this.transitiveNgContentCount++;
this.result.push(this.commandFactory.createNgContent(ast.index, ast.ngContentIndex));
return null;
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
this.embeddedTemplateIndex++;
var childVisitor = new CommandBuilderVisitor(this.commandFactory, this.embeddedTemplateIndex);
templateVisitAll(childVisitor, ast.children);
var isMerged = childVisitor.transitiveNgContentCount > 0;
var variableNameAndValues = [];
ast.vars.forEach((varAst) => {
variableNameAndValues.push(varAst.name);
variableNameAndValues.push(varAst.value.length > 0 ? varAst.value : IMPLICIT_TEMPLATE_VAR);
});
var directives = [];
ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => {
directiveAst.visit(this, new DirectiveContext(index, [], [], directives));
});
this.result.push(this.commandFactory.createEmbeddedTemplate(
this.embeddedTemplateIndex, this._readAttrNameAndValues(directives, ast.attrs),
variableNameAndValues, directives, isMerged, ast.ngContentIndex, childVisitor.result));
this.transitiveNgContentCount += childVisitor.transitiveNgContentCount;
this.embeddedTemplateIndex = childVisitor.embeddedTemplateIndex;
return null;
}
visitElement(ast: ElementAst, context: any): any {
var component = ast.getComponent();
var eventTargetAndNames = visitAndReturnContext(this, ast.outputs, []);
var variableNameAndValues = [];
if (isBlank(component)) {
ast.exportAsVars.forEach((varAst) => {
variableNameAndValues.push(varAst.name);
variableNameAndValues.push(null);
});
}
var directives = [];
ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => {
directiveAst.visit(this, new DirectiveContext(index, eventTargetAndNames,
variableNameAndValues, directives));
});
eventTargetAndNames = removeKeyValueArrayDuplicates(eventTargetAndNames);
var attrNameAndValues = this._readAttrNameAndValues(directives, ast.attrs);
if (isPresent(component)) {
this.result.push(this.commandFactory.createBeginComponent(
ast.name, attrNameAndValues, eventTargetAndNames, variableNameAndValues, directives,
component.template.encapsulation, ast.ngContentIndex));
templateVisitAll(this, ast.children);
this.result.push(this.commandFactory.createEndComponent());
} else {
this.result.push(this.commandFactory.createBeginElement(
ast.name, attrNameAndValues, eventTargetAndNames, variableNameAndValues, directives,
ast.isBound(), ast.ngContentIndex));
templateVisitAll(this, ast.children);
this.result.push(this.commandFactory.createEndElement());
}
return null;
}
visitVariable(ast: VariableAst, ctx: any): any { return null; }
visitAttr(ast: AttrAst, attrNameAndValues: string[]): any {
attrNameAndValues.push(ast.name);
attrNameAndValues.push(ast.value);
return null;
}
visitBoundText(ast: BoundTextAst, context: any): any {
this.result.push(this.commandFactory.createText(null, true, ast.ngContentIndex));
return null;
}
visitText(ast: TextAst, context: any): any {
this.result.push(this.commandFactory.createText(ast.value, false, ast.ngContentIndex));
return null;
}
visitDirective(ast: DirectiveAst, ctx: DirectiveContext): any {
ctx.targetDirectives.push(ast.directive);
templateVisitAll(this, ast.hostEvents, ctx.eventTargetAndNames);
ast.exportAsVars.forEach(varAst => {
ctx.targetVariableNameAndValues.push(varAst.name);
ctx.targetVariableNameAndValues.push(ctx.index);
});
return null;
}
visitEvent(ast: BoundEventAst, eventTargetAndNames: string[]): any {
eventTargetAndNames.push(ast.target);
eventTargetAndNames.push(ast.name);
return null;
}
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
}
function removeKeyValueArrayDuplicates(keyValueArray: string[]): string[] {
var knownPairs = new Set();
var resultKeyValueArray = [];
for (var i = 0; i < keyValueArray.length; i += 2) {
var key = keyValueArray[i];
var value = keyValueArray[i + 1];
var pairId = `${key}:${value}`;
if (!SetWrapper.has(knownPairs, pairId)) {
resultKeyValueArray.push(key);
resultKeyValueArray.push(value);
knownPairs.add(pairId);
}
}
return resultKeyValueArray;
}
function keyValueArrayToMap(keyValueArr: string[]): {[key: string]: string} {
var data: {[key: string]: string} = {};
for (var i = 0; i < keyValueArr.length; i += 2) {
data[keyValueArr[i]] = keyValueArr[i + 1];
}
return data;
}
function mapToKeyValueArray(data: {[key: string]: string}): string[] {
var entryArray = [];
StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); });
// We need to sort to get a defined output order
// for tests and for caching generated artifacts...
ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
var keyValueArray = [];
entryArray.forEach((entry) => {
keyValueArray.push(entry[0]);
keyValueArray.push(entry[1]);
});
return keyValueArray;
}
function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string {
if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) {
return `${attrValue1} ${attrValue2}`;
} else {
return attrValue2;
}
}
class DirectiveContext {
constructor(public index: number, public eventTargetAndNames: string[],
public targetVariableNameAndValues: any[],
public targetDirectives: CompileDirectiveMetadata[]) {}
}
class Expression {
constructor(public value: string) {}
}
function escapeValue(value: any): string {
if (value instanceof Expression) {
return value.value;
} else if (isString(value)) {
return escapeSingleQuoteString(value);
} else if (isBlank(value)) {
return 'null';
} else {
return `${value}`;
}
}
function codeGenArray(data: any[]): string {
var base = `[${data.map(escapeValue).join(',')}]`;
return IS_DART ? `const ${base}` : base;
}
function codeGenDirectivesArray(directives: CompileDirectiveMetadata[]): string {
var expressions = directives.map(
directiveType => `${moduleRef(directiveType.type.moduleUrl)}${directiveType.type.name}`);
var base = `[${expressions.join(',')}]`;
return IS_DART ? `const ${base}` : base;
}
function codeGenViewEncapsulation(value: ViewEncapsulation): string {
if (IS_DART) {
return `${TEMPLATE_COMMANDS_MODULE_REF}${value}`;
} else {
return `${value}`;
}
}

View File

@ -17,7 +17,8 @@ import {TemplateNormalizer} from 'angular2/src/compiler/template_normalizer';
import {RuntimeMetadataResolver} from 'angular2/src/compiler/runtime_metadata';
import {ChangeDetectionCompiler} from 'angular2/src/compiler/change_detector_compiler';
import {StyleCompiler} from 'angular2/src/compiler/style_compiler';
import {CommandCompiler} from 'angular2/src/compiler/command_compiler';
import {ViewCompiler} from 'angular2/src/compiler/view_compiler';
import {ProtoViewCompiler} from 'angular2/src/compiler/proto_view_compiler';
import {TemplateCompiler} from 'angular2/src/compiler/template_compiler';
import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection';
import {Compiler} from 'angular2/src/core/linker/compiler';
@ -44,7 +45,8 @@ export const COMPILER_PROVIDERS: Array<Type | Provider | any[]> = CONST_EXPR([
RuntimeMetadataResolver,
DEFAULT_PACKAGE_URL_PROVIDER,
StyleCompiler,
CommandCompiler,
ProtoViewCompiler,
ViewCompiler,
ChangeDetectionCompiler,
new Provider(ChangeDetectorGenConfig, {useFactory: _createChangeDetectorGenConfig, deps: []}),
TemplateCompiler,

View File

@ -7,6 +7,7 @@ import {
RegExpWrapper,
StringWrapper
} from 'angular2/src/facade/lang';
import {unimplemented} from 'angular2/src/facade/exceptions';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {
ChangeDetectionStrategy,
@ -21,6 +22,16 @@ import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/linker/i
// group 2: "event" from "(event)"
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;
export abstract class CompileMetadataWithType {
static fromJson(data: {[key: string]: any}): CompileMetadataWithType {
return _COMPILE_METADATA_FROM_JSON[data['class']](data);
}
abstract toJson(): {[key: string]: any};
get type(): CompileTypeMetadata { return unimplemented(); }
}
/**
* Metadata regarding compilation of a type.
*/
@ -107,7 +118,7 @@ export class CompileTemplateMetadata {
/**
* Metadata regarding compilation of a directive.
*/
export class CompileDirectiveMetadata {
export class CompileDirectiveMetadata implements CompileMetadataWithType {
static create({type, isComponent, dynamicLoadable, selector, exportAs, changeDetection, inputs,
outputs, host, lifecycleHooks, template}: {
type?: CompileTypeMetadata,
@ -241,6 +252,7 @@ export class CompileDirectiveMetadata {
toJson(): {[key: string]: any} {
return {
'class': 'Directive',
'isComponent': this.isComponent,
'dynamicLoadable': this.dynamicLoadable,
'selector': this.selector,
@ -284,3 +296,38 @@ export function createHostComponentMeta(componentType: CompileTypeMetadata,
selector: '*'
});
}
export class CompilePipeMetadata implements CompileMetadataWithType {
type: CompileTypeMetadata;
name: string;
pure: boolean;
constructor({type, name,
pure}: {type?: CompileTypeMetadata, name?: string, pure?: boolean} = {}) {
this.type = type;
this.name = name;
this.pure = normalizeBool(pure);
}
static fromJson(data: {[key: string]: any}): CompilePipeMetadata {
return new CompilePipeMetadata({
type: isPresent(data['type']) ? CompileTypeMetadata.fromJson(data['type']) : data['type'],
name: data['name'],
pure: data['pure']
});
}
toJson(): {[key: string]: any} {
return {
'class': 'Pipe',
'type': isPresent(this.type) ? this.type.toJson() : null,
'name': this.name,
'pure': this.pure
};
}
}
var _COMPILE_METADATA_FROM_JSON = {
'Directive': CompileDirectiveMetadata.fromJson,
'Pipe': CompilePipeMetadata.fromJson
};

View File

@ -0,0 +1,397 @@
import {
isPresent,
isBlank,
Type,
isString,
StringWrapper,
IS_DART,
CONST_EXPR
} from 'angular2/src/facade/lang';
import {
SetWrapper,
StringMapWrapper,
ListWrapper,
MapWrapper
} from 'angular2/src/facade/collection';
import {
TemplateAst,
TemplateAstVisitor,
NgContentAst,
EmbeddedTemplateAst,
ElementAst,
VariableAst,
BoundEventAst,
BoundElementPropertyAst,
AttrAst,
BoundTextAst,
TextAst,
DirectiveAst,
BoundDirectivePropertyAst,
templateVisitAll
} from './template_ast';
import {
CompileTypeMetadata,
CompileDirectiveMetadata,
CompilePipeMetadata
} from './directive_metadata';
import {SourceExpressions, SourceExpression, moduleRef} from './source_module';
import {AppProtoView, AppView} from 'angular2/src/core/linker/view';
import {ViewType} from 'angular2/src/core/linker/view_type';
import {AppProtoElement, AppElement} from 'angular2/src/core/linker/element';
import {ResolvedMetadataCache} from 'angular2/src/core/linker/resolved_metadata_cache';
import {
escapeSingleQuoteString,
codeGenConstConstructorCall,
codeGenValueFn,
codeGenFnHeader,
MODULE_SUFFIX,
codeGenStringMap,
Expression,
Statement
} from './util';
import {Injectable} from 'angular2/src/core/di';
export const PROTO_VIEW_JIT_IMPORTS = CONST_EXPR(
{'AppProtoView': AppProtoView, 'AppProtoElement': AppProtoElement, 'ViewType': ViewType});
// TODO: have a single file that reexports everything needed for
// codegen explicitly
// - helps understanding what codegen works against
// - less imports in codegen code
export var APP_VIEW_MODULE_REF = moduleRef('package:angular2/src/core/linker/view' + MODULE_SUFFIX);
export var VIEW_TYPE_MODULE_REF =
moduleRef('package:angular2/src/core/linker/view_type' + MODULE_SUFFIX);
export var APP_EL_MODULE_REF =
moduleRef('package:angular2/src/core/linker/element' + MODULE_SUFFIX);
export var METADATA_MODULE_REF =
moduleRef('package:angular2/src/core/metadata/view' + MODULE_SUFFIX);
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
const CLASS_ATTR = 'class';
const STYLE_ATTR = 'style';
@Injectable()
export class ProtoViewCompiler {
constructor() {}
compileProtoViewRuntime(metadataCache: ResolvedMetadataCache, component: CompileDirectiveMetadata,
template: TemplateAst[], pipes: CompilePipeMetadata[]):
CompileProtoViews<AppProtoView, AppProtoElement, any> {
var protoViewFactory = new RuntimeProtoViewFactory(metadataCache, component, pipes);
var allProtoViews = [];
protoViewFactory.createCompileProtoView(template, [], [], allProtoViews);
return new CompileProtoViews<AppProtoView, AppProtoElement, any>([], allProtoViews);
}
compileProtoViewCodeGen(resolvedMetadataCacheExpr: Expression,
component: CompileDirectiveMetadata, template: TemplateAst[],
pipes: CompilePipeMetadata[]):
CompileProtoViews<Expression, Expression, string> {
var protoViewFactory = new CodeGenProtoViewFactory(resolvedMetadataCacheExpr, component, pipes);
var allProtoViews = [];
var allStatements = [];
protoViewFactory.createCompileProtoView(template, [], allStatements, allProtoViews);
return new CompileProtoViews<Expression, Expression, string>(
allStatements.map(stmt => stmt.statement), allProtoViews);
}
}
export class CompileProtoViews<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT> {
constructor(public declarations: STATEMENT[],
public protoViews: CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL>[]) {}
}
export class CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL> {
constructor(public embeddedTemplateIndex: number,
public protoElements: CompileProtoElement<APP_PROTO_EL>[],
public protoView: APP_PROTO_VIEW) {}
}
export class CompileProtoElement<APP_PROTO_EL> {
constructor(public boundElementIndex, public attrNameAndValues: string[][],
public variableNameAndValues: string[][], public renderEvents: BoundEventAst[],
public directives: CompileDirectiveMetadata[], public embeddedTemplateIndex: number,
public appProtoEl: APP_PROTO_EL) {}
}
function visitAndReturnContext(visitor: TemplateAstVisitor, asts: TemplateAst[],
context: any): any {
templateVisitAll(visitor, asts, context);
return context;
}
abstract class ProtoViewFactory<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT> {
constructor(public component: CompileDirectiveMetadata) {}
abstract createAppProtoView(embeddedTemplateIndex: number, viewType: ViewType,
templateVariableBindings: string[][],
targetStatements: STATEMENT[]): APP_PROTO_VIEW;
abstract createAppProtoElement(boundElementIndex: number, attrNameAndValues: string[][],
variableNameAndValues: string[][],
directives: CompileDirectiveMetadata[],
targetStatements: STATEMENT[]): APP_PROTO_EL;
createCompileProtoView(template: TemplateAst[], templateVariableBindings: string[][],
targetStatements: STATEMENT[],
targetProtoViews: CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL>[]):
CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL> {
var embeddedTemplateIndex = targetProtoViews.length;
// Note: targetProtoViews needs to be in depth first order.
// So we "reserve" a space here that we fill after the recursion is done
targetProtoViews.push(null);
var builder = new ProtoViewBuilderVisitor<APP_PROTO_VIEW, APP_PROTO_EL, any>(
this, targetStatements, targetProtoViews);
templateVisitAll(builder, template);
var viewType = getViewType(this.component, embeddedTemplateIndex);
var appProtoView = this.createAppProtoView(embeddedTemplateIndex, viewType,
templateVariableBindings, targetStatements);
var cpv = new CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL>(
embeddedTemplateIndex, builder.protoElements, appProtoView);
targetProtoViews[embeddedTemplateIndex] = cpv;
return cpv;
}
}
class CodeGenProtoViewFactory extends ProtoViewFactory<Expression, Expression, Statement> {
private _nextVarId: number = 0;
constructor(public resolvedMetadataCacheExpr: Expression, component: CompileDirectiveMetadata,
public pipes: CompilePipeMetadata[]) {
super(component);
}
private _nextProtoViewVar(embeddedTemplateIndex: number): string {
return `appProtoView${this._nextVarId++}_${this.component.type.name}${embeddedTemplateIndex}`;
}
createAppProtoView(embeddedTemplateIndex: number, viewType: ViewType,
templateVariableBindings: string[][],
targetStatements: Statement[]): Expression {
var protoViewVarName = this._nextProtoViewVar(embeddedTemplateIndex);
var viewTypeExpr = codeGenViewType(viewType);
var pipesExpr = embeddedTemplateIndex === 0 ?
codeGenTypesArray(this.pipes.map(pipeMeta => pipeMeta.type)) :
null;
var statement =
`var ${protoViewVarName} = ${APP_VIEW_MODULE_REF}AppProtoView.create(${this.resolvedMetadataCacheExpr.expression}, ${viewTypeExpr}, ${pipesExpr}, ${codeGenStringMap(templateVariableBindings)});`;
targetStatements.push(new Statement(statement));
return new Expression(protoViewVarName);
}
createAppProtoElement(boundElementIndex: number, attrNameAndValues: string[][],
variableNameAndValues: string[][], directives: CompileDirectiveMetadata[],
targetStatements: Statement[]): Expression {
var varName = `appProtoEl${this._nextVarId++}_${this.component.type.name}`;
var value = `${APP_EL_MODULE_REF}AppProtoElement.create(
${this.resolvedMetadataCacheExpr.expression},
${boundElementIndex},
${codeGenStringMap(attrNameAndValues)},
${codeGenDirectivesArray(directives)},
${codeGenStringMap(variableNameAndValues)}
)`;
var statement = `var ${varName} = ${value};`;
targetStatements.push(new Statement(statement));
return new Expression(varName);
}
}
class RuntimeProtoViewFactory extends ProtoViewFactory<AppProtoView, AppProtoElement, any> {
constructor(public metadataCache: ResolvedMetadataCache, component: CompileDirectiveMetadata,
public pipes: CompilePipeMetadata[]) {
super(component);
}
createAppProtoView(embeddedTemplateIndex: number, viewType: ViewType,
templateVariableBindings: string[][], targetStatements: any[]): AppProtoView {
var pipes =
embeddedTemplateIndex === 0 ? this.pipes.map(pipeMeta => pipeMeta.type.runtime) : [];
var templateVars = keyValueArrayToStringMap(templateVariableBindings);
return AppProtoView.create(this.metadataCache, viewType, pipes, templateVars);
}
createAppProtoElement(boundElementIndex: number, attrNameAndValues: string[][],
variableNameAndValues: string[][], directives: CompileDirectiveMetadata[],
targetStatements: any[]): AppProtoElement {
var attrs = keyValueArrayToStringMap(attrNameAndValues);
return AppProtoElement.create(this.metadataCache, boundElementIndex, attrs,
directives.map(dirMeta => dirMeta.type.runtime),
keyValueArrayToStringMap(variableNameAndValues));
}
}
class ProtoViewBuilderVisitor<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT> implements
TemplateAstVisitor {
protoElements: CompileProtoElement<APP_PROTO_EL>[] = [];
boundElementCount: number = 0;
constructor(public factory: ProtoViewFactory<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT>,
public allStatements: STATEMENT[],
public allProtoViews: CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL>[]) {}
private _readAttrNameAndValues(directives: CompileDirectiveMetadata[],
attrAsts: TemplateAst[]): string[][] {
var attrs = visitAndReturnContext(this, attrAsts, {});
directives.forEach(directiveMeta => {
StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => {
var prevValue = attrs[name];
attrs[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
});
});
return mapToKeyValueArray(attrs);
}
visitBoundText(ast: BoundTextAst, context: any): any { return null; }
visitText(ast: TextAst, context: any): any { return null; }
visitNgContent(ast: NgContentAst, context: any): any { return null; }
visitElement(ast: ElementAst, context: any): any {
var boundElementIndex = null;
if (ast.isBound()) {
boundElementIndex = this.boundElementCount++;
}
var component = ast.getComponent();
var variableNameAndValues: string[][] = [];
if (isBlank(component)) {
ast.exportAsVars.forEach((varAst) => { variableNameAndValues.push([varAst.name, null]); });
}
var directives = [];
var renderEvents: Map<string, BoundEventAst> =
visitAndReturnContext(this, ast.outputs, new Map<string, BoundEventAst>());
ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => {
directiveAst.visit(this, new DirectiveContext(index, boundElementIndex, renderEvents,
variableNameAndValues, directives));
});
var renderEventArray = [];
renderEvents.forEach((eventAst, _) => renderEventArray.push(eventAst));
var attrNameAndValues = this._readAttrNameAndValues(directives, ast.attrs);
this._addProtoElement(ast.isBound(), boundElementIndex, attrNameAndValues,
variableNameAndValues, renderEventArray, directives, null);
templateVisitAll(this, ast.children);
return null;
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
var boundElementIndex = this.boundElementCount++;
var directives: CompileDirectiveMetadata[] = [];
ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => {
directiveAst.visit(
this, new DirectiveContext(index, boundElementIndex, new Map<string, BoundEventAst>(), [],
directives));
});
var attrNameAndValues = this._readAttrNameAndValues(directives, ast.attrs);
var templateVariableBindings = ast.vars.map(
varAst => [varAst.value.length > 0 ? varAst.value : IMPLICIT_TEMPLATE_VAR, varAst.name]);
var nestedProtoView = this.factory.createCompileProtoView(
ast.children, templateVariableBindings, this.allStatements, this.allProtoViews);
this._addProtoElement(true, boundElementIndex, attrNameAndValues, [], [], directives,
nestedProtoView.embeddedTemplateIndex);
return null;
}
private _addProtoElement(isBound: boolean, boundElementIndex, attrNameAndValues: string[][],
variableNameAndValues: string[][], renderEvents: BoundEventAst[],
directives: CompileDirectiveMetadata[], embeddedTemplateIndex: number) {
var appProtoEl = null;
if (isBound) {
appProtoEl =
this.factory.createAppProtoElement(boundElementIndex, attrNameAndValues,
variableNameAndValues, directives, this.allStatements);
}
var compileProtoEl = new CompileProtoElement<APP_PROTO_EL>(
boundElementIndex, attrNameAndValues, variableNameAndValues, renderEvents, directives,
embeddedTemplateIndex, appProtoEl);
this.protoElements.push(compileProtoEl);
}
visitVariable(ast: VariableAst, ctx: any): any { return null; }
visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any {
attrNameAndValues[ast.name] = ast.value;
return null;
}
visitDirective(ast: DirectiveAst, ctx: DirectiveContext): any {
ctx.targetDirectives.push(ast.directive);
templateVisitAll(this, ast.hostEvents, ctx.hostEventTargetAndNames);
ast.exportAsVars.forEach(
varAst => { ctx.targetVariableNameAndValues.push([varAst.name, ctx.index]); });
return null;
}
visitEvent(ast: BoundEventAst, eventTargetAndNames: Map<string, BoundEventAst>): any {
eventTargetAndNames.set(ast.fullName, ast);
return null;
}
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
}
function mapToKeyValueArray(data: {[key: string]: string}): string[][] {
var entryArray = [];
StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); });
// We need to sort to get a defined output order
// for tests and for caching generated artifacts...
ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
var keyValueArray = [];
entryArray.forEach((entry) => { keyValueArray.push([entry[0], entry[1]]); });
return keyValueArray;
}
function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string {
if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) {
return `${attrValue1} ${attrValue2}`;
} else {
return attrValue2;
}
}
class DirectiveContext {
constructor(public index: number, public boundElementIndex: number,
public hostEventTargetAndNames: Map<string, BoundEventAst>,
public targetVariableNameAndValues: any[][],
public targetDirectives: CompileDirectiveMetadata[]) {}
}
function keyValueArrayToStringMap(keyValueArray: any[][]): {[key: string]: any} {
var stringMap: {[key: string]: string} = {};
for (var i = 0; i < keyValueArray.length; i++) {
var entry = keyValueArray[i];
stringMap[entry[0]] = entry[1];
}
return stringMap;
}
function codeGenDirectivesArray(directives: CompileDirectiveMetadata[]): string {
var expressions = directives.map(directiveType => typeRef(directiveType.type));
return `[${expressions.join(',')}]`;
}
function codeGenTypesArray(types: CompileTypeMetadata[]): string {
var expressions = types.map(typeRef);
return `[${expressions.join(',')}]`;
}
function codeGenViewType(value: ViewType): string {
if (IS_DART) {
return `${VIEW_TYPE_MODULE_REF}${value}`;
} else {
return `${value}`;
}
}
function typeRef(type: CompileTypeMetadata): string {
return `${moduleRef(type.moduleUrl)}${type.name}`;
}
function getViewType(component: CompileDirectiveMetadata, embeddedTemplateIndex: number): ViewType {
if (embeddedTemplateIndex > 0) {
return ViewType.EMBEDDED;
} else if (component.type.isHost) {
return ViewType.HOST;
} else {
return ViewType.COMPONENT;
}
}

View File

@ -1,23 +1,23 @@
import {Compiler, Compiler_, internalCreateProtoView} from 'angular2/src/core/linker/compiler';
import {ProtoViewRef} from 'angular2/src/core/linker/view_ref';
import {ProtoViewFactory} from 'angular2/src/core/linker/proto_view_factory';
import {Compiler, Compiler_} from 'angular2/src/core/linker/compiler';
import {HostViewFactoryRef, HostViewFactoryRef_} from 'angular2/src/core/linker/view_ref';
import {TemplateCompiler} from './template_compiler';
import {Injectable} from 'angular2/src/core/di';
import {Type} from 'angular2/src/facade/lang';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
export abstract class RuntimeCompiler extends Compiler {}
export abstract class RuntimeCompiler extends Compiler {
abstract compileInHost(componentType: Type): Promise<HostViewFactoryRef>;
abstract clearCache();
}
@Injectable()
export class RuntimeCompiler_ extends Compiler_ implements RuntimeCompiler {
constructor(_protoViewFactory: ProtoViewFactory, private _templateCompiler: TemplateCompiler) {
super(_protoViewFactory);
}
constructor(private _templateCompiler: TemplateCompiler) { super(); }
compileInHost(componentType: Type): Promise<ProtoViewRef> {
compileInHost(componentType: Type): Promise<HostViewFactoryRef_> {
return this._templateCompiler.compileHostComponentRuntime(componentType)
.then(compiledHostTemplate => internalCreateProtoView(this, compiledHostTemplate));
.then(hostViewFactory => new HostViewFactoryRef_(hostViewFactory));
}
clearCache() {

View File

@ -11,25 +11,29 @@ import {BaseException} from 'angular2/src/facade/exceptions';
import * as cpl from './directive_metadata';
import * as md from 'angular2/src/core/metadata/directives';
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
import {PipeResolver} from 'angular2/src/core/linker/pipe_resolver';
import {ViewResolver} from 'angular2/src/core/linker/view_resolver';
import {ViewMetadata} from 'angular2/src/core/metadata/view';
import {hasLifecycleHook} from 'angular2/src/core/linker/directive_lifecycle_reflector';
import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/linker/interfaces';
import {reflector} from 'angular2/src/core/reflection/reflection';
import {Injectable, Inject, Optional} from 'angular2/src/core/di';
import {PLATFORM_DIRECTIVES} from 'angular2/src/core/platform_directives_and_pipes';
import {PLATFORM_DIRECTIVES, PLATFORM_PIPES} from 'angular2/src/core/platform_directives_and_pipes';
import {MODULE_SUFFIX} from './util';
import {getUrlScheme} from 'angular2/src/compiler/url_resolver';
@Injectable()
export class RuntimeMetadataResolver {
private _cache = new Map<Type, cpl.CompileDirectiveMetadata>();
private _directiveCache = new Map<Type, cpl.CompileDirectiveMetadata>();
private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>();
constructor(private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver,
@Optional() @Inject(PLATFORM_DIRECTIVES) private _platformDirectives: Type[]) {}
constructor(private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver,
private _viewResolver: ViewResolver,
@Optional() @Inject(PLATFORM_DIRECTIVES) private _platformDirectives: Type[],
@Optional() @Inject(PLATFORM_PIPES) private _platformPipes: Type[]) {}
getMetadata(directiveType: Type): cpl.CompileDirectiveMetadata {
var meta = this._cache.get(directiveType);
getDirectiveMetadata(directiveType: Type): cpl.CompileDirectiveMetadata {
var meta = this._directiveCache.get(directiveType);
if (isBlank(meta)) {
var dirMeta = this._directiveResolver.resolve(directiveType);
var moduleUrl = null;
@ -63,7 +67,23 @@ export class RuntimeMetadataResolver {
host: dirMeta.host,
lifecycleHooks: LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, directiveType))
});
this._cache.set(directiveType, meta);
this._directiveCache.set(directiveType, meta);
}
return meta;
}
getPipeMetadata(pipeType: Type): cpl.CompilePipeMetadata {
var meta = this._pipeCache.get(pipeType);
if (isBlank(meta)) {
var pipeMeta = this._pipeResolver.resolve(pipeType);
var moduleUrl = reflector.importUri(pipeType);
meta = new cpl.CompilePipeMetadata({
type: new cpl.CompileTypeMetadata(
{name: stringify(pipeType), moduleUrl: moduleUrl, runtime: pipeType}),
name: pipeMeta.name,
pure: pipeMeta.pure
});
this._pipeCache.set(pipeType, meta);
}
return meta;
}
@ -72,13 +92,25 @@ export class RuntimeMetadataResolver {
var view = this._viewResolver.resolve(component);
var directives = flattenDirectives(view, this._platformDirectives);
for (var i = 0; i < directives.length; i++) {
if (!isValidDirective(directives[i])) {
if (!isValidType(directives[i])) {
throw new BaseException(
`Unexpected directive value '${stringify(directives[i])}' on the View of component '${stringify(component)}'`);
}
}
return directives.map(type => this.getMetadata(type));
return directives.map(type => this.getDirectiveMetadata(type));
}
getViewPipesMetadata(component: Type): cpl.CompilePipeMetadata[] {
var view = this._viewResolver.resolve(component);
var pipes = flattenPipes(view, this._platformPipes);
for (var i = 0; i < pipes.length; i++) {
if (!isValidType(pipes[i])) {
throw new BaseException(
`Unexpected piped value '${stringify(pipes[i])}' on the View of component '${stringify(component)}'`);
}
}
return pipes.map(type => this.getPipeMetadata(type));
}
}
@ -93,6 +125,17 @@ function flattenDirectives(view: ViewMetadata, platformDirectives: any[]): Type[
return directives;
}
function flattenPipes(view: ViewMetadata, platformPipes: any[]): Type[] {
let pipes = [];
if (isPresent(platformPipes)) {
flattenArray(platformPipes, pipes);
}
if (isPresent(view.pipes)) {
flattenArray(view.pipes, pipes);
}
return pipes;
}
function flattenArray(tree: any[], out: Array<Type | any[]>): void {
for (var i = 0; i < tree.length; i++) {
var item = resolveForwardRef(tree[i]);
@ -104,7 +147,7 @@ function flattenArray(tree: any[], out: Array<Type | any[]>): void {
}
}
function isValidDirective(value: Type): boolean {
function isValidType(value: Type): boolean {
return isPresent(value) && (value instanceof Type);
}

View File

@ -10,6 +10,10 @@ export function moduleRef(moduleUrl): string {
* Represents generated source code with module references. Internal to the Angular compiler.
*/
export class SourceModule {
static getSourceWithoutImports(sourceWithModuleRefs: string): string {
return StringWrapper.replaceAllMapped(sourceWithModuleRefs, MODULE_REGEXP, (match) => '');
}
constructor(public moduleUrl: string, public sourceWithModuleRefs: string) {}
getSourceWithImports(): SourceWithImports {

View File

@ -14,7 +14,10 @@ import {
MODULE_SUFFIX
} from './util';
import {Injectable} from 'angular2/src/core/di';
import {COMPONENT_VARIABLE, HOST_ATTR, CONTENT_ATTR} from 'angular2/src/core/render/view_factory';
const COMPONENT_VARIABLE = '%COMP%';
const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
@Injectable()
export class StyleCompiler {

View File

@ -1,37 +1,74 @@
import {IS_DART, Type, Json, isBlank, stringify} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {ListWrapper, SetWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {
CompiledComponentTemplate,
TemplateCmd,
CompiledHostTemplate,
BeginComponentCmd
} from 'angular2/src/core/linker/template_commands';
IS_DART,
Type,
Json,
isBlank,
isPresent,
stringify,
evalExpression
} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {
ListWrapper,
SetWrapper,
MapWrapper,
StringMapWrapper
} from 'angular2/src/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {
createHostComponentMeta,
CompileDirectiveMetadata,
CompileTypeMetadata,
CompileTemplateMetadata
CompileTemplateMetadata,
CompilePipeMetadata,
CompileMetadataWithType
} from './directive_metadata';
import {TemplateAst} from './template_ast';
import {
TemplateAst,
TemplateAstVisitor,
NgContentAst,
EmbeddedTemplateAst,
ElementAst,
VariableAst,
BoundEventAst,
BoundElementPropertyAst,
AttrAst,
BoundTextAst,
TextAst,
DirectiveAst,
BoundDirectivePropertyAst,
templateVisitAll
} from './template_ast';
import {Injectable} from 'angular2/src/core/di';
import {SourceModule, moduleRef} from './source_module';
import {ChangeDetectionCompiler} from './change_detector_compiler';
import {SourceModule, moduleRef, SourceExpression} from './source_module';
import {ChangeDetectionCompiler, CHANGE_DETECTION_JIT_IMPORTS} from './change_detector_compiler';
import {StyleCompiler} from './style_compiler';
import {CommandCompiler} from './command_compiler';
import {TemplateParser} from './template_parser';
import {ViewCompiler, VIEW_JIT_IMPORTS} from './view_compiler';
import {
ProtoViewCompiler,
APP_VIEW_MODULE_REF,
CompileProtoView,
PROTO_VIEW_JIT_IMPORTS
} from './proto_view_compiler';
import {TemplateParser, PipeCollector} from './template_parser';
import {TemplateNormalizer} from './template_normalizer';
import {RuntimeMetadataResolver} from './runtime_metadata';
import {HostViewFactory} from 'angular2/src/core/linker/view';
import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection';
import {ResolvedMetadataCache} from 'angular2/src/core/linker/resolved_metadata_cache';
import {TEMPLATE_COMMANDS_MODULE_REF} from './command_compiler';
import {
codeGenExportVariable,
escapeSingleQuoteString,
codeGenValueFn,
MODULE_SUFFIX
MODULE_SUFFIX,
addAll,
Expression
} from './util';
export var METADATA_CACHE_MODULE_REF =
moduleRef('package:angular2/src/core/linker/resolved_metadata_cache' + MODULE_SUFFIX);
/**
* An internal module of the Angular compiler that begins with component types,
* extracts templates, and eventually produces a compiled version of the component
@ -40,15 +77,16 @@ import {
@Injectable()
export class TemplateCompiler {
private _hostCacheKeys = new Map<Type, any>();
private _compiledTemplateCache = new Map<any, CompiledComponentTemplate>();
private _compiledTemplateDone = new Map<any, Promise<CompiledComponentTemplate>>();
private _nextTemplateId: number = 0;
private _compiledTemplateCache = new Map<any, CompiledTemplate>();
private _compiledTemplateDone = new Map<any, Promise<CompiledTemplate>>();
constructor(private _runtimeMetadataResolver: RuntimeMetadataResolver,
private _templateNormalizer: TemplateNormalizer,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _commandCompiler: CommandCompiler,
private _cdCompiler: ChangeDetectionCompiler) {}
private _cdCompiler: ChangeDetectionCompiler,
private _protoViewCompiler: ProtoViewCompiler, private _viewCompiler: ViewCompiler,
private _resolvedMetadataCache: ResolvedMetadataCache,
private _genConfig: ChangeDetectorGenConfig) {}
normalizeDirectiveMetadata(directive: CompileDirectiveMetadata):
Promise<CompileDirectiveMetadata> {
@ -75,99 +113,29 @@ export class TemplateCompiler {
}));
}
compileHostComponentRuntime(type: Type): Promise<CompiledHostTemplate> {
compileHostComponentRuntime(type: Type): Promise<HostViewFactory> {
var compMeta: CompileDirectiveMetadata =
this._runtimeMetadataResolver.getDirectiveMetadata(type);
var hostCacheKey = this._hostCacheKeys.get(type);
if (isBlank(hostCacheKey)) {
hostCacheKey = new Object();
this._hostCacheKeys.set(type, hostCacheKey);
var compMeta: CompileDirectiveMetadata = this._runtimeMetadataResolver.getMetadata(type);
assertComponent(compMeta);
var hostMeta: CompileDirectiveMetadata =
createHostComponentMeta(compMeta.type, compMeta.selector);
this._compileComponentRuntime(hostCacheKey, hostMeta, [compMeta], new Set());
this._compileComponentRuntime(hostCacheKey, hostMeta, [compMeta], [], new Set());
}
return this._compiledTemplateDone.get(hostCacheKey)
.then(compiledTemplate => new CompiledHostTemplate(compiledTemplate));
.then((compiledTemplate: CompiledTemplate) =>
new HostViewFactory(compMeta.selector, compiledTemplate.viewFactory));
}
clearCache() {
this._hostCacheKeys.clear();
this._styleCompiler.clearCache();
this._compiledTemplateCache.clear();
this._compiledTemplateDone.clear();
}
private _compileComponentRuntime(
cacheKey: any, compMeta: CompileDirectiveMetadata, viewDirectives: CompileDirectiveMetadata[],
compilingComponentCacheKeys: Set<any>): CompiledComponentTemplate {
let uniqViewDirectives = removeDuplicates(viewDirectives);
var compiledTemplate = this._compiledTemplateCache.get(cacheKey);
var done = this._compiledTemplateDone.get(cacheKey);
if (isBlank(compiledTemplate)) {
var styles = [];
var changeDetectorFactory;
var commands = [];
var templateId = `${stringify(compMeta.type.runtime)}Template${this._nextTemplateId++}`;
compiledTemplate = new CompiledComponentTemplate(
templateId, (dispatcher) => changeDetectorFactory(dispatcher), commands, styles);
this._compiledTemplateCache.set(cacheKey, compiledTemplate);
compilingComponentCacheKeys.add(cacheKey);
done = PromiseWrapper
.all([<any>this._styleCompiler.compileComponentRuntime(compMeta.template)].concat(
uniqViewDirectives.map(dirMeta => this.normalizeDirectiveMetadata(dirMeta))))
.then((stylesAndNormalizedViewDirMetas: any[]) => {
var childPromises = [];
var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1);
var parsedTemplate = this._templateParser.parse(
compMeta.template.template, normalizedViewDirMetas, compMeta.type.name);
var changeDetectorFactories = this._cdCompiler.compileComponentRuntime(
compMeta.type, compMeta.changeDetection, parsedTemplate);
changeDetectorFactory = changeDetectorFactories[0];
var tmpStyles: string[] = stylesAndNormalizedViewDirMetas[0];
tmpStyles.forEach(style => styles.push(style));
var tmpCommands: TemplateCmd[] = this._compileCommandsRuntime(
compMeta, parsedTemplate, changeDetectorFactories,
compilingComponentCacheKeys, childPromises);
tmpCommands.forEach(cmd => commands.push(cmd));
return PromiseWrapper.all(childPromises);
})
.then((_) => {
SetWrapper.delete(compilingComponentCacheKeys, cacheKey);
return compiledTemplate;
});
this._compiledTemplateDone.set(cacheKey, done);
}
return compiledTemplate;
}
private _compileCommandsRuntime(compMeta: CompileDirectiveMetadata, parsedTemplate: TemplateAst[],
changeDetectorFactories: Function[],
compilingComponentCacheKeys: Set<Type>,
childPromises: Promise<any>[]): TemplateCmd[] {
var cmds: TemplateCmd[] = this._commandCompiler.compileComponentRuntime(
compMeta, parsedTemplate, changeDetectorFactories,
(childComponentDir: CompileDirectiveMetadata) => {
var childCacheKey = childComponentDir.type.runtime;
var childViewDirectives: CompileDirectiveMetadata[] =
this._runtimeMetadataResolver.getViewDirectivesMetadata(
childComponentDir.type.runtime);
var childIsRecursive = SetWrapper.has(compilingComponentCacheKeys, childCacheKey);
var childTemplate = this._compileComponentRuntime(
childCacheKey, childComponentDir, childViewDirectives, compilingComponentCacheKeys);
if (!childIsRecursive) {
// Only wait for a child if it is not a cycle
childPromises.push(this._compiledTemplateDone.get(childCacheKey));
}
return () => childTemplate;
});
cmds.forEach(cmd => {
if (cmd instanceof BeginComponentCmd) {
cmd.templateGetter();
}
});
return cmds;
this._hostCacheKeys.clear();
}
compileTemplatesCodeGen(components: NormalizedComponentWithViewDirectives[]): SourceModule {
@ -175,38 +143,22 @@ export class TemplateCompiler {
throw new BaseException('No components given');
}
var declarations = [];
var templateArguments = [];
var componentMetas: CompileDirectiveMetadata[] = [];
components.forEach(componentWithDirs => {
var compMeta = <CompileDirectiveMetadata>componentWithDirs.component;
assertComponent(compMeta);
componentMetas.push(compMeta);
this._processTemplateCodeGen(compMeta, componentWithDirs.directives, declarations,
templateArguments);
this._compileComponentCodeGen(compMeta, componentWithDirs.directives, componentWithDirs.pipes,
declarations);
if (compMeta.dynamicLoadable) {
var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector);
componentMetas.push(hostMeta);
this._processTemplateCodeGen(hostMeta, [compMeta], declarations, templateArguments);
var viewFactoryExpression =
this._compileComponentCodeGen(hostMeta, [compMeta], [], declarations);
var constructionKeyword = IS_DART ? 'const' : 'new';
var compiledTemplateExpr =
`${constructionKeyword} ${APP_VIEW_MODULE_REF}HostViewFactory('${compMeta.selector}',${viewFactoryExpression})`;
var varName = codeGenHostViewFactoryName(compMeta.type);
declarations.push(`${codeGenExportVariable(varName)}${compiledTemplateExpr};`);
}
});
ListWrapper.forEachWithIndex(componentMetas, (compMeta: CompileDirectiveMetadata,
index: number) => {
var templateId = `${compMeta.type.moduleUrl}|${compMeta.type.name}`;
var constructionKeyword = IS_DART ? 'const' : 'new';
var compiledTemplateExpr =
`${constructionKeyword} ${TEMPLATE_COMMANDS_MODULE_REF}CompiledComponentTemplate('${templateId}',${(<any[]>templateArguments[index]).join(',')})`;
var variableValueExpr;
if (compMeta.type.isHost) {
variableValueExpr =
`${constructionKeyword} ${TEMPLATE_COMMANDS_MODULE_REF}CompiledHostTemplate(${compiledTemplateExpr})`;
} else {
variableValueExpr = compiledTemplateExpr;
}
var varName = templateVariableName(compMeta.type);
declarations.push(`${codeGenExportVariable(varName)}${variableValueExpr};`);
declarations.push(`${codeGenValueFn([], varName, templateGetterName(compMeta.type))};`);
});
var moduleUrl = components[0].component.type.moduleUrl;
return new SourceModule(`${templateModuleUrl(moduleUrl)}`, declarations.join('\n'));
}
@ -215,31 +167,149 @@ export class TemplateCompiler {
return this._styleCompiler.compileStylesheetCodeGen(stylesheetUrl, cssText);
}
private _processTemplateCodeGen(compMeta: CompileDirectiveMetadata,
directives: CompileDirectiveMetadata[],
targetDeclarations: string[], targetTemplateArguments: any[][]) {
let uniqueDirectives = removeDuplicates(directives);
private _compileComponentRuntime(cacheKey: any, compMeta: CompileDirectiveMetadata,
viewDirectives: CompileDirectiveMetadata[],
pipes: CompilePipeMetadata[],
compilingComponentCacheKeys: Set<any>): CompiledTemplate {
let uniqViewDirectives = <CompileDirectiveMetadata[]>removeDuplicates(viewDirectives);
let uniqViewPipes = <CompilePipeMetadata[]>removeDuplicates(pipes);
var compiledTemplate = this._compiledTemplateCache.get(cacheKey);
var done = this._compiledTemplateDone.get(cacheKey);
if (isBlank(compiledTemplate)) {
compiledTemplate = new CompiledTemplate();
this._compiledTemplateCache.set(cacheKey, compiledTemplate);
compilingComponentCacheKeys.add(cacheKey);
done = PromiseWrapper
.all([<any>this._styleCompiler.compileComponentRuntime(compMeta.template)].concat(
uniqViewDirectives.map(dirMeta => this.normalizeDirectiveMetadata(dirMeta))))
.then((stylesAndNormalizedViewDirMetas: any[]) => {
var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1);
var styles = stylesAndNormalizedViewDirMetas[0];
var parsedTemplate = this._templateParser.parse(
compMeta.template.template, normalizedViewDirMetas, uniqViewPipes,
compMeta.type.name);
var childPromises = [];
var usedDirectives = DirectiveCollector.findUsedDirectives(parsedTemplate);
usedDirectives.components.forEach(
component => this._compileNestedComponentRuntime(
component, compilingComponentCacheKeys, childPromises));
return PromiseWrapper.all(childPromises)
.then((_) => {
var filteredPipes = filterPipes(parsedTemplate, uniqViewPipes);
compiledTemplate.init(this._createViewFactoryRuntime(
compMeta, parsedTemplate, usedDirectives.directives, styles,
filteredPipes));
SetWrapper.delete(compilingComponentCacheKeys, cacheKey);
return compiledTemplate;
});
});
this._compiledTemplateDone.set(cacheKey, done);
}
return compiledTemplate;
}
private _compileNestedComponentRuntime(childComponentDir: CompileDirectiveMetadata,
compilingComponentCacheKeys: Set<Type>,
childPromises: Promise<any>[]) {
var childCacheKey = childComponentDir.type.runtime;
var childViewDirectives: CompileDirectiveMetadata[] =
this._runtimeMetadataResolver.getViewDirectivesMetadata(childComponentDir.type.runtime);
var childViewPipes: CompilePipeMetadata[] =
this._runtimeMetadataResolver.getViewPipesMetadata(childComponentDir.type.runtime);
var childIsRecursive = SetWrapper.has(compilingComponentCacheKeys, childCacheKey);
this._compileComponentRuntime(childCacheKey, childComponentDir, childViewDirectives,
childViewPipes, compilingComponentCacheKeys);
if (!childIsRecursive) {
// Only wait for a child if it is not a cycle
childPromises.push(this._compiledTemplateDone.get(childCacheKey));
}
}
private _createViewFactoryRuntime(compMeta: CompileDirectiveMetadata,
parsedTemplate: TemplateAst[],
directives: CompileDirectiveMetadata[], styles: string[],
pipes: CompilePipeMetadata[]): Function {
if (IS_DART || !this._genConfig.useJit) {
var changeDetectorFactories = this._cdCompiler.compileComponentRuntime(
compMeta.type, compMeta.changeDetection, parsedTemplate);
var protoViews = this._protoViewCompiler.compileProtoViewRuntime(
this._resolvedMetadataCache, compMeta, parsedTemplate, pipes);
return this._viewCompiler.compileComponentRuntime(
compMeta, parsedTemplate, styles, protoViews.protoViews, changeDetectorFactories,
(compMeta) => this._getNestedComponentViewFactory(compMeta));
} else {
var declarations = [];
var viewFactoryExpr = this._createViewFactoryCodeGen('resolvedMetadataCache', compMeta,
new SourceExpression([], 'styles'),
parsedTemplate, pipes, declarations);
var vars: {[key: string]: any} =
{'exports': {}, 'styles': styles, 'resolvedMetadataCache': this._resolvedMetadataCache};
directives.forEach(dirMeta => {
vars[dirMeta.type.name] = dirMeta.type.runtime;
if (dirMeta.isComponent && dirMeta.type.runtime !== compMeta.type.runtime) {
vars[`viewFactory_${dirMeta.type.name}0`] = this._getNestedComponentViewFactory(dirMeta);
}
});
pipes.forEach(pipeMeta => vars[pipeMeta.type.name] = pipeMeta.type.runtime);
var declarationsWithoutImports =
SourceModule.getSourceWithoutImports(declarations.join('\n'));
return evalExpression(
`viewFactory_${compMeta.type.name}`, viewFactoryExpr, declarationsWithoutImports,
mergeStringMaps(
[vars, CHANGE_DETECTION_JIT_IMPORTS, PROTO_VIEW_JIT_IMPORTS, VIEW_JIT_IMPORTS]));
}
}
private _getNestedComponentViewFactory(compMeta: CompileDirectiveMetadata): Function {
return this._compiledTemplateCache.get(compMeta.type.runtime).viewFactory;
}
private _compileComponentCodeGen(compMeta: CompileDirectiveMetadata,
directives: CompileDirectiveMetadata[],
pipes: CompilePipeMetadata[],
targetDeclarations: string[]): string {
let uniqueDirectives = <CompileDirectiveMetadata[]>removeDuplicates(directives);
let uniqPipes = <CompilePipeMetadata[]>removeDuplicates(pipes);
var styleExpr = this._styleCompiler.compileComponentCodeGen(compMeta.template);
var parsedTemplate = this._templateParser.parse(compMeta.template.template, uniqueDirectives,
compMeta.type.name);
uniqPipes, compMeta.type.name);
var filteredPipes = filterPipes(parsedTemplate, uniqPipes);
return this._createViewFactoryCodeGen(
`${METADATA_CACHE_MODULE_REF}CODEGEN_RESOLVED_METADATA_CACHE`, compMeta, styleExpr,
parsedTemplate, filteredPipes, targetDeclarations);
}
private _createViewFactoryCodeGen(resolvedMetadataCacheExpr: string,
compMeta: CompileDirectiveMetadata, styleExpr: SourceExpression,
parsedTemplate: TemplateAst[], pipes: CompilePipeMetadata[],
targetDeclarations: string[]): string {
var changeDetectorsExprs = this._cdCompiler.compileComponentCodeGen(
compMeta.type, compMeta.changeDetection, parsedTemplate);
var commandsExpr = this._commandCompiler.compileComponentCodeGen(
compMeta, parsedTemplate, changeDetectorsExprs.expressions,
codeGenComponentTemplateFactory);
var protoViewExprs = this._protoViewCompiler.compileProtoViewCodeGen(
new Expression(resolvedMetadataCacheExpr), compMeta, parsedTemplate, pipes);
var viewFactoryExpr = this._viewCompiler.compileComponentCodeGen(
compMeta, parsedTemplate, styleExpr, protoViewExprs.protoViews, changeDetectorsExprs,
codeGenComponentViewFactoryName);
addAll(styleExpr.declarations, targetDeclarations);
addAll(changeDetectorsExprs.declarations, targetDeclarations);
addAll(commandsExpr.declarations, targetDeclarations);
addAll(protoViewExprs.declarations, targetDeclarations);
addAll(viewFactoryExpr.declarations, targetDeclarations);
targetTemplateArguments.push(
[changeDetectorsExprs.expressions[0], commandsExpr.expression, styleExpr.expression]);
return viewFactoryExpr.expression;
}
}
export class NormalizedComponentWithViewDirectives {
constructor(public component: CompileDirectiveMetadata,
public directives: CompileDirectiveMetadata[]) {}
public directives: CompileDirectiveMetadata[], public pipes: CompilePipeMetadata[]) {}
}
class CompiledTemplate {
viewFactory: Function = null;
init(viewFactory: Function) { this.viewFactory = viewFactory; }
}
function assertComponent(meta: CompileDirectiveMetadata) {
@ -248,30 +318,28 @@ function assertComponent(meta: CompileDirectiveMetadata) {
}
}
function templateVariableName(type: CompileTypeMetadata): string {
return `${type.name}Template`;
}
function templateGetterName(type: CompileTypeMetadata): string {
return `${templateVariableName(type)}Getter`;
}
function templateModuleUrl(moduleUrl: string): string {
var urlWithoutSuffix = moduleUrl.substring(0, moduleUrl.length - MODULE_SUFFIX.length);
return `${urlWithoutSuffix}.template${MODULE_SUFFIX}`;
}
function addAll(source: any[], target: any[]) {
for (var i = 0; i < source.length; i++) {
target.push(source[i]);
}
function codeGenHostViewFactoryName(type: CompileTypeMetadata): string {
return `hostViewFactory_${type.name}`;
}
function codeGenComponentTemplateFactory(nestedCompType: CompileDirectiveMetadata): string {
return `${moduleRef(templateModuleUrl(nestedCompType.type.moduleUrl))}${templateGetterName(nestedCompType.type)}`;
function codeGenComponentViewFactoryName(nestedCompType: CompileDirectiveMetadata): string {
return `${moduleRef(templateModuleUrl(nestedCompType.type.moduleUrl))}viewFactory_${nestedCompType.type.name}0`;
}
function removeDuplicates(items: CompileDirectiveMetadata[]): CompileDirectiveMetadata[] {
function mergeStringMaps(maps: Array<{[key: string]: any}>): {[key: string]: any} {
var result = {};
maps.forEach(
(map) => { StringMapWrapper.forEach(map, (value, key) => { result[key] = value; }); });
return result;
}
function removeDuplicates(items: CompileMetadataWithType[]): CompileMetadataWithType[] {
let res = [];
items.forEach(item => {
let hasMatch =
@ -284,3 +352,100 @@ function removeDuplicates(items: CompileDirectiveMetadata[]): CompileDirectiveMe
});
return res;
}
class DirectiveCollector implements TemplateAstVisitor {
static findUsedDirectives(parsedTemplate: TemplateAst[]): DirectiveCollector {
var collector = new DirectiveCollector();
templateVisitAll(collector, parsedTemplate);
return collector;
}
directives: CompileDirectiveMetadata[] = [];
components: CompileDirectiveMetadata[] = [];
visitBoundText(ast: BoundTextAst, context: any): any { return null; }
visitText(ast: TextAst, context: any): any { return null; }
visitNgContent(ast: NgContentAst, context: any): any { return null; }
visitElement(ast: ElementAst, context: any): any {
templateVisitAll(this, ast.directives);
templateVisitAll(this, ast.children);
return null;
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
templateVisitAll(this, ast.directives);
templateVisitAll(this, ast.children);
return null;
}
visitVariable(ast: VariableAst, ctx: any): any { return null; }
visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any { return null; }
visitDirective(ast: DirectiveAst, ctx: any): any {
if (ast.directive.isComponent) {
this.components.push(ast.directive);
}
this.directives.push(ast.directive);
return null;
}
visitEvent(ast: BoundEventAst, eventTargetAndNames: Map<string, BoundEventAst>): any {
return null;
}
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
}
function filterPipes(template: TemplateAst[],
allPipes: CompilePipeMetadata[]): CompilePipeMetadata[] {
var visitor = new PipeVisitor();
templateVisitAll(visitor, template);
return allPipes.filter((pipeMeta) => SetWrapper.has(visitor.collector.pipes, pipeMeta.name));
}
class PipeVisitor implements TemplateAstVisitor {
collector: PipeCollector = new PipeCollector();
visitBoundText(ast: BoundTextAst, context: any): any {
ast.value.visit(this.collector);
return null;
}
visitText(ast: TextAst, context: any): any { return null; }
visitNgContent(ast: NgContentAst, context: any): any { return null; }
visitElement(ast: ElementAst, context: any): any {
templateVisitAll(this, ast.inputs);
templateVisitAll(this, ast.outputs);
templateVisitAll(this, ast.directives);
templateVisitAll(this, ast.children);
return null;
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
templateVisitAll(this, ast.outputs);
templateVisitAll(this, ast.directives);
templateVisitAll(this, ast.children);
return null;
}
visitVariable(ast: VariableAst, ctx: any): any { return null; }
visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any { return null; }
visitDirective(ast: DirectiveAst, ctx: any): any {
templateVisitAll(this, ast.inputs);
templateVisitAll(this, ast.hostEvents);
templateVisitAll(this, ast.hostProperties);
return null;
}
visitEvent(ast: BoundEventAst, eventTargetAndNames: Map<string, BoundEventAst>): any {
ast.handler.visit(this.collector);
return null;
}
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {
ast.value.visit(this.collector);
return null;
}
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {
ast.value.visit(this.collector);
return null;
}
}

View File

@ -5,10 +5,11 @@ import {CONST_EXPR} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {Parser, AST, ASTWithSource} from 'angular2/src/core/change_detection/change_detection';
import {TemplateBinding} from 'angular2/src/core/change_detection/parser/ast';
import {CompileDirectiveMetadata} from './directive_metadata';
import {CompileDirectiveMetadata, CompilePipeMetadata} from './directive_metadata';
import {HtmlParser} from './html_parser';
import {splitNsName} from './html_tags';
import {ParseSourceSpan, ParseError, ParseLocation} from './parse_util';
import {RecursiveAstVisitor, BindingPipe} from 'angular2/src/core/change_detection/parser/ast';
import {
@ -88,9 +89,10 @@ export class TemplateParser {
private _htmlParser: HtmlParser,
@Optional() @Inject(TEMPLATE_TRANSFORMS) public transforms: TemplateAstVisitor[]) {}
parse(template: string, directives: CompileDirectiveMetadata[],
parse(template: string, directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[],
templateUrl: string): TemplateAst[] {
var parseVisitor = new TemplateParseVisitor(directives, this._exprParser, this._schemaRegistry);
var parseVisitor =
new TemplateParseVisitor(directives, pipes, this._exprParser, this._schemaRegistry);
var htmlAstWithErrors = this._htmlParser.parse(template, templateUrl);
var result = htmlVisitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_COMPONENT);
var errors: ParseError[] = htmlAstWithErrors.errors.concat(parseVisitor.errors);
@ -111,9 +113,10 @@ class TemplateParseVisitor implements HtmlAstVisitor {
errors: TemplateParseError[] = [];
directivesIndex = new Map<CompileDirectiveMetadata, number>();
ngContentCount: number = 0;
pipesByName: Map<string, CompilePipeMetadata>;
constructor(directives: CompileDirectiveMetadata[], private _exprParser: Parser,
private _schemaRegistry: ElementSchemaRegistry) {
constructor(directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[],
private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry) {
this.selectorMatcher = new SelectorMatcher();
ListWrapper.forEachWithIndex(directives,
(directive: CompileDirectiveMetadata, index: number) => {
@ -121,6 +124,8 @@ class TemplateParseVisitor implements HtmlAstVisitor {
this.selectorMatcher.addSelectables(selector, directive);
this.directivesIndex.set(directive, index);
});
this.pipesByName = new Map<string, CompilePipeMetadata>();
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
}
private _reportError(message: string, sourceSpan: ParseSourceSpan) {
@ -130,7 +135,9 @@ class TemplateParseVisitor implements HtmlAstVisitor {
private _parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
var sourceInfo = sourceSpan.start.toString();
try {
return this._exprParser.parseInterpolation(value, sourceInfo);
var ast = this._exprParser.parseInterpolation(value, sourceInfo);
this._checkPipes(ast, sourceSpan);
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
@ -140,7 +147,9 @@ class TemplateParseVisitor implements HtmlAstVisitor {
private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
var sourceInfo = sourceSpan.start.toString();
try {
return this._exprParser.parseAction(value, sourceInfo);
var ast = this._exprParser.parseAction(value, sourceInfo);
this._checkPipes(ast, sourceSpan);
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
@ -150,7 +159,9 @@ class TemplateParseVisitor implements HtmlAstVisitor {
private _parseBinding(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
var sourceInfo = sourceSpan.start.toString();
try {
return this._exprParser.parseBinding(value, sourceInfo);
var ast = this._exprParser.parseBinding(value, sourceInfo);
this._checkPipes(ast, sourceSpan);
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
@ -160,13 +171,31 @@ class TemplateParseVisitor implements HtmlAstVisitor {
private _parseTemplateBindings(value: string, sourceSpan: ParseSourceSpan): TemplateBinding[] {
var sourceInfo = sourceSpan.start.toString();
try {
return this._exprParser.parseTemplateBindings(value, sourceInfo);
var bindings = this._exprParser.parseTemplateBindings(value, sourceInfo);
bindings.forEach((binding) => {
if (isPresent(binding.expression)) {
this._checkPipes(binding.expression, sourceSpan);
}
});
return bindings;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return [];
}
}
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
if (isPresent(ast)) {
var collector = new PipeCollector();
ast.visit(collector);
collector.pipes.forEach((pipeName) => {
if (!this.pipesByName.has(pipeName)) {
this._reportError(`The pipe '${pipeName}' could not be found`, sourceSpan);
}
});
}
}
visitText(ast: HtmlTextAst, component: Component): any {
var ngContentIndex = component.findNgContentIndex(TEXT_CSS_SELECTOR);
var expr = this._parseInterpolation(ast.value, ast.sourceSpan);
@ -714,3 +743,14 @@ function createElementCssSelector(elementName: string, matchableAttrs: string[][
var EMPTY_COMPONENT = new Component(new SelectorMatcher(), null);
var NON_BINDABLE_VISITOR = new NonBindableVisitor();
export class PipeCollector extends RecursiveAstVisitor {
pipes: Set<string> = new Set<string>();
visitPipe(ast: BindingPipe): any {
this.pipes.add(ast.name);
ast.exp.visit(this);
this.visitAll(ast.args);
return null;
}
}

View File

@ -1,4 +1,11 @@
import {IS_DART, StringWrapper, isBlank} from 'angular2/src/facade/lang';
import {
IS_DART,
StringWrapper,
isBlank,
isPresent,
isString,
isArray
} from 'angular2/src/facade/lang';
var CAMEL_CASE_REGEXP = /([A-Z])/g;
var DASH_CASE_REGEXP = /-([a-z])/g;
@ -7,6 +14,8 @@ var DOUBLE_QUOTE_ESCAPE_STRING_RE = /"|\\|\n|\r|\$/g;
export var MODULE_SUFFIX = IS_DART ? '.dart' : '.js';
export var CONST_VAR = IS_DART ? 'const' : 'var';
export function camelCaseToDashCase(input: string): string {
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP,
(m) => { return '-' + m[1].toLowerCase(); });
@ -63,12 +72,19 @@ export function codeGenConstConstructorCall(name: string): string {
export function codeGenValueFn(params: string[], value: string, fnName: string = ''): string {
if (IS_DART) {
return `${fnName}(${params.join(',')}) => ${value}`;
return `${codeGenFnHeader(params, fnName)} => ${value}`;
} else {
return `function ${fnName}(${params.join(',')}) { return ${value}; }`;
return `${codeGenFnHeader(params, fnName)} { return ${value}; }`;
}
}
export function codeGenFnHeader(params: string[], fnName: string = ''): string {
if (IS_DART) {
return `${fnName}(${params.join(',')})`;
} else {
return `function ${fnName}(${params.join(',')})`;
}
}
export function codeGenToString(expr: string): string {
if (IS_DART) {
return `'\${${expr}}'`;
@ -86,3 +102,77 @@ export function splitAtColon(input: string, defaultValues: string[]): string[] {
return defaultValues;
}
}
export class Statement {
constructor(public statement: string) {}
}
export class Expression {
constructor(public expression: string, public isArray = false) {}
}
export function escapeValue(value: any): string {
if (value instanceof Expression) {
return value.expression;
} else if (isString(value)) {
return escapeSingleQuoteString(value);
} else if (isBlank(value)) {
return 'null';
} else {
return `${value}`;
}
}
export function codeGenArray(data: any[]): string {
return `[${data.map(escapeValue).join(',')}]`;
}
export function codeGenFlatArray(values: any[]): string {
var result = '([';
var isFirstArrayEntry = true;
var concatFn = IS_DART ? '.addAll' : 'concat';
for (var i = 0; i < values.length; i++) {
var value = values[i];
if (value instanceof Expression && (<Expression>value).isArray) {
result += `]).${concatFn}(${value.expression}).${concatFn}([`;
isFirstArrayEntry = true;
} else {
if (!isFirstArrayEntry) {
result += ',';
}
isFirstArrayEntry = false;
result += escapeValue(value);
}
}
result += '])';
return result;
}
export function codeGenStringMap(keyValueArray: any[][]): string {
return `{${keyValueArray.map(codeGenKeyValue).join(',')}}`;
}
function codeGenKeyValue(keyValue: any[]): string {
return `${escapeValue(keyValue[0])}:${escapeValue(keyValue[1])}`;
}
export function addAll(source: any[], target: any[]) {
for (var i = 0; i < source.length; i++) {
target.push(source[i]);
}
}
export function flattenArray(source: any[], target: any[]): any[] {
if (isPresent(source)) {
for (var i = 0; i < source.length; i++) {
var item = source[i];
if (isArray(item)) {
flattenArray(item, target);
} else {
target.push(item);
}
}
}
return target;
}

View File

@ -0,0 +1,600 @@
import {
isPresent,
isBlank,
Type,
isString,
StringWrapper,
IS_DART,
CONST_EXPR
} from 'angular2/src/facade/lang';
import {SetWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {
TemplateAst,
TemplateAstVisitor,
NgContentAst,
EmbeddedTemplateAst,
ElementAst,
VariableAst,
BoundEventAst,
BoundElementPropertyAst,
AttrAst,
BoundTextAst,
TextAst,
DirectiveAst,
BoundDirectivePropertyAst,
templateVisitAll
} from './template_ast';
import {CompileTypeMetadata, CompileDirectiveMetadata} from './directive_metadata';
import {SourceExpressions, SourceExpression, moduleRef} from './source_module';
import {
AppProtoView,
AppView,
flattenNestedViewRenderNodes,
checkSlotCount
} from 'angular2/src/core/linker/view';
import {ViewType} from 'angular2/src/core/linker/view_type';
import {AppViewManager_} from 'angular2/src/core/linker/view_manager';
import {AppProtoElement, AppElement} from 'angular2/src/core/linker/element';
import {Renderer, ParentRenderer} from 'angular2/src/core/render/api';
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
import {
escapeSingleQuoteString,
codeGenConstConstructorCall,
codeGenValueFn,
codeGenFnHeader,
MODULE_SUFFIX,
Statement,
escapeValue,
codeGenArray,
codeGenFlatArray,
Expression,
flattenArray,
CONST_VAR
} from './util';
import {ResolvedProvider, Injectable, Injector} from 'angular2/src/core/di';
import {
APP_VIEW_MODULE_REF,
APP_EL_MODULE_REF,
METADATA_MODULE_REF,
CompileProtoView,
CompileProtoElement
} from './proto_view_compiler';
export const VIEW_JIT_IMPORTS = CONST_EXPR({
'AppView': AppView,
'AppElement': AppElement,
'flattenNestedViewRenderNodes': flattenNestedViewRenderNodes,
'checkSlotCount': checkSlotCount
});
@Injectable()
export class ViewCompiler {
constructor() {}
compileComponentRuntime(component: CompileDirectiveMetadata, template: TemplateAst[],
styles: Array<string | any[]>,
protoViews: CompileProtoView<AppProtoView, AppProtoElement>[],
changeDetectorFactories: Function[],
componentViewFactory: Function): Function {
var viewFactory = new RuntimeViewFactory(component, styles, protoViews, changeDetectorFactories,
componentViewFactory);
return viewFactory.createViewFactory(template, 0, []);
}
compileComponentCodeGen(component: CompileDirectiveMetadata, template: TemplateAst[],
styles: SourceExpression,
protoViews: CompileProtoView<Expression, Expression>[],
changeDetectorFactoryExpressions: SourceExpressions,
componentViewFactory: Function): SourceExpression {
var viewFactory = new CodeGenViewFactory(
component, styles, protoViews, changeDetectorFactoryExpressions, componentViewFactory);
var targetStatements: Statement[] = [];
var viewFactoryExpression = viewFactory.createViewFactory(template, 0, targetStatements);
return new SourceExpression(targetStatements.map(stmt => stmt.statement),
viewFactoryExpression.expression);
}
}
interface ViewFactory<EXPRESSION, STATEMENT> {
createText(renderer: EXPRESSION, parent: EXPRESSION, text: string,
targetStatements: STATEMENT[]): EXPRESSION;
createElement(renderer: EXPRESSION, parent: EXPRESSION, name: string, rootSelector: EXPRESSION,
targetStatements: STATEMENT[]): EXPRESSION;
createTemplateAnchor(renderer: EXPRESSION, parent: EXPRESSION,
targetStatements: STATEMENT[]): EXPRESSION;
createGlobalEventListener(renderer: EXPRESSION, view: EXPRESSION, boundElementIndex: number,
eventAst: BoundEventAst, targetStatements: STATEMENT[]): EXPRESSION;
createElementEventListener(renderer: EXPRESSION, view: EXPRESSION, boundElementIndex: number,
renderNode: EXPRESSION, eventAst: BoundEventAst,
targetStatements: STATEMENT[]);
setElementAttribute(renderer: EXPRESSION, renderNode: EXPRESSION, attrName: string,
attrValue: string, targetStatements: STATEMENT[]);
createAppElement(appProtoEl: EXPRESSION, view: EXPRESSION, renderNode: EXPRESSION,
parentAppEl: EXPRESSION, embeddedViewFactory: EXPRESSION,
targetStatements: STATEMENT[]): EXPRESSION;
createAndSetComponentView(renderer: EXPRESSION, viewManager: EXPRESSION, view: EXPRESSION,
appEl: EXPRESSION, component: CompileDirectiveMetadata,
contentNodesByNgContentIndex: EXPRESSION[][],
targetStatements: STATEMENT[]);
getProjectedNodes(projectableNodes: EXPRESSION, ngContentIndex: number): EXPRESSION;
appendProjectedNodes(renderer: EXPRESSION, parent: EXPRESSION, nodes: EXPRESSION,
targetStatements: STATEMENT[]);
createViewFactory(asts: TemplateAst[], embeddedTemplateIndex: number,
targetStatements: STATEMENT[]): EXPRESSION;
}
class CodeGenViewFactory implements ViewFactory<Expression, Statement> {
private _nextVarId: number = 0;
constructor(public component: CompileDirectiveMetadata, public styles: SourceExpression,
public protoViews: CompileProtoView<Expression, Expression>[],
public changeDetectorExpressions: SourceExpressions,
public componentViewFactory: Function) {}
private _nextVar(prefix: string): string {
return `${prefix}${this._nextVarId++}_${this.component.type.name}`;
}
private _nextRenderVar(): string { return this._nextVar('render'); }
private _nextAppVar(): string { return this._nextVar('app'); }
private _nextDisposableVar(): string {
return `disposable${this._nextVarId++}_${this.component.type.name}`;
}
createText(renderer: Expression, parent: Expression, text: string,
targetStatements: Statement[]): Expression {
var varName = this._nextRenderVar();
var statement =
`var ${varName} = ${renderer.expression}.createText(${isPresent(parent) ? parent.expression : null}, ${escapeSingleQuoteString(text)});`;
targetStatements.push(new Statement(statement));
return new Expression(varName);
}
createElement(renderer: Expression, parentRenderNode: Expression, name: string,
rootSelector: Expression, targetStatements: Statement[]): Expression {
var varName = this._nextRenderVar();
var valueExpr;
if (isPresent(rootSelector)) {
valueExpr = `${rootSelector.expression} == null ?
${renderer.expression}.createElement(${isPresent(parentRenderNode) ? parentRenderNode.expression : null}, ${escapeSingleQuoteString(name)}) :
${renderer.expression}.selectRootElement(${rootSelector.expression});`;
} else {
valueExpr =
`${renderer.expression}.createElement(${isPresent(parentRenderNode) ? parentRenderNode.expression : null}, ${escapeSingleQuoteString(name)})`;
}
var statement = `var ${varName} = ${valueExpr};`;
targetStatements.push(new Statement(statement));
return new Expression(varName);
}
createTemplateAnchor(renderer: Expression, parentRenderNode: Expression,
targetStatements: Statement[]): Expression {
var varName = this._nextRenderVar();
var valueExpr =
`${renderer.expression}.createTemplateAnchor(${isPresent(parentRenderNode) ? parentRenderNode.expression : null});`;
targetStatements.push(new Statement(`var ${varName} = ${valueExpr}`));
return new Expression(varName);
}
createGlobalEventListener(renderer: Expression, appView: Expression, boundElementIndex: number,
eventAst: BoundEventAst, targetStatements: Statement[]): Expression {
var disposableVar = this._nextDisposableVar();
var eventHandlerExpr = codeGenEventHandler(appView, boundElementIndex, eventAst.fullName);
targetStatements.push(new Statement(
`var ${disposableVar} = ${renderer.expression}.listenGlobal(${escapeValue(eventAst.target)}, ${escapeValue(eventAst.name)}, ${eventHandlerExpr});`));
return new Expression(disposableVar);
}
createElementEventListener(renderer: Expression, appView: Expression, boundElementIndex: number,
renderNode: Expression, eventAst: BoundEventAst,
targetStatements: Statement[]) {
var eventHandlerExpr = codeGenEventHandler(appView, boundElementIndex, eventAst.fullName);
targetStatements.push(new Statement(
`${renderer.expression}.listen(${renderNode.expression}, ${escapeValue(eventAst.name)}, ${eventHandlerExpr});`));
}
setElementAttribute(renderer: Expression, renderNode: Expression, attrName: string,
attrValue: string, targetStatements: Statement[]) {
targetStatements.push(new Statement(
`${renderer.expression}.setElementAttribute(${renderNode.expression}, ${escapeSingleQuoteString(attrName)}, ${escapeSingleQuoteString(attrValue)});`));
}
createAppElement(appProtoEl: Expression, appView: Expression, renderNode: Expression,
parentAppEl: Expression, embeddedViewFactory: Expression,
targetStatements: Statement[]): Expression {
var appVar = this._nextAppVar();
var varValue =
`new ${APP_EL_MODULE_REF}AppElement(${appProtoEl.expression}, ${appView.expression},
${isPresent(parentAppEl) ? parentAppEl.expression : null}, ${renderNode.expression}, ${isPresent(embeddedViewFactory) ? embeddedViewFactory.expression : null})`;
targetStatements.push(new Statement(`var ${appVar} = ${varValue};`));
return new Expression(appVar);
}
createAndSetComponentView(renderer: Expression, viewManager: Expression, view: Expression,
appEl: Expression, component: CompileDirectiveMetadata,
contentNodesByNgContentIndex: Expression[][],
targetStatements: Statement[]) {
var codeGenContentNodes;
if (this.component.type.isHost) {
codeGenContentNodes = `${view.expression}.projectableNodes`;
} else {
codeGenContentNodes =
`[${contentNodesByNgContentIndex.map( nodes => codeGenFlatArray(nodes) ).join(',')}]`;
}
targetStatements.push(new Statement(
`${this.componentViewFactory(component)}(${renderer.expression}, ${viewManager.expression}, ${appEl.expression}, ${codeGenContentNodes}, null, null, null);`));
}
getProjectedNodes(projectableNodes: Expression, ngContentIndex: number): Expression {
return new Expression(`${projectableNodes.expression}[${ngContentIndex}]`, true);
}
appendProjectedNodes(renderer: Expression, parent: Expression, nodes: Expression,
targetStatements: Statement[]) {
targetStatements.push(new Statement(
`${renderer.expression}.projectNodes(${parent.expression}, ${APP_VIEW_MODULE_REF}flattenNestedViewRenderNodes(${nodes.expression}));`));
}
createViewFactory(asts: TemplateAst[], embeddedTemplateIndex: number,
targetStatements: Statement[]): Expression {
var compileProtoView = this.protoViews[embeddedTemplateIndex];
var isHostView = this.component.type.isHost;
var isComponentView = embeddedTemplateIndex === 0 && !isHostView;
var visitor = new ViewBuilderVisitor<Expression, Statement>(
new Expression('renderer'), new Expression('viewManager'),
new Expression('projectableNodes'), isHostView ? new Expression('rootSelector') : null,
new Expression('view'), compileProtoView, targetStatements, this);
templateVisitAll(
visitor, asts,
new ParentElement(isComponentView ? new Expression('parentRenderNode') : null, null, null));
var appProtoView = compileProtoView.protoView.expression;
var viewFactoryName = codeGenViewFactoryName(this.component, embeddedTemplateIndex);
var changeDetectorFactory = this.changeDetectorExpressions.expressions[embeddedTemplateIndex];
var factoryArgs = [
'parentRenderer',
'viewManager',
'containerEl',
'projectableNodes',
'rootSelector',
'dynamicallyCreatedProviders',
'rootInjector'
];
var initRendererStmts = [];
var rendererExpr = `parentRenderer`;
if (embeddedTemplateIndex === 0) {
var renderCompTypeVar = this._nextVar('renderType');
targetStatements.push(new Statement(`var ${renderCompTypeVar} = null;`));
var stylesVar = this._nextVar('styles');
targetStatements.push(
new Statement(`${CONST_VAR} ${stylesVar} = ${this.styles.expression};`));
var encapsulation = this.component.template.encapsulation;
initRendererStmts.push(`if (${renderCompTypeVar} == null) {
${renderCompTypeVar} = viewManager.createRenderComponentType(${codeGenViewEncapsulation(encapsulation)}, ${stylesVar});
}`);
rendererExpr = `parentRenderer.renderComponent(${renderCompTypeVar})`;
}
var statement = `
${codeGenFnHeader(factoryArgs, viewFactoryName)}{
${initRendererStmts.join('\n')}
var renderer = ${rendererExpr};
var view = new ${APP_VIEW_MODULE_REF}AppView(
${appProtoView}, renderer, viewManager,
projectableNodes,
containerEl,
dynamicallyCreatedProviders, rootInjector,
${changeDetectorFactory}()
);
${APP_VIEW_MODULE_REF}checkSlotCount(${escapeValue(this.component.type.name)}, ${this.component.template.ngContentSelectors.length}, projectableNodes);
${isComponentView ? 'var parentRenderNode = renderer.createViewRoot(view.containerAppElement.nativeElement);' : ''}
${visitor.renderStmts.map(stmt => stmt.statement).join('\n')}
${visitor.appStmts.map(stmt => stmt.statement).join('\n')}
view.init(${codeGenFlatArray(visitor.rootNodesOrAppElements)}, ${codeGenArray(visitor.renderNodes)}, ${codeGenArray(visitor.appDisposables)},
${codeGenArray(visitor.appElements)});
return view;
}`;
targetStatements.push(new Statement(statement));
return new Expression(viewFactoryName);
}
}
class RuntimeViewFactory implements ViewFactory<any, any> {
constructor(public component: CompileDirectiveMetadata, public styles: Array<string | any[]>,
public protoViews: CompileProtoView<AppProtoView, AppProtoElement>[],
public changeDetectorFactories: Function[], public componentViewFactory: Function) {}
createText(renderer: Renderer, parent: any, text: string, targetStatements: any[]): any {
return renderer.createText(parent, text);
}
createElement(renderer: Renderer, parent: any, name: string, rootSelector: string,
targetStatements: any[]): any {
var el;
if (isPresent(rootSelector)) {
el = renderer.selectRootElement(rootSelector);
} else {
el = renderer.createElement(parent, name);
}
return el;
}
createTemplateAnchor(renderer: Renderer, parent: any, targetStatements: any[]): any {
return renderer.createTemplateAnchor(parent);
}
createGlobalEventListener(renderer: Renderer, appView: AppView, boundElementIndex: number,
eventAst: BoundEventAst, targetStatements: any[]): any {
return renderer.listenGlobal(
eventAst.target, eventAst.name,
(event) => appView.triggerEventHandlers(eventAst.fullName, event, boundElementIndex));
}
createElementEventListener(renderer: Renderer, appView: AppView, boundElementIndex: number,
renderNode: any, eventAst: BoundEventAst, targetStatements: any[]) {
renderer.listen(renderNode, eventAst.name, (event) => appView.triggerEventHandlers(
eventAst.fullName, event, boundElementIndex));
}
setElementAttribute(renderer: Renderer, renderNode: any, attrName: string, attrValue: string,
targetStatements: any[]) {
renderer.setElementAttribute(renderNode, attrName, attrValue);
}
createAppElement(appProtoEl: AppProtoElement, appView: AppView, renderNode: any,
parentAppEl: AppElement, embeddedViewFactory: Function,
targetStatements: any[]): any {
return new AppElement(appProtoEl, appView, parentAppEl, renderNode, embeddedViewFactory);
}
createAndSetComponentView(renderer: Renderer, viewManager: AppViewManager_, appView: AppView,
appEl: AppElement, component: CompileDirectiveMetadata,
contentNodesByNgContentIndex: Array<Array<any | any[]>>,
targetStatements: any[]) {
var flattenedContentNodes;
if (this.component.type.isHost) {
flattenedContentNodes = appView.projectableNodes;
} else {
flattenedContentNodes = ListWrapper.createFixedSize(contentNodesByNgContentIndex.length);
for (var i = 0; i < contentNodesByNgContentIndex.length; i++) {
flattenedContentNodes[i] = flattenArray(contentNodesByNgContentIndex[i], []);
}
}
this.componentViewFactory(component)(renderer, viewManager, appEl, flattenedContentNodes);
}
getProjectedNodes(projectableNodes: any[][], ngContentIndex: number): any[] {
return projectableNodes[ngContentIndex];
}
appendProjectedNodes(renderer: Renderer, parent: any, nodes: any[], targetStatements: any[]) {
renderer.projectNodes(parent, flattenNestedViewRenderNodes(nodes));
}
createViewFactory(asts: TemplateAst[], embeddedTemplateIndex: number,
targetStatements: any[]): Function {
var compileProtoView = this.protoViews[embeddedTemplateIndex];
var isComponentView = compileProtoView.protoView.type === ViewType.COMPONENT;
var renderComponentType = null;
return (parentRenderer: ParentRenderer, viewManager: AppViewManager_, containerEl: AppElement,
projectableNodes: any[][], rootSelector: string = null,
dynamicallyCreatedProviders: ResolvedProvider[] = null,
rootInjector: Injector = null) => {
checkSlotCount(this.component.type.name, this.component.template.ngContentSelectors.length,
projectableNodes);
var renderer;
if (embeddedTemplateIndex === 0) {
if (isBlank(renderComponentType)) {
renderComponentType = viewManager.createRenderComponentType(
this.component.template.encapsulation, this.styles);
}
renderer = parentRenderer.renderComponent(renderComponentType);
} else {
renderer = <Renderer>parentRenderer;
}
var changeDetector = this.changeDetectorFactories[embeddedTemplateIndex]();
var view =
new AppView(compileProtoView.protoView, renderer, viewManager, projectableNodes,
containerEl, dynamicallyCreatedProviders, rootInjector, changeDetector);
var visitor = new ViewBuilderVisitor<any, any>(
renderer, viewManager, projectableNodes, rootSelector, view, compileProtoView, [], this);
var parentRenderNode =
isComponentView ? renderer.createViewRoot(containerEl.nativeElement) : null;
templateVisitAll(visitor, asts, new ParentElement(parentRenderNode, null, null));
view.init(flattenArray(visitor.rootNodesOrAppElements, []), visitor.renderNodes,
visitor.appDisposables, visitor.appElements);
return view;
};
}
}
class ParentElement<EXPRESSION> {
public contentNodesByNgContentIndex: Array<EXPRESSION>[];
constructor(public renderNode: EXPRESSION, public appEl: EXPRESSION,
public component: CompileDirectiveMetadata) {
if (isPresent(component)) {
this.contentNodesByNgContentIndex =
ListWrapper.createFixedSize(component.template.ngContentSelectors.length);
for (var i = 0; i < this.contentNodesByNgContentIndex.length; i++) {
this.contentNodesByNgContentIndex[i] = [];
}
} else {
this.contentNodesByNgContentIndex = null;
}
}
addContentNode(ngContentIndex: number, nodeExpr: EXPRESSION) {
this.contentNodesByNgContentIndex[ngContentIndex].push(nodeExpr);
}
}
class ViewBuilderVisitor<EXPRESSION, STATEMENT> implements TemplateAstVisitor {
renderStmts: Array<STATEMENT> = [];
renderNodes: EXPRESSION[] = [];
appStmts: Array<STATEMENT> = [];
appElements: EXPRESSION[] = [];
appDisposables: EXPRESSION[] = [];
rootNodesOrAppElements: EXPRESSION[] = [];
elementCount: number = 0;
constructor(public renderer: EXPRESSION, public viewManager: EXPRESSION,
public projectableNodes: EXPRESSION, public rootSelector: EXPRESSION,
public view: EXPRESSION, public protoView: CompileProtoView<EXPRESSION, EXPRESSION>,
public targetStatements: STATEMENT[],
public factory: ViewFactory<EXPRESSION, STATEMENT>) {}
private _addRenderNode(renderNode: EXPRESSION, appEl: EXPRESSION, ngContentIndex: number,
parent: ParentElement<EXPRESSION>) {
this.renderNodes.push(renderNode);
if (isPresent(parent.component)) {
if (isPresent(ngContentIndex)) {
parent.addContentNode(ngContentIndex, isPresent(appEl) ? appEl : renderNode);
}
} else if (isBlank(parent.renderNode)) {
this.rootNodesOrAppElements.push(isPresent(appEl) ? appEl : renderNode);
}
}
private _getParentRenderNode(ngContentIndex: number,
parent: ParentElement<EXPRESSION>): EXPRESSION {
return isPresent(parent.component) &&
parent.component.template.encapsulation !== ViewEncapsulation.Native ?
null :
parent.renderNode;
}
visitBoundText(ast: BoundTextAst, parent: ParentElement<EXPRESSION>): any {
return this._visitText('', ast.ngContentIndex, parent);
}
visitText(ast: TextAst, parent: ParentElement<EXPRESSION>): any {
return this._visitText(ast.value, ast.ngContentIndex, parent);
}
private _visitText(value: string, ngContentIndex: number, parent: ParentElement<EXPRESSION>) {
var renderNode = this.factory.createText(
this.renderer, this._getParentRenderNode(ngContentIndex, parent), value, this.renderStmts);
this._addRenderNode(renderNode, null, ngContentIndex, parent);
return null;
}
visitNgContent(ast: NgContentAst, parent: ParentElement<EXPRESSION>): any {
var nodesExpression = this.factory.getProjectedNodes(this.projectableNodes, ast.index);
if (isPresent(parent.component)) {
if (isPresent(ast.ngContentIndex)) {
parent.addContentNode(ast.ngContentIndex, nodesExpression);
}
} else {
if (isPresent(parent.renderNode)) {
this.factory.appendProjectedNodes(this.renderer, parent.renderNode, nodesExpression,
this.renderStmts);
} else {
this.rootNodesOrAppElements.push(nodesExpression);
}
}
return null;
}
visitElement(ast: ElementAst, parent: ParentElement<EXPRESSION>): any {
var renderNode = this.factory.createElement(
this.renderer, this._getParentRenderNode(ast.ngContentIndex, parent), ast.name,
this.rootSelector, this.renderStmts);
var component = ast.getComponent();
var elementIndex = this.elementCount++;
var protoEl = this.protoView.protoElements[elementIndex];
protoEl.renderEvents.forEach((eventAst) => {
if (isPresent(eventAst.target)) {
var disposable = this.factory.createGlobalEventListener(
this.renderer, this.view, protoEl.boundElementIndex, eventAst, this.renderStmts);
this.appDisposables.push(disposable);
} else {
this.factory.createElementEventListener(this.renderer, this.view, protoEl.boundElementIndex,
renderNode, eventAst, this.renderStmts);
}
});
for (var i = 0; i < protoEl.attrNameAndValues.length; i++) {
var attrName = protoEl.attrNameAndValues[i][0];
var attrValue = protoEl.attrNameAndValues[i][1];
this.factory.setElementAttribute(this.renderer, renderNode, attrName, attrValue,
this.renderStmts);
}
var appEl = null;
if (isPresent(protoEl.appProtoEl)) {
appEl = this.factory.createAppElement(protoEl.appProtoEl, this.view, renderNode, parent.appEl,
null, this.appStmts);
this.appElements.push(appEl);
}
this._addRenderNode(renderNode, appEl, ast.ngContentIndex, parent);
var newParent = new ParentElement<EXPRESSION>(
renderNode, isPresent(appEl) ? appEl : parent.appEl, component);
templateVisitAll(this, ast.children, newParent);
if (isPresent(appEl) && isPresent(component)) {
this.factory.createAndSetComponentView(this.renderer, this.viewManager, this.view, appEl,
component, newParent.contentNodesByNgContentIndex,
this.appStmts);
}
return null;
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: ParentElement<EXPRESSION>): any {
var renderNode = this.factory.createTemplateAnchor(
this.renderer, this._getParentRenderNode(ast.ngContentIndex, parent), this.renderStmts);
var elementIndex = this.elementCount++;
var protoEl = this.protoView.protoElements[elementIndex];
var embeddedViewFactory = this.factory.createViewFactory(
ast.children, protoEl.embeddedTemplateIndex, this.targetStatements);
var appEl = this.factory.createAppElement(protoEl.appProtoEl, this.view, renderNode,
parent.appEl, embeddedViewFactory, this.appStmts);
this._addRenderNode(renderNode, appEl, ast.ngContentIndex, parent);
this.appElements.push(appEl);
return null;
}
visitVariable(ast: VariableAst, ctx: any): any { return null; }
visitAttr(ast: AttrAst, ctx: any): any { return null; }
visitDirective(ast: DirectiveAst, ctx: any): any { return null; }
visitEvent(ast: BoundEventAst, ctx: any): any { return null; }
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
}
function codeGenEventHandler(view: Expression, boundElementIndex: number,
eventName: string): string {
return codeGenValueFn(
['event'],
`${view.expression}.triggerEventHandlers(${escapeValue(eventName)}, event, ${boundElementIndex})`);
}
function codeGenViewFactoryName(component: CompileDirectiveMetadata,
embeddedTemplateIndex: number): string {
return `viewFactory_${component.type.name}${embeddedTemplateIndex}`;
}
function codeGenViewEncapsulation(value: ViewEncapsulation): string {
if (IS_DART) {
return `${METADATA_MODULE_REF}${value}`;
} else {
return `${value}`;
}
}