refactor(core): rename angular2/src/compiler -> angular2/src/core/compiler

This commit is contained in:
Tobias Bosch
2015-10-02 07:39:29 -07:00
parent 6248a5e846
commit aa3d94149f
41 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,206 @@
import {ListWrapper} from 'angular2/src/core/facade/collection';
import {isPresent, isBlank} from 'angular2/src/core/facade/lang';
import {reflector} from 'angular2/src/core/reflection/reflection';
import {
DirectiveIndex,
BindingRecord,
DirectiveRecord,
ChangeDetectionStrategy,
ChangeDetectorDefinition,
ChangeDetectorGenConfig,
ASTWithSource
} from 'angular2/src/core/change_detection/change_detection';
import {CompileDirectiveMetadata, CompileTypeMetadata} from './directive_metadata';
import {
TemplateAst,
ElementAst,
BoundTextAst,
PropertyBindingType,
DirectiveAst,
TemplateAstVisitor,
templateVisitAll,
NgContentAst,
EmbeddedTemplateAst,
VariableAst,
BoundElementPropertyAst,
BoundEventAst,
BoundDirectivePropertyAst,
AttrAst,
TextAst
} from './template_ast';
import {LifecycleHooks} from 'angular2/src/core/linker/interfaces';
export function createChangeDetectorDefinitions(
componentType: CompileTypeMetadata, componentStrategy: ChangeDetectionStrategy,
genConfig: ChangeDetectorGenConfig, parsedTemplate: TemplateAst[]): ChangeDetectorDefinition[] {
var pvVisitors = [];
var visitor = new ProtoViewVisitor(null, pvVisitors, componentStrategy);
templateVisitAll(visitor, parsedTemplate);
return createChangeDefinitions(pvVisitors, componentType, genConfig);
}
class ProtoViewVisitor implements TemplateAstVisitor {
viewIndex: number;
boundTextCount: number = 0;
boundElementCount: number = 0;
variableNames: string[] = [];
bindingRecords: BindingRecord[] = [];
eventRecords: BindingRecord[] = [];
directiveRecords: DirectiveRecord[] = [];
constructor(public parent: ProtoViewVisitor, public allVisitors: ProtoViewVisitor[],
public strategy: ChangeDetectionStrategy) {
this.viewIndex = allVisitors.length;
allVisitors.push(this);
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
this.boundElementCount++;
for (var i = 0; i < ast.directives.length; i++) {
ast.directives[i].visit(this, i);
}
var childVisitor =
new ProtoViewVisitor(this, this.allVisitors, ChangeDetectionStrategy.Default);
// Attention: variables present on an embedded template count towards
// the embedded template and not the template anchor!
templateVisitAll(childVisitor, ast.vars);
templateVisitAll(childVisitor, ast.children);
return null;
}
visitElement(ast: ElementAst, context: any): any {
if (ast.isBound()) {
this.boundElementCount++;
}
templateVisitAll(this, ast.inputs, null);
templateVisitAll(this, ast.outputs);
templateVisitAll(this, ast.exportAsVars);
for (var i = 0; i < ast.directives.length; i++) {
ast.directives[i].visit(this, i);
}
templateVisitAll(this, ast.children);
return null;
}
visitNgContent(ast: NgContentAst, context: any): any { return null; }
visitVariable(ast: VariableAst, context: any): any {
this.variableNames.push(ast.name);
return null;
}
visitEvent(ast: BoundEventAst, directiveRecord: DirectiveRecord): any {
var bindingRecord =
isPresent(directiveRecord) ?
BindingRecord.createForHostEvent(ast.handler, ast.fullName, directiveRecord) :
BindingRecord.createForEvent(ast.handler, ast.fullName, this.boundElementCount - 1);
this.eventRecords.push(bindingRecord);
return null;
}
visitElementProperty(ast: BoundElementPropertyAst, directiveRecord: DirectiveRecord): any {
var boundElementIndex = this.boundElementCount - 1;
var dirIndex = isPresent(directiveRecord) ? directiveRecord.directiveIndex : null;
var bindingRecord;
if (ast.type === PropertyBindingType.Property) {
bindingRecord =
isPresent(dirIndex) ?
BindingRecord.createForHostProperty(dirIndex, ast.value, ast.name) :
BindingRecord.createForElementProperty(ast.value, boundElementIndex, ast.name);
} else if (ast.type === PropertyBindingType.Attribute) {
bindingRecord =
isPresent(dirIndex) ?
BindingRecord.createForHostAttribute(dirIndex, ast.value, ast.name) :
BindingRecord.createForElementAttribute(ast.value, boundElementIndex, ast.name);
} else if (ast.type === PropertyBindingType.Class) {
bindingRecord =
isPresent(dirIndex) ?
BindingRecord.createForHostClass(dirIndex, ast.value, ast.name) :
BindingRecord.createForElementClass(ast.value, boundElementIndex, ast.name);
} else if (ast.type === PropertyBindingType.Style) {
bindingRecord =
isPresent(dirIndex) ?
BindingRecord.createForHostStyle(dirIndex, ast.value, ast.name, ast.unit) :
BindingRecord.createForElementStyle(ast.value, boundElementIndex, ast.name, ast.unit);
}
this.bindingRecords.push(bindingRecord);
return null;
}
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));
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 directiveRecord = new DirectiveRecord({
directiveIndex: directiveIndex,
callAfterContentInit:
directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.AfterContentInit) !== -1,
callAfterContentChecked:
directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.AfterContentChecked) !== -1,
callAfterViewInit:
directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.AfterViewInit) !== -1,
callAfterViewChecked:
directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.AfterViewChecked) !== -1,
callOnChanges: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1,
callDoCheck: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1,
callOnInit: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1,
changeDetection: directiveMetadata.changeDetection
});
this.directiveRecords.push(directiveRecord);
templateVisitAll(this, ast.inputs, directiveRecord);
var bindingRecords = this.bindingRecords;
if (directiveRecord.callOnChanges) {
bindingRecords.push(BindingRecord.createDirectiveOnChanges(directiveRecord));
}
if (directiveRecord.callOnInit) {
bindingRecords.push(BindingRecord.createDirectiveOnInit(directiveRecord));
}
if (directiveRecord.callDoCheck) {
bindingRecords.push(BindingRecord.createDirectiveDoCheck(directiveRecord));
}
templateVisitAll(this, ast.hostProperties, directiveRecord);
templateVisitAll(this, ast.hostEvents, directiveRecord);
templateVisitAll(this, ast.exportAsVars);
return null;
}
visitDirectiveProperty(ast: BoundDirectivePropertyAst, directiveRecord: DirectiveRecord): any {
// TODO: these setters should eventually be created by change detection, to make
// it monomorphic!
var setter = reflector.setter(ast.directiveName);
this.bindingRecords.push(
BindingRecord.createForDirective(ast.value, ast.directiveName, setter, directiveRecord));
return null;
}
}
function createChangeDefinitions(pvVisitors: ProtoViewVisitor[], componentType: CompileTypeMetadata,
genConfig: ChangeDetectorGenConfig): ChangeDetectorDefinition[] {
var pvVariableNames = _collectNestedProtoViewsVariableNames(pvVisitors);
return pvVisitors.map(pvVisitor => {
var id = `${componentType.name}_${pvVisitor.viewIndex}`;
return new ChangeDetectorDefinition(
id, pvVisitor.strategy, pvVariableNames[pvVisitor.viewIndex], pvVisitor.bindingRecords,
pvVisitor.eventRecords, pvVisitor.directiveRecords, genConfig);
});
}
function _collectNestedProtoViewsVariableNames(pvVisitors: ProtoViewVisitor[]): string[][] {
var nestedPvVariableNames: string[][] = ListWrapper.createFixedSize(pvVisitors.length);
pvVisitors.forEach((pv) => {
var parentVariableNames: string[] =
isPresent(pv.parent) ? nestedPvVariableNames[pv.parent.viewIndex] : [];
nestedPvVariableNames[pv.viewIndex] = parentVariableNames.concat(pv.variableNames);
});
return nestedPvVariableNames;
}

View File

@ -0,0 +1,86 @@
import {CompileTypeMetadata} from './directive_metadata';
import {SourceExpressions, moduleRef} from './source_module';
import {
ChangeDetectorJITGenerator
} from 'angular2/src/core/change_detection/change_detection_jit_generator';
import {createChangeDetectorDefinitions} from './change_definition_factory';
import {isJsObject, CONST_EXPR} from 'angular2/src/core/facade/lang';
import {
ChangeDetectorGenConfig,
ChangeDetectorDefinition,
DynamicProtoChangeDetector,
ChangeDetectionStrategy
} from 'angular2/src/core/change_detection/change_detection';
import {TemplateAst} from './template_ast';
import {Codegen} from 'angular2/src/transform/template_compiler/change_detector_codegen';
import {IS_DART, MODULE_SUFFIX} from './util';
import {Injectable} from 'angular2/src/core/di';
const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
const UTIL = "ChangeDetectionUtil";
var ABSTRACT_CHANGE_DETECTOR_MODULE = moduleRef(
`package:angular2/src/core/change_detection/abstract_change_detector${MODULE_SUFFIX}`);
var UTIL_MODULE =
moduleRef(`package:angular2/src/core/change_detection/change_detection_util${MODULE_SUFFIX}`);
var PREGEN_PROTO_CHANGE_DETECTOR_MODULE = moduleRef(
`package:angular2/src/core/change_detection/pregen_proto_change_detector${MODULE_SUFFIX}`);
@Injectable()
export class ChangeDetectionCompiler {
constructor(private _genConfig: ChangeDetectorGenConfig) {}
compileComponentRuntime(componentType: CompileTypeMetadata, strategy: ChangeDetectionStrategy,
parsedTemplate: TemplateAst[]): Function[] {
var changeDetectorDefinitions =
createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate);
return changeDetectorDefinitions.map(definition =>
this._createChangeDetectorFactory(definition));
}
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).generate();
}
}
compileComponentCodeGen(componentType: CompileTypeMetadata, strategy: ChangeDetectionStrategy,
parsedTemplate: TemplateAst[]): SourceExpressions {
var changeDetectorDefinitions =
createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate);
var factories = [];
var index = 0;
var sourceParts = changeDetectorDefinitions.map(definition => {
var codegen: any;
var sourcePart;
// TODO(tbosch): move the 2 code generators to the same place, one with .dart and one with .ts
// suffix
// and have the same API for calling them!
if (IS_DART) {
codegen = new Codegen(PREGEN_PROTO_CHANGE_DETECTOR_MODULE);
var className = definition.id;
var typeRef = (index === 0 && componentType.isHost) ?
'dynamic' :
`${moduleRef(componentType.moduleUrl)}${componentType.name}`;
codegen.generate(typeRef, className, definition);
factories.push(`(dispatcher) => new ${className}(dispatcher)`);
sourcePart = codegen.toString();
} else {
codegen = new ChangeDetectorJITGenerator(
definition, `${UTIL_MODULE}${UTIL}`,
`${ABSTRACT_CHANGE_DETECTOR_MODULE}${ABSTRACT_CHANGE_DETECTOR}`);
factories.push(`function(dispatcher) { return new ${codegen.typeName}(dispatcher); }`);
sourcePart = codegen.generateSource();
}
index++;
return sourcePart;
});
return new SourceExpressions(sourceParts, factories);
}
}

View File

@ -0,0 +1,359 @@
import {isPresent, isBlank, Type, isString} from 'angular2/src/core/facade/lang';
import {SetWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
import {
TemplateCmd,
text,
ngContent,
beginElement,
endElement,
beginComponent,
endComponent,
embeddedTemplate,
CompiledTemplate
} 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/render/api';
import {
shimHostAttribute,
shimContentAttribute,
shimContentAttributeExpr,
shimHostAttributeExpr
} from './style_compiler';
import {escapeSingleQuoteString, 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';
@Injectable()
export class CommandCompiler {
compileComponentRuntime(component: CompileDirectiveMetadata, appId: string, templateId: number,
template: TemplateAst[], changeDetectorFactories: Function[],
componentTemplateFactory: Function): TemplateCmd[] {
var visitor = new CommandBuilderVisitor(
new RuntimeCommandFactory(component, appId, templateId, componentTemplateFactory,
changeDetectorFactories),
0);
templateVisitAll(visitor, template);
return visitor.result;
}
compileComponentCodeGen(component: CompileDirectiveMetadata, appIdExpr: string,
templateIdExpr: string, template: TemplateAst[],
changeDetectorFactoryExpressions: string[],
componentTemplateFactory: Function): SourceExpression {
var visitor = new CommandBuilderVisitor(
new CodegenCommandFactory(component, appIdExpr, templateIdExpr, componentTemplateFactory,
changeDetectorFactoryExpressions),
0);
templateVisitAll(visitor, template);
var source = `[${visitor.result.join(',')}]`;
return new SourceExpression([], source);
}
}
interface CommandFactory<R> {
createText(value: string, isBound: boolean, ngContentIndex: number): R;
createNgContent(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[],
nativeShadow: boolean, 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 appId: string,
private templateId: number, private componentTemplateFactory: Function,
private changeDetectorFactories: Function[]) {}
private _mapDirectives(directives: CompileDirectiveMetadata[]): Type[] {
return directives.map(directive => directive.type.runtime);
}
private _addStyleShimAttributes(attrNameAndValues: string[],
localComponent: CompileDirectiveMetadata,
localTemplateId: number): string[] {
var additionalStyles = [];
if (isPresent(localComponent) &&
localComponent.template.encapsulation === ViewEncapsulation.Emulated) {
additionalStyles.push(shimHostAttribute(this.appId, localTemplateId));
additionalStyles.push('');
}
if (this.component.template.encapsulation === ViewEncapsulation.Emulated) {
additionalStyles.push(shimContentAttribute(this.appId, this.templateId));
additionalStyles.push('');
}
return additionalStyles.concat(attrNameAndValues);
}
createText(value: string, isBound: boolean, ngContentIndex: number): TemplateCmd {
return text(value, isBound, ngContentIndex);
}
createNgContent(ngContentIndex: number): TemplateCmd { return ngContent(ngContentIndex); }
createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isBound: boolean, ngContentIndex: number): TemplateCmd {
return beginElement(name, this._addStyleShimAttributes(attrNameAndValues, null, null),
eventTargetAndNames, variableNameAndValues, this._mapDirectives(directives),
isBound, ngContentIndex);
}
createEndElement(): TemplateCmd { return endElement(); }
createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
nativeShadow: boolean, ngContentIndex: number): TemplateCmd {
var nestedTemplate = this.componentTemplateFactory(directives[0]);
return beginComponent(
name, this._addStyleShimAttributes(attrNameAndValues, directives[0], nestedTemplate.id),
eventTargetAndNames, variableNameAndValues, this._mapDirectives(directives), nativeShadow,
ngContentIndex, nestedTemplate);
}
createEndComponent(): TemplateCmd { return endComponent(); }
createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isMerged: boolean, ngContentIndex: number,
children: TemplateCmd[]): TemplateCmd {
return embeddedTemplate(attrNameAndValues, variableNameAndValues,
this._mapDirectives(directives), isMerged, ngContentIndex,
this.changeDetectorFactories[embeddedTemplateIndex], children);
}
}
class CodegenCommandFactory implements CommandFactory<string> {
constructor(private component: CompileDirectiveMetadata, private appIdExpr: string,
private templateIdExpr: string, private componentTemplateFactory: Function,
private changeDetectorFactoryExpressions: string[]) {}
private _addStyleShimAttributes(attrNameAndValues: string[],
localComponent: CompileDirectiveMetadata,
localTemplateIdExpr: string): any[] {
var additionalStlyes = [];
if (isPresent(localComponent) &&
localComponent.template.encapsulation === ViewEncapsulation.Emulated) {
additionalStlyes.push(
new Expression(shimHostAttributeExpr(this.appIdExpr, localTemplateIdExpr)));
additionalStlyes.push('');
}
if (this.component.template.encapsulation === ViewEncapsulation.Emulated) {
additionalStlyes.push(
new Expression(shimContentAttributeExpr(this.appIdExpr, this.templateIdExpr)));
additionalStlyes.push('');
}
return additionalStlyes.concat(attrNameAndValues);
}
createText(value: string, isBound: boolean, ngContentIndex: number): string {
return `${TEMPLATE_COMMANDS_MODULE_REF}text(${escapeSingleQuoteString(value)}, ${isBound}, ${ngContentIndex})`;
}
createNgContent(ngContentIndex: number): string {
return `${TEMPLATE_COMMANDS_MODULE_REF}ngContent(${ngContentIndex})`;
}
createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isBound: boolean, ngContentIndex: number): string {
var attrsExpression = codeGenArray(this._addStyleShimAttributes(attrNameAndValues, null, null));
return `${TEMPLATE_COMMANDS_MODULE_REF}beginElement(${escapeSingleQuoteString(name)}, ${attrsExpression}, ${codeGenArray(eventTargetAndNames)}, ${codeGenArray(variableNameAndValues)}, ${codeGenDirectivesArray(directives)}, ${isBound}, ${ngContentIndex})`;
}
createEndElement(): string { return `${TEMPLATE_COMMANDS_MODULE_REF}endElement()`; }
createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
nativeShadow: boolean, ngContentIndex: number): string {
var nestedCompExpr = this.componentTemplateFactory(directives[0]);
var attrsExpression = codeGenArray(
this._addStyleShimAttributes(attrNameAndValues, directives[0], `${nestedCompExpr}.id`));
return `${TEMPLATE_COMMANDS_MODULE_REF}beginComponent(${escapeSingleQuoteString(name)}, ${attrsExpression}, ${codeGenArray(eventTargetAndNames)}, ${codeGenArray(variableNameAndValues)}, ${codeGenDirectivesArray(directives)}, ${nativeShadow}, ${ngContentIndex}, ${nestedCompExpr})`;
}
createEndComponent(): string { return `${TEMPLATE_COMMANDS_MODULE_REF}endComponent()`; }
createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[],
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isMerged: boolean, ngContentIndex: number, children: string[]): string {
return `${TEMPLATE_COMMANDS_MODULE_REF}embeddedTemplate(${codeGenArray(attrNameAndValues)}, ${codeGenArray(variableNameAndValues)}, ` +
`${codeGenDirectivesArray(directives)}, ${isMerged}, ${ngContentIndex}, ${this.changeDetectorFactoryExpressions[embeddedTemplateIndex]}, [${children.join(',')}])`;
}
}
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 attrNameAndValues: string[] = visitAndReturnContext(this, attrAsts, []);
directives.forEach(directiveMeta => {
StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => {
attrNameAndValues.push(name);
attrNameAndValues.push(value);
});
});
return removeKeyValueArrayDuplicates(attrNameAndValues);
}
visitNgContent(ast: NgContentAst, context: any): any {
this.transitiveNgContentCount++;
this.result.push(this.commandFactory.createNgContent(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 === ViewEncapsulation.Native, 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;
}
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 {
return `[${data.map(escapeValue).join(',')}]`;
}
function codeGenDirectivesArray(directives: CompileDirectiveMetadata[]): string {
var expressions = directives.map(
directiveType => `${moduleRef(directiveType.type.moduleUrl)}${directiveType.type.name}`);
return `[${expressions.join(',')}]`;
}

View File

@ -0,0 +1,45 @@
export {TemplateCompiler} from './template_compiler';
export {
CompileDirectiveMetadata,
CompileTypeMetadata,
CompileTemplateMetadata
} from './directive_metadata';
export {SourceModule, SourceWithImports} from './source_module';
import {assertionsEnabled, Type} from 'angular2/src/core/facade/lang';
import {bind, Binding} from 'angular2/src/core/di';
import {TemplateParser} from 'angular2/src/compiler/template_parser';
import {HtmlParser} from 'angular2/src/compiler/html_parser';
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 {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';
import {RuntimeCompiler} from 'angular2/src/compiler/runtime_compiler';
import {ElementSchemaRegistry} from 'angular2/src/core/render/dom/schema/element_schema_registry';
import {
DomElementSchemaRegistry
} from 'angular2/src/core/render/dom/schema/dom_element_schema_registry';
export function compilerBindings(): Array<Type | Binding | any[]> {
return [
HtmlParser,
TemplateParser,
TemplateNormalizer,
RuntimeMetadataResolver,
StyleCompiler,
CommandCompiler,
ChangeDetectionCompiler,
bind(ChangeDetectorGenConfig)
.toValue(
new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled(), false, true)),
TemplateCompiler,
RuntimeCompiler,
bind(Compiler).toAlias(RuntimeCompiler),
DomElementSchemaRegistry,
bind(ElementSchemaRegistry).toAlias(DomElementSchemaRegistry)
];
}

View File

@ -0,0 +1,273 @@
import {
isPresent,
isBlank,
normalizeBool,
serializeEnum,
Type,
RegExpWrapper,
StringWrapper
} from 'angular2/src/core/facade/lang';
import {StringMapWrapper} from 'angular2/src/core/facade/collection';
import {
ChangeDetectionStrategy,
CHANGE_DECTION_STRATEGY_VALUES
} from 'angular2/src/core/change_detection/change_detection';
import {ViewEncapsulation, VIEW_ENCAPSULATION_VALUES} from 'angular2/src/core/render/api';
import {CssSelector} from 'angular2/src/core/render/dom/compiler/selector';
import {splitAtColon} from './util';
import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/linker/interfaces';
// group 1: "property" from "[property]"
// group 2: "event" from "(event)"
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;
export class CompileTypeMetadata {
runtime: Type;
name: string;
moduleUrl: string;
isHost: boolean;
constructor({runtime, name, moduleUrl, isHost}:
{runtime?: Type, name?: string, moduleUrl?: string, isHost?: boolean} = {}) {
this.runtime = runtime;
this.name = name;
this.moduleUrl = moduleUrl;
this.isHost = normalizeBool(isHost);
}
static fromJson(data: StringMap<string, any>): CompileTypeMetadata {
return new CompileTypeMetadata(
{name: data['name'], moduleUrl: data['moduleUrl'], isHost: data['isHost']});
}
toJson(): StringMap<string, any> {
return {
// Note: Runtime type can't be serialized...
'name': this.name,
'moduleUrl': this.moduleUrl,
'isHost': this.isHost
};
}
}
export class CompileTemplateMetadata {
encapsulation: ViewEncapsulation;
template: string;
templateUrl: string;
styles: string[];
styleUrls: string[];
ngContentSelectors: string[];
constructor({encapsulation, template, templateUrl, styles, styleUrls, ngContentSelectors}: {
encapsulation?: ViewEncapsulation,
template?: string,
templateUrl?: string,
styles?: string[],
styleUrls?: string[],
ngContentSelectors?: string[]
} = {}) {
this.encapsulation = encapsulation;
this.template = template;
this.templateUrl = templateUrl;
this.styles = isPresent(styles) ? styles : [];
this.styleUrls = isPresent(styleUrls) ? styleUrls : [];
this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
}
static fromJson(data: StringMap<string, any>): CompileTemplateMetadata {
return new CompileTemplateMetadata({
encapsulation: isPresent(data['encapsulation']) ?
VIEW_ENCAPSULATION_VALUES[data['encapsulation']] :
data['encapsulation'],
template: data['template'],
templateUrl: data['templateUrl'],
styles: data['styles'],
styleUrls: data['styleUrls'],
ngContentSelectors: data['ngContentSelectors']
});
}
toJson(): StringMap<string, any> {
return {
'encapsulation':
isPresent(this.encapsulation) ? serializeEnum(this.encapsulation) : this.encapsulation,
'template': this.template,
'templateUrl': this.templateUrl,
'styles': this.styles,
'styleUrls': this.styleUrls,
'ngContentSelectors': this.ngContentSelectors
};
}
}
export class CompileDirectiveMetadata {
static create({type, isComponent, dynamicLoadable, selector, exportAs, changeDetection, inputs,
outputs, host, lifecycleHooks, template}: {
type?: CompileTypeMetadata,
isComponent?: boolean,
dynamicLoadable?: boolean,
selector?: string,
exportAs?: string,
changeDetection?: ChangeDetectionStrategy,
inputs?: string[],
outputs?: string[],
host?: StringMap<string, string>,
lifecycleHooks?: LifecycleHooks[],
template?: CompileTemplateMetadata
} = {}): CompileDirectiveMetadata {
var hostListeners = {};
var hostProperties = {};
var hostAttributes = {};
if (isPresent(host)) {
StringMapWrapper.forEach(host, (value: string, key: string) => {
var matches = RegExpWrapper.firstMatch(HOST_REG_EXP, key);
if (isBlank(matches)) {
hostAttributes[key] = value;
} else if (isPresent(matches[1])) {
hostProperties[matches[1]] = value;
} else if (isPresent(matches[2])) {
hostListeners[matches[2]] = value;
}
});
}
var inputsMap = {};
if (isPresent(inputs)) {
inputs.forEach((bindConfig: string) => {
// canonical syntax: `dirProp: elProp`
// if there is no `:`, use dirProp = elProp
var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]);
inputsMap[parts[0]] = parts[1];
});
}
var outputsMap = {};
if (isPresent(outputs)) {
outputs.forEach((bindConfig: string) => {
// canonical syntax: `dirProp: elProp`
// if there is no `:`, use dirProp = elProp
var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]);
outputsMap[parts[0]] = parts[1];
});
}
return new CompileDirectiveMetadata({
type: type,
isComponent: normalizeBool(isComponent),
dynamicLoadable: normalizeBool(dynamicLoadable),
selector: selector,
exportAs: exportAs,
changeDetection: changeDetection,
inputs: inputsMap,
outputs: outputsMap,
hostListeners: hostListeners,
hostProperties: hostProperties,
hostAttributes: hostAttributes,
lifecycleHooks: isPresent(lifecycleHooks) ? lifecycleHooks : [], template: template
});
}
type: CompileTypeMetadata;
isComponent: boolean;
dynamicLoadable: boolean;
selector: string;
exportAs: string;
changeDetection: ChangeDetectionStrategy;
inputs: StringMap<string, string>;
outputs: StringMap<string, string>;
hostListeners: StringMap<string, string>;
hostProperties: StringMap<string, string>;
hostAttributes: StringMap<string, string>;
lifecycleHooks: LifecycleHooks[];
template: CompileTemplateMetadata;
constructor({type, isComponent, dynamicLoadable, selector, exportAs, changeDetection, inputs,
outputs, hostListeners, hostProperties, hostAttributes, lifecycleHooks, template}: {
type?: CompileTypeMetadata,
isComponent?: boolean,
dynamicLoadable?: boolean,
selector?: string,
exportAs?: string,
changeDetection?: ChangeDetectionStrategy,
inputs?: StringMap<string, string>,
outputs?: StringMap<string, string>,
hostListeners?: StringMap<string, string>,
hostProperties?: StringMap<string, string>,
hostAttributes?: StringMap<string, string>,
lifecycleHooks?: LifecycleHooks[],
template?: CompileTemplateMetadata
} = {}) {
this.type = type;
this.isComponent = isComponent;
this.dynamicLoadable = dynamicLoadable;
this.selector = selector;
this.exportAs = exportAs;
this.changeDetection = changeDetection;
this.inputs = inputs;
this.outputs = outputs;
this.hostListeners = hostListeners;
this.hostProperties = hostProperties;
this.hostAttributes = hostAttributes;
this.lifecycleHooks = lifecycleHooks;
this.template = template;
}
static fromJson(data: StringMap<string, any>): CompileDirectiveMetadata {
return new CompileDirectiveMetadata({
isComponent: data['isComponent'],
dynamicLoadable: data['dynamicLoadable'],
selector: data['selector'],
exportAs: data['exportAs'],
type: isPresent(data['type']) ? CompileTypeMetadata.fromJson(data['type']) : data['type'],
changeDetection: isPresent(data['changeDetection']) ?
CHANGE_DECTION_STRATEGY_VALUES[data['changeDetection']] :
data['changeDetection'],
inputs: data['inputs'],
outputs: data['outputs'],
hostListeners: data['hostListeners'],
hostProperties: data['hostProperties'],
hostAttributes: data['hostAttributes'],
lifecycleHooks:
(<any[]>data['lifecycleHooks']).map(hookValue => LIFECYCLE_HOOKS_VALUES[hookValue]),
template: isPresent(data['template']) ? CompileTemplateMetadata.fromJson(data['template']) :
data['template']
});
}
toJson(): StringMap<string, any> {
return {
'isComponent': this.isComponent,
'dynamicLoadable': this.dynamicLoadable,
'selector': this.selector,
'exportAs': this.exportAs,
'type': isPresent(this.type) ? this.type.toJson() : this.type,
'changeDetection': isPresent(this.changeDetection) ? serializeEnum(this.changeDetection) :
this.changeDetection,
'inputs': this.inputs,
'outputs': this.outputs,
'hostListeners': this.hostListeners,
'hostProperties': this.hostProperties,
'hostAttributes': this.hostAttributes,
'lifecycleHooks': this.lifecycleHooks.map(hook => serializeEnum(hook)),
'template': isPresent(this.template) ? this.template.toJson() : this.template
};
}
}
export function createHostComponentMeta(componentType: CompileTypeMetadata,
componentSelector: string): CompileDirectiveMetadata {
var template = CssSelector.parse(componentSelector)[0].getMatchingElementTemplate();
return CompileDirectiveMetadata.create({
type: new CompileTypeMetadata({
runtime: Object,
name: `Host${componentType.name}`,
moduleUrl: componentType.moduleUrl,
isHost: true
}),
template: new CompileTemplateMetadata(
{template: template, templateUrl: '', styles: [], styleUrls: [], ngContentSelectors: []}),
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
outputs: [],
host: {},
lifecycleHooks: [],
isComponent: true,
dynamicLoadable: false,
selector: '*'
});
}

View File

@ -0,0 +1,39 @@
import {isPresent} from 'angular2/src/core/facade/lang';
export interface HtmlAst {
sourceInfo: string;
visit(visitor: HtmlAstVisitor, context: any): any;
}
export class HtmlTextAst implements HtmlAst {
constructor(public value: string, public sourceInfo: string) {}
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitText(this, context); }
}
export class HtmlAttrAst implements HtmlAst {
constructor(public name: string, public value: string, public sourceInfo: string) {}
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitAttr(this, context); }
}
export class HtmlElementAst implements HtmlAst {
constructor(public name: string, public attrs: HtmlAttrAst[], public children: HtmlAst[],
public sourceInfo: string) {}
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitElement(this, context); }
}
export interface HtmlAstVisitor {
visitElement(ast: HtmlElementAst, context: any): any;
visitAttr(ast: HtmlAttrAst, context: any): any;
visitText(ast: HtmlTextAst, context: any): any;
}
export function htmlVisitAll(visitor: HtmlAstVisitor, asts: HtmlAst[], context: any = null): any[] {
var result = [];
asts.forEach(ast => {
var astResult = ast.visit(visitor, context);
if (isPresent(astResult)) {
result.push(astResult);
}
});
return result;
}

View File

@ -0,0 +1,120 @@
import {
isPresent,
StringWrapper,
stringify,
assertionsEnabled,
StringJoiner
} from 'angular2/src/core/facade/lang';
import {DOM} from 'angular2/src/core/dom/dom_adapter';
import {
HtmlAst,
HtmlAttrAst,
HtmlTextAst,
HtmlElementAst,
HtmlAstVisitor,
htmlVisitAll
} from './html_ast';
import {escapeDoubleQuoteString} from './util';
import {Injectable} from 'angular2/src/core/di';
const NG_NON_BINDABLE = 'ng-non-bindable';
@Injectable()
export class HtmlParser {
parse(template: string, sourceInfo: string): HtmlAst[] {
var root = DOM.createTemplate(template);
return parseChildNodes(root, sourceInfo);
}
unparse(nodes: HtmlAst[]): string {
var visitor = new UnparseVisitor();
var parts = [];
htmlVisitAll(visitor, nodes, parts);
return parts.join('');
}
}
function parseText(text: Text, indexInParent: number, parentSourceInfo: string): HtmlTextAst {
// TODO(tbosch): add source row/column source info from parse5 / package:html
var value = DOM.getText(text);
return new HtmlTextAst(value,
`${parentSourceInfo} > #text(${value}):nth-child(${indexInParent})`);
}
function parseAttr(element: Element, parentSourceInfo: string, attrName: string, attrValue: string):
HtmlAttrAst {
// TODO(tbosch): add source row/column source info from parse5 / package:html
return new HtmlAttrAst(attrName, attrValue, `${parentSourceInfo}[${attrName}=${attrValue}]`);
}
function parseElement(element: Element, indexInParent: number, parentSourceInfo: string):
HtmlElementAst {
// normalize nodename always as lower case so that following build steps
// can rely on this
var nodeName = DOM.nodeName(element).toLowerCase();
// TODO(tbosch): add source row/column source info from parse5 / package:html
var sourceInfo = `${parentSourceInfo} > ${nodeName}:nth-child(${indexInParent})`;
var attrs = parseAttrs(element, sourceInfo);
var childNodes = parseChildNodes(element, sourceInfo);
return new HtmlElementAst(nodeName, attrs, childNodes, sourceInfo);
}
function parseAttrs(element: Element, elementSourceInfo: string): HtmlAttrAst[] {
// Note: sort the attributes early in the pipeline to get
// consistent results throughout the pipeline, as attribute order is not defined
// in DOM parsers!
var attrMap = DOM.attributeMap(element);
var attrList: string[][] = [];
attrMap.forEach((value, name) => attrList.push([name, value]));
attrList.sort((entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
return attrList.map(entry => parseAttr(element, elementSourceInfo, entry[0], entry[1]));
}
function parseChildNodes(element: Element, parentSourceInfo: string): HtmlAst[] {
var root = DOM.templateAwareRoot(element);
var childNodes = DOM.childNodesAsList(root);
var result = [];
var index = 0;
childNodes.forEach(childNode => {
var childResult = null;
if (DOM.isTextNode(childNode)) {
var text = <Text>childNode;
childResult = parseText(text, index, parentSourceInfo);
} else if (DOM.isElementNode(childNode)) {
var el = <Element>childNode;
childResult = parseElement(el, index, parentSourceInfo);
}
if (isPresent(childResult)) {
// Won't have a childResult for e.g. comment nodes
result.push(childResult);
}
index++;
});
return result;
}
class UnparseVisitor implements HtmlAstVisitor {
visitElement(ast: HtmlElementAst, parts: string[]): any {
parts.push(`<${ast.name}`);
var attrs = [];
htmlVisitAll(this, ast.attrs, attrs);
if (ast.attrs.length > 0) {
parts.push(' ');
parts.push(attrs.join(' '));
}
parts.push(`>`);
htmlVisitAll(this, ast.children, parts);
parts.push(`</${ast.name}>`);
return null;
}
visitAttr(ast: HtmlAttrAst, parts: string[]): any {
parts.push(`${ast.name}=${escapeDoubleQuoteString(ast.value)}`);
return null;
}
visitText(ast: HtmlTextAst, parts: string[]): any {
parts.push(ast.value);
return null;
}
}

View File

@ -0,0 +1,28 @@
import {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 {TemplateCompiler} from './template_compiler';
import {Injectable} from 'angular2/src/core/di';
import {Type} from 'angular2/src/core/facade/lang';
import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async';
@Injectable()
export class RuntimeCompiler extends Compiler {
/**
* @private
*/
constructor(_protoViewFactory: ProtoViewFactory, private _templateCompiler: TemplateCompiler) {
super(_protoViewFactory);
}
compileInHost(componentType: Type): Promise<ProtoViewRef> {
return this._templateCompiler.compileHostComponentRuntime(componentType)
.then(compiledHostTemplate => internalCreateProtoView(this, compiledHostTemplate));
}
clearCache() {
super.clearCache();
this._templateCompiler.clearCache();
}
}

View File

@ -0,0 +1,121 @@
import {resolveForwardRef} from 'angular2/src/core/di';
import {
Type,
isBlank,
isPresent,
isArray,
stringify,
RegExpWrapper
} from 'angular2/src/core/facade/lang';
import {BaseException} from 'angular2/src/core/facade/exceptions';
import {MapWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
import * as cpl from './directive_metadata';
import * as dirAnn from 'angular2/src/core/metadata/directives';
import {DirectiveResolver} from 'angular2/src/core/linker/directive_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} from 'angular2/src/core/di';
import {MODULE_SUFFIX} from './util';
// group 1: "property" from "[property]"
// group 2: "event" from "(event)"
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;
@Injectable()
export class RuntimeMetadataResolver {
private _cache = new Map<Type, cpl.CompileDirectiveMetadata>();
constructor(private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver) {}
getMetadata(directiveType: Type): cpl.CompileDirectiveMetadata {
var meta = this._cache.get(directiveType);
if (isBlank(meta)) {
var directiveAnnotation = this._directiveResolver.resolve(directiveType);
var moduleUrl = calcModuleUrl(directiveType, directiveAnnotation);
var templateMeta = null;
var changeDetectionStrategy = null;
if (directiveAnnotation instanceof dirAnn.ComponentMetadata) {
var compAnnotation = <dirAnn.ComponentMetadata>directiveAnnotation;
var viewAnnotation = this._viewResolver.resolve(directiveType);
templateMeta = new cpl.CompileTemplateMetadata({
encapsulation: viewAnnotation.encapsulation,
template: viewAnnotation.template,
templateUrl: viewAnnotation.templateUrl,
styles: viewAnnotation.styles,
styleUrls: viewAnnotation.styleUrls
});
changeDetectionStrategy = compAnnotation.changeDetection;
}
meta = cpl.CompileDirectiveMetadata.create({
selector: directiveAnnotation.selector,
exportAs: directiveAnnotation.exportAs,
isComponent: isPresent(templateMeta),
dynamicLoadable: true,
type: new cpl.CompileTypeMetadata(
{name: stringify(directiveType), moduleUrl: moduleUrl, runtime: directiveType}),
template: templateMeta,
changeDetection: changeDetectionStrategy,
inputs: directiveAnnotation.inputs,
outputs: directiveAnnotation.outputs,
host: directiveAnnotation.host,
lifecycleHooks: ListWrapper.filter(LIFECYCLE_HOOKS_VALUES,
hook => hasLifecycleHook(hook, directiveType))
});
this._cache.set(directiveType, meta);
}
return meta;
}
getViewDirectivesMetadata(component: Type): cpl.CompileDirectiveMetadata[] {
var view = this._viewResolver.resolve(component);
var directives = flattenDirectives(view);
for (var i = 0; i < directives.length; i++) {
if (!isValidDirective(directives[i])) {
throw new BaseException(
`Unexpected directive value '${stringify(directives[i])}' on the View of component '${stringify(component)}'`);
}
}
return removeDuplicatedDirectives(directives.map(type => this.getMetadata(type)));
}
}
function removeDuplicatedDirectives(directives: cpl.CompileDirectiveMetadata[]):
cpl.CompileDirectiveMetadata[] {
var directivesMap = new Map<Type, cpl.CompileDirectiveMetadata>();
directives.forEach((dirMeta) => { directivesMap.set(dirMeta.type.runtime, dirMeta); });
return MapWrapper.values(directivesMap);
}
function flattenDirectives(view: ViewMetadata): Type[] {
if (isBlank(view.directives)) return [];
var directives = [];
flattenList(view.directives, directives);
return directives;
}
function flattenList(tree: any[], out: Array<Type | any[]>): void {
for (var i = 0; i < tree.length; i++) {
var item = resolveForwardRef(tree[i]);
if (isArray(item)) {
flattenList(item, out);
} else {
out.push(item);
}
}
}
function isValidDirective(value: Type): boolean {
return isPresent(value) && (value instanceof Type);
}
function calcModuleUrl(type: Type, directiveAnnotation: dirAnn.DirectiveMetadata): string {
if (isPresent(directiveAnnotation.moduleId)) {
return `package:${directiveAnnotation.moduleId}${MODULE_SUFFIX}`;
} else {
return reflector.importUri(type);
}
}

View File

@ -0,0 +1,44 @@
import {StringWrapper, isBlank} from 'angular2/src/core/facade/lang';
var MODULE_REGEXP = /#MODULE\[([^\]]*)\]/g;
export function moduleRef(moduleUrl): string {
return `#MODULE[${moduleUrl}]`;
}
export class SourceModule {
constructor(public moduleUrl: string, public sourceWithModuleRefs: string) {}
getSourceWithImports(): SourceWithImports {
var moduleAliases = {};
var imports: string[][] = [];
var newSource =
StringWrapper.replaceAllMapped(this.sourceWithModuleRefs, MODULE_REGEXP, (match) => {
var moduleUrl = match[1];
var alias = moduleAliases[moduleUrl];
if (isBlank(alias)) {
if (moduleUrl == this.moduleUrl) {
alias = '';
} else {
alias = `import${imports.length}`;
imports.push([moduleUrl, alias]);
}
moduleAliases[moduleUrl] = alias;
}
return alias.length > 0 ? `${alias}.` : '';
});
return new SourceWithImports(newSource, imports);
}
}
export class SourceExpression {
constructor(public declarations: string[], public expression: string) {}
}
export class SourceExpressions {
constructor(public declarations: string[], public expressions: string[]) {}
}
export class SourceWithImports {
constructor(public source: string, public imports: string[][]) {}
}

View File

@ -0,0 +1,151 @@
import {CompileTypeMetadata, CompileTemplateMetadata} from './directive_metadata';
import {SourceModule, SourceExpression, moduleRef} from './source_module';
import {ViewEncapsulation} from 'angular2/src/core/render/api';
import {XHR} from 'angular2/src/core/render/xhr';
import {StringWrapper, isBlank} from 'angular2/src/core/facade/lang';
import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async';
import {ShadowCss} from 'angular2/src/core/render/dom/compiler/shadow_css';
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
import {resolveStyleUrls} from './style_url_resolver';
import {
escapeSingleQuoteString,
IS_DART,
codeGenConcatArray,
codeGenMapArray,
codeGenReplaceAll,
codeGenExportVariable,
codeGenToString,
MODULE_SUFFIX
} from './util';
import {Injectable} from 'angular2/src/core/di';
const COMPONENT_VARIABLE = '%COMP%';
var COMPONENT_REGEX = /%COMP%/g;
const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
const HOST_ATTR_EXPR = `'_nghost-'+${COMPONENT_VARIABLE}`;
const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
const CONTENT_ATTR_EXPR = `'_ngcontent-'+${COMPONENT_VARIABLE}`;
@Injectable()
export class StyleCompiler {
private _styleCache: Map<string, Promise<string[]>> = new Map<string, Promise<string[]>>();
private _shadowCss: ShadowCss = new ShadowCss();
constructor(private _xhr: XHR, private _urlResolver: UrlResolver) {}
compileComponentRuntime(appId: string, templateId: number,
template: CompileTemplateMetadata): Promise<string[]> {
var styles = template.styles;
var styleAbsUrls = template.styleUrls;
return this._loadStyles(styles, styleAbsUrls,
template.encapsulation === ViewEncapsulation.Emulated)
.then(styles => styles.map(style => StringWrapper.replaceAll(
style, COMPONENT_REGEX, componentId(appId, templateId))));
}
compileComponentCodeGen(appIdExpression: string, templateIdExpression: string,
template: CompileTemplateMetadata): SourceExpression {
var shim = template.encapsulation === ViewEncapsulation.Emulated;
var suffix;
if (shim) {
suffix = codeGenMapArray(
['style'],
`style${codeGenReplaceAll(COMPONENT_VARIABLE, componentIdExpression(appIdExpression, templateIdExpression))}`);
} else {
suffix = '';
}
return this._styleCodeGen(template.styles, template.styleUrls, shim, suffix);
}
compileStylesheetCodeGen(stylesheetUrl: string, cssText: string): SourceModule[] {
var styleWithImports = resolveStyleUrls(this._urlResolver, stylesheetUrl, cssText);
return [
this._styleModule(
stylesheetUrl, false,
this._styleCodeGen([styleWithImports.style], styleWithImports.styleUrls, false, '')),
this._styleModule(
stylesheetUrl, true,
this._styleCodeGen([styleWithImports.style], styleWithImports.styleUrls, true, ''))
];
}
clearCache() { this._styleCache.clear(); }
private _loadStyles(plainStyles: string[], absUrls: string[],
encapsulate: boolean): Promise<string[]> {
var promises = absUrls.map((absUrl) => {
var cacheKey = `${absUrl}${encapsulate ? '.shim' : ''}`;
var result = this._styleCache.get(cacheKey);
if (isBlank(result)) {
result = this._xhr.get(absUrl).then((style) => {
var styleWithImports = resolveStyleUrls(this._urlResolver, absUrl, style);
return this._loadStyles([styleWithImports.style], styleWithImports.styleUrls,
encapsulate);
});
this._styleCache.set(cacheKey, result);
}
return result;
});
return PromiseWrapper.all(promises).then((nestedStyles: string[][]) => {
var result = plainStyles.map(plainStyle => this._shimIfNeeded(plainStyle, encapsulate));
nestedStyles.forEach(styles => styles.forEach(style => result.push(style)));
return result;
});
}
private _styleCodeGen(plainStyles: string[], absUrls: string[], shim: boolean,
suffix: string): SourceExpression {
var expressionSource = `(`;
expressionSource +=
`[${plainStyles.map( plainStyle => escapeSingleQuoteString(this._shimIfNeeded(plainStyle, shim)) ).join(',')}]`;
for (var i = 0; i < absUrls.length; i++) {
var moduleUrl = this._createModuleUrl(absUrls[i], shim);
expressionSource += codeGenConcatArray(`${moduleRef(moduleUrl)}STYLES`);
}
expressionSource += `)${suffix}`;
return new SourceExpression([], expressionSource);
}
private _styleModule(stylesheetUrl: string, shim: boolean,
expression: SourceExpression): SourceModule {
var moduleSource = `
${expression.declarations.join('\n')}
${codeGenExportVariable('STYLES')}${expression.expression};
`;
return new SourceModule(this._createModuleUrl(stylesheetUrl, shim), moduleSource);
}
private _shimIfNeeded(style: string, shim: boolean): string {
return shim ? this._shadowCss.shimCssText(style, CONTENT_ATTR, HOST_ATTR) : style;
}
private _createModuleUrl(stylesheetUrl: string, shim: boolean): string {
return shim ? `${stylesheetUrl}.shim${MODULE_SUFFIX}` : `${stylesheetUrl}${MODULE_SUFFIX}`;
}
}
export function shimContentAttribute(appId: string, templateId: number): string {
return StringWrapper.replaceAll(CONTENT_ATTR, COMPONENT_REGEX, componentId(appId, templateId));
}
export function shimContentAttributeExpr(appIdExpr: string, templateIdExpr: string): string {
return StringWrapper.replaceAll(CONTENT_ATTR_EXPR, COMPONENT_REGEX,
componentIdExpression(appIdExpr, templateIdExpr));
}
export function shimHostAttribute(appId: string, templateId: number): string {
return StringWrapper.replaceAll(HOST_ATTR, COMPONENT_REGEX, componentId(appId, templateId));
}
export function shimHostAttributeExpr(appIdExpr: string, templateIdExpr: string): string {
return StringWrapper.replaceAll(HOST_ATTR_EXPR, COMPONENT_REGEX,
componentIdExpression(appIdExpr, templateIdExpr));
}
function componentId(appId: string, templateId: number): string {
return `${appId}-${templateId}`;
}
function componentIdExpression(appIdExpression: string, templateIdExpression: string): string {
return `${appIdExpression}+'-'+${codeGenToString(templateIdExpression)}`;
}

View File

@ -0,0 +1,52 @@
// Some of the code comes from WebComponents.JS
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
import {RegExp, RegExpWrapper, StringWrapper, isPresent} from 'angular2/src/core/facade/lang';
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
/**
* Rewrites URLs by resolving '@import' and 'url()' URLs from the given base URL,
* removes and returns the @import urls
*/
export function resolveStyleUrls(resolver: UrlResolver, baseUrl: string, cssText: string):
StyleWithImports {
var foundUrls = [];
cssText = extractUrls(resolver, baseUrl, cssText, foundUrls);
cssText = replaceUrls(resolver, baseUrl, cssText);
return new StyleWithImports(cssText, foundUrls);
}
export class StyleWithImports {
constructor(public style: string, public styleUrls: string[]) {}
}
function extractUrls(resolver: UrlResolver, baseUrl: string, cssText: string, foundUrls: string[]):
string {
return StringWrapper.replaceAllMapped(cssText, _cssImportRe, (m) => {
var url = isPresent(m[1]) ? m[1] : m[2];
foundUrls.push(resolver.resolve(baseUrl, url));
return '';
});
}
function replaceUrls(resolver: UrlResolver, baseUrl: string, cssText: string): string {
return StringWrapper.replaceAllMapped(cssText, _cssUrlRe, (m) => {
var pre = m[1];
var originalUrl = m[2];
if (RegExpWrapper.test(_dataUrlRe, originalUrl)) {
// Do not attempt to resolve data: URLs
return m[0];
}
var url = StringWrapper.replaceAll(originalUrl, _quoteRe, '');
var post = m[3];
var resolvedUrl = resolver.resolve(baseUrl, url);
return pre + "'" + resolvedUrl + "'" + post;
});
}
var _cssUrlRe = /(url\()([^)]*)(\))/g;
var _cssImportRe = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g;
var _quoteRe = /['"]/g;
var _dataUrlRe = /^['"]?data:/g;

View File

@ -0,0 +1,145 @@
import {AST} from 'angular2/src/core/change_detection/change_detection';
import {isPresent} from 'angular2/src/core/facade/lang';
import {CompileDirectiveMetadata} from './directive_metadata';
export interface TemplateAst {
sourceInfo: string;
visit(visitor: TemplateAstVisitor, context: any): any;
}
export class TextAst implements TemplateAst {
constructor(public value: string, public ngContentIndex: number, public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitText(this, context); }
}
export class BoundTextAst implements TemplateAst {
constructor(public value: AST, public ngContentIndex: number, public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitBoundText(this, context);
}
}
export class AttrAst implements TemplateAst {
constructor(public name: string, public value: string, public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitAttr(this, context); }
}
export class BoundElementPropertyAst implements TemplateAst {
constructor(public name: string, public type: PropertyBindingType, public value: AST,
public unit: string, public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitElementProperty(this, context);
}
}
export class BoundEventAst implements TemplateAst {
constructor(public name: string, public target: string, public handler: AST,
public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitEvent(this, context);
}
get fullName() {
if (isPresent(this.target)) {
return `${this.target}:${this.name}`;
} else {
return this.name;
}
}
}
export class VariableAst implements TemplateAst {
constructor(public name: string, public value: string, public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitVariable(this, context);
}
}
export class ElementAst implements TemplateAst {
constructor(public name: string, public attrs: AttrAst[],
public inputs: BoundElementPropertyAst[], public outputs: BoundEventAst[],
public exportAsVars: VariableAst[], public directives: DirectiveAst[],
public children: TemplateAst[], public ngContentIndex: number,
public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitElement(this, context);
}
isBound(): boolean {
return (this.inputs.length > 0 || this.outputs.length > 0 || this.exportAsVars.length > 0 ||
this.directives.length > 0);
}
getComponent(): CompileDirectiveMetadata {
return this.directives.length > 0 && this.directives[0].directive.isComponent ?
this.directives[0].directive :
null;
}
}
export class EmbeddedTemplateAst implements TemplateAst {
constructor(public attrs: AttrAst[], public vars: VariableAst[],
public directives: DirectiveAst[], public children: TemplateAst[],
public ngContentIndex: number, public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitEmbeddedTemplate(this, context);
}
}
export class BoundDirectivePropertyAst implements TemplateAst {
constructor(public directiveName: string, public templateName: string, public value: AST,
public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitDirectiveProperty(this, context);
}
}
export class DirectiveAst implements TemplateAst {
constructor(public directive: CompileDirectiveMetadata,
public inputs: BoundDirectivePropertyAst[],
public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[],
public exportAsVars: VariableAst[], public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitDirective(this, context);
}
}
export class NgContentAst implements TemplateAst {
constructor(public ngContentIndex: number, public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitNgContent(this, context);
}
}
export enum PropertyBindingType {
Property,
Attribute,
Class,
Style
}
export interface TemplateAstVisitor {
visitNgContent(ast: NgContentAst, context: any): any;
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any;
visitElement(ast: ElementAst, context: any): any;
visitVariable(ast: VariableAst, context: any): any;
visitEvent(ast: BoundEventAst, context: any): any;
visitElementProperty(ast: BoundElementPropertyAst, context: any): any;
visitAttr(ast: AttrAst, context: any): any;
visitBoundText(ast: BoundTextAst, context: any): any;
visitText(ast: TextAst, context: any): any;
visitDirective(ast: DirectiveAst, context: any): any;
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any;
}
export function templateVisitAll(visitor: TemplateAstVisitor, asts: TemplateAst[],
context: any = null): any[] {
var result = [];
asts.forEach(ast => {
var astResult = ast.visit(visitor, context);
if (isPresent(astResult)) {
result.push(astResult);
}
});
return result;
}

View File

@ -0,0 +1,273 @@
import {Type, Json, isBlank, stringify} from 'angular2/src/core/facade/lang';
import {BaseException} from 'angular2/src/core/facade/exceptions';
import {ListWrapper, SetWrapper} from 'angular2/src/core/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async';
import {
CompiledTemplate,
TemplateCmd,
nextTemplateId,
CompiledHostTemplate
} from 'angular2/src/core/linker/template_commands';
import {
createHostComponentMeta,
CompileDirectiveMetadata,
CompileTypeMetadata,
CompileTemplateMetadata
} from './directive_metadata';
import {TemplateAst} from './template_ast';
import {Injectable} from 'angular2/src/core/di';
import {SourceModule, moduleRef} from './source_module';
import {ChangeDetectionCompiler} from './change_detector_compiler';
import {StyleCompiler} from './style_compiler';
import {CommandCompiler} from './command_compiler';
import {TemplateParser} from './template_parser';
import {TemplateNormalizer} from './template_normalizer';
import {RuntimeMetadataResolver} from './runtime_metadata';
import {APP_ID} from 'angular2/src/core/render/dom/dom_tokens';
import {TEMPLATE_COMMANDS_MODULE_REF} from './command_compiler';
import {
IS_DART,
codeGenExportVariable,
escapeSingleQuoteString,
codeGenValueFn,
MODULE_SUFFIX
} from './util';
import {Inject} from 'angular2/src/core/di';
@Injectable()
export class TemplateCompiler {
private _hostCacheKeys = new Map<Type, any>();
private _compiledTemplateCache = new Map<any, CompiledTemplate>();
private _compiledTemplateDone = new Map<any, Promise<CompiledTemplate>>();
private _appId: string;
constructor(private _runtimeMetadataResolver: RuntimeMetadataResolver,
private _templateNormalizer: TemplateNormalizer,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _commandCompiler: CommandCompiler,
private _cdCompiler: ChangeDetectionCompiler, @Inject(APP_ID) appId: string) {
this._appId = appId;
}
normalizeDirectiveMetadata(directive:
CompileDirectiveMetadata): Promise<CompileDirectiveMetadata> {
if (!directive.isComponent) {
// For non components there is nothing to be normalized yet.
return PromiseWrapper.resolve(directive);
}
var normalizedTemplatePromise;
if (directive.isComponent) {
normalizedTemplatePromise =
this._templateNormalizer.normalizeTemplate(directive.type, directive.template);
} else {
normalizedTemplatePromise = PromiseWrapper.resolve(null);
}
return normalizedTemplatePromise.then(
(normalizedTemplate) => new CompileDirectiveMetadata({
type: directive.type,
isComponent: directive.isComponent,
dynamicLoadable: directive.dynamicLoadable,
selector: directive.selector,
exportAs: directive.exportAs,
changeDetection: directive.changeDetection,
inputs: directive.inputs,
outputs: directive.outputs,
hostListeners: directive.hostListeners,
hostProperties: directive.hostProperties,
hostAttributes: directive.hostAttributes,
lifecycleHooks: directive.lifecycleHooks, template: normalizedTemplate
}));
}
compileHostComponentRuntime(type: Type): Promise<CompiledHostTemplate> {
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());
}
return this._compiledTemplateDone.get(hostCacheKey)
.then(compiledTemplate => new CompiledHostTemplate(() => compiledTemplate));
}
clearCache() {
this._hostCacheKeys.clear();
this._styleCompiler.clearCache();
this._compiledTemplateCache.clear();
this._compiledTemplateDone.clear();
}
private _compileComponentRuntime(cacheKey: any, compMeta: CompileDirectiveMetadata,
viewDirectives: CompileDirectiveMetadata[],
compilingComponentCacheKeys: Set<any>): CompiledTemplate {
var compiledTemplate = this._compiledTemplateCache.get(cacheKey);
var done = this._compiledTemplateDone.get(cacheKey);
if (isBlank(compiledTemplate)) {
var styles;
var changeDetectorFactory;
var commands;
var templateId = nextTemplateId();
compiledTemplate =
new CompiledTemplate(templateId, (_a, _b) => [changeDetectorFactory, commands, styles]);
this._compiledTemplateCache.set(cacheKey, compiledTemplate);
compilingComponentCacheKeys.add(cacheKey);
done =
PromiseWrapper.all([
<any>this._styleCompiler.compileComponentRuntime(this._appId, templateId,
compMeta.template)
].concat(viewDirectives.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];
styles = stylesAndNormalizedViewDirMetas[0];
commands = this._compileCommandsRuntime(compMeta, templateId, parsedTemplate,
changeDetectorFactories,
compilingComponentCacheKeys, childPromises);
return PromiseWrapper.all(childPromises);
})
.then((_) => {
SetWrapper.delete(compilingComponentCacheKeys, cacheKey);
return compiledTemplate;
});
this._compiledTemplateDone.set(cacheKey, done);
}
return compiledTemplate;
}
private _compileCommandsRuntime(compMeta: CompileDirectiveMetadata, templateId: number,
parsedTemplate: TemplateAst[],
changeDetectorFactories: Function[],
compilingComponentCacheKeys: Set<Type>,
childPromises: Promise<any>[]): TemplateCmd[] {
return this._commandCompiler.compileComponentRuntime(
compMeta, this._appId, templateId, 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;
});
}
compileTemplatesCodeGen(components: NormalizedComponentWithViewDirectives[]): SourceModule {
if (components.length === 0) {
throw new BaseException('No components given');
}
var declarations = [];
var templateArguments = [];
var componentMetas: CompileDirectiveMetadata[] = [];
var templateIdVariable = 'templateId';
var appIdVariable = 'appId';
components.forEach(componentWithDirs => {
var compMeta = <CompileDirectiveMetadata>componentWithDirs.component;
assertComponent(compMeta);
componentMetas.push(compMeta);
this._processTemplateCodeGen(compMeta, appIdVariable, templateIdVariable,
<CompileDirectiveMetadata[]>componentWithDirs.directives,
declarations, templateArguments);
if (compMeta.dynamicLoadable) {
var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector);
componentMetas.push(hostMeta);
this._processTemplateCodeGen(hostMeta, appIdVariable, templateIdVariable, [compMeta],
declarations, templateArguments);
}
});
ListWrapper.forEachWithIndex(componentMetas, (compMeta: CompileDirectiveMetadata,
index: number) => {
var templateDataFn = codeGenValueFn([appIdVariable, templateIdVariable],
`[${(<any[]>templateArguments[index]).join(',')}]`);
var compiledTemplateExpr =
`new ${TEMPLATE_COMMANDS_MODULE_REF}CompiledTemplate(${TEMPLATE_COMMANDS_MODULE_REF}nextTemplateId(),${templateDataFn})`;
var variableValueExpr;
if (compMeta.type.isHost) {
var factoryName = `_hostTemplateFactory${index}`;
declarations.push(`${codeGenValueFn([], compiledTemplateExpr, factoryName)};`);
var constructionKeyword = IS_DART ? 'const' : 'new';
variableValueExpr =
`${constructionKeyword} ${TEMPLATE_COMMANDS_MODULE_REF}CompiledHostTemplate(${factoryName})`;
} else {
variableValueExpr = compiledTemplateExpr;
}
declarations.push(
`${codeGenExportVariable(templateVariableName(compMeta.type), compMeta.type.isHost)}${variableValueExpr};`);
});
var moduleUrl = components[0].component.type.moduleUrl;
return new SourceModule(`${templateModuleUrl(moduleUrl)}`, declarations.join('\n'));
}
compileStylesheetCodeGen(stylesheetUrl: string, cssText: string): SourceModule[] {
return this._styleCompiler.compileStylesheetCodeGen(stylesheetUrl, cssText);
}
private _processTemplateCodeGen(compMeta: CompileDirectiveMetadata, appIdExpr: string,
templateIdExpr: string, directives: CompileDirectiveMetadata[],
targetDeclarations: string[], targetTemplateArguments: any[][]) {
var styleExpr =
this._styleCompiler.compileComponentCodeGen(appIdExpr, templateIdExpr, compMeta.template);
var parsedTemplate =
this._templateParser.parse(compMeta.template.template, directives, compMeta.type.name);
var changeDetectorsExprs = this._cdCompiler.compileComponentCodeGen(
compMeta.type, compMeta.changeDetection, parsedTemplate);
var commandsExpr = this._commandCompiler.compileComponentCodeGen(
compMeta, appIdExpr, templateIdExpr, parsedTemplate, changeDetectorsExprs.expressions,
codeGenComponentTemplateFactory);
addAll(styleExpr.declarations, targetDeclarations);
addAll(changeDetectorsExprs.declarations, targetDeclarations);
addAll(commandsExpr.declarations, targetDeclarations);
targetTemplateArguments.push(
[changeDetectorsExprs.expressions[0], commandsExpr.expression, styleExpr.expression]);
}
}
export class NormalizedComponentWithViewDirectives {
constructor(public component: CompileDirectiveMetadata,
public directives: CompileDirectiveMetadata[]) {}
}
function assertComponent(meta: CompileDirectiveMetadata) {
if (!meta.isComponent) {
throw new BaseException(`Could not compile '${meta.type.name}' because it is not a component.`);
}
}
function templateVariableName(type: CompileTypeMetadata): string {
return `${type.name}Template`;
}
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 codeGenComponentTemplateFactory(nestedCompType: CompileDirectiveMetadata): string {
return `${moduleRef(templateModuleUrl(nestedCompType.type.moduleUrl))}${templateVariableName(nestedCompType.type)}`;
}

View File

@ -0,0 +1,119 @@
import {
CompileTypeMetadata,
CompileDirectiveMetadata,
CompileTemplateMetadata
} from './directive_metadata';
import {isPresent, isBlank} from 'angular2/src/core/facade/lang';
import {BaseException} from 'angular2/src/core/facade/exceptions';
import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async';
import {XHR} from 'angular2/src/core/render/xhr';
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
import {resolveStyleUrls} from './style_url_resolver';
import {Injectable} from 'angular2/src/core/di';
import {ViewEncapsulation} from 'angular2/src/core/render/api';
import {
HtmlAstVisitor,
HtmlElementAst,
HtmlTextAst,
HtmlAttrAst,
HtmlAst,
htmlVisitAll
} from './html_ast';
import {HtmlParser} from './html_parser';
import {preparseElement, PreparsedElement, PreparsedElementType} from './template_preparser';
@Injectable()
export class TemplateNormalizer {
constructor(private _xhr: XHR, private _urlResolver: UrlResolver,
private _domParser: HtmlParser) {}
normalizeTemplate(directiveType: CompileTypeMetadata,
template: CompileTemplateMetadata): Promise<CompileTemplateMetadata> {
if (isPresent(template.template)) {
return PromiseWrapper.resolve(this.normalizeLoadedTemplate(
directiveType, template, template.template, directiveType.moduleUrl));
} else if (isPresent(template.templateUrl)) {
var sourceAbsUrl = this._urlResolver.resolve(directiveType.moduleUrl, template.templateUrl);
return this._xhr.get(sourceAbsUrl)
.then(templateContent => this.normalizeLoadedTemplate(directiveType, template,
templateContent, sourceAbsUrl));
} else {
throw new BaseException(`No template specified for component ${directiveType.name}`);
}
}
normalizeLoadedTemplate(directiveType: CompileTypeMetadata, templateMeta: CompileTemplateMetadata,
template: string, templateAbsUrl: string): CompileTemplateMetadata {
var domNodes = this._domParser.parse(template, directiveType.name);
var visitor = new TemplatePreparseVisitor();
htmlVisitAll(visitor, domNodes);
var allStyles = templateMeta.styles.concat(visitor.styles);
var allStyleAbsUrls =
visitor.styleUrls.map(url => this._urlResolver.resolve(templateAbsUrl, url))
.concat(templateMeta.styleUrls.map(
url => this._urlResolver.resolve(directiveType.moduleUrl, url)));
var allResolvedStyles = allStyles.map(style => {
var styleWithImports = resolveStyleUrls(this._urlResolver, templateAbsUrl, style);
styleWithImports.styleUrls.forEach(styleUrl => allStyleAbsUrls.push(styleUrl));
return styleWithImports.style;
});
var encapsulation = templateMeta.encapsulation;
if (encapsulation === ViewEncapsulation.Emulated && allResolvedStyles.length === 0 &&
allStyleAbsUrls.length === 0) {
encapsulation = ViewEncapsulation.None;
}
return new CompileTemplateMetadata({
encapsulation: encapsulation,
template: template,
templateUrl: templateAbsUrl,
styles: allResolvedStyles,
styleUrls: allStyleAbsUrls,
ngContentSelectors: visitor.ngContentSelectors
});
}
}
class TemplatePreparseVisitor implements HtmlAstVisitor {
ngContentSelectors: string[] = [];
styles: string[] = [];
styleUrls: string[] = [];
ngNonBindableStackCount: number = 0;
visitElement(ast: HtmlElementAst, context: any): any {
var preparsedElement = preparseElement(ast);
switch (preparsedElement.type) {
case PreparsedElementType.NG_CONTENT:
if (this.ngNonBindableStackCount === 0) {
this.ngContentSelectors.push(preparsedElement.selectAttr);
}
break;
case PreparsedElementType.STYLE:
var textContent = '';
ast.children.forEach(child => {
if (child instanceof HtmlTextAst) {
textContent += (<HtmlTextAst>child).value;
}
});
this.styles.push(textContent);
break;
case PreparsedElementType.STYLESHEET:
this.styleUrls.push(preparsedElement.hrefAttr);
break;
}
if (preparsedElement.nonBindable) {
this.ngNonBindableStackCount++;
}
htmlVisitAll(this, ast.children);
if (preparsedElement.nonBindable) {
this.ngNonBindableStackCount--;
}
return null;
}
visitAttr(ast: HtmlAttrAst, context: any): any { return null; }
visitText(ast: HtmlTextAst, context: any): any { return null; }
}

View File

@ -0,0 +1,667 @@
import {
MapWrapper,
ListWrapper,
StringMapWrapper,
SetWrapper
} from 'angular2/src/core/facade/collection';
import {
RegExpWrapper,
isPresent,
StringWrapper,
StringJoiner,
stringify,
assertionsEnabled,
isBlank
} from 'angular2/src/core/facade/lang';
import {Injectable} from 'angular2/src/core/di';
import {BaseException} from 'angular2/src/core/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 {HtmlParser} from './html_parser';
import {
ElementAst,
BoundElementPropertyAst,
BoundEventAst,
VariableAst,
TemplateAst,
TextAst,
BoundTextAst,
EmbeddedTemplateAst,
AttrAst,
NgContentAst,
PropertyBindingType,
DirectiveAst,
BoundDirectivePropertyAst
} from './template_ast';
import {CssSelector, SelectorMatcher} from 'angular2/src/core/render/dom/compiler/selector';
import {ElementSchemaRegistry} from 'angular2/src/core/render/dom/schema/element_schema_registry';
import {preparseElement, PreparsedElement, PreparsedElementType} from './template_preparser';
import {
HtmlAstVisitor,
HtmlAst,
HtmlElementAst,
HtmlAttrAst,
HtmlTextAst,
htmlVisitAll
} from './html_ast';
import {dashCaseToCamelCase, camelCaseToDashCase, splitAtColon} from './util';
// Group 1 = "bind-"
// Group 2 = "var-" or "#"
// Group 3 = "on-"
// Group 4 = "bindon-"
// Group 5 = the identifier after "bind-", "var-/#", or "on-"
// Group 6 = idenitifer inside [()]
// Group 7 = idenitifer inside []
// Group 8 = identifier inside ()
var BIND_NAME_REGEXP =
/^(?:(?:(?:(bind-)|(var-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g;
const TEMPLATE_ELEMENT = 'template';
const TEMPLATE_ATTR = 'template';
const TEMPLATE_ATTR_PREFIX = '*';
const CLASS_ATTR = 'class';
var PROPERTY_PARTS_SEPARATOR = new RegExp('\\.');
const ATTRIBUTE_PREFIX = 'attr';
const CLASS_PREFIX = 'class';
const STYLE_PREFIX = 'style';
var TEXT_CSS_SELECTOR = CssSelector.parse('*')[0];
@Injectable()
export class TemplateParser {
constructor(private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry,
private _htmlParser: HtmlParser) {}
parse(template: string, directives: CompileDirectiveMetadata[],
sourceInfo: string): TemplateAst[] {
var parseVisitor = new TemplateParseVisitor(directives, this._exprParser, this._schemaRegistry);
var result =
htmlVisitAll(parseVisitor, this._htmlParser.parse(template, sourceInfo), EMPTY_COMPONENT);
if (parseVisitor.errors.length > 0) {
var errorString = parseVisitor.errors.join('\n');
throw new BaseException(`Template parse errors:\n${errorString}`);
}
return result;
}
}
class TemplateParseVisitor implements HtmlAstVisitor {
selectorMatcher: SelectorMatcher;
errors: string[] = [];
directivesIndex = new Map<CompileDirectiveMetadata, number>();
constructor(directives: CompileDirectiveMetadata[], private _exprParser: Parser,
private _schemaRegistry: ElementSchemaRegistry) {
this.selectorMatcher = new SelectorMatcher();
ListWrapper.forEachWithIndex(directives,
(directive: CompileDirectiveMetadata, index: number) => {
var selector = CssSelector.parse(directive.selector);
this.selectorMatcher.addSelectables(selector, directive);
this.directivesIndex.set(directive, index);
});
}
private _reportError(message: string) { this.errors.push(message); }
private _parseInterpolation(value: string, sourceInfo: string): ASTWithSource {
try {
return this._exprParser.parseInterpolation(value, sourceInfo);
} catch (e) {
this._reportError(`${e}`); // sourceInfo is already contained in the AST
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
private _parseAction(value: string, sourceInfo: string): ASTWithSource {
try {
return this._exprParser.parseAction(value, sourceInfo);
} catch (e) {
this._reportError(`${e}`); // sourceInfo is already contained in the AST
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
private _parseBinding(value: string, sourceInfo: string): ASTWithSource {
try {
return this._exprParser.parseBinding(value, sourceInfo);
} catch (e) {
this._reportError(`${e}`); // sourceInfo is already contained in the AST
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
private _parseTemplateBindings(value: string, sourceInfo: string): TemplateBinding[] {
try {
return this._exprParser.parseTemplateBindings(value, sourceInfo);
} catch (e) {
this._reportError(`${e}`); // sourceInfo is already contained in the AST
return [];
}
}
visitText(ast: HtmlTextAst, component: Component): any {
var ngContentIndex = component.findNgContentIndex(TEXT_CSS_SELECTOR);
var expr = this._parseInterpolation(ast.value, ast.sourceInfo);
if (isPresent(expr)) {
return new BoundTextAst(expr, ngContentIndex, ast.sourceInfo);
} else {
return new TextAst(ast.value, ngContentIndex, ast.sourceInfo);
}
}
visitAttr(ast: HtmlAttrAst, contex: any): any {
return new AttrAst(ast.name, ast.value, ast.sourceInfo);
}
visitElement(element: HtmlElementAst, component: Component): any {
var nodeName = element.name;
var preparsedElement = preparseElement(element);
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
preparsedElement.type === PreparsedElementType.STYLE ||
preparsedElement.type === PreparsedElementType.STYLESHEET) {
// Skipping <script> for security reasons
// Skipping <style> and stylesheets as we already processed them
// in the StyleCompiler
return null;
}
var matchableAttrs: string[][] = [];
var elementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
var vars: VariableAst[] = [];
var events: BoundEventAst[] = [];
var templateElementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
var templateVars: VariableAst[] = [];
var templateMatchableAttrs: string[][] = [];
var hasInlineTemplates = false;
var attrs = [];
element.attrs.forEach(attr => {
matchableAttrs.push([attr.name, attr.value]);
var hasBinding = this._parseAttr(attr, matchableAttrs, elementOrDirectiveProps, events, vars);
var hasTemplateBinding = this._parseInlineTemplateBinding(
attr, templateMatchableAttrs, templateElementOrDirectiveProps, templateVars);
if (!hasBinding && !hasTemplateBinding) {
// don't include the bindings as attributes as well in the AST
attrs.push(this.visitAttr(attr, null));
}
if (hasTemplateBinding) {
hasInlineTemplates = true;
}
});
var isTemplateElement = nodeName == TEMPLATE_ELEMENT;
var elementCssSelector = createElementCssSelector(nodeName, matchableAttrs);
var directives = this._createDirectiveAsts(
element.name, this._parseDirectives(this.selectorMatcher, elementCssSelector),
elementOrDirectiveProps, isTemplateElement ? [] : vars, element.sourceInfo);
var elementProps: BoundElementPropertyAst[] =
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directives);
var children = htmlVisitAll(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this,
element.children, Component.create(directives));
var elementNgContentIndex =
hasInlineTemplates ? null : component.findNgContentIndex(elementCssSelector);
var parsedElement;
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
parsedElement = new NgContentAst(elementNgContentIndex, element.sourceInfo);
} else if (isTemplateElement) {
this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps, events,
element.sourceInfo);
parsedElement = new EmbeddedTemplateAst(attrs, vars, directives, children,
elementNgContentIndex, element.sourceInfo);
} else {
this._assertOnlyOneComponent(directives, element.sourceInfo);
var elementExportAsVars = ListWrapper.filter(vars, varAst => varAst.value.length === 0);
parsedElement =
new ElementAst(nodeName, attrs, elementProps, events, elementExportAsVars, directives,
children, elementNgContentIndex, element.sourceInfo);
}
if (hasInlineTemplates) {
var templateCssSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
var templateDirectives = this._createDirectiveAsts(
element.name, this._parseDirectives(this.selectorMatcher, templateCssSelector),
templateElementOrDirectiveProps, [], element.sourceInfo);
var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, templateElementOrDirectiveProps, templateDirectives);
this._assertNoComponentsNorElementBindingsOnTemplate(templateDirectives, templateElementProps,
[], element.sourceInfo);
parsedElement = new EmbeddedTemplateAst([], templateVars, templateDirectives, [parsedElement],
component.findNgContentIndex(templateCssSelector),
element.sourceInfo);
}
return parsedElement;
}
private _parseInlineTemplateBinding(attr: HtmlAttrAst, targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[],
targetVars: VariableAst[]): boolean {
var templateBindingsSource = null;
if (attr.name == TEMPLATE_ATTR) {
templateBindingsSource = attr.value;
} else if (StringWrapper.startsWith(attr.name, TEMPLATE_ATTR_PREFIX)) {
var key = StringWrapper.substring(attr.name, TEMPLATE_ATTR_PREFIX.length); // remove the star
templateBindingsSource = (attr.value.length == 0) ? key : key + ' ' + attr.value;
}
if (isPresent(templateBindingsSource)) {
var bindings = this._parseTemplateBindings(templateBindingsSource, attr.sourceInfo);
for (var i = 0; i < bindings.length; i++) {
var binding = bindings[i];
var dashCaseKey = camelCaseToDashCase(binding.key);
if (binding.keyIsVar) {
targetVars.push(
new VariableAst(dashCaseToCamelCase(binding.key), binding.name, attr.sourceInfo));
targetMatchableAttrs.push([dashCaseKey, binding.name]);
} else if (isPresent(binding.expression)) {
this._parsePropertyAst(dashCaseKey, binding.expression, attr.sourceInfo,
targetMatchableAttrs, targetProps);
} else {
targetMatchableAttrs.push([dashCaseKey, '']);
}
}
return true;
}
return false;
}
private _parseAttr(attr: HtmlAttrAst, targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[], targetEvents: BoundEventAst[],
targetVars: VariableAst[]): boolean {
var attrName = this._normalizeAttributeName(attr.name);
var attrValue = attr.value;
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
var hasBinding = false;
if (isPresent(bindParts)) {
hasBinding = true;
if (isPresent(bindParts[1])) { // match: bind-prop
this._parseProperty(bindParts[5], attrValue, attr.sourceInfo, targetMatchableAttrs,
targetProps);
} else if (isPresent(
bindParts[2])) { // match: var-name / var-name="iden" / #name / #name="iden"
var identifier = bindParts[5];
this._parseVariable(identifier, attrValue, attr.sourceInfo, targetMatchableAttrs,
targetVars);
} else if (isPresent(bindParts[3])) { // match: on-event
this._parseEvent(bindParts[5], attrValue, attr.sourceInfo, targetMatchableAttrs,
targetEvents);
} else if (isPresent(bindParts[4])) { // match: bindon-prop
this._parseProperty(bindParts[5], attrValue, attr.sourceInfo, targetMatchableAttrs,
targetProps);
this._parseAssignmentEvent(bindParts[5], attrValue, attr.sourceInfo, targetMatchableAttrs,
targetEvents);
} else if (isPresent(bindParts[6])) { // match: [(expr)]
this._parseProperty(bindParts[6], attrValue, attr.sourceInfo, targetMatchableAttrs,
targetProps);
this._parseAssignmentEvent(bindParts[6], attrValue, attr.sourceInfo, targetMatchableAttrs,
targetEvents);
} else if (isPresent(bindParts[7])) { // match: [expr]
this._parseProperty(bindParts[7], attrValue, attr.sourceInfo, targetMatchableAttrs,
targetProps);
} else if (isPresent(bindParts[8])) { // match: (event)
this._parseEvent(bindParts[8], attrValue, attr.sourceInfo, targetMatchableAttrs,
targetEvents);
}
} else {
hasBinding = this._parsePropertyInterpolation(attrName, attrValue, attr.sourceInfo,
targetMatchableAttrs, targetProps);
}
if (!hasBinding) {
this._parseLiteralAttr(attrName, attrValue, attr.sourceInfo, targetProps);
}
return hasBinding;
}
private _normalizeAttributeName(attrName: string): string {
return StringWrapper.startsWith(attrName, 'data-') ? StringWrapper.substring(attrName, 5) :
attrName;
}
private _parseVariable(identifier: string, value: string, sourceInfo: any,
targetMatchableAttrs: string[][], targetVars: VariableAst[]) {
targetVars.push(new VariableAst(dashCaseToCamelCase(identifier), value, sourceInfo));
targetMatchableAttrs.push([identifier, value]);
}
private _parseProperty(name: string, expression: string, sourceInfo: any,
targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[]) {
this._parsePropertyAst(name, this._parseBinding(expression, sourceInfo), sourceInfo,
targetMatchableAttrs, targetProps);
}
private _parsePropertyInterpolation(name: string, value: string, sourceInfo: any,
targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[]): boolean {
var expr = this._parseInterpolation(value, sourceInfo);
if (isPresent(expr)) {
this._parsePropertyAst(name, expr, sourceInfo, targetMatchableAttrs, targetProps);
return true;
}
return false;
}
private _parsePropertyAst(name: string, ast: ASTWithSource, sourceInfo: any,
targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[]) {
targetMatchableAttrs.push([name, ast.source]);
targetProps.push(new BoundElementOrDirectiveProperty(name, ast, false, sourceInfo));
}
private _parseAssignmentEvent(name: string, expression: string, sourceInfo: string,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
this._parseEvent(name, `${expression}=$event`, sourceInfo, targetMatchableAttrs, targetEvents);
}
private _parseEvent(name: string, expression: string, sourceInfo: string,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
// long format: 'target: eventName'
var parts = splitAtColon(name, [null, name]);
var target = parts[0];
var eventName = parts[1];
targetEvents.push(new BoundEventAst(dashCaseToCamelCase(eventName), target,
this._parseAction(expression, sourceInfo), sourceInfo));
// Don't detect directives for event names for now,
// so don't add the event name to the matchableAttrs
}
private _parseLiteralAttr(name: string, value: string, sourceInfo: string,
targetProps: BoundElementOrDirectiveProperty[]) {
targetProps.push(new BoundElementOrDirectiveProperty(
dashCaseToCamelCase(name), this._exprParser.wrapLiteralPrimitive(value, sourceInfo), true,
sourceInfo));
}
private _parseDirectives(selectorMatcher: SelectorMatcher,
elementCssSelector: CssSelector): CompileDirectiveMetadata[] {
var directives = [];
selectorMatcher.match(elementCssSelector,
(selector, directive) => { directives.push(directive); });
// Need to sort the directives so that we get consistent results throughout,
// as selectorMatcher uses Maps inside.
// Also need to make components the first directive in the array
ListWrapper.sort(directives,
(dir1: CompileDirectiveMetadata, dir2: CompileDirectiveMetadata) => {
var dir1Comp = dir1.isComponent;
var dir2Comp = dir2.isComponent;
if (dir1Comp && !dir2Comp) {
return -1;
} else if (!dir1Comp && dir2Comp) {
return 1;
} else {
return this.directivesIndex.get(dir1) - this.directivesIndex.get(dir2);
}
});
return directives;
}
private _createDirectiveAsts(elementName: string, directives: CompileDirectiveMetadata[],
props: BoundElementOrDirectiveProperty[],
possibleExportAsVars: VariableAst[],
sourceInfo: string): DirectiveAst[] {
var matchedVariables = new Set<string>();
var directiveAsts = directives.map((directive: CompileDirectiveMetadata) => {
var hostProperties: BoundElementPropertyAst[] = [];
var hostEvents: BoundEventAst[] = [];
var directiveProperties: BoundDirectivePropertyAst[] = [];
this._createDirectiveHostPropertyAsts(elementName, directive.hostProperties, sourceInfo,
hostProperties);
this._createDirectiveHostEventAsts(directive.hostListeners, sourceInfo, hostEvents);
this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties);
var exportAsVars = [];
possibleExportAsVars.forEach((varAst) => {
if ((varAst.value.length === 0 && directive.isComponent) ||
(directive.exportAs == varAst.value)) {
exportAsVars.push(varAst);
matchedVariables.add(varAst.name);
}
});
return new DirectiveAst(directive, directiveProperties, hostProperties, hostEvents,
exportAsVars, sourceInfo);
});
possibleExportAsVars.forEach((varAst) => {
if (varAst.value.length > 0 && !SetWrapper.has(matchedVariables, varAst.name)) {
this._reportError(
`There is no directive with "exportAs" set to "${varAst.value}" at ${varAst.sourceInfo}`);
}
});
return directiveAsts;
}
private _createDirectiveHostPropertyAsts(elementName: string,
hostProps: StringMap<string, string>, sourceInfo: string,
targetPropertyAsts: BoundElementPropertyAst[]) {
if (isPresent(hostProps)) {
StringMapWrapper.forEach(hostProps, (expression, propName) => {
var exprAst = this._parseBinding(expression, sourceInfo);
targetPropertyAsts.push(
this._createElementPropertyAst(elementName, propName, exprAst, sourceInfo));
});
}
}
private _createDirectiveHostEventAsts(hostListeners: StringMap<string, string>,
sourceInfo: string, targetEventAsts: BoundEventAst[]) {
if (isPresent(hostListeners)) {
StringMapWrapper.forEach(hostListeners, (expression, propName) => {
this._parseEvent(propName, expression, sourceInfo, [], targetEventAsts);
});
}
}
private _createDirectivePropertyAsts(directiveProperties: StringMap<string, string>,
boundProps: BoundElementOrDirectiveProperty[],
targetBoundDirectiveProps: BoundDirectivePropertyAst[]) {
if (isPresent(directiveProperties)) {
var boundPropsByName = new Map<string, BoundElementOrDirectiveProperty>();
boundProps.forEach(boundProp => {
var key = dashCaseToCamelCase(boundProp.name);
var prevValue = boundPropsByName.get(boundProp.name);
if (isBlank(prevValue) || prevValue.isLiteral) {
// give [a]="b" a higher precedence thatn a="b" on the same element
boundPropsByName.set(key, boundProp);
}
});
StringMapWrapper.forEach(directiveProperties, (elProp: string, dirProp: string) => {
elProp = dashCaseToCamelCase(elProp);
var boundProp = boundPropsByName.get(elProp);
// Bindings are optional, so this binding only needs to be set up if an expression is given.
if (isPresent(boundProp)) {
targetBoundDirectiveProps.push(new BoundDirectivePropertyAst(
dirProp, boundProp.name, boundProp.expression, boundProp.sourceInfo));
}
});
}
}
private _createElementPropertyAsts(elementName: string, props: BoundElementOrDirectiveProperty[],
directives: DirectiveAst[]): BoundElementPropertyAst[] {
var boundElementProps: BoundElementPropertyAst[] = [];
var boundDirectivePropsIndex = new Map<string, BoundDirectivePropertyAst>();
directives.forEach((directive: DirectiveAst) => {
directive.inputs.forEach((prop: BoundDirectivePropertyAst) => {
boundDirectivePropsIndex.set(prop.templateName, prop);
});
});
props.forEach((prop: BoundElementOrDirectiveProperty) => {
if (!prop.isLiteral && isBlank(boundDirectivePropsIndex.get(prop.name))) {
boundElementProps.push(this._createElementPropertyAst(elementName, prop.name,
prop.expression, prop.sourceInfo));
}
});
return boundElementProps;
}
private _createElementPropertyAst(elementName: string, name: string, ast: AST,
sourceInfo: any): BoundElementPropertyAst {
var unit = null;
var bindingType;
var boundPropertyName;
var parts = StringWrapper.split(name, PROPERTY_PARTS_SEPARATOR);
if (parts.length === 1) {
boundPropertyName = this._schemaRegistry.getMappedPropName(dashCaseToCamelCase(parts[0]));
bindingType = PropertyBindingType.Property;
if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName)) {
this._reportError(
`Can't bind to '${boundPropertyName}' since it isn't a known native property in ${sourceInfo}`);
}
} else if (parts[0] == ATTRIBUTE_PREFIX) {
boundPropertyName = dashCaseToCamelCase(parts[1]);
bindingType = PropertyBindingType.Attribute;
} else if (parts[0] == CLASS_PREFIX) {
// keep original case!
boundPropertyName = parts[1];
bindingType = PropertyBindingType.Class;
} else if (parts[0] == STYLE_PREFIX) {
unit = parts.length > 2 ? parts[2] : null;
boundPropertyName = dashCaseToCamelCase(parts[1]);
bindingType = PropertyBindingType.Style;
} else {
this._reportError(`Invalid property name ${name} in ${sourceInfo}`);
bindingType = null;
}
return new BoundElementPropertyAst(boundPropertyName, bindingType, ast, unit, sourceInfo);
}
private _findComponentDirectiveNames(directives: DirectiveAst[]): string[] {
var componentTypeNames: string[] = [];
directives.forEach(directive => {
var typeName = directive.directive.type.name;
if (directive.directive.isComponent) {
componentTypeNames.push(typeName);
}
});
return componentTypeNames;
}
private _assertOnlyOneComponent(directives: DirectiveAst[], sourceInfo: string) {
var componentTypeNames = this._findComponentDirectiveNames(directives);
if (componentTypeNames.length > 1) {
this._reportError(
`More than one component: ${componentTypeNames.join(',')} in ${sourceInfo}`);
}
}
private _assertNoComponentsNorElementBindingsOnTemplate(directives: DirectiveAst[],
elementProps: BoundElementPropertyAst[],
events: BoundEventAst[],
sourceInfo: string) {
var componentTypeNames: string[] = this._findComponentDirectiveNames(directives);
if (componentTypeNames.length > 0) {
this._reportError(
`Components on an embedded template: ${componentTypeNames.join(',')} in ${sourceInfo}`);
}
elementProps.forEach(prop => {
this._reportError(
`Property binding ${prop.name} not used by any directive on an embedded template in ${prop.sourceInfo}`);
});
events.forEach(event => {
this._reportError(
`Event binding ${event.name} on an embedded template in ${event.sourceInfo}`);
});
}
}
class NonBindableVisitor implements HtmlAstVisitor {
visitElement(ast: HtmlElementAst, component: Component): ElementAst {
var preparsedElement = preparseElement(ast);
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
preparsedElement.type === PreparsedElementType.STYLE ||
preparsedElement.type === PreparsedElementType.STYLESHEET) {
// Skipping <script> for security reasons
// Skipping <style> and stylesheets as we already processed them
// in the StyleCompiler
return null;
}
var attrNameAndValues = ast.attrs.map(attrAst => [attrAst.name, attrAst.value]);
var selector = createElementCssSelector(ast.name, attrNameAndValues);
var ngContentIndex = component.findNgContentIndex(selector);
var children = htmlVisitAll(this, ast.children, EMPTY_COMPONENT);
return new ElementAst(ast.name, htmlVisitAll(this, ast.attrs), [], [], [], [], children,
ngContentIndex, ast.sourceInfo);
}
visitAttr(ast: HtmlAttrAst, context: any): AttrAst {
return new AttrAst(ast.name, ast.value, ast.sourceInfo);
}
visitText(ast: HtmlTextAst, component: Component): TextAst {
var ngContentIndex = component.findNgContentIndex(TEXT_CSS_SELECTOR);
return new TextAst(ast.value, ngContentIndex, ast.sourceInfo);
}
}
class BoundElementOrDirectiveProperty {
constructor(public name: string, public expression: AST, public isLiteral: boolean,
public sourceInfo: string) {}
}
class ParseError {
constructor(public message: string, public sourceInfo: string) {}
}
export function splitClasses(classAttrValue: string): string[] {
return StringWrapper.split(classAttrValue.trim(), /\s+/g);
}
class Component {
static create(directives: DirectiveAst[]): Component {
if (directives.length === 0 || !directives[0].directive.isComponent) {
return EMPTY_COMPONENT;
}
var matcher = new SelectorMatcher();
var ngContentSelectors = directives[0].directive.template.ngContentSelectors;
var wildcardNgContentIndex = null;
for (var i = 0; i < ngContentSelectors.length; i++) {
var selector = ngContentSelectors[i];
if (StringWrapper.equals(selector, '*')) {
wildcardNgContentIndex = i;
} else {
matcher.addSelectables(CssSelector.parse(ngContentSelectors[i]), i);
}
}
return new Component(matcher, wildcardNgContentIndex);
}
constructor(public ngContentIndexMatcher: SelectorMatcher,
public wildcardNgContentIndex: number) {}
findNgContentIndex(selector: CssSelector): number {
var ngContentIndices = [];
if (isPresent(this.wildcardNgContentIndex)) {
ngContentIndices.push(this.wildcardNgContentIndex);
}
this.ngContentIndexMatcher.match(
selector, (selector, ngContentIndex) => { ngContentIndices.push(ngContentIndex); });
ListWrapper.sort(ngContentIndices);
return ngContentIndices.length > 0 ? ngContentIndices[0] : null;
}
}
function createElementCssSelector(elementName: string, matchableAttrs: string[][]): CssSelector {
var cssSelector = new CssSelector();
cssSelector.setElement(elementName);
for (var i = 0; i < matchableAttrs.length; i++) {
var attrName = matchableAttrs[i][0].toLowerCase();
var attrValue = matchableAttrs[i][1];
cssSelector.addAttribute(attrName, attrValue);
if (attrName == CLASS_ATTR) {
var classes = splitClasses(attrValue);
classes.forEach(className => cssSelector.addClassName(className));
}
}
return cssSelector;
}
var EMPTY_COMPONENT = new Component(new SelectorMatcher(), null);
var NON_BINDABLE_VISITOR = new NonBindableVisitor();

View File

@ -0,0 +1,64 @@
import {HtmlElementAst} from './html_ast';
import {isBlank, isPresent} from 'angular2/src/core/facade/lang';
const NG_CONTENT_SELECT_ATTR = 'select';
const NG_CONTENT_ELEMENT = 'ng-content';
const LINK_ELEMENT = 'link';
const LINK_STYLE_REL_ATTR = 'rel';
const LINK_STYLE_HREF_ATTR = 'href';
const LINK_STYLE_REL_VALUE = 'stylesheet';
const STYLE_ELEMENT = 'style';
const SCRIPT_ELEMENT = 'script';
const NG_NON_BINDABLE_ATTR = 'ng-non-bindable';
export function preparseElement(ast: HtmlElementAst): PreparsedElement {
var selectAttr = null;
var hrefAttr = null;
var relAttr = null;
var nonBindable = false;
ast.attrs.forEach(attr => {
if (attr.name == NG_CONTENT_SELECT_ATTR) {
selectAttr = attr.value;
} else if (attr.name == LINK_STYLE_HREF_ATTR) {
hrefAttr = attr.value;
} else if (attr.name == LINK_STYLE_REL_ATTR) {
relAttr = attr.value;
} else if (attr.name == NG_NON_BINDABLE_ATTR) {
nonBindable = true;
}
});
selectAttr = normalizeNgContentSelect(selectAttr);
var nodeName = ast.name;
var type = PreparsedElementType.OTHER;
if (nodeName == NG_CONTENT_ELEMENT) {
type = PreparsedElementType.NG_CONTENT;
} else if (nodeName == STYLE_ELEMENT) {
type = PreparsedElementType.STYLE;
} else if (nodeName == SCRIPT_ELEMENT) {
type = PreparsedElementType.SCRIPT;
} else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
type = PreparsedElementType.STYLESHEET;
}
return new PreparsedElement(type, selectAttr, hrefAttr, nonBindable);
}
export enum PreparsedElementType {
NG_CONTENT,
STYLE,
STYLESHEET,
SCRIPT,
OTHER
}
export class PreparsedElement {
constructor(public type: PreparsedElementType, public selectAttr: string, public hrefAttr: string,
public nonBindable: boolean) {}
}
function normalizeNgContentSelect(selectAttr: string): string {
if (isBlank(selectAttr) || selectAttr.length === 0) {
return '*';
}
return selectAttr;
}

View File

@ -0,0 +1,97 @@
import {StringWrapper, isBlank, isJsObject} from 'angular2/src/core/facade/lang';
var CAMEL_CASE_REGEXP = /([A-Z])/g;
var DASH_CASE_REGEXP = /-([a-z])/g;
var SINGLE_QUOTE_ESCAPE_STRING_RE = /'|\\|\n|\$/g;
var DOUBLE_QUOTE_ESCAPE_STRING_RE = /"|\\|\n|\$/g;
export var IS_DART = !isJsObject({});
export var MODULE_SUFFIX = IS_DART ? '.dart' : '.js';
export function camelCaseToDashCase(input: string): string {
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP,
(m) => { return '-' + m[1].toLowerCase(); });
}
export function dashCaseToCamelCase(input: string): string {
return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP,
(m) => { return m[1].toUpperCase(); });
}
export function escapeSingleQuoteString(input: string): string {
if (isBlank(input)) {
return null;
}
return `'${escapeString(input, SINGLE_QUOTE_ESCAPE_STRING_RE)}'`;
}
export function escapeDoubleQuoteString(input: string): string {
if (isBlank(input)) {
return null;
}
return `"${escapeString(input, DOUBLE_QUOTE_ESCAPE_STRING_RE)}"`;
}
function escapeString(input: string, re: RegExp): string {
return StringWrapper.replaceAllMapped(input, re, (match) => {
if (match[0] == '$') {
return IS_DART ? '\\$' : '$';
} else if (match[0] == '\n') {
return '\\n';
} else {
return `\\${match[0]}`;
}
});
}
export function codeGenExportVariable(name: string, isConst: boolean = false): string {
var declaration = isConst ? `const ${name}` : `var ${name}`;
return IS_DART ? `${declaration} = ` : `${declaration} = exports['${name}'] = `;
}
export function codeGenConcatArray(expression: string): string {
return `${IS_DART ? '..addAll' : '.concat'}(${expression})`;
}
export function codeGenMapArray(argNames: string[], callback: string): string {
if (IS_DART) {
return `.map( (${argNames.join(',')}) => ${callback} ).toList()`;
} else {
return `.map(function(${argNames.join(',')}) { return ${callback}; })`;
}
}
export function codeGenReplaceAll(pattern: string, expression: string): string {
if (IS_DART) {
return `.replaceAll('${pattern}', ${expression})`;
} else {
return `.replace(/${pattern}/g, ${expression})`;
}
}
export function codeGenValueFn(params: string[], value: string, fnName: string = ''): string {
if (IS_DART) {
return `${fnName}(${params.join(',')}) => ${value}`;
} else {
return `function ${fnName}(${params.join(',')}) { return ${value}; }`;
}
}
export function codeGenToString(expr: string): string {
if (IS_DART) {
return `'\${${expr}}'`;
} else {
// JS automatically convets to string...
return expr;
}
}
export function splitAtColon(input: string, defaultValues: string[]): string[] {
var parts = StringWrapper.split(input.trim(), /\s*:\s*/g);
if (parts.length > 1) {
return parts;
} else {
return defaultValues;
}
}