feat(core): speed up view creation via code gen for view factories.
BREAKING CHANGE: - Platform pipes can only contain types and arrays of types, but no bindings any more. - When using transformers, platform pipes need to be specified explicitly in the pubspec.yaml via the new config option `platform_pipes`. - `Compiler.compileInHost` now returns a `HostViewFactoryRef` - Component view is not yet created when component constructor is called. -> use `onInit` lifecycle callback to access the view of a component - `ViewRef#setLocal` has been moved to new type `EmbeddedViewRef` - `internalView` is gone, use `EmbeddedViewRef.rootNodes` to access the root nodes of an embedded view - `renderer.setElementProperty`, `..setElementStyle`, `..setElementAttribute` now take a native element instead of an ElementRef - `Renderer` interface now operates on plain native nodes, instead of `RenderElementRef`s or `RenderViewRef`s Closes #5993
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
|
||||
@ -43,7 +43,7 @@ export function createChangeDetectorDefinitions(
|
||||
|
||||
class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
viewIndex: number;
|
||||
boundTextCount: number = 0;
|
||||
nodeCount: number = 0;
|
||||
boundElementCount: number = 0;
|
||||
variableNames: string[] = [];
|
||||
bindingRecords: BindingRecord[] = [];
|
||||
@ -57,6 +57,7 @@ class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
}
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
this.nodeCount++;
|
||||
this.boundElementCount++;
|
||||
templateVisitAll(this, ast.outputs);
|
||||
for (var i = 0; i < ast.directives.length; i++) {
|
||||
@ -73,6 +74,7 @@ class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
}
|
||||
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
this.nodeCount++;
|
||||
if (ast.isBound()) {
|
||||
this.boundElementCount++;
|
||||
}
|
||||
@ -132,14 +134,20 @@ class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
}
|
||||
visitAttr(ast: AttrAst, context: any): any { return null; }
|
||||
visitBoundText(ast: BoundTextAst, context: any): any {
|
||||
var boundTextIndex = this.boundTextCount++;
|
||||
this.bindingRecords.push(BindingRecord.createForTextNode(ast.value, boundTextIndex));
|
||||
var nodeIndex = this.nodeCount++;
|
||||
this.bindingRecords.push(BindingRecord.createForTextNode(ast.value, nodeIndex));
|
||||
return null;
|
||||
}
|
||||
visitText(ast: TextAst, context: any): any {
|
||||
this.nodeCount++;
|
||||
return null;
|
||||
}
|
||||
visitText(ast: TextAst, context: any): any { return null; }
|
||||
visitDirective(ast: DirectiveAst, directiveIndexAsNumber: number): any {
|
||||
var directiveIndex = new DirectiveIndex(this.boundElementCount - 1, directiveIndexAsNumber);
|
||||
var directiveMetadata = ast.directive;
|
||||
var outputsArray = [];
|
||||
StringMapWrapper.forEach(ast.directive.outputs, (eventName, dirProperty) => outputsArray.push(
|
||||
[dirProperty, eventName]));
|
||||
var directiveRecord = new DirectiveRecord({
|
||||
directiveIndex: directiveIndex,
|
||||
callAfterContentInit:
|
||||
@ -153,7 +161,9 @@ class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
callOnChanges: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1,
|
||||
callDoCheck: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1,
|
||||
callOnInit: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1,
|
||||
changeDetection: directiveMetadata.changeDetection
|
||||
callOnDestroy: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1,
|
||||
changeDetection: directiveMetadata.changeDetection,
|
||||
outputs: outputsArray
|
||||
});
|
||||
this.directiveRecords.push(directiveRecord);
|
||||
|
||||
|
@ -3,6 +3,9 @@ import {SourceExpressions, moduleRef} from './source_module';
|
||||
import {
|
||||
ChangeDetectorJITGenerator
|
||||
} from 'angular2/src/core/change_detection/change_detection_jit_generator';
|
||||
import {AbstractChangeDetector} from 'angular2/src/core/change_detection/abstract_change_detector';
|
||||
import {ChangeDetectionUtil} from 'angular2/src/core/change_detection/change_detection_util';
|
||||
import {ChangeDetectorState} from 'angular2/src/core/change_detection/constants';
|
||||
|
||||
import {createChangeDetectorDefinitions} from './change_definition_factory';
|
||||
import {IS_DART, isJsObject, CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
@ -23,6 +26,12 @@ const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
|
||||
const UTIL = "ChangeDetectionUtil";
|
||||
const CHANGE_DETECTOR_STATE = "ChangeDetectorState";
|
||||
|
||||
export const CHANGE_DETECTION_JIT_IMPORTS = CONST_EXPR({
|
||||
'AbstractChangeDetector': AbstractChangeDetector,
|
||||
'ChangeDetectionUtil': ChangeDetectionUtil,
|
||||
'ChangeDetectorState': ChangeDetectorState
|
||||
});
|
||||
|
||||
var ABSTRACT_CHANGE_DETECTOR_MODULE = moduleRef(
|
||||
`package:angular2/src/core/change_detection/abstract_change_detector${MODULE_SUFFIX}`);
|
||||
var UTIL_MODULE =
|
||||
@ -45,14 +54,8 @@ export class ChangeDetectionCompiler {
|
||||
}
|
||||
|
||||
private _createChangeDetectorFactory(definition: ChangeDetectorDefinition): Function {
|
||||
if (IS_DART || !this._genConfig.useJit) {
|
||||
var proto = new DynamicProtoChangeDetector(definition);
|
||||
return (dispatcher) => proto.instantiate(dispatcher);
|
||||
} else {
|
||||
return new ChangeDetectorJITGenerator(definition, UTIL, ABSTRACT_CHANGE_DETECTOR,
|
||||
CHANGE_DETECTOR_STATE)
|
||||
.generate();
|
||||
}
|
||||
var proto = new DynamicProtoChangeDetector(definition);
|
||||
return () => proto.instantiate();
|
||||
}
|
||||
|
||||
compileComponentCodeGen(componentType: CompileTypeMetadata, strategy: ChangeDetectionStrategy,
|
||||
@ -81,7 +84,7 @@ export class ChangeDetectionCompiler {
|
||||
definition, `${UTIL_MODULE}${UTIL}`,
|
||||
`${ABSTRACT_CHANGE_DETECTOR_MODULE}${ABSTRACT_CHANGE_DETECTOR}`,
|
||||
`${CONSTANTS_MODULE}${CHANGE_DETECTOR_STATE}`);
|
||||
factories.push(`function(dispatcher) { return new ${codegen.typeName}(dispatcher); }`);
|
||||
factories.push(`function() { return new ${codegen.typeName}(); }`);
|
||||
sourcePart = codegen.generateSource();
|
||||
}
|
||||
index++;
|
||||
|
@ -1,375 +0,0 @@
|
||||
import {isPresent, isBlank, Type, isString, StringWrapper, IS_DART} from 'angular2/src/facade/lang';
|
||||
import {SetWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {
|
||||
TemplateCmd,
|
||||
TextCmd,
|
||||
NgContentCmd,
|
||||
BeginElementCmd,
|
||||
EndElementCmd,
|
||||
BeginComponentCmd,
|
||||
EndComponentCmd,
|
||||
EmbeddedTemplateCmd,
|
||||
CompiledComponentTemplate
|
||||
} from 'angular2/src/core/linker/template_commands';
|
||||
import {
|
||||
TemplateAst,
|
||||
TemplateAstVisitor,
|
||||
NgContentAst,
|
||||
EmbeddedTemplateAst,
|
||||
ElementAst,
|
||||
VariableAst,
|
||||
BoundEventAst,
|
||||
BoundElementPropertyAst,
|
||||
AttrAst,
|
||||
BoundTextAst,
|
||||
TextAst,
|
||||
DirectiveAst,
|
||||
BoundDirectivePropertyAst,
|
||||
templateVisitAll
|
||||
} from './template_ast';
|
||||
import {CompileTypeMetadata, CompileDirectiveMetadata} from './directive_metadata';
|
||||
import {SourceExpressions, SourceExpression, moduleRef} from './source_module';
|
||||
|
||||
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
|
||||
import {
|
||||
escapeSingleQuoteString,
|
||||
codeGenConstConstructorCall,
|
||||
codeGenValueFn,
|
||||
MODULE_SUFFIX
|
||||
} from './util';
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
|
||||
export var TEMPLATE_COMMANDS_MODULE_REF =
|
||||
moduleRef(`package:angular2/src/core/linker/template_commands${MODULE_SUFFIX}`);
|
||||
|
||||
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
|
||||
const CLASS_ATTR = 'class';
|
||||
const STYLE_ATTR = 'style';
|
||||
|
||||
@Injectable()
|
||||
export class CommandCompiler {
|
||||
compileComponentRuntime(component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
changeDetectorFactories: Function[],
|
||||
componentTemplateFactory: Function): TemplateCmd[] {
|
||||
var visitor = new CommandBuilderVisitor(
|
||||
new RuntimeCommandFactory(component, componentTemplateFactory, changeDetectorFactories), 0);
|
||||
templateVisitAll(visitor, template);
|
||||
return visitor.result;
|
||||
}
|
||||
|
||||
compileComponentCodeGen(component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
changeDetectorFactoryExpressions: string[],
|
||||
componentTemplateFactory: Function): SourceExpression {
|
||||
var visitor =
|
||||
new CommandBuilderVisitor(new CodegenCommandFactory(component, componentTemplateFactory,
|
||||
changeDetectorFactoryExpressions),
|
||||
0);
|
||||
templateVisitAll(visitor, template);
|
||||
return new SourceExpression([], codeGenArray(visitor.result));
|
||||
}
|
||||
}
|
||||
|
||||
interface CommandFactory<R> {
|
||||
createText(value: string, isBound: boolean, ngContentIndex: number): R;
|
||||
createNgContent(index: number, ngContentIndex: number): R;
|
||||
createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
|
||||
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
|
||||
isBound: boolean, ngContentIndex: number): R;
|
||||
createEndElement(): R;
|
||||
createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
|
||||
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
|
||||
encapsulation: ViewEncapsulation, ngContentIndex: number): R;
|
||||
createEndComponent(): R;
|
||||
createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[],
|
||||
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
|
||||
isMerged: boolean, ngContentIndex: number, children: R[]): R;
|
||||
}
|
||||
|
||||
class RuntimeCommandFactory implements CommandFactory<TemplateCmd> {
|
||||
constructor(private component: CompileDirectiveMetadata,
|
||||
private componentTemplateFactory: Function,
|
||||
private changeDetectorFactories: Function[]) {}
|
||||
private _mapDirectives(directives: CompileDirectiveMetadata[]): Type[] {
|
||||
return directives.map(directive => directive.type.runtime);
|
||||
}
|
||||
|
||||
createText(value: string, isBound: boolean, ngContentIndex: number): TemplateCmd {
|
||||
return new TextCmd(value, isBound, ngContentIndex);
|
||||
}
|
||||
createNgContent(index: number, ngContentIndex: number): TemplateCmd {
|
||||
return new NgContentCmd(index, ngContentIndex);
|
||||
}
|
||||
createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
|
||||
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
|
||||
isBound: boolean, ngContentIndex: number): TemplateCmd {
|
||||
return new BeginElementCmd(name, attrNameAndValues, eventTargetAndNames, variableNameAndValues,
|
||||
this._mapDirectives(directives), isBound, ngContentIndex);
|
||||
}
|
||||
createEndElement(): TemplateCmd { return new EndElementCmd(); }
|
||||
createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
|
||||
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
|
||||
encapsulation: ViewEncapsulation, ngContentIndex: number): TemplateCmd {
|
||||
var nestedTemplateAccessor = this.componentTemplateFactory(directives[0]);
|
||||
return new BeginComponentCmd(name, attrNameAndValues, eventTargetAndNames,
|
||||
variableNameAndValues, this._mapDirectives(directives),
|
||||
encapsulation, ngContentIndex, nestedTemplateAccessor);
|
||||
}
|
||||
createEndComponent(): TemplateCmd { return new EndComponentCmd(); }
|
||||
createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[],
|
||||
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
|
||||
isMerged: boolean, ngContentIndex: number,
|
||||
children: TemplateCmd[]): TemplateCmd {
|
||||
return new EmbeddedTemplateCmd(attrNameAndValues, variableNameAndValues,
|
||||
this._mapDirectives(directives), isMerged, ngContentIndex,
|
||||
this.changeDetectorFactories[embeddedTemplateIndex], children);
|
||||
}
|
||||
}
|
||||
|
||||
class CodegenCommandFactory implements CommandFactory<Expression> {
|
||||
constructor(private component: CompileDirectiveMetadata,
|
||||
private componentTemplateFactory: Function,
|
||||
private changeDetectorFactoryExpressions: string[]) {}
|
||||
|
||||
createText(value: string, isBound: boolean, ngContentIndex: number): Expression {
|
||||
return new Expression(
|
||||
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'TextCmd')}(${escapeSingleQuoteString(value)}, ${isBound}, ${ngContentIndex})`);
|
||||
}
|
||||
createNgContent(index: number, ngContentIndex: number): Expression {
|
||||
return new Expression(
|
||||
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'NgContentCmd')}(${index}, ${ngContentIndex})`);
|
||||
}
|
||||
createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
|
||||
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
|
||||
isBound: boolean, ngContentIndex: number): Expression {
|
||||
var attrsExpression = codeGenArray(attrNameAndValues);
|
||||
return new Expression(
|
||||
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'BeginElementCmd')}(${escapeSingleQuoteString(name)}, ${attrsExpression}, ` +
|
||||
`${codeGenArray(eventTargetAndNames)}, ${codeGenArray(variableNameAndValues)}, ${codeGenDirectivesArray(directives)}, ${isBound}, ${ngContentIndex})`);
|
||||
}
|
||||
createEndElement(): Expression {
|
||||
return new Expression(
|
||||
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'EndElementCmd')}()`);
|
||||
}
|
||||
createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
|
||||
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
|
||||
encapsulation: ViewEncapsulation, ngContentIndex: number): Expression {
|
||||
var attrsExpression = codeGenArray(attrNameAndValues);
|
||||
return new Expression(
|
||||
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'BeginComponentCmd')}(${escapeSingleQuoteString(name)}, ${attrsExpression}, ` +
|
||||
`${codeGenArray(eventTargetAndNames)}, ${codeGenArray(variableNameAndValues)}, ${codeGenDirectivesArray(directives)}, ${codeGenViewEncapsulation(encapsulation)}, ${ngContentIndex}, ${this.componentTemplateFactory(directives[0])})`);
|
||||
}
|
||||
createEndComponent(): Expression {
|
||||
return new Expression(
|
||||
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'EndComponentCmd')}()`);
|
||||
}
|
||||
createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[],
|
||||
variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
|
||||
isMerged: boolean, ngContentIndex: number,
|
||||
children: Expression[]): Expression {
|
||||
return new Expression(
|
||||
`${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'EmbeddedTemplateCmd')}(${codeGenArray(attrNameAndValues)}, ${codeGenArray(variableNameAndValues)}, ` +
|
||||
`${codeGenDirectivesArray(directives)}, ${isMerged}, ${ngContentIndex}, ${this.changeDetectorFactoryExpressions[embeddedTemplateIndex]}, ${codeGenArray(children)})`);
|
||||
}
|
||||
}
|
||||
|
||||
function visitAndReturnContext(visitor: TemplateAstVisitor, asts: TemplateAst[],
|
||||
context: any): any {
|
||||
templateVisitAll(visitor, asts, context);
|
||||
return context;
|
||||
}
|
||||
|
||||
class CommandBuilderVisitor<R> implements TemplateAstVisitor {
|
||||
result: R[] = [];
|
||||
transitiveNgContentCount: number = 0;
|
||||
constructor(public commandFactory: CommandFactory<R>, public embeddedTemplateIndex: number) {}
|
||||
|
||||
private _readAttrNameAndValues(directives: CompileDirectiveMetadata[],
|
||||
attrAsts: TemplateAst[]): string[] {
|
||||
var attrs = keyValueArrayToMap(visitAndReturnContext(this, attrAsts, []));
|
||||
directives.forEach(directiveMeta => {
|
||||
StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => {
|
||||
var prevValue = attrs[name];
|
||||
attrs[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
|
||||
});
|
||||
});
|
||||
return mapToKeyValueArray(attrs);
|
||||
}
|
||||
|
||||
visitNgContent(ast: NgContentAst, context: any): any {
|
||||
this.transitiveNgContentCount++;
|
||||
this.result.push(this.commandFactory.createNgContent(ast.index, ast.ngContentIndex));
|
||||
return null;
|
||||
}
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
this.embeddedTemplateIndex++;
|
||||
var childVisitor = new CommandBuilderVisitor(this.commandFactory, this.embeddedTemplateIndex);
|
||||
templateVisitAll(childVisitor, ast.children);
|
||||
var isMerged = childVisitor.transitiveNgContentCount > 0;
|
||||
var variableNameAndValues = [];
|
||||
ast.vars.forEach((varAst) => {
|
||||
variableNameAndValues.push(varAst.name);
|
||||
variableNameAndValues.push(varAst.value.length > 0 ? varAst.value : IMPLICIT_TEMPLATE_VAR);
|
||||
});
|
||||
var directives = [];
|
||||
ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => {
|
||||
directiveAst.visit(this, new DirectiveContext(index, [], [], directives));
|
||||
});
|
||||
this.result.push(this.commandFactory.createEmbeddedTemplate(
|
||||
this.embeddedTemplateIndex, this._readAttrNameAndValues(directives, ast.attrs),
|
||||
variableNameAndValues, directives, isMerged, ast.ngContentIndex, childVisitor.result));
|
||||
this.transitiveNgContentCount += childVisitor.transitiveNgContentCount;
|
||||
this.embeddedTemplateIndex = childVisitor.embeddedTemplateIndex;
|
||||
return null;
|
||||
}
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
var component = ast.getComponent();
|
||||
var eventTargetAndNames = visitAndReturnContext(this, ast.outputs, []);
|
||||
var variableNameAndValues = [];
|
||||
if (isBlank(component)) {
|
||||
ast.exportAsVars.forEach((varAst) => {
|
||||
variableNameAndValues.push(varAst.name);
|
||||
variableNameAndValues.push(null);
|
||||
});
|
||||
}
|
||||
var directives = [];
|
||||
ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => {
|
||||
directiveAst.visit(this, new DirectiveContext(index, eventTargetAndNames,
|
||||
variableNameAndValues, directives));
|
||||
});
|
||||
eventTargetAndNames = removeKeyValueArrayDuplicates(eventTargetAndNames);
|
||||
|
||||
var attrNameAndValues = this._readAttrNameAndValues(directives, ast.attrs);
|
||||
if (isPresent(component)) {
|
||||
this.result.push(this.commandFactory.createBeginComponent(
|
||||
ast.name, attrNameAndValues, eventTargetAndNames, variableNameAndValues, directives,
|
||||
component.template.encapsulation, ast.ngContentIndex));
|
||||
templateVisitAll(this, ast.children);
|
||||
this.result.push(this.commandFactory.createEndComponent());
|
||||
} else {
|
||||
this.result.push(this.commandFactory.createBeginElement(
|
||||
ast.name, attrNameAndValues, eventTargetAndNames, variableNameAndValues, directives,
|
||||
ast.isBound(), ast.ngContentIndex));
|
||||
templateVisitAll(this, ast.children);
|
||||
this.result.push(this.commandFactory.createEndElement());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
visitVariable(ast: VariableAst, ctx: any): any { return null; }
|
||||
visitAttr(ast: AttrAst, attrNameAndValues: string[]): any {
|
||||
attrNameAndValues.push(ast.name);
|
||||
attrNameAndValues.push(ast.value);
|
||||
return null;
|
||||
}
|
||||
visitBoundText(ast: BoundTextAst, context: any): any {
|
||||
this.result.push(this.commandFactory.createText(null, true, ast.ngContentIndex));
|
||||
return null;
|
||||
}
|
||||
visitText(ast: TextAst, context: any): any {
|
||||
this.result.push(this.commandFactory.createText(ast.value, false, ast.ngContentIndex));
|
||||
return null;
|
||||
}
|
||||
visitDirective(ast: DirectiveAst, ctx: DirectiveContext): any {
|
||||
ctx.targetDirectives.push(ast.directive);
|
||||
templateVisitAll(this, ast.hostEvents, ctx.eventTargetAndNames);
|
||||
ast.exportAsVars.forEach(varAst => {
|
||||
ctx.targetVariableNameAndValues.push(varAst.name);
|
||||
ctx.targetVariableNameAndValues.push(ctx.index);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
visitEvent(ast: BoundEventAst, eventTargetAndNames: string[]): any {
|
||||
eventTargetAndNames.push(ast.target);
|
||||
eventTargetAndNames.push(ast.name);
|
||||
return null;
|
||||
}
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
|
||||
}
|
||||
|
||||
function removeKeyValueArrayDuplicates(keyValueArray: string[]): string[] {
|
||||
var knownPairs = new Set();
|
||||
var resultKeyValueArray = [];
|
||||
for (var i = 0; i < keyValueArray.length; i += 2) {
|
||||
var key = keyValueArray[i];
|
||||
var value = keyValueArray[i + 1];
|
||||
var pairId = `${key}:${value}`;
|
||||
if (!SetWrapper.has(knownPairs, pairId)) {
|
||||
resultKeyValueArray.push(key);
|
||||
resultKeyValueArray.push(value);
|
||||
knownPairs.add(pairId);
|
||||
}
|
||||
}
|
||||
return resultKeyValueArray;
|
||||
}
|
||||
|
||||
function keyValueArrayToMap(keyValueArr: string[]): {[key: string]: string} {
|
||||
var data: {[key: string]: string} = {};
|
||||
for (var i = 0; i < keyValueArr.length; i += 2) {
|
||||
data[keyValueArr[i]] = keyValueArr[i + 1];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function mapToKeyValueArray(data: {[key: string]: string}): string[] {
|
||||
var entryArray = [];
|
||||
StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); });
|
||||
// We need to sort to get a defined output order
|
||||
// for tests and for caching generated artifacts...
|
||||
ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
|
||||
var keyValueArray = [];
|
||||
entryArray.forEach((entry) => {
|
||||
keyValueArray.push(entry[0]);
|
||||
keyValueArray.push(entry[1]);
|
||||
});
|
||||
return keyValueArray;
|
||||
}
|
||||
|
||||
function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string {
|
||||
if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) {
|
||||
return `${attrValue1} ${attrValue2}`;
|
||||
} else {
|
||||
return attrValue2;
|
||||
}
|
||||
}
|
||||
|
||||
class DirectiveContext {
|
||||
constructor(public index: number, public eventTargetAndNames: string[],
|
||||
public targetVariableNameAndValues: any[],
|
||||
public targetDirectives: CompileDirectiveMetadata[]) {}
|
||||
}
|
||||
|
||||
class Expression {
|
||||
constructor(public value: string) {}
|
||||
}
|
||||
|
||||
function escapeValue(value: any): string {
|
||||
if (value instanceof Expression) {
|
||||
return value.value;
|
||||
} else if (isString(value)) {
|
||||
return escapeSingleQuoteString(value);
|
||||
} else if (isBlank(value)) {
|
||||
return 'null';
|
||||
} else {
|
||||
return `${value}`;
|
||||
}
|
||||
}
|
||||
|
||||
function codeGenArray(data: any[]): string {
|
||||
var base = `[${data.map(escapeValue).join(',')}]`;
|
||||
return IS_DART ? `const ${base}` : base;
|
||||
}
|
||||
|
||||
function codeGenDirectivesArray(directives: CompileDirectiveMetadata[]): string {
|
||||
var expressions = directives.map(
|
||||
directiveType => `${moduleRef(directiveType.type.moduleUrl)}${directiveType.type.name}`);
|
||||
var base = `[${expressions.join(',')}]`;
|
||||
return IS_DART ? `const ${base}` : base;
|
||||
}
|
||||
|
||||
function codeGenViewEncapsulation(value: ViewEncapsulation): string {
|
||||
if (IS_DART) {
|
||||
return `${TEMPLATE_COMMANDS_MODULE_REF}${value}`;
|
||||
} else {
|
||||
return `${value}`;
|
||||
}
|
||||
}
|
@ -17,7 +17,8 @@ import {TemplateNormalizer} from 'angular2/src/compiler/template_normalizer';
|
||||
import {RuntimeMetadataResolver} from 'angular2/src/compiler/runtime_metadata';
|
||||
import {ChangeDetectionCompiler} from 'angular2/src/compiler/change_detector_compiler';
|
||||
import {StyleCompiler} from 'angular2/src/compiler/style_compiler';
|
||||
import {CommandCompiler} from 'angular2/src/compiler/command_compiler';
|
||||
import {ViewCompiler} from 'angular2/src/compiler/view_compiler';
|
||||
import {ProtoViewCompiler} from 'angular2/src/compiler/proto_view_compiler';
|
||||
import {TemplateCompiler} from 'angular2/src/compiler/template_compiler';
|
||||
import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection';
|
||||
import {Compiler} from 'angular2/src/core/linker/compiler';
|
||||
@ -44,7 +45,8 @@ export const COMPILER_PROVIDERS: Array<Type | Provider | any[]> = CONST_EXPR([
|
||||
RuntimeMetadataResolver,
|
||||
DEFAULT_PACKAGE_URL_PROVIDER,
|
||||
StyleCompiler,
|
||||
CommandCompiler,
|
||||
ProtoViewCompiler,
|
||||
ViewCompiler,
|
||||
ChangeDetectionCompiler,
|
||||
new Provider(ChangeDetectorGenConfig, {useFactory: _createChangeDetectorGenConfig, deps: []}),
|
||||
TemplateCompiler,
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
RegExpWrapper,
|
||||
StringWrapper
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {unimplemented} from 'angular2/src/facade/exceptions';
|
||||
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
@ -21,6 +22,16 @@ import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/linker/i
|
||||
// group 2: "event" from "(event)"
|
||||
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;
|
||||
|
||||
export abstract class CompileMetadataWithType {
|
||||
static fromJson(data: {[key: string]: any}): CompileMetadataWithType {
|
||||
return _COMPILE_METADATA_FROM_JSON[data['class']](data);
|
||||
}
|
||||
|
||||
abstract toJson(): {[key: string]: any};
|
||||
|
||||
get type(): CompileTypeMetadata { return unimplemented(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata regarding compilation of a type.
|
||||
*/
|
||||
@ -107,7 +118,7 @@ export class CompileTemplateMetadata {
|
||||
/**
|
||||
* Metadata regarding compilation of a directive.
|
||||
*/
|
||||
export class CompileDirectiveMetadata {
|
||||
export class CompileDirectiveMetadata implements CompileMetadataWithType {
|
||||
static create({type, isComponent, dynamicLoadable, selector, exportAs, changeDetection, inputs,
|
||||
outputs, host, lifecycleHooks, template}: {
|
||||
type?: CompileTypeMetadata,
|
||||
@ -241,6 +252,7 @@ export class CompileDirectiveMetadata {
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
'class': 'Directive',
|
||||
'isComponent': this.isComponent,
|
||||
'dynamicLoadable': this.dynamicLoadable,
|
||||
'selector': this.selector,
|
||||
@ -284,3 +296,38 @@ export function createHostComponentMeta(componentType: CompileTypeMetadata,
|
||||
selector: '*'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export class CompilePipeMetadata implements CompileMetadataWithType {
|
||||
type: CompileTypeMetadata;
|
||||
name: string;
|
||||
pure: boolean;
|
||||
constructor({type, name,
|
||||
pure}: {type?: CompileTypeMetadata, name?: string, pure?: boolean} = {}) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.pure = normalizeBool(pure);
|
||||
}
|
||||
|
||||
static fromJson(data: {[key: string]: any}): CompilePipeMetadata {
|
||||
return new CompilePipeMetadata({
|
||||
type: isPresent(data['type']) ? CompileTypeMetadata.fromJson(data['type']) : data['type'],
|
||||
name: data['name'],
|
||||
pure: data['pure']
|
||||
});
|
||||
}
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
'class': 'Pipe',
|
||||
'type': isPresent(this.type) ? this.type.toJson() : null,
|
||||
'name': this.name,
|
||||
'pure': this.pure
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var _COMPILE_METADATA_FROM_JSON = {
|
||||
'Directive': CompileDirectiveMetadata.fromJson,
|
||||
'Pipe': CompilePipeMetadata.fromJson
|
||||
};
|
||||
|
397
modules/angular2/src/compiler/proto_view_compiler.ts
Normal file
397
modules/angular2/src/compiler/proto_view_compiler.ts
Normal file
@ -0,0 +1,397 @@
|
||||
import {
|
||||
isPresent,
|
||||
isBlank,
|
||||
Type,
|
||||
isString,
|
||||
StringWrapper,
|
||||
IS_DART,
|
||||
CONST_EXPR
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
SetWrapper,
|
||||
StringMapWrapper,
|
||||
ListWrapper,
|
||||
MapWrapper
|
||||
} from 'angular2/src/facade/collection';
|
||||
import {
|
||||
TemplateAst,
|
||||
TemplateAstVisitor,
|
||||
NgContentAst,
|
||||
EmbeddedTemplateAst,
|
||||
ElementAst,
|
||||
VariableAst,
|
||||
BoundEventAst,
|
||||
BoundElementPropertyAst,
|
||||
AttrAst,
|
||||
BoundTextAst,
|
||||
TextAst,
|
||||
DirectiveAst,
|
||||
BoundDirectivePropertyAst,
|
||||
templateVisitAll
|
||||
} from './template_ast';
|
||||
import {
|
||||
CompileTypeMetadata,
|
||||
CompileDirectiveMetadata,
|
||||
CompilePipeMetadata
|
||||
} from './directive_metadata';
|
||||
import {SourceExpressions, SourceExpression, moduleRef} from './source_module';
|
||||
import {AppProtoView, AppView} from 'angular2/src/core/linker/view';
|
||||
import {ViewType} from 'angular2/src/core/linker/view_type';
|
||||
import {AppProtoElement, AppElement} from 'angular2/src/core/linker/element';
|
||||
import {ResolvedMetadataCache} from 'angular2/src/core/linker/resolved_metadata_cache';
|
||||
import {
|
||||
escapeSingleQuoteString,
|
||||
codeGenConstConstructorCall,
|
||||
codeGenValueFn,
|
||||
codeGenFnHeader,
|
||||
MODULE_SUFFIX,
|
||||
codeGenStringMap,
|
||||
Expression,
|
||||
Statement
|
||||
} from './util';
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
|
||||
export const PROTO_VIEW_JIT_IMPORTS = CONST_EXPR(
|
||||
{'AppProtoView': AppProtoView, 'AppProtoElement': AppProtoElement, 'ViewType': ViewType});
|
||||
|
||||
// TODO: have a single file that reexports everything needed for
|
||||
// codegen explicitly
|
||||
// - helps understanding what codegen works against
|
||||
// - less imports in codegen code
|
||||
export var APP_VIEW_MODULE_REF = moduleRef('package:angular2/src/core/linker/view' + MODULE_SUFFIX);
|
||||
export var VIEW_TYPE_MODULE_REF =
|
||||
moduleRef('package:angular2/src/core/linker/view_type' + MODULE_SUFFIX);
|
||||
export var APP_EL_MODULE_REF =
|
||||
moduleRef('package:angular2/src/core/linker/element' + MODULE_SUFFIX);
|
||||
export var METADATA_MODULE_REF =
|
||||
moduleRef('package:angular2/src/core/metadata/view' + MODULE_SUFFIX);
|
||||
|
||||
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
|
||||
const CLASS_ATTR = 'class';
|
||||
const STYLE_ATTR = 'style';
|
||||
|
||||
@Injectable()
|
||||
export class ProtoViewCompiler {
|
||||
constructor() {}
|
||||
|
||||
compileProtoViewRuntime(metadataCache: ResolvedMetadataCache, component: CompileDirectiveMetadata,
|
||||
template: TemplateAst[], pipes: CompilePipeMetadata[]):
|
||||
CompileProtoViews<AppProtoView, AppProtoElement, any> {
|
||||
var protoViewFactory = new RuntimeProtoViewFactory(metadataCache, component, pipes);
|
||||
var allProtoViews = [];
|
||||
protoViewFactory.createCompileProtoView(template, [], [], allProtoViews);
|
||||
return new CompileProtoViews<AppProtoView, AppProtoElement, any>([], allProtoViews);
|
||||
}
|
||||
|
||||
compileProtoViewCodeGen(resolvedMetadataCacheExpr: Expression,
|
||||
component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
pipes: CompilePipeMetadata[]):
|
||||
CompileProtoViews<Expression, Expression, string> {
|
||||
var protoViewFactory = new CodeGenProtoViewFactory(resolvedMetadataCacheExpr, component, pipes);
|
||||
var allProtoViews = [];
|
||||
var allStatements = [];
|
||||
protoViewFactory.createCompileProtoView(template, [], allStatements, allProtoViews);
|
||||
return new CompileProtoViews<Expression, Expression, string>(
|
||||
allStatements.map(stmt => stmt.statement), allProtoViews);
|
||||
}
|
||||
}
|
||||
|
||||
export class CompileProtoViews<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT> {
|
||||
constructor(public declarations: STATEMENT[],
|
||||
public protoViews: CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL>[]) {}
|
||||
}
|
||||
|
||||
|
||||
export class CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL> {
|
||||
constructor(public embeddedTemplateIndex: number,
|
||||
public protoElements: CompileProtoElement<APP_PROTO_EL>[],
|
||||
public protoView: APP_PROTO_VIEW) {}
|
||||
}
|
||||
|
||||
export class CompileProtoElement<APP_PROTO_EL> {
|
||||
constructor(public boundElementIndex, public attrNameAndValues: string[][],
|
||||
public variableNameAndValues: string[][], public renderEvents: BoundEventAst[],
|
||||
public directives: CompileDirectiveMetadata[], public embeddedTemplateIndex: number,
|
||||
public appProtoEl: APP_PROTO_EL) {}
|
||||
}
|
||||
|
||||
function visitAndReturnContext(visitor: TemplateAstVisitor, asts: TemplateAst[],
|
||||
context: any): any {
|
||||
templateVisitAll(visitor, asts, context);
|
||||
return context;
|
||||
}
|
||||
|
||||
abstract class ProtoViewFactory<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT> {
|
||||
constructor(public component: CompileDirectiveMetadata) {}
|
||||
|
||||
abstract createAppProtoView(embeddedTemplateIndex: number, viewType: ViewType,
|
||||
templateVariableBindings: string[][],
|
||||
targetStatements: STATEMENT[]): APP_PROTO_VIEW;
|
||||
|
||||
abstract createAppProtoElement(boundElementIndex: number, attrNameAndValues: string[][],
|
||||
variableNameAndValues: string[][],
|
||||
directives: CompileDirectiveMetadata[],
|
||||
targetStatements: STATEMENT[]): APP_PROTO_EL;
|
||||
|
||||
createCompileProtoView(template: TemplateAst[], templateVariableBindings: string[][],
|
||||
targetStatements: STATEMENT[],
|
||||
targetProtoViews: CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL>[]):
|
||||
CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL> {
|
||||
var embeddedTemplateIndex = targetProtoViews.length;
|
||||
// Note: targetProtoViews needs to be in depth first order.
|
||||
// So we "reserve" a space here that we fill after the recursion is done
|
||||
targetProtoViews.push(null);
|
||||
var builder = new ProtoViewBuilderVisitor<APP_PROTO_VIEW, APP_PROTO_EL, any>(
|
||||
this, targetStatements, targetProtoViews);
|
||||
templateVisitAll(builder, template);
|
||||
var viewType = getViewType(this.component, embeddedTemplateIndex);
|
||||
var appProtoView = this.createAppProtoView(embeddedTemplateIndex, viewType,
|
||||
templateVariableBindings, targetStatements);
|
||||
var cpv = new CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL>(
|
||||
embeddedTemplateIndex, builder.protoElements, appProtoView);
|
||||
targetProtoViews[embeddedTemplateIndex] = cpv;
|
||||
return cpv;
|
||||
}
|
||||
}
|
||||
|
||||
class CodeGenProtoViewFactory extends ProtoViewFactory<Expression, Expression, Statement> {
|
||||
private _nextVarId: number = 0;
|
||||
|
||||
constructor(public resolvedMetadataCacheExpr: Expression, component: CompileDirectiveMetadata,
|
||||
public pipes: CompilePipeMetadata[]) {
|
||||
super(component);
|
||||
}
|
||||
|
||||
private _nextProtoViewVar(embeddedTemplateIndex: number): string {
|
||||
return `appProtoView${this._nextVarId++}_${this.component.type.name}${embeddedTemplateIndex}`;
|
||||
}
|
||||
|
||||
createAppProtoView(embeddedTemplateIndex: number, viewType: ViewType,
|
||||
templateVariableBindings: string[][],
|
||||
targetStatements: Statement[]): Expression {
|
||||
var protoViewVarName = this._nextProtoViewVar(embeddedTemplateIndex);
|
||||
var viewTypeExpr = codeGenViewType(viewType);
|
||||
var pipesExpr = embeddedTemplateIndex === 0 ?
|
||||
codeGenTypesArray(this.pipes.map(pipeMeta => pipeMeta.type)) :
|
||||
null;
|
||||
var statement =
|
||||
`var ${protoViewVarName} = ${APP_VIEW_MODULE_REF}AppProtoView.create(${this.resolvedMetadataCacheExpr.expression}, ${viewTypeExpr}, ${pipesExpr}, ${codeGenStringMap(templateVariableBindings)});`;
|
||||
targetStatements.push(new Statement(statement));
|
||||
return new Expression(protoViewVarName);
|
||||
}
|
||||
|
||||
createAppProtoElement(boundElementIndex: number, attrNameAndValues: string[][],
|
||||
variableNameAndValues: string[][], directives: CompileDirectiveMetadata[],
|
||||
targetStatements: Statement[]): Expression {
|
||||
var varName = `appProtoEl${this._nextVarId++}_${this.component.type.name}`;
|
||||
var value = `${APP_EL_MODULE_REF}AppProtoElement.create(
|
||||
${this.resolvedMetadataCacheExpr.expression},
|
||||
${boundElementIndex},
|
||||
${codeGenStringMap(attrNameAndValues)},
|
||||
${codeGenDirectivesArray(directives)},
|
||||
${codeGenStringMap(variableNameAndValues)}
|
||||
)`;
|
||||
var statement = `var ${varName} = ${value};`;
|
||||
targetStatements.push(new Statement(statement));
|
||||
return new Expression(varName);
|
||||
}
|
||||
}
|
||||
|
||||
class RuntimeProtoViewFactory extends ProtoViewFactory<AppProtoView, AppProtoElement, any> {
|
||||
constructor(public metadataCache: ResolvedMetadataCache, component: CompileDirectiveMetadata,
|
||||
public pipes: CompilePipeMetadata[]) {
|
||||
super(component);
|
||||
}
|
||||
|
||||
createAppProtoView(embeddedTemplateIndex: number, viewType: ViewType,
|
||||
templateVariableBindings: string[][], targetStatements: any[]): AppProtoView {
|
||||
var pipes =
|
||||
embeddedTemplateIndex === 0 ? this.pipes.map(pipeMeta => pipeMeta.type.runtime) : [];
|
||||
var templateVars = keyValueArrayToStringMap(templateVariableBindings);
|
||||
return AppProtoView.create(this.metadataCache, viewType, pipes, templateVars);
|
||||
}
|
||||
|
||||
createAppProtoElement(boundElementIndex: number, attrNameAndValues: string[][],
|
||||
variableNameAndValues: string[][], directives: CompileDirectiveMetadata[],
|
||||
targetStatements: any[]): AppProtoElement {
|
||||
var attrs = keyValueArrayToStringMap(attrNameAndValues);
|
||||
return AppProtoElement.create(this.metadataCache, boundElementIndex, attrs,
|
||||
directives.map(dirMeta => dirMeta.type.runtime),
|
||||
keyValueArrayToStringMap(variableNameAndValues));
|
||||
}
|
||||
}
|
||||
|
||||
class ProtoViewBuilderVisitor<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT> implements
|
||||
TemplateAstVisitor {
|
||||
protoElements: CompileProtoElement<APP_PROTO_EL>[] = [];
|
||||
boundElementCount: number = 0;
|
||||
|
||||
constructor(public factory: ProtoViewFactory<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT>,
|
||||
public allStatements: STATEMENT[],
|
||||
public allProtoViews: CompileProtoView<APP_PROTO_VIEW, APP_PROTO_EL>[]) {}
|
||||
|
||||
private _readAttrNameAndValues(directives: CompileDirectiveMetadata[],
|
||||
attrAsts: TemplateAst[]): string[][] {
|
||||
var attrs = visitAndReturnContext(this, attrAsts, {});
|
||||
directives.forEach(directiveMeta => {
|
||||
StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => {
|
||||
var prevValue = attrs[name];
|
||||
attrs[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
|
||||
});
|
||||
});
|
||||
return mapToKeyValueArray(attrs);
|
||||
}
|
||||
|
||||
visitBoundText(ast: BoundTextAst, context: any): any { return null; }
|
||||
visitText(ast: TextAst, context: any): any { return null; }
|
||||
|
||||
visitNgContent(ast: NgContentAst, context: any): any { return null; }
|
||||
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
var boundElementIndex = null;
|
||||
if (ast.isBound()) {
|
||||
boundElementIndex = this.boundElementCount++;
|
||||
}
|
||||
var component = ast.getComponent();
|
||||
|
||||
var variableNameAndValues: string[][] = [];
|
||||
if (isBlank(component)) {
|
||||
ast.exportAsVars.forEach((varAst) => { variableNameAndValues.push([varAst.name, null]); });
|
||||
}
|
||||
var directives = [];
|
||||
var renderEvents: Map<string, BoundEventAst> =
|
||||
visitAndReturnContext(this, ast.outputs, new Map<string, BoundEventAst>());
|
||||
ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => {
|
||||
directiveAst.visit(this, new DirectiveContext(index, boundElementIndex, renderEvents,
|
||||
variableNameAndValues, directives));
|
||||
});
|
||||
var renderEventArray = [];
|
||||
renderEvents.forEach((eventAst, _) => renderEventArray.push(eventAst));
|
||||
|
||||
var attrNameAndValues = this._readAttrNameAndValues(directives, ast.attrs);
|
||||
this._addProtoElement(ast.isBound(), boundElementIndex, attrNameAndValues,
|
||||
variableNameAndValues, renderEventArray, directives, null);
|
||||
templateVisitAll(this, ast.children);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
var boundElementIndex = this.boundElementCount++;
|
||||
var directives: CompileDirectiveMetadata[] = [];
|
||||
ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => {
|
||||
directiveAst.visit(
|
||||
this, new DirectiveContext(index, boundElementIndex, new Map<string, BoundEventAst>(), [],
|
||||
directives));
|
||||
});
|
||||
|
||||
var attrNameAndValues = this._readAttrNameAndValues(directives, ast.attrs);
|
||||
var templateVariableBindings = ast.vars.map(
|
||||
varAst => [varAst.value.length > 0 ? varAst.value : IMPLICIT_TEMPLATE_VAR, varAst.name]);
|
||||
var nestedProtoView = this.factory.createCompileProtoView(
|
||||
ast.children, templateVariableBindings, this.allStatements, this.allProtoViews);
|
||||
this._addProtoElement(true, boundElementIndex, attrNameAndValues, [], [], directives,
|
||||
nestedProtoView.embeddedTemplateIndex);
|
||||
return null;
|
||||
}
|
||||
|
||||
private _addProtoElement(isBound: boolean, boundElementIndex, attrNameAndValues: string[][],
|
||||
variableNameAndValues: string[][], renderEvents: BoundEventAst[],
|
||||
directives: CompileDirectiveMetadata[], embeddedTemplateIndex: number) {
|
||||
var appProtoEl = null;
|
||||
if (isBound) {
|
||||
appProtoEl =
|
||||
this.factory.createAppProtoElement(boundElementIndex, attrNameAndValues,
|
||||
variableNameAndValues, directives, this.allStatements);
|
||||
}
|
||||
var compileProtoEl = new CompileProtoElement<APP_PROTO_EL>(
|
||||
boundElementIndex, attrNameAndValues, variableNameAndValues, renderEvents, directives,
|
||||
embeddedTemplateIndex, appProtoEl);
|
||||
this.protoElements.push(compileProtoEl);
|
||||
}
|
||||
|
||||
visitVariable(ast: VariableAst, ctx: any): any { return null; }
|
||||
visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any {
|
||||
attrNameAndValues[ast.name] = ast.value;
|
||||
return null;
|
||||
}
|
||||
visitDirective(ast: DirectiveAst, ctx: DirectiveContext): any {
|
||||
ctx.targetDirectives.push(ast.directive);
|
||||
templateVisitAll(this, ast.hostEvents, ctx.hostEventTargetAndNames);
|
||||
ast.exportAsVars.forEach(
|
||||
varAst => { ctx.targetVariableNameAndValues.push([varAst.name, ctx.index]); });
|
||||
return null;
|
||||
}
|
||||
visitEvent(ast: BoundEventAst, eventTargetAndNames: Map<string, BoundEventAst>): any {
|
||||
eventTargetAndNames.set(ast.fullName, ast);
|
||||
return null;
|
||||
}
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
|
||||
}
|
||||
|
||||
function mapToKeyValueArray(data: {[key: string]: string}): string[][] {
|
||||
var entryArray = [];
|
||||
StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); });
|
||||
// We need to sort to get a defined output order
|
||||
// for tests and for caching generated artifacts...
|
||||
ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
|
||||
var keyValueArray = [];
|
||||
entryArray.forEach((entry) => { keyValueArray.push([entry[0], entry[1]]); });
|
||||
return keyValueArray;
|
||||
}
|
||||
|
||||
function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string {
|
||||
if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) {
|
||||
return `${attrValue1} ${attrValue2}`;
|
||||
} else {
|
||||
return attrValue2;
|
||||
}
|
||||
}
|
||||
|
||||
class DirectiveContext {
|
||||
constructor(public index: number, public boundElementIndex: number,
|
||||
public hostEventTargetAndNames: Map<string, BoundEventAst>,
|
||||
public targetVariableNameAndValues: any[][],
|
||||
public targetDirectives: CompileDirectiveMetadata[]) {}
|
||||
}
|
||||
|
||||
function keyValueArrayToStringMap(keyValueArray: any[][]): {[key: string]: any} {
|
||||
var stringMap: {[key: string]: string} = {};
|
||||
for (var i = 0; i < keyValueArray.length; i++) {
|
||||
var entry = keyValueArray[i];
|
||||
stringMap[entry[0]] = entry[1];
|
||||
}
|
||||
return stringMap;
|
||||
}
|
||||
|
||||
function codeGenDirectivesArray(directives: CompileDirectiveMetadata[]): string {
|
||||
var expressions = directives.map(directiveType => typeRef(directiveType.type));
|
||||
return `[${expressions.join(',')}]`;
|
||||
}
|
||||
|
||||
function codeGenTypesArray(types: CompileTypeMetadata[]): string {
|
||||
var expressions = types.map(typeRef);
|
||||
return `[${expressions.join(',')}]`;
|
||||
}
|
||||
|
||||
function codeGenViewType(value: ViewType): string {
|
||||
if (IS_DART) {
|
||||
return `${VIEW_TYPE_MODULE_REF}${value}`;
|
||||
} else {
|
||||
return `${value}`;
|
||||
}
|
||||
}
|
||||
|
||||
function typeRef(type: CompileTypeMetadata): string {
|
||||
return `${moduleRef(type.moduleUrl)}${type.name}`;
|
||||
}
|
||||
|
||||
function getViewType(component: CompileDirectiveMetadata, embeddedTemplateIndex: number): ViewType {
|
||||
if (embeddedTemplateIndex > 0) {
|
||||
return ViewType.EMBEDDED;
|
||||
} else if (component.type.isHost) {
|
||||
return ViewType.HOST;
|
||||
} else {
|
||||
return ViewType.COMPONENT;
|
||||
}
|
||||
}
|
@ -1,23 +1,23 @@
|
||||
import {Compiler, Compiler_, internalCreateProtoView} from 'angular2/src/core/linker/compiler';
|
||||
import {ProtoViewRef} from 'angular2/src/core/linker/view_ref';
|
||||
import {ProtoViewFactory} from 'angular2/src/core/linker/proto_view_factory';
|
||||
import {Compiler, Compiler_} from 'angular2/src/core/linker/compiler';
|
||||
import {HostViewFactoryRef, HostViewFactoryRef_} from 'angular2/src/core/linker/view_ref';
|
||||
import {TemplateCompiler} from './template_compiler';
|
||||
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {Type} from 'angular2/src/facade/lang';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
|
||||
export abstract class RuntimeCompiler extends Compiler {}
|
||||
export abstract class RuntimeCompiler extends Compiler {
|
||||
abstract compileInHost(componentType: Type): Promise<HostViewFactoryRef>;
|
||||
abstract clearCache();
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class RuntimeCompiler_ extends Compiler_ implements RuntimeCompiler {
|
||||
constructor(_protoViewFactory: ProtoViewFactory, private _templateCompiler: TemplateCompiler) {
|
||||
super(_protoViewFactory);
|
||||
}
|
||||
constructor(private _templateCompiler: TemplateCompiler) { super(); }
|
||||
|
||||
compileInHost(componentType: Type): Promise<ProtoViewRef> {
|
||||
compileInHost(componentType: Type): Promise<HostViewFactoryRef_> {
|
||||
return this._templateCompiler.compileHostComponentRuntime(componentType)
|
||||
.then(compiledHostTemplate => internalCreateProtoView(this, compiledHostTemplate));
|
||||
.then(hostViewFactory => new HostViewFactoryRef_(hostViewFactory));
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
|
@ -11,25 +11,29 @@ import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
import * as cpl from './directive_metadata';
|
||||
import * as md from 'angular2/src/core/metadata/directives';
|
||||
import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
|
||||
import {PipeResolver} from 'angular2/src/core/linker/pipe_resolver';
|
||||
import {ViewResolver} from 'angular2/src/core/linker/view_resolver';
|
||||
import {ViewMetadata} from 'angular2/src/core/metadata/view';
|
||||
import {hasLifecycleHook} from 'angular2/src/core/linker/directive_lifecycle_reflector';
|
||||
import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/linker/interfaces';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
import {Injectable, Inject, Optional} from 'angular2/src/core/di';
|
||||
import {PLATFORM_DIRECTIVES} from 'angular2/src/core/platform_directives_and_pipes';
|
||||
import {PLATFORM_DIRECTIVES, PLATFORM_PIPES} from 'angular2/src/core/platform_directives_and_pipes';
|
||||
import {MODULE_SUFFIX} from './util';
|
||||
import {getUrlScheme} from 'angular2/src/compiler/url_resolver';
|
||||
|
||||
@Injectable()
|
||||
export class RuntimeMetadataResolver {
|
||||
private _cache = new Map<Type, cpl.CompileDirectiveMetadata>();
|
||||
private _directiveCache = new Map<Type, cpl.CompileDirectiveMetadata>();
|
||||
private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>();
|
||||
|
||||
constructor(private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver,
|
||||
@Optional() @Inject(PLATFORM_DIRECTIVES) private _platformDirectives: Type[]) {}
|
||||
constructor(private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver,
|
||||
private _viewResolver: ViewResolver,
|
||||
@Optional() @Inject(PLATFORM_DIRECTIVES) private _platformDirectives: Type[],
|
||||
@Optional() @Inject(PLATFORM_PIPES) private _platformPipes: Type[]) {}
|
||||
|
||||
getMetadata(directiveType: Type): cpl.CompileDirectiveMetadata {
|
||||
var meta = this._cache.get(directiveType);
|
||||
getDirectiveMetadata(directiveType: Type): cpl.CompileDirectiveMetadata {
|
||||
var meta = this._directiveCache.get(directiveType);
|
||||
if (isBlank(meta)) {
|
||||
var dirMeta = this._directiveResolver.resolve(directiveType);
|
||||
var moduleUrl = null;
|
||||
@ -63,7 +67,23 @@ export class RuntimeMetadataResolver {
|
||||
host: dirMeta.host,
|
||||
lifecycleHooks: LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, directiveType))
|
||||
});
|
||||
this._cache.set(directiveType, meta);
|
||||
this._directiveCache.set(directiveType, meta);
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
getPipeMetadata(pipeType: Type): cpl.CompilePipeMetadata {
|
||||
var meta = this._pipeCache.get(pipeType);
|
||||
if (isBlank(meta)) {
|
||||
var pipeMeta = this._pipeResolver.resolve(pipeType);
|
||||
var moduleUrl = reflector.importUri(pipeType);
|
||||
meta = new cpl.CompilePipeMetadata({
|
||||
type: new cpl.CompileTypeMetadata(
|
||||
{name: stringify(pipeType), moduleUrl: moduleUrl, runtime: pipeType}),
|
||||
name: pipeMeta.name,
|
||||
pure: pipeMeta.pure
|
||||
});
|
||||
this._pipeCache.set(pipeType, meta);
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
@ -72,13 +92,25 @@ export class RuntimeMetadataResolver {
|
||||
var view = this._viewResolver.resolve(component);
|
||||
var directives = flattenDirectives(view, this._platformDirectives);
|
||||
for (var i = 0; i < directives.length; i++) {
|
||||
if (!isValidDirective(directives[i])) {
|
||||
if (!isValidType(directives[i])) {
|
||||
throw new BaseException(
|
||||
`Unexpected directive value '${stringify(directives[i])}' on the View of component '${stringify(component)}'`);
|
||||
}
|
||||
}
|
||||
|
||||
return directives.map(type => this.getMetadata(type));
|
||||
return directives.map(type => this.getDirectiveMetadata(type));
|
||||
}
|
||||
|
||||
getViewPipesMetadata(component: Type): cpl.CompilePipeMetadata[] {
|
||||
var view = this._viewResolver.resolve(component);
|
||||
var pipes = flattenPipes(view, this._platformPipes);
|
||||
for (var i = 0; i < pipes.length; i++) {
|
||||
if (!isValidType(pipes[i])) {
|
||||
throw new BaseException(
|
||||
`Unexpected piped value '${stringify(pipes[i])}' on the View of component '${stringify(component)}'`);
|
||||
}
|
||||
}
|
||||
return pipes.map(type => this.getPipeMetadata(type));
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,6 +125,17 @@ function flattenDirectives(view: ViewMetadata, platformDirectives: any[]): Type[
|
||||
return directives;
|
||||
}
|
||||
|
||||
function flattenPipes(view: ViewMetadata, platformPipes: any[]): Type[] {
|
||||
let pipes = [];
|
||||
if (isPresent(platformPipes)) {
|
||||
flattenArray(platformPipes, pipes);
|
||||
}
|
||||
if (isPresent(view.pipes)) {
|
||||
flattenArray(view.pipes, pipes);
|
||||
}
|
||||
return pipes;
|
||||
}
|
||||
|
||||
function flattenArray(tree: any[], out: Array<Type | any[]>): void {
|
||||
for (var i = 0; i < tree.length; i++) {
|
||||
var item = resolveForwardRef(tree[i]);
|
||||
@ -104,7 +147,7 @@ function flattenArray(tree: any[], out: Array<Type | any[]>): void {
|
||||
}
|
||||
}
|
||||
|
||||
function isValidDirective(value: Type): boolean {
|
||||
function isValidType(value: Type): boolean {
|
||||
return isPresent(value) && (value instanceof Type);
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,10 @@ export function moduleRef(moduleUrl): string {
|
||||
* Represents generated source code with module references. Internal to the Angular compiler.
|
||||
*/
|
||||
export class SourceModule {
|
||||
static getSourceWithoutImports(sourceWithModuleRefs: string): string {
|
||||
return StringWrapper.replaceAllMapped(sourceWithModuleRefs, MODULE_REGEXP, (match) => '');
|
||||
}
|
||||
|
||||
constructor(public moduleUrl: string, public sourceWithModuleRefs: string) {}
|
||||
|
||||
getSourceWithImports(): SourceWithImports {
|
||||
|
@ -14,7 +14,10 @@ import {
|
||||
MODULE_SUFFIX
|
||||
} from './util';
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {COMPONENT_VARIABLE, HOST_ATTR, CONTENT_ATTR} from 'angular2/src/core/render/view_factory';
|
||||
|
||||
const COMPONENT_VARIABLE = '%COMP%';
|
||||
const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
|
||||
const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
|
||||
|
||||
@Injectable()
|
||||
export class StyleCompiler {
|
||||
|
@ -1,37 +1,74 @@
|
||||
import {IS_DART, Type, Json, isBlank, stringify} from 'angular2/src/facade/lang';
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
import {ListWrapper, SetWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
|
||||
import {
|
||||
CompiledComponentTemplate,
|
||||
TemplateCmd,
|
||||
CompiledHostTemplate,
|
||||
BeginComponentCmd
|
||||
} from 'angular2/src/core/linker/template_commands';
|
||||
IS_DART,
|
||||
Type,
|
||||
Json,
|
||||
isBlank,
|
||||
isPresent,
|
||||
stringify,
|
||||
evalExpression
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
import {
|
||||
ListWrapper,
|
||||
SetWrapper,
|
||||
MapWrapper,
|
||||
StringMapWrapper
|
||||
} from 'angular2/src/facade/collection';
|
||||
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
|
||||
import {
|
||||
createHostComponentMeta,
|
||||
CompileDirectiveMetadata,
|
||||
CompileTypeMetadata,
|
||||
CompileTemplateMetadata
|
||||
CompileTemplateMetadata,
|
||||
CompilePipeMetadata,
|
||||
CompileMetadataWithType
|
||||
} from './directive_metadata';
|
||||
import {TemplateAst} from './template_ast';
|
||||
import {
|
||||
TemplateAst,
|
||||
TemplateAstVisitor,
|
||||
NgContentAst,
|
||||
EmbeddedTemplateAst,
|
||||
ElementAst,
|
||||
VariableAst,
|
||||
BoundEventAst,
|
||||
BoundElementPropertyAst,
|
||||
AttrAst,
|
||||
BoundTextAst,
|
||||
TextAst,
|
||||
DirectiveAst,
|
||||
BoundDirectivePropertyAst,
|
||||
templateVisitAll
|
||||
} from './template_ast';
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {SourceModule, moduleRef} from './source_module';
|
||||
import {ChangeDetectionCompiler} from './change_detector_compiler';
|
||||
import {SourceModule, moduleRef, SourceExpression} from './source_module';
|
||||
import {ChangeDetectionCompiler, CHANGE_DETECTION_JIT_IMPORTS} from './change_detector_compiler';
|
||||
import {StyleCompiler} from './style_compiler';
|
||||
import {CommandCompiler} from './command_compiler';
|
||||
import {TemplateParser} from './template_parser';
|
||||
import {ViewCompiler, VIEW_JIT_IMPORTS} from './view_compiler';
|
||||
import {
|
||||
ProtoViewCompiler,
|
||||
APP_VIEW_MODULE_REF,
|
||||
CompileProtoView,
|
||||
PROTO_VIEW_JIT_IMPORTS
|
||||
} from './proto_view_compiler';
|
||||
import {TemplateParser, PipeCollector} from './template_parser';
|
||||
import {TemplateNormalizer} from './template_normalizer';
|
||||
import {RuntimeMetadataResolver} from './runtime_metadata';
|
||||
import {HostViewFactory} from 'angular2/src/core/linker/view';
|
||||
import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection';
|
||||
import {ResolvedMetadataCache} from 'angular2/src/core/linker/resolved_metadata_cache';
|
||||
|
||||
import {TEMPLATE_COMMANDS_MODULE_REF} from './command_compiler';
|
||||
import {
|
||||
codeGenExportVariable,
|
||||
escapeSingleQuoteString,
|
||||
codeGenValueFn,
|
||||
MODULE_SUFFIX
|
||||
MODULE_SUFFIX,
|
||||
addAll,
|
||||
Expression
|
||||
} from './util';
|
||||
|
||||
export var METADATA_CACHE_MODULE_REF =
|
||||
moduleRef('package:angular2/src/core/linker/resolved_metadata_cache' + MODULE_SUFFIX);
|
||||
|
||||
/**
|
||||
* An internal module of the Angular compiler that begins with component types,
|
||||
* extracts templates, and eventually produces a compiled version of the component
|
||||
@ -40,15 +77,16 @@ import {
|
||||
@Injectable()
|
||||
export class TemplateCompiler {
|
||||
private _hostCacheKeys = new Map<Type, any>();
|
||||
private _compiledTemplateCache = new Map<any, CompiledComponentTemplate>();
|
||||
private _compiledTemplateDone = new Map<any, Promise<CompiledComponentTemplate>>();
|
||||
private _nextTemplateId: number = 0;
|
||||
private _compiledTemplateCache = new Map<any, CompiledTemplate>();
|
||||
private _compiledTemplateDone = new Map<any, Promise<CompiledTemplate>>();
|
||||
|
||||
constructor(private _runtimeMetadataResolver: RuntimeMetadataResolver,
|
||||
private _templateNormalizer: TemplateNormalizer,
|
||||
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
||||
private _commandCompiler: CommandCompiler,
|
||||
private _cdCompiler: ChangeDetectionCompiler) {}
|
||||
private _cdCompiler: ChangeDetectionCompiler,
|
||||
private _protoViewCompiler: ProtoViewCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _resolvedMetadataCache: ResolvedMetadataCache,
|
||||
private _genConfig: ChangeDetectorGenConfig) {}
|
||||
|
||||
normalizeDirectiveMetadata(directive: CompileDirectiveMetadata):
|
||||
Promise<CompileDirectiveMetadata> {
|
||||
@ -75,99 +113,29 @@ export class TemplateCompiler {
|
||||
}));
|
||||
}
|
||||
|
||||
compileHostComponentRuntime(type: Type): Promise<CompiledHostTemplate> {
|
||||
compileHostComponentRuntime(type: Type): Promise<HostViewFactory> {
|
||||
var compMeta: CompileDirectiveMetadata =
|
||||
this._runtimeMetadataResolver.getDirectiveMetadata(type);
|
||||
var hostCacheKey = this._hostCacheKeys.get(type);
|
||||
if (isBlank(hostCacheKey)) {
|
||||
hostCacheKey = new Object();
|
||||
this._hostCacheKeys.set(type, hostCacheKey);
|
||||
var compMeta: CompileDirectiveMetadata = this._runtimeMetadataResolver.getMetadata(type);
|
||||
assertComponent(compMeta);
|
||||
var hostMeta: CompileDirectiveMetadata =
|
||||
createHostComponentMeta(compMeta.type, compMeta.selector);
|
||||
|
||||
this._compileComponentRuntime(hostCacheKey, hostMeta, [compMeta], new Set());
|
||||
this._compileComponentRuntime(hostCacheKey, hostMeta, [compMeta], [], new Set());
|
||||
}
|
||||
return this._compiledTemplateDone.get(hostCacheKey)
|
||||
.then(compiledTemplate => new CompiledHostTemplate(compiledTemplate));
|
||||
.then((compiledTemplate: CompiledTemplate) =>
|
||||
new HostViewFactory(compMeta.selector, compiledTemplate.viewFactory));
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this._hostCacheKeys.clear();
|
||||
this._styleCompiler.clearCache();
|
||||
this._compiledTemplateCache.clear();
|
||||
this._compiledTemplateDone.clear();
|
||||
}
|
||||
|
||||
private _compileComponentRuntime(
|
||||
cacheKey: any, compMeta: CompileDirectiveMetadata, viewDirectives: CompileDirectiveMetadata[],
|
||||
compilingComponentCacheKeys: Set<any>): CompiledComponentTemplate {
|
||||
let uniqViewDirectives = removeDuplicates(viewDirectives);
|
||||
var compiledTemplate = this._compiledTemplateCache.get(cacheKey);
|
||||
var done = this._compiledTemplateDone.get(cacheKey);
|
||||
if (isBlank(compiledTemplate)) {
|
||||
var styles = [];
|
||||
var changeDetectorFactory;
|
||||
var commands = [];
|
||||
var templateId = `${stringify(compMeta.type.runtime)}Template${this._nextTemplateId++}`;
|
||||
compiledTemplate = new CompiledComponentTemplate(
|
||||
templateId, (dispatcher) => changeDetectorFactory(dispatcher), commands, styles);
|
||||
this._compiledTemplateCache.set(cacheKey, compiledTemplate);
|
||||
compilingComponentCacheKeys.add(cacheKey);
|
||||
done = PromiseWrapper
|
||||
.all([<any>this._styleCompiler.compileComponentRuntime(compMeta.template)].concat(
|
||||
uniqViewDirectives.map(dirMeta => this.normalizeDirectiveMetadata(dirMeta))))
|
||||
.then((stylesAndNormalizedViewDirMetas: any[]) => {
|
||||
var childPromises = [];
|
||||
var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1);
|
||||
var parsedTemplate = this._templateParser.parse(
|
||||
compMeta.template.template, normalizedViewDirMetas, compMeta.type.name);
|
||||
|
||||
var changeDetectorFactories = this._cdCompiler.compileComponentRuntime(
|
||||
compMeta.type, compMeta.changeDetection, parsedTemplate);
|
||||
changeDetectorFactory = changeDetectorFactories[0];
|
||||
var tmpStyles: string[] = stylesAndNormalizedViewDirMetas[0];
|
||||
tmpStyles.forEach(style => styles.push(style));
|
||||
var tmpCommands: TemplateCmd[] = this._compileCommandsRuntime(
|
||||
compMeta, parsedTemplate, changeDetectorFactories,
|
||||
compilingComponentCacheKeys, childPromises);
|
||||
tmpCommands.forEach(cmd => commands.push(cmd));
|
||||
return PromiseWrapper.all(childPromises);
|
||||
})
|
||||
.then((_) => {
|
||||
SetWrapper.delete(compilingComponentCacheKeys, cacheKey);
|
||||
return compiledTemplate;
|
||||
});
|
||||
this._compiledTemplateDone.set(cacheKey, done);
|
||||
}
|
||||
return compiledTemplate;
|
||||
}
|
||||
|
||||
private _compileCommandsRuntime(compMeta: CompileDirectiveMetadata, parsedTemplate: TemplateAst[],
|
||||
changeDetectorFactories: Function[],
|
||||
compilingComponentCacheKeys: Set<Type>,
|
||||
childPromises: Promise<any>[]): TemplateCmd[] {
|
||||
var cmds: TemplateCmd[] = this._commandCompiler.compileComponentRuntime(
|
||||
compMeta, parsedTemplate, changeDetectorFactories,
|
||||
(childComponentDir: CompileDirectiveMetadata) => {
|
||||
var childCacheKey = childComponentDir.type.runtime;
|
||||
var childViewDirectives: CompileDirectiveMetadata[] =
|
||||
this._runtimeMetadataResolver.getViewDirectivesMetadata(
|
||||
childComponentDir.type.runtime);
|
||||
var childIsRecursive = SetWrapper.has(compilingComponentCacheKeys, childCacheKey);
|
||||
var childTemplate = this._compileComponentRuntime(
|
||||
childCacheKey, childComponentDir, childViewDirectives, compilingComponentCacheKeys);
|
||||
if (!childIsRecursive) {
|
||||
// Only wait for a child if it is not a cycle
|
||||
childPromises.push(this._compiledTemplateDone.get(childCacheKey));
|
||||
}
|
||||
return () => childTemplate;
|
||||
});
|
||||
cmds.forEach(cmd => {
|
||||
if (cmd instanceof BeginComponentCmd) {
|
||||
cmd.templateGetter();
|
||||
}
|
||||
});
|
||||
return cmds;
|
||||
this._hostCacheKeys.clear();
|
||||
}
|
||||
|
||||
compileTemplatesCodeGen(components: NormalizedComponentWithViewDirectives[]): SourceModule {
|
||||
@ -175,38 +143,22 @@ export class TemplateCompiler {
|
||||
throw new BaseException('No components given');
|
||||
}
|
||||
var declarations = [];
|
||||
var templateArguments = [];
|
||||
var componentMetas: CompileDirectiveMetadata[] = [];
|
||||
components.forEach(componentWithDirs => {
|
||||
var compMeta = <CompileDirectiveMetadata>componentWithDirs.component;
|
||||
assertComponent(compMeta);
|
||||
componentMetas.push(compMeta);
|
||||
|
||||
this._processTemplateCodeGen(compMeta, componentWithDirs.directives, declarations,
|
||||
templateArguments);
|
||||
this._compileComponentCodeGen(compMeta, componentWithDirs.directives, componentWithDirs.pipes,
|
||||
declarations);
|
||||
if (compMeta.dynamicLoadable) {
|
||||
var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector);
|
||||
componentMetas.push(hostMeta);
|
||||
this._processTemplateCodeGen(hostMeta, [compMeta], declarations, templateArguments);
|
||||
var viewFactoryExpression =
|
||||
this._compileComponentCodeGen(hostMeta, [compMeta], [], declarations);
|
||||
var constructionKeyword = IS_DART ? 'const' : 'new';
|
||||
var compiledTemplateExpr =
|
||||
`${constructionKeyword} ${APP_VIEW_MODULE_REF}HostViewFactory('${compMeta.selector}',${viewFactoryExpression})`;
|
||||
var varName = codeGenHostViewFactoryName(compMeta.type);
|
||||
declarations.push(`${codeGenExportVariable(varName)}${compiledTemplateExpr};`);
|
||||
}
|
||||
});
|
||||
ListWrapper.forEachWithIndex(componentMetas, (compMeta: CompileDirectiveMetadata,
|
||||
index: number) => {
|
||||
var templateId = `${compMeta.type.moduleUrl}|${compMeta.type.name}`;
|
||||
var constructionKeyword = IS_DART ? 'const' : 'new';
|
||||
var compiledTemplateExpr =
|
||||
`${constructionKeyword} ${TEMPLATE_COMMANDS_MODULE_REF}CompiledComponentTemplate('${templateId}',${(<any[]>templateArguments[index]).join(',')})`;
|
||||
var variableValueExpr;
|
||||
if (compMeta.type.isHost) {
|
||||
variableValueExpr =
|
||||
`${constructionKeyword} ${TEMPLATE_COMMANDS_MODULE_REF}CompiledHostTemplate(${compiledTemplateExpr})`;
|
||||
} else {
|
||||
variableValueExpr = compiledTemplateExpr;
|
||||
}
|
||||
var varName = templateVariableName(compMeta.type);
|
||||
declarations.push(`${codeGenExportVariable(varName)}${variableValueExpr};`);
|
||||
declarations.push(`${codeGenValueFn([], varName, templateGetterName(compMeta.type))};`);
|
||||
});
|
||||
var moduleUrl = components[0].component.type.moduleUrl;
|
||||
return new SourceModule(`${templateModuleUrl(moduleUrl)}`, declarations.join('\n'));
|
||||
}
|
||||
@ -215,31 +167,149 @@ export class TemplateCompiler {
|
||||
return this._styleCompiler.compileStylesheetCodeGen(stylesheetUrl, cssText);
|
||||
}
|
||||
|
||||
private _processTemplateCodeGen(compMeta: CompileDirectiveMetadata,
|
||||
directives: CompileDirectiveMetadata[],
|
||||
targetDeclarations: string[], targetTemplateArguments: any[][]) {
|
||||
let uniqueDirectives = removeDuplicates(directives);
|
||||
|
||||
|
||||
private _compileComponentRuntime(cacheKey: any, compMeta: CompileDirectiveMetadata,
|
||||
viewDirectives: CompileDirectiveMetadata[],
|
||||
pipes: CompilePipeMetadata[],
|
||||
compilingComponentCacheKeys: Set<any>): CompiledTemplate {
|
||||
let uniqViewDirectives = <CompileDirectiveMetadata[]>removeDuplicates(viewDirectives);
|
||||
let uniqViewPipes = <CompilePipeMetadata[]>removeDuplicates(pipes);
|
||||
var compiledTemplate = this._compiledTemplateCache.get(cacheKey);
|
||||
var done = this._compiledTemplateDone.get(cacheKey);
|
||||
if (isBlank(compiledTemplate)) {
|
||||
compiledTemplate = new CompiledTemplate();
|
||||
this._compiledTemplateCache.set(cacheKey, compiledTemplate);
|
||||
compilingComponentCacheKeys.add(cacheKey);
|
||||
done = PromiseWrapper
|
||||
.all([<any>this._styleCompiler.compileComponentRuntime(compMeta.template)].concat(
|
||||
uniqViewDirectives.map(dirMeta => this.normalizeDirectiveMetadata(dirMeta))))
|
||||
.then((stylesAndNormalizedViewDirMetas: any[]) => {
|
||||
var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1);
|
||||
var styles = stylesAndNormalizedViewDirMetas[0];
|
||||
var parsedTemplate = this._templateParser.parse(
|
||||
compMeta.template.template, normalizedViewDirMetas, uniqViewPipes,
|
||||
compMeta.type.name);
|
||||
|
||||
var childPromises = [];
|
||||
var usedDirectives = DirectiveCollector.findUsedDirectives(parsedTemplate);
|
||||
usedDirectives.components.forEach(
|
||||
component => this._compileNestedComponentRuntime(
|
||||
component, compilingComponentCacheKeys, childPromises));
|
||||
return PromiseWrapper.all(childPromises)
|
||||
.then((_) => {
|
||||
var filteredPipes = filterPipes(parsedTemplate, uniqViewPipes);
|
||||
compiledTemplate.init(this._createViewFactoryRuntime(
|
||||
compMeta, parsedTemplate, usedDirectives.directives, styles,
|
||||
filteredPipes));
|
||||
SetWrapper.delete(compilingComponentCacheKeys, cacheKey);
|
||||
return compiledTemplate;
|
||||
});
|
||||
});
|
||||
this._compiledTemplateDone.set(cacheKey, done);
|
||||
}
|
||||
return compiledTemplate;
|
||||
}
|
||||
|
||||
private _compileNestedComponentRuntime(childComponentDir: CompileDirectiveMetadata,
|
||||
compilingComponentCacheKeys: Set<Type>,
|
||||
childPromises: Promise<any>[]) {
|
||||
var childCacheKey = childComponentDir.type.runtime;
|
||||
var childViewDirectives: CompileDirectiveMetadata[] =
|
||||
this._runtimeMetadataResolver.getViewDirectivesMetadata(childComponentDir.type.runtime);
|
||||
var childViewPipes: CompilePipeMetadata[] =
|
||||
this._runtimeMetadataResolver.getViewPipesMetadata(childComponentDir.type.runtime);
|
||||
var childIsRecursive = SetWrapper.has(compilingComponentCacheKeys, childCacheKey);
|
||||
this._compileComponentRuntime(childCacheKey, childComponentDir, childViewDirectives,
|
||||
childViewPipes, compilingComponentCacheKeys);
|
||||
if (!childIsRecursive) {
|
||||
// Only wait for a child if it is not a cycle
|
||||
childPromises.push(this._compiledTemplateDone.get(childCacheKey));
|
||||
}
|
||||
}
|
||||
|
||||
private _createViewFactoryRuntime(compMeta: CompileDirectiveMetadata,
|
||||
parsedTemplate: TemplateAst[],
|
||||
directives: CompileDirectiveMetadata[], styles: string[],
|
||||
pipes: CompilePipeMetadata[]): Function {
|
||||
if (IS_DART || !this._genConfig.useJit) {
|
||||
var changeDetectorFactories = this._cdCompiler.compileComponentRuntime(
|
||||
compMeta.type, compMeta.changeDetection, parsedTemplate);
|
||||
var protoViews = this._protoViewCompiler.compileProtoViewRuntime(
|
||||
this._resolvedMetadataCache, compMeta, parsedTemplate, pipes);
|
||||
return this._viewCompiler.compileComponentRuntime(
|
||||
compMeta, parsedTemplate, styles, protoViews.protoViews, changeDetectorFactories,
|
||||
(compMeta) => this._getNestedComponentViewFactory(compMeta));
|
||||
} else {
|
||||
var declarations = [];
|
||||
var viewFactoryExpr = this._createViewFactoryCodeGen('resolvedMetadataCache', compMeta,
|
||||
new SourceExpression([], 'styles'),
|
||||
parsedTemplate, pipes, declarations);
|
||||
var vars: {[key: string]: any} =
|
||||
{'exports': {}, 'styles': styles, 'resolvedMetadataCache': this._resolvedMetadataCache};
|
||||
directives.forEach(dirMeta => {
|
||||
vars[dirMeta.type.name] = dirMeta.type.runtime;
|
||||
if (dirMeta.isComponent && dirMeta.type.runtime !== compMeta.type.runtime) {
|
||||
vars[`viewFactory_${dirMeta.type.name}0`] = this._getNestedComponentViewFactory(dirMeta);
|
||||
}
|
||||
});
|
||||
pipes.forEach(pipeMeta => vars[pipeMeta.type.name] = pipeMeta.type.runtime);
|
||||
var declarationsWithoutImports =
|
||||
SourceModule.getSourceWithoutImports(declarations.join('\n'));
|
||||
return evalExpression(
|
||||
`viewFactory_${compMeta.type.name}`, viewFactoryExpr, declarationsWithoutImports,
|
||||
mergeStringMaps(
|
||||
[vars, CHANGE_DETECTION_JIT_IMPORTS, PROTO_VIEW_JIT_IMPORTS, VIEW_JIT_IMPORTS]));
|
||||
}
|
||||
}
|
||||
|
||||
private _getNestedComponentViewFactory(compMeta: CompileDirectiveMetadata): Function {
|
||||
return this._compiledTemplateCache.get(compMeta.type.runtime).viewFactory;
|
||||
}
|
||||
|
||||
private _compileComponentCodeGen(compMeta: CompileDirectiveMetadata,
|
||||
directives: CompileDirectiveMetadata[],
|
||||
pipes: CompilePipeMetadata[],
|
||||
targetDeclarations: string[]): string {
|
||||
let uniqueDirectives = <CompileDirectiveMetadata[]>removeDuplicates(directives);
|
||||
let uniqPipes = <CompilePipeMetadata[]>removeDuplicates(pipes);
|
||||
var styleExpr = this._styleCompiler.compileComponentCodeGen(compMeta.template);
|
||||
var parsedTemplate = this._templateParser.parse(compMeta.template.template, uniqueDirectives,
|
||||
compMeta.type.name);
|
||||
uniqPipes, compMeta.type.name);
|
||||
var filteredPipes = filterPipes(parsedTemplate, uniqPipes);
|
||||
return this._createViewFactoryCodeGen(
|
||||
`${METADATA_CACHE_MODULE_REF}CODEGEN_RESOLVED_METADATA_CACHE`, compMeta, styleExpr,
|
||||
parsedTemplate, filteredPipes, targetDeclarations);
|
||||
}
|
||||
|
||||
private _createViewFactoryCodeGen(resolvedMetadataCacheExpr: string,
|
||||
compMeta: CompileDirectiveMetadata, styleExpr: SourceExpression,
|
||||
parsedTemplate: TemplateAst[], pipes: CompilePipeMetadata[],
|
||||
targetDeclarations: string[]): string {
|
||||
var changeDetectorsExprs = this._cdCompiler.compileComponentCodeGen(
|
||||
compMeta.type, compMeta.changeDetection, parsedTemplate);
|
||||
var commandsExpr = this._commandCompiler.compileComponentCodeGen(
|
||||
compMeta, parsedTemplate, changeDetectorsExprs.expressions,
|
||||
codeGenComponentTemplateFactory);
|
||||
var protoViewExprs = this._protoViewCompiler.compileProtoViewCodeGen(
|
||||
new Expression(resolvedMetadataCacheExpr), compMeta, parsedTemplate, pipes);
|
||||
var viewFactoryExpr = this._viewCompiler.compileComponentCodeGen(
|
||||
compMeta, parsedTemplate, styleExpr, protoViewExprs.protoViews, changeDetectorsExprs,
|
||||
codeGenComponentViewFactoryName);
|
||||
|
||||
addAll(styleExpr.declarations, targetDeclarations);
|
||||
addAll(changeDetectorsExprs.declarations, targetDeclarations);
|
||||
addAll(commandsExpr.declarations, targetDeclarations);
|
||||
addAll(protoViewExprs.declarations, targetDeclarations);
|
||||
addAll(viewFactoryExpr.declarations, targetDeclarations);
|
||||
|
||||
targetTemplateArguments.push(
|
||||
[changeDetectorsExprs.expressions[0], commandsExpr.expression, styleExpr.expression]);
|
||||
return viewFactoryExpr.expression;
|
||||
}
|
||||
}
|
||||
|
||||
export class NormalizedComponentWithViewDirectives {
|
||||
constructor(public component: CompileDirectiveMetadata,
|
||||
public directives: CompileDirectiveMetadata[]) {}
|
||||
public directives: CompileDirectiveMetadata[], public pipes: CompilePipeMetadata[]) {}
|
||||
}
|
||||
|
||||
class CompiledTemplate {
|
||||
viewFactory: Function = null;
|
||||
init(viewFactory: Function) { this.viewFactory = viewFactory; }
|
||||
}
|
||||
|
||||
function assertComponent(meta: CompileDirectiveMetadata) {
|
||||
@ -248,30 +318,28 @@ function assertComponent(meta: CompileDirectiveMetadata) {
|
||||
}
|
||||
}
|
||||
|
||||
function templateVariableName(type: CompileTypeMetadata): string {
|
||||
return `${type.name}Template`;
|
||||
}
|
||||
|
||||
function templateGetterName(type: CompileTypeMetadata): string {
|
||||
return `${templateVariableName(type)}Getter`;
|
||||
}
|
||||
|
||||
function templateModuleUrl(moduleUrl: string): string {
|
||||
var urlWithoutSuffix = moduleUrl.substring(0, moduleUrl.length - MODULE_SUFFIX.length);
|
||||
return `${urlWithoutSuffix}.template${MODULE_SUFFIX}`;
|
||||
}
|
||||
|
||||
function addAll(source: any[], target: any[]) {
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
target.push(source[i]);
|
||||
}
|
||||
|
||||
function codeGenHostViewFactoryName(type: CompileTypeMetadata): string {
|
||||
return `hostViewFactory_${type.name}`;
|
||||
}
|
||||
|
||||
function codeGenComponentTemplateFactory(nestedCompType: CompileDirectiveMetadata): string {
|
||||
return `${moduleRef(templateModuleUrl(nestedCompType.type.moduleUrl))}${templateGetterName(nestedCompType.type)}`;
|
||||
function codeGenComponentViewFactoryName(nestedCompType: CompileDirectiveMetadata): string {
|
||||
return `${moduleRef(templateModuleUrl(nestedCompType.type.moduleUrl))}viewFactory_${nestedCompType.type.name}0`;
|
||||
}
|
||||
|
||||
function removeDuplicates(items: CompileDirectiveMetadata[]): CompileDirectiveMetadata[] {
|
||||
function mergeStringMaps(maps: Array<{[key: string]: any}>): {[key: string]: any} {
|
||||
var result = {};
|
||||
maps.forEach(
|
||||
(map) => { StringMapWrapper.forEach(map, (value, key) => { result[key] = value; }); });
|
||||
return result;
|
||||
}
|
||||
|
||||
function removeDuplicates(items: CompileMetadataWithType[]): CompileMetadataWithType[] {
|
||||
let res = [];
|
||||
items.forEach(item => {
|
||||
let hasMatch =
|
||||
@ -284,3 +352,100 @@ function removeDuplicates(items: CompileDirectiveMetadata[]): CompileDirectiveMe
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
class DirectiveCollector implements TemplateAstVisitor {
|
||||
static findUsedDirectives(parsedTemplate: TemplateAst[]): DirectiveCollector {
|
||||
var collector = new DirectiveCollector();
|
||||
templateVisitAll(collector, parsedTemplate);
|
||||
return collector;
|
||||
}
|
||||
|
||||
directives: CompileDirectiveMetadata[] = [];
|
||||
components: CompileDirectiveMetadata[] = [];
|
||||
|
||||
visitBoundText(ast: BoundTextAst, context: any): any { return null; }
|
||||
visitText(ast: TextAst, context: any): any { return null; }
|
||||
|
||||
visitNgContent(ast: NgContentAst, context: any): any { return null; }
|
||||
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
templateVisitAll(this, ast.directives);
|
||||
templateVisitAll(this, ast.children);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
templateVisitAll(this, ast.directives);
|
||||
templateVisitAll(this, ast.children);
|
||||
return null;
|
||||
}
|
||||
visitVariable(ast: VariableAst, ctx: any): any { return null; }
|
||||
visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any { return null; }
|
||||
visitDirective(ast: DirectiveAst, ctx: any): any {
|
||||
if (ast.directive.isComponent) {
|
||||
this.components.push(ast.directive);
|
||||
}
|
||||
this.directives.push(ast.directive);
|
||||
return null;
|
||||
}
|
||||
visitEvent(ast: BoundEventAst, eventTargetAndNames: Map<string, BoundEventAst>): any {
|
||||
return null;
|
||||
}
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
|
||||
}
|
||||
|
||||
|
||||
function filterPipes(template: TemplateAst[],
|
||||
allPipes: CompilePipeMetadata[]): CompilePipeMetadata[] {
|
||||
var visitor = new PipeVisitor();
|
||||
templateVisitAll(visitor, template);
|
||||
return allPipes.filter((pipeMeta) => SetWrapper.has(visitor.collector.pipes, pipeMeta.name));
|
||||
}
|
||||
|
||||
class PipeVisitor implements TemplateAstVisitor {
|
||||
collector: PipeCollector = new PipeCollector();
|
||||
|
||||
visitBoundText(ast: BoundTextAst, context: any): any {
|
||||
ast.value.visit(this.collector);
|
||||
return null;
|
||||
}
|
||||
visitText(ast: TextAst, context: any): any { return null; }
|
||||
|
||||
visitNgContent(ast: NgContentAst, context: any): any { return null; }
|
||||
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
templateVisitAll(this, ast.inputs);
|
||||
templateVisitAll(this, ast.outputs);
|
||||
templateVisitAll(this, ast.directives);
|
||||
templateVisitAll(this, ast.children);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
templateVisitAll(this, ast.outputs);
|
||||
templateVisitAll(this, ast.directives);
|
||||
templateVisitAll(this, ast.children);
|
||||
return null;
|
||||
}
|
||||
visitVariable(ast: VariableAst, ctx: any): any { return null; }
|
||||
visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any { return null; }
|
||||
visitDirective(ast: DirectiveAst, ctx: any): any {
|
||||
templateVisitAll(this, ast.inputs);
|
||||
templateVisitAll(this, ast.hostEvents);
|
||||
templateVisitAll(this, ast.hostProperties);
|
||||
return null;
|
||||
}
|
||||
visitEvent(ast: BoundEventAst, eventTargetAndNames: Map<string, BoundEventAst>): any {
|
||||
ast.handler.visit(this.collector);
|
||||
return null;
|
||||
}
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {
|
||||
ast.value.visit(this.collector);
|
||||
return null;
|
||||
}
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {
|
||||
ast.value.visit(this.collector);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -5,10 +5,11 @@ import {CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
import {Parser, AST, ASTWithSource} from 'angular2/src/core/change_detection/change_detection';
|
||||
import {TemplateBinding} from 'angular2/src/core/change_detection/parser/ast';
|
||||
import {CompileDirectiveMetadata} from './directive_metadata';
|
||||
import {CompileDirectiveMetadata, CompilePipeMetadata} from './directive_metadata';
|
||||
import {HtmlParser} from './html_parser';
|
||||
import {splitNsName} from './html_tags';
|
||||
import {ParseSourceSpan, ParseError, ParseLocation} from './parse_util';
|
||||
import {RecursiveAstVisitor, BindingPipe} from 'angular2/src/core/change_detection/parser/ast';
|
||||
|
||||
|
||||
import {
|
||||
@ -88,9 +89,10 @@ export class TemplateParser {
|
||||
private _htmlParser: HtmlParser,
|
||||
@Optional() @Inject(TEMPLATE_TRANSFORMS) public transforms: TemplateAstVisitor[]) {}
|
||||
|
||||
parse(template: string, directives: CompileDirectiveMetadata[],
|
||||
parse(template: string, directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[],
|
||||
templateUrl: string): TemplateAst[] {
|
||||
var parseVisitor = new TemplateParseVisitor(directives, this._exprParser, this._schemaRegistry);
|
||||
var parseVisitor =
|
||||
new TemplateParseVisitor(directives, pipes, this._exprParser, this._schemaRegistry);
|
||||
var htmlAstWithErrors = this._htmlParser.parse(template, templateUrl);
|
||||
var result = htmlVisitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_COMPONENT);
|
||||
var errors: ParseError[] = htmlAstWithErrors.errors.concat(parseVisitor.errors);
|
||||
@ -111,9 +113,10 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
errors: TemplateParseError[] = [];
|
||||
directivesIndex = new Map<CompileDirectiveMetadata, number>();
|
||||
ngContentCount: number = 0;
|
||||
pipesByName: Map<string, CompilePipeMetadata>;
|
||||
|
||||
constructor(directives: CompileDirectiveMetadata[], private _exprParser: Parser,
|
||||
private _schemaRegistry: ElementSchemaRegistry) {
|
||||
constructor(directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[],
|
||||
private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry) {
|
||||
this.selectorMatcher = new SelectorMatcher();
|
||||
ListWrapper.forEachWithIndex(directives,
|
||||
(directive: CompileDirectiveMetadata, index: number) => {
|
||||
@ -121,6 +124,8 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
this.selectorMatcher.addSelectables(selector, directive);
|
||||
this.directivesIndex.set(directive, index);
|
||||
});
|
||||
this.pipesByName = new Map<string, CompilePipeMetadata>();
|
||||
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
|
||||
}
|
||||
|
||||
private _reportError(message: string, sourceSpan: ParseSourceSpan) {
|
||||
@ -130,7 +135,9 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
private _parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
||||
var sourceInfo = sourceSpan.start.toString();
|
||||
try {
|
||||
return this._exprParser.parseInterpolation(value, sourceInfo);
|
||||
var ast = this._exprParser.parseInterpolation(value, sourceInfo);
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
return ast;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
|
||||
@ -140,7 +147,9 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
||||
var sourceInfo = sourceSpan.start.toString();
|
||||
try {
|
||||
return this._exprParser.parseAction(value, sourceInfo);
|
||||
var ast = this._exprParser.parseAction(value, sourceInfo);
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
return ast;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
|
||||
@ -150,7 +159,9 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
private _parseBinding(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
||||
var sourceInfo = sourceSpan.start.toString();
|
||||
try {
|
||||
return this._exprParser.parseBinding(value, sourceInfo);
|
||||
var ast = this._exprParser.parseBinding(value, sourceInfo);
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
return ast;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
|
||||
@ -160,13 +171,31 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
private _parseTemplateBindings(value: string, sourceSpan: ParseSourceSpan): TemplateBinding[] {
|
||||
var sourceInfo = sourceSpan.start.toString();
|
||||
try {
|
||||
return this._exprParser.parseTemplateBindings(value, sourceInfo);
|
||||
var bindings = this._exprParser.parseTemplateBindings(value, sourceInfo);
|
||||
bindings.forEach((binding) => {
|
||||
if (isPresent(binding.expression)) {
|
||||
this._checkPipes(binding.expression, sourceSpan);
|
||||
}
|
||||
});
|
||||
return bindings;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
|
||||
if (isPresent(ast)) {
|
||||
var collector = new PipeCollector();
|
||||
ast.visit(collector);
|
||||
collector.pipes.forEach((pipeName) => {
|
||||
if (!this.pipesByName.has(pipeName)) {
|
||||
this._reportError(`The pipe '${pipeName}' could not be found`, sourceSpan);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
visitText(ast: HtmlTextAst, component: Component): any {
|
||||
var ngContentIndex = component.findNgContentIndex(TEXT_CSS_SELECTOR);
|
||||
var expr = this._parseInterpolation(ast.value, ast.sourceSpan);
|
||||
@ -714,3 +743,14 @@ function createElementCssSelector(elementName: string, matchableAttrs: string[][
|
||||
|
||||
var EMPTY_COMPONENT = new Component(new SelectorMatcher(), null);
|
||||
var NON_BINDABLE_VISITOR = new NonBindableVisitor();
|
||||
|
||||
|
||||
export class PipeCollector extends RecursiveAstVisitor {
|
||||
pipes: Set<string> = new Set<string>();
|
||||
visitPipe(ast: BindingPipe): any {
|
||||
this.pipes.add(ast.name);
|
||||
ast.exp.visit(this);
|
||||
this.visitAll(ast.args);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,11 @@
|
||||
import {IS_DART, StringWrapper, isBlank} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
IS_DART,
|
||||
StringWrapper,
|
||||
isBlank,
|
||||
isPresent,
|
||||
isString,
|
||||
isArray
|
||||
} from 'angular2/src/facade/lang';
|
||||
|
||||
var CAMEL_CASE_REGEXP = /([A-Z])/g;
|
||||
var DASH_CASE_REGEXP = /-([a-z])/g;
|
||||
@ -7,6 +14,8 @@ var DOUBLE_QUOTE_ESCAPE_STRING_RE = /"|\\|\n|\r|\$/g;
|
||||
|
||||
export var MODULE_SUFFIX = IS_DART ? '.dart' : '.js';
|
||||
|
||||
export var CONST_VAR = IS_DART ? 'const' : 'var';
|
||||
|
||||
export function camelCaseToDashCase(input: string): string {
|
||||
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP,
|
||||
(m) => { return '-' + m[1].toLowerCase(); });
|
||||
@ -63,12 +72,19 @@ export function codeGenConstConstructorCall(name: string): string {
|
||||
|
||||
export function codeGenValueFn(params: string[], value: string, fnName: string = ''): string {
|
||||
if (IS_DART) {
|
||||
return `${fnName}(${params.join(',')}) => ${value}`;
|
||||
return `${codeGenFnHeader(params, fnName)} => ${value}`;
|
||||
} else {
|
||||
return `function ${fnName}(${params.join(',')}) { return ${value}; }`;
|
||||
return `${codeGenFnHeader(params, fnName)} { return ${value}; }`;
|
||||
}
|
||||
}
|
||||
|
||||
export function codeGenFnHeader(params: string[], fnName: string = ''): string {
|
||||
if (IS_DART) {
|
||||
return `${fnName}(${params.join(',')})`;
|
||||
} else {
|
||||
return `function ${fnName}(${params.join(',')})`;
|
||||
}
|
||||
}
|
||||
export function codeGenToString(expr: string): string {
|
||||
if (IS_DART) {
|
||||
return `'\${${expr}}'`;
|
||||
@ -86,3 +102,77 @@ export function splitAtColon(input: string, defaultValues: string[]): string[] {
|
||||
return defaultValues;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Statement {
|
||||
constructor(public statement: string) {}
|
||||
}
|
||||
|
||||
export class Expression {
|
||||
constructor(public expression: string, public isArray = false) {}
|
||||
}
|
||||
|
||||
export function escapeValue(value: any): string {
|
||||
if (value instanceof Expression) {
|
||||
return value.expression;
|
||||
} else if (isString(value)) {
|
||||
return escapeSingleQuoteString(value);
|
||||
} else if (isBlank(value)) {
|
||||
return 'null';
|
||||
} else {
|
||||
return `${value}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function codeGenArray(data: any[]): string {
|
||||
return `[${data.map(escapeValue).join(',')}]`;
|
||||
}
|
||||
|
||||
export function codeGenFlatArray(values: any[]): string {
|
||||
var result = '([';
|
||||
var isFirstArrayEntry = true;
|
||||
var concatFn = IS_DART ? '.addAll' : 'concat';
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var value = values[i];
|
||||
if (value instanceof Expression && (<Expression>value).isArray) {
|
||||
result += `]).${concatFn}(${value.expression}).${concatFn}([`;
|
||||
isFirstArrayEntry = true;
|
||||
} else {
|
||||
if (!isFirstArrayEntry) {
|
||||
result += ',';
|
||||
}
|
||||
isFirstArrayEntry = false;
|
||||
result += escapeValue(value);
|
||||
}
|
||||
}
|
||||
result += '])';
|
||||
return result;
|
||||
}
|
||||
|
||||
export function codeGenStringMap(keyValueArray: any[][]): string {
|
||||
return `{${keyValueArray.map(codeGenKeyValue).join(',')}}`;
|
||||
}
|
||||
|
||||
function codeGenKeyValue(keyValue: any[]): string {
|
||||
return `${escapeValue(keyValue[0])}:${escapeValue(keyValue[1])}`;
|
||||
}
|
||||
|
||||
export function addAll(source: any[], target: any[]) {
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
target.push(source[i]);
|
||||
}
|
||||
}
|
||||
|
||||
export function flattenArray(source: any[], target: any[]): any[] {
|
||||
if (isPresent(source)) {
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
var item = source[i];
|
||||
if (isArray(item)) {
|
||||
flattenArray(item, target);
|
||||
} else {
|
||||
target.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
600
modules/angular2/src/compiler/view_compiler.ts
Normal file
600
modules/angular2/src/compiler/view_compiler.ts
Normal file
@ -0,0 +1,600 @@
|
||||
import {
|
||||
isPresent,
|
||||
isBlank,
|
||||
Type,
|
||||
isString,
|
||||
StringWrapper,
|
||||
IS_DART,
|
||||
CONST_EXPR
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {SetWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {
|
||||
TemplateAst,
|
||||
TemplateAstVisitor,
|
||||
NgContentAst,
|
||||
EmbeddedTemplateAst,
|
||||
ElementAst,
|
||||
VariableAst,
|
||||
BoundEventAst,
|
||||
BoundElementPropertyAst,
|
||||
AttrAst,
|
||||
BoundTextAst,
|
||||
TextAst,
|
||||
DirectiveAst,
|
||||
BoundDirectivePropertyAst,
|
||||
templateVisitAll
|
||||
} from './template_ast';
|
||||
import {CompileTypeMetadata, CompileDirectiveMetadata} from './directive_metadata';
|
||||
import {SourceExpressions, SourceExpression, moduleRef} from './source_module';
|
||||
import {
|
||||
AppProtoView,
|
||||
AppView,
|
||||
flattenNestedViewRenderNodes,
|
||||
checkSlotCount
|
||||
} from 'angular2/src/core/linker/view';
|
||||
import {ViewType} from 'angular2/src/core/linker/view_type';
|
||||
import {AppViewManager_} from 'angular2/src/core/linker/view_manager';
|
||||
import {AppProtoElement, AppElement} from 'angular2/src/core/linker/element';
|
||||
import {Renderer, ParentRenderer} from 'angular2/src/core/render/api';
|
||||
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
|
||||
import {
|
||||
escapeSingleQuoteString,
|
||||
codeGenConstConstructorCall,
|
||||
codeGenValueFn,
|
||||
codeGenFnHeader,
|
||||
MODULE_SUFFIX,
|
||||
Statement,
|
||||
escapeValue,
|
||||
codeGenArray,
|
||||
codeGenFlatArray,
|
||||
Expression,
|
||||
flattenArray,
|
||||
CONST_VAR
|
||||
} from './util';
|
||||
import {ResolvedProvider, Injectable, Injector} from 'angular2/src/core/di';
|
||||
|
||||
import {
|
||||
APP_VIEW_MODULE_REF,
|
||||
APP_EL_MODULE_REF,
|
||||
METADATA_MODULE_REF,
|
||||
CompileProtoView,
|
||||
CompileProtoElement
|
||||
} from './proto_view_compiler';
|
||||
|
||||
export const VIEW_JIT_IMPORTS = CONST_EXPR({
|
||||
'AppView': AppView,
|
||||
'AppElement': AppElement,
|
||||
'flattenNestedViewRenderNodes': flattenNestedViewRenderNodes,
|
||||
'checkSlotCount': checkSlotCount
|
||||
});
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class ViewCompiler {
|
||||
constructor() {}
|
||||
|
||||
compileComponentRuntime(component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
styles: Array<string | any[]>,
|
||||
protoViews: CompileProtoView<AppProtoView, AppProtoElement>[],
|
||||
changeDetectorFactories: Function[],
|
||||
componentViewFactory: Function): Function {
|
||||
var viewFactory = new RuntimeViewFactory(component, styles, protoViews, changeDetectorFactories,
|
||||
componentViewFactory);
|
||||
return viewFactory.createViewFactory(template, 0, []);
|
||||
}
|
||||
|
||||
compileComponentCodeGen(component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
styles: SourceExpression,
|
||||
protoViews: CompileProtoView<Expression, Expression>[],
|
||||
changeDetectorFactoryExpressions: SourceExpressions,
|
||||
componentViewFactory: Function): SourceExpression {
|
||||
var viewFactory = new CodeGenViewFactory(
|
||||
component, styles, protoViews, changeDetectorFactoryExpressions, componentViewFactory);
|
||||
var targetStatements: Statement[] = [];
|
||||
var viewFactoryExpression = viewFactory.createViewFactory(template, 0, targetStatements);
|
||||
return new SourceExpression(targetStatements.map(stmt => stmt.statement),
|
||||
viewFactoryExpression.expression);
|
||||
}
|
||||
}
|
||||
|
||||
interface ViewFactory<EXPRESSION, STATEMENT> {
|
||||
createText(renderer: EXPRESSION, parent: EXPRESSION, text: string,
|
||||
targetStatements: STATEMENT[]): EXPRESSION;
|
||||
|
||||
createElement(renderer: EXPRESSION, parent: EXPRESSION, name: string, rootSelector: EXPRESSION,
|
||||
targetStatements: STATEMENT[]): EXPRESSION;
|
||||
|
||||
createTemplateAnchor(renderer: EXPRESSION, parent: EXPRESSION,
|
||||
targetStatements: STATEMENT[]): EXPRESSION;
|
||||
|
||||
createGlobalEventListener(renderer: EXPRESSION, view: EXPRESSION, boundElementIndex: number,
|
||||
eventAst: BoundEventAst, targetStatements: STATEMENT[]): EXPRESSION;
|
||||
|
||||
createElementEventListener(renderer: EXPRESSION, view: EXPRESSION, boundElementIndex: number,
|
||||
renderNode: EXPRESSION, eventAst: BoundEventAst,
|
||||
targetStatements: STATEMENT[]);
|
||||
|
||||
setElementAttribute(renderer: EXPRESSION, renderNode: EXPRESSION, attrName: string,
|
||||
attrValue: string, targetStatements: STATEMENT[]);
|
||||
|
||||
createAppElement(appProtoEl: EXPRESSION, view: EXPRESSION, renderNode: EXPRESSION,
|
||||
parentAppEl: EXPRESSION, embeddedViewFactory: EXPRESSION,
|
||||
targetStatements: STATEMENT[]): EXPRESSION;
|
||||
|
||||
createAndSetComponentView(renderer: EXPRESSION, viewManager: EXPRESSION, view: EXPRESSION,
|
||||
appEl: EXPRESSION, component: CompileDirectiveMetadata,
|
||||
contentNodesByNgContentIndex: EXPRESSION[][],
|
||||
targetStatements: STATEMENT[]);
|
||||
|
||||
getProjectedNodes(projectableNodes: EXPRESSION, ngContentIndex: number): EXPRESSION;
|
||||
|
||||
appendProjectedNodes(renderer: EXPRESSION, parent: EXPRESSION, nodes: EXPRESSION,
|
||||
targetStatements: STATEMENT[]);
|
||||
|
||||
createViewFactory(asts: TemplateAst[], embeddedTemplateIndex: number,
|
||||
targetStatements: STATEMENT[]): EXPRESSION;
|
||||
}
|
||||
|
||||
class CodeGenViewFactory implements ViewFactory<Expression, Statement> {
|
||||
private _nextVarId: number = 0;
|
||||
constructor(public component: CompileDirectiveMetadata, public styles: SourceExpression,
|
||||
public protoViews: CompileProtoView<Expression, Expression>[],
|
||||
public changeDetectorExpressions: SourceExpressions,
|
||||
public componentViewFactory: Function) {}
|
||||
|
||||
private _nextVar(prefix: string): string {
|
||||
return `${prefix}${this._nextVarId++}_${this.component.type.name}`;
|
||||
}
|
||||
|
||||
private _nextRenderVar(): string { return this._nextVar('render'); }
|
||||
|
||||
private _nextAppVar(): string { return this._nextVar('app'); }
|
||||
|
||||
private _nextDisposableVar(): string {
|
||||
return `disposable${this._nextVarId++}_${this.component.type.name}`;
|
||||
}
|
||||
|
||||
createText(renderer: Expression, parent: Expression, text: string,
|
||||
targetStatements: Statement[]): Expression {
|
||||
var varName = this._nextRenderVar();
|
||||
var statement =
|
||||
`var ${varName} = ${renderer.expression}.createText(${isPresent(parent) ? parent.expression : null}, ${escapeSingleQuoteString(text)});`;
|
||||
targetStatements.push(new Statement(statement));
|
||||
return new Expression(varName);
|
||||
}
|
||||
|
||||
createElement(renderer: Expression, parentRenderNode: Expression, name: string,
|
||||
rootSelector: Expression, targetStatements: Statement[]): Expression {
|
||||
var varName = this._nextRenderVar();
|
||||
var valueExpr;
|
||||
if (isPresent(rootSelector)) {
|
||||
valueExpr = `${rootSelector.expression} == null ?
|
||||
${renderer.expression}.createElement(${isPresent(parentRenderNode) ? parentRenderNode.expression : null}, ${escapeSingleQuoteString(name)}) :
|
||||
${renderer.expression}.selectRootElement(${rootSelector.expression});`;
|
||||
} else {
|
||||
valueExpr =
|
||||
`${renderer.expression}.createElement(${isPresent(parentRenderNode) ? parentRenderNode.expression : null}, ${escapeSingleQuoteString(name)})`;
|
||||
}
|
||||
var statement = `var ${varName} = ${valueExpr};`;
|
||||
targetStatements.push(new Statement(statement));
|
||||
return new Expression(varName);
|
||||
}
|
||||
|
||||
createTemplateAnchor(renderer: Expression, parentRenderNode: Expression,
|
||||
targetStatements: Statement[]): Expression {
|
||||
var varName = this._nextRenderVar();
|
||||
var valueExpr =
|
||||
`${renderer.expression}.createTemplateAnchor(${isPresent(parentRenderNode) ? parentRenderNode.expression : null});`;
|
||||
targetStatements.push(new Statement(`var ${varName} = ${valueExpr}`));
|
||||
return new Expression(varName);
|
||||
}
|
||||
|
||||
createGlobalEventListener(renderer: Expression, appView: Expression, boundElementIndex: number,
|
||||
eventAst: BoundEventAst, targetStatements: Statement[]): Expression {
|
||||
var disposableVar = this._nextDisposableVar();
|
||||
var eventHandlerExpr = codeGenEventHandler(appView, boundElementIndex, eventAst.fullName);
|
||||
targetStatements.push(new Statement(
|
||||
`var ${disposableVar} = ${renderer.expression}.listenGlobal(${escapeValue(eventAst.target)}, ${escapeValue(eventAst.name)}, ${eventHandlerExpr});`));
|
||||
return new Expression(disposableVar);
|
||||
}
|
||||
|
||||
createElementEventListener(renderer: Expression, appView: Expression, boundElementIndex: number,
|
||||
renderNode: Expression, eventAst: BoundEventAst,
|
||||
targetStatements: Statement[]) {
|
||||
var eventHandlerExpr = codeGenEventHandler(appView, boundElementIndex, eventAst.fullName);
|
||||
targetStatements.push(new Statement(
|
||||
`${renderer.expression}.listen(${renderNode.expression}, ${escapeValue(eventAst.name)}, ${eventHandlerExpr});`));
|
||||
}
|
||||
|
||||
setElementAttribute(renderer: Expression, renderNode: Expression, attrName: string,
|
||||
attrValue: string, targetStatements: Statement[]) {
|
||||
targetStatements.push(new Statement(
|
||||
`${renderer.expression}.setElementAttribute(${renderNode.expression}, ${escapeSingleQuoteString(attrName)}, ${escapeSingleQuoteString(attrValue)});`));
|
||||
}
|
||||
|
||||
createAppElement(appProtoEl: Expression, appView: Expression, renderNode: Expression,
|
||||
parentAppEl: Expression, embeddedViewFactory: Expression,
|
||||
targetStatements: Statement[]): Expression {
|
||||
var appVar = this._nextAppVar();
|
||||
var varValue =
|
||||
`new ${APP_EL_MODULE_REF}AppElement(${appProtoEl.expression}, ${appView.expression},
|
||||
${isPresent(parentAppEl) ? parentAppEl.expression : null}, ${renderNode.expression}, ${isPresent(embeddedViewFactory) ? embeddedViewFactory.expression : null})`;
|
||||
targetStatements.push(new Statement(`var ${appVar} = ${varValue};`));
|
||||
return new Expression(appVar);
|
||||
}
|
||||
|
||||
createAndSetComponentView(renderer: Expression, viewManager: Expression, view: Expression,
|
||||
appEl: Expression, component: CompileDirectiveMetadata,
|
||||
contentNodesByNgContentIndex: Expression[][],
|
||||
targetStatements: Statement[]) {
|
||||
var codeGenContentNodes;
|
||||
if (this.component.type.isHost) {
|
||||
codeGenContentNodes = `${view.expression}.projectableNodes`;
|
||||
} else {
|
||||
codeGenContentNodes =
|
||||
`[${contentNodesByNgContentIndex.map( nodes => codeGenFlatArray(nodes) ).join(',')}]`;
|
||||
}
|
||||
targetStatements.push(new Statement(
|
||||
`${this.componentViewFactory(component)}(${renderer.expression}, ${viewManager.expression}, ${appEl.expression}, ${codeGenContentNodes}, null, null, null);`));
|
||||
}
|
||||
|
||||
getProjectedNodes(projectableNodes: Expression, ngContentIndex: number): Expression {
|
||||
return new Expression(`${projectableNodes.expression}[${ngContentIndex}]`, true);
|
||||
}
|
||||
|
||||
appendProjectedNodes(renderer: Expression, parent: Expression, nodes: Expression,
|
||||
targetStatements: Statement[]) {
|
||||
targetStatements.push(new Statement(
|
||||
`${renderer.expression}.projectNodes(${parent.expression}, ${APP_VIEW_MODULE_REF}flattenNestedViewRenderNodes(${nodes.expression}));`));
|
||||
}
|
||||
|
||||
createViewFactory(asts: TemplateAst[], embeddedTemplateIndex: number,
|
||||
targetStatements: Statement[]): Expression {
|
||||
var compileProtoView = this.protoViews[embeddedTemplateIndex];
|
||||
var isHostView = this.component.type.isHost;
|
||||
var isComponentView = embeddedTemplateIndex === 0 && !isHostView;
|
||||
var visitor = new ViewBuilderVisitor<Expression, Statement>(
|
||||
new Expression('renderer'), new Expression('viewManager'),
|
||||
new Expression('projectableNodes'), isHostView ? new Expression('rootSelector') : null,
|
||||
new Expression('view'), compileProtoView, targetStatements, this);
|
||||
|
||||
templateVisitAll(
|
||||
visitor, asts,
|
||||
new ParentElement(isComponentView ? new Expression('parentRenderNode') : null, null, null));
|
||||
|
||||
var appProtoView = compileProtoView.protoView.expression;
|
||||
var viewFactoryName = codeGenViewFactoryName(this.component, embeddedTemplateIndex);
|
||||
var changeDetectorFactory = this.changeDetectorExpressions.expressions[embeddedTemplateIndex];
|
||||
var factoryArgs = [
|
||||
'parentRenderer',
|
||||
'viewManager',
|
||||
'containerEl',
|
||||
'projectableNodes',
|
||||
'rootSelector',
|
||||
'dynamicallyCreatedProviders',
|
||||
'rootInjector'
|
||||
];
|
||||
var initRendererStmts = [];
|
||||
var rendererExpr = `parentRenderer`;
|
||||
if (embeddedTemplateIndex === 0) {
|
||||
var renderCompTypeVar = this._nextVar('renderType');
|
||||
targetStatements.push(new Statement(`var ${renderCompTypeVar} = null;`));
|
||||
var stylesVar = this._nextVar('styles');
|
||||
targetStatements.push(
|
||||
new Statement(`${CONST_VAR} ${stylesVar} = ${this.styles.expression};`));
|
||||
var encapsulation = this.component.template.encapsulation;
|
||||
initRendererStmts.push(`if (${renderCompTypeVar} == null) {
|
||||
${renderCompTypeVar} = viewManager.createRenderComponentType(${codeGenViewEncapsulation(encapsulation)}, ${stylesVar});
|
||||
}`);
|
||||
rendererExpr = `parentRenderer.renderComponent(${renderCompTypeVar})`;
|
||||
}
|
||||
var statement = `
|
||||
${codeGenFnHeader(factoryArgs, viewFactoryName)}{
|
||||
${initRendererStmts.join('\n')}
|
||||
var renderer = ${rendererExpr};
|
||||
var view = new ${APP_VIEW_MODULE_REF}AppView(
|
||||
${appProtoView}, renderer, viewManager,
|
||||
projectableNodes,
|
||||
containerEl,
|
||||
dynamicallyCreatedProviders, rootInjector,
|
||||
${changeDetectorFactory}()
|
||||
);
|
||||
${APP_VIEW_MODULE_REF}checkSlotCount(${escapeValue(this.component.type.name)}, ${this.component.template.ngContentSelectors.length}, projectableNodes);
|
||||
${isComponentView ? 'var parentRenderNode = renderer.createViewRoot(view.containerAppElement.nativeElement);' : ''}
|
||||
${visitor.renderStmts.map(stmt => stmt.statement).join('\n')}
|
||||
${visitor.appStmts.map(stmt => stmt.statement).join('\n')}
|
||||
|
||||
view.init(${codeGenFlatArray(visitor.rootNodesOrAppElements)}, ${codeGenArray(visitor.renderNodes)}, ${codeGenArray(visitor.appDisposables)},
|
||||
${codeGenArray(visitor.appElements)});
|
||||
return view;
|
||||
}`;
|
||||
targetStatements.push(new Statement(statement));
|
||||
return new Expression(viewFactoryName);
|
||||
}
|
||||
}
|
||||
|
||||
class RuntimeViewFactory implements ViewFactory<any, any> {
|
||||
constructor(public component: CompileDirectiveMetadata, public styles: Array<string | any[]>,
|
||||
public protoViews: CompileProtoView<AppProtoView, AppProtoElement>[],
|
||||
public changeDetectorFactories: Function[], public componentViewFactory: Function) {}
|
||||
|
||||
createText(renderer: Renderer, parent: any, text: string, targetStatements: any[]): any {
|
||||
return renderer.createText(parent, text);
|
||||
}
|
||||
|
||||
createElement(renderer: Renderer, parent: any, name: string, rootSelector: string,
|
||||
targetStatements: any[]): any {
|
||||
var el;
|
||||
if (isPresent(rootSelector)) {
|
||||
el = renderer.selectRootElement(rootSelector);
|
||||
} else {
|
||||
el = renderer.createElement(parent, name);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
createTemplateAnchor(renderer: Renderer, parent: any, targetStatements: any[]): any {
|
||||
return renderer.createTemplateAnchor(parent);
|
||||
}
|
||||
|
||||
createGlobalEventListener(renderer: Renderer, appView: AppView, boundElementIndex: number,
|
||||
eventAst: BoundEventAst, targetStatements: any[]): any {
|
||||
return renderer.listenGlobal(
|
||||
eventAst.target, eventAst.name,
|
||||
(event) => appView.triggerEventHandlers(eventAst.fullName, event, boundElementIndex));
|
||||
}
|
||||
|
||||
createElementEventListener(renderer: Renderer, appView: AppView, boundElementIndex: number,
|
||||
renderNode: any, eventAst: BoundEventAst, targetStatements: any[]) {
|
||||
renderer.listen(renderNode, eventAst.name, (event) => appView.triggerEventHandlers(
|
||||
eventAst.fullName, event, boundElementIndex));
|
||||
}
|
||||
|
||||
setElementAttribute(renderer: Renderer, renderNode: any, attrName: string, attrValue: string,
|
||||
targetStatements: any[]) {
|
||||
renderer.setElementAttribute(renderNode, attrName, attrValue);
|
||||
}
|
||||
|
||||
createAppElement(appProtoEl: AppProtoElement, appView: AppView, renderNode: any,
|
||||
parentAppEl: AppElement, embeddedViewFactory: Function,
|
||||
targetStatements: any[]): any {
|
||||
return new AppElement(appProtoEl, appView, parentAppEl, renderNode, embeddedViewFactory);
|
||||
}
|
||||
|
||||
createAndSetComponentView(renderer: Renderer, viewManager: AppViewManager_, appView: AppView,
|
||||
appEl: AppElement, component: CompileDirectiveMetadata,
|
||||
contentNodesByNgContentIndex: Array<Array<any | any[]>>,
|
||||
targetStatements: any[]) {
|
||||
var flattenedContentNodes;
|
||||
if (this.component.type.isHost) {
|
||||
flattenedContentNodes = appView.projectableNodes;
|
||||
} else {
|
||||
flattenedContentNodes = ListWrapper.createFixedSize(contentNodesByNgContentIndex.length);
|
||||
for (var i = 0; i < contentNodesByNgContentIndex.length; i++) {
|
||||
flattenedContentNodes[i] = flattenArray(contentNodesByNgContentIndex[i], []);
|
||||
}
|
||||
}
|
||||
this.componentViewFactory(component)(renderer, viewManager, appEl, flattenedContentNodes);
|
||||
}
|
||||
|
||||
getProjectedNodes(projectableNodes: any[][], ngContentIndex: number): any[] {
|
||||
return projectableNodes[ngContentIndex];
|
||||
}
|
||||
|
||||
appendProjectedNodes(renderer: Renderer, parent: any, nodes: any[], targetStatements: any[]) {
|
||||
renderer.projectNodes(parent, flattenNestedViewRenderNodes(nodes));
|
||||
}
|
||||
|
||||
createViewFactory(asts: TemplateAst[], embeddedTemplateIndex: number,
|
||||
targetStatements: any[]): Function {
|
||||
var compileProtoView = this.protoViews[embeddedTemplateIndex];
|
||||
var isComponentView = compileProtoView.protoView.type === ViewType.COMPONENT;
|
||||
var renderComponentType = null;
|
||||
return (parentRenderer: ParentRenderer, viewManager: AppViewManager_, containerEl: AppElement,
|
||||
projectableNodes: any[][], rootSelector: string = null,
|
||||
dynamicallyCreatedProviders: ResolvedProvider[] = null,
|
||||
rootInjector: Injector = null) => {
|
||||
checkSlotCount(this.component.type.name, this.component.template.ngContentSelectors.length,
|
||||
projectableNodes);
|
||||
var renderer;
|
||||
if (embeddedTemplateIndex === 0) {
|
||||
if (isBlank(renderComponentType)) {
|
||||
renderComponentType = viewManager.createRenderComponentType(
|
||||
this.component.template.encapsulation, this.styles);
|
||||
}
|
||||
renderer = parentRenderer.renderComponent(renderComponentType);
|
||||
} else {
|
||||
renderer = <Renderer>parentRenderer;
|
||||
}
|
||||
var changeDetector = this.changeDetectorFactories[embeddedTemplateIndex]();
|
||||
var view =
|
||||
new AppView(compileProtoView.protoView, renderer, viewManager, projectableNodes,
|
||||
containerEl, dynamicallyCreatedProviders, rootInjector, changeDetector);
|
||||
var visitor = new ViewBuilderVisitor<any, any>(
|
||||
renderer, viewManager, projectableNodes, rootSelector, view, compileProtoView, [], this);
|
||||
var parentRenderNode =
|
||||
isComponentView ? renderer.createViewRoot(containerEl.nativeElement) : null;
|
||||
templateVisitAll(visitor, asts, new ParentElement(parentRenderNode, null, null));
|
||||
view.init(flattenArray(visitor.rootNodesOrAppElements, []), visitor.renderNodes,
|
||||
visitor.appDisposables, visitor.appElements);
|
||||
return view;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ParentElement<EXPRESSION> {
|
||||
public contentNodesByNgContentIndex: Array<EXPRESSION>[];
|
||||
|
||||
constructor(public renderNode: EXPRESSION, public appEl: EXPRESSION,
|
||||
public component: CompileDirectiveMetadata) {
|
||||
if (isPresent(component)) {
|
||||
this.contentNodesByNgContentIndex =
|
||||
ListWrapper.createFixedSize(component.template.ngContentSelectors.length);
|
||||
for (var i = 0; i < this.contentNodesByNgContentIndex.length; i++) {
|
||||
this.contentNodesByNgContentIndex[i] = [];
|
||||
}
|
||||
} else {
|
||||
this.contentNodesByNgContentIndex = null;
|
||||
}
|
||||
}
|
||||
|
||||
addContentNode(ngContentIndex: number, nodeExpr: EXPRESSION) {
|
||||
this.contentNodesByNgContentIndex[ngContentIndex].push(nodeExpr);
|
||||
}
|
||||
}
|
||||
|
||||
class ViewBuilderVisitor<EXPRESSION, STATEMENT> implements TemplateAstVisitor {
|
||||
renderStmts: Array<STATEMENT> = [];
|
||||
renderNodes: EXPRESSION[] = [];
|
||||
appStmts: Array<STATEMENT> = [];
|
||||
appElements: EXPRESSION[] = [];
|
||||
appDisposables: EXPRESSION[] = [];
|
||||
|
||||
rootNodesOrAppElements: EXPRESSION[] = [];
|
||||
|
||||
elementCount: number = 0;
|
||||
|
||||
constructor(public renderer: EXPRESSION, public viewManager: EXPRESSION,
|
||||
public projectableNodes: EXPRESSION, public rootSelector: EXPRESSION,
|
||||
public view: EXPRESSION, public protoView: CompileProtoView<EXPRESSION, EXPRESSION>,
|
||||
public targetStatements: STATEMENT[],
|
||||
public factory: ViewFactory<EXPRESSION, STATEMENT>) {}
|
||||
|
||||
private _addRenderNode(renderNode: EXPRESSION, appEl: EXPRESSION, ngContentIndex: number,
|
||||
parent: ParentElement<EXPRESSION>) {
|
||||
this.renderNodes.push(renderNode);
|
||||
if (isPresent(parent.component)) {
|
||||
if (isPresent(ngContentIndex)) {
|
||||
parent.addContentNode(ngContentIndex, isPresent(appEl) ? appEl : renderNode);
|
||||
}
|
||||
} else if (isBlank(parent.renderNode)) {
|
||||
this.rootNodesOrAppElements.push(isPresent(appEl) ? appEl : renderNode);
|
||||
}
|
||||
}
|
||||
|
||||
private _getParentRenderNode(ngContentIndex: number,
|
||||
parent: ParentElement<EXPRESSION>): EXPRESSION {
|
||||
return isPresent(parent.component) &&
|
||||
parent.component.template.encapsulation !== ViewEncapsulation.Native ?
|
||||
null :
|
||||
parent.renderNode;
|
||||
}
|
||||
|
||||
visitBoundText(ast: BoundTextAst, parent: ParentElement<EXPRESSION>): any {
|
||||
return this._visitText('', ast.ngContentIndex, parent);
|
||||
}
|
||||
visitText(ast: TextAst, parent: ParentElement<EXPRESSION>): any {
|
||||
return this._visitText(ast.value, ast.ngContentIndex, parent);
|
||||
}
|
||||
private _visitText(value: string, ngContentIndex: number, parent: ParentElement<EXPRESSION>) {
|
||||
var renderNode = this.factory.createText(
|
||||
this.renderer, this._getParentRenderNode(ngContentIndex, parent), value, this.renderStmts);
|
||||
this._addRenderNode(renderNode, null, ngContentIndex, parent);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitNgContent(ast: NgContentAst, parent: ParentElement<EXPRESSION>): any {
|
||||
var nodesExpression = this.factory.getProjectedNodes(this.projectableNodes, ast.index);
|
||||
if (isPresent(parent.component)) {
|
||||
if (isPresent(ast.ngContentIndex)) {
|
||||
parent.addContentNode(ast.ngContentIndex, nodesExpression);
|
||||
}
|
||||
} else {
|
||||
if (isPresent(parent.renderNode)) {
|
||||
this.factory.appendProjectedNodes(this.renderer, parent.renderNode, nodesExpression,
|
||||
this.renderStmts);
|
||||
} else {
|
||||
this.rootNodesOrAppElements.push(nodesExpression);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
visitElement(ast: ElementAst, parent: ParentElement<EXPRESSION>): any {
|
||||
var renderNode = this.factory.createElement(
|
||||
this.renderer, this._getParentRenderNode(ast.ngContentIndex, parent), ast.name,
|
||||
this.rootSelector, this.renderStmts);
|
||||
|
||||
var component = ast.getComponent();
|
||||
var elementIndex = this.elementCount++;
|
||||
var protoEl = this.protoView.protoElements[elementIndex];
|
||||
|
||||
protoEl.renderEvents.forEach((eventAst) => {
|
||||
if (isPresent(eventAst.target)) {
|
||||
var disposable = this.factory.createGlobalEventListener(
|
||||
this.renderer, this.view, protoEl.boundElementIndex, eventAst, this.renderStmts);
|
||||
this.appDisposables.push(disposable);
|
||||
} else {
|
||||
this.factory.createElementEventListener(this.renderer, this.view, protoEl.boundElementIndex,
|
||||
renderNode, eventAst, this.renderStmts);
|
||||
}
|
||||
});
|
||||
for (var i = 0; i < protoEl.attrNameAndValues.length; i++) {
|
||||
var attrName = protoEl.attrNameAndValues[i][0];
|
||||
var attrValue = protoEl.attrNameAndValues[i][1];
|
||||
this.factory.setElementAttribute(this.renderer, renderNode, attrName, attrValue,
|
||||
this.renderStmts);
|
||||
}
|
||||
var appEl = null;
|
||||
if (isPresent(protoEl.appProtoEl)) {
|
||||
appEl = this.factory.createAppElement(protoEl.appProtoEl, this.view, renderNode, parent.appEl,
|
||||
null, this.appStmts);
|
||||
this.appElements.push(appEl);
|
||||
}
|
||||
this._addRenderNode(renderNode, appEl, ast.ngContentIndex, parent);
|
||||
|
||||
var newParent = new ParentElement<EXPRESSION>(
|
||||
renderNode, isPresent(appEl) ? appEl : parent.appEl, component);
|
||||
templateVisitAll(this, ast.children, newParent);
|
||||
if (isPresent(appEl) && isPresent(component)) {
|
||||
this.factory.createAndSetComponentView(this.renderer, this.viewManager, this.view, appEl,
|
||||
component, newParent.contentNodesByNgContentIndex,
|
||||
this.appStmts);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: ParentElement<EXPRESSION>): any {
|
||||
var renderNode = this.factory.createTemplateAnchor(
|
||||
this.renderer, this._getParentRenderNode(ast.ngContentIndex, parent), this.renderStmts);
|
||||
|
||||
var elementIndex = this.elementCount++;
|
||||
var protoEl = this.protoView.protoElements[elementIndex];
|
||||
var embeddedViewFactory = this.factory.createViewFactory(
|
||||
ast.children, protoEl.embeddedTemplateIndex, this.targetStatements);
|
||||
|
||||
var appEl = this.factory.createAppElement(protoEl.appProtoEl, this.view, renderNode,
|
||||
parent.appEl, embeddedViewFactory, this.appStmts);
|
||||
this._addRenderNode(renderNode, appEl, ast.ngContentIndex, parent);
|
||||
this.appElements.push(appEl);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitVariable(ast: VariableAst, ctx: any): any { return null; }
|
||||
visitAttr(ast: AttrAst, ctx: any): any { return null; }
|
||||
visitDirective(ast: DirectiveAst, ctx: any): any { return null; }
|
||||
visitEvent(ast: BoundEventAst, ctx: any): any { return null; }
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
|
||||
}
|
||||
|
||||
|
||||
function codeGenEventHandler(view: Expression, boundElementIndex: number,
|
||||
eventName: string): string {
|
||||
return codeGenValueFn(
|
||||
['event'],
|
||||
`${view.expression}.triggerEventHandlers(${escapeValue(eventName)}, event, ${boundElementIndex})`);
|
||||
}
|
||||
|
||||
function codeGenViewFactoryName(component: CompileDirectiveMetadata,
|
||||
embeddedTemplateIndex: number): string {
|
||||
return `viewFactory_${component.type.name}${embeddedTemplateIndex}`;
|
||||
}
|
||||
|
||||
function codeGenViewEncapsulation(value: ViewEncapsulation): string {
|
||||
if (IS_DART) {
|
||||
return `${METADATA_MODULE_REF}${value}`;
|
||||
} else {
|
||||
return `${value}`;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user