diff --git a/modules/angular2/src/compiler/change_definition_factory.ts b/modules/angular2/src/compiler/change_definition_factory.ts index 6ff6fab91b..67e26cfd17 100644 --- a/modules/angular2/src/compiler/change_definition_factory.ts +++ b/modules/angular2/src/compiler/change_definition_factory.ts @@ -14,7 +14,7 @@ import { ASTWithSource } from 'angular2/src/core/change_detection/change_detection'; -import {DirectiveMetadata, TypeMetadata} from './api'; +import {NormalizedDirectiveMetadata, TypeMetadata} from './directive_metadata'; import { TemplateAst, ElementAst, @@ -203,5 +203,5 @@ function _collectNestedProtoViewsVariableNames(pvVisitors: ProtoViewVisitor[]): function _protoViewId(hostComponentType: TypeMetadata, pvIndex: number, viewType: string): string { - return `${hostComponentType.typeName}_${viewType}_${pvIndex}`; + return `${hostComponentType.name}_${viewType}_${pvIndex}`; } diff --git a/modules/angular2/src/compiler/change_detector_compiler.ts b/modules/angular2/src/compiler/change_detector_compiler.ts index 412e06a449..0123d666a9 100644 --- a/modules/angular2/src/compiler/change_detector_compiler.ts +++ b/modules/angular2/src/compiler/change_detector_compiler.ts @@ -1,4 +1,5 @@ -import {TypeMetadata, SourceModule} from './api'; +import {TypeMetadata} from './directive_metadata'; +import {SourceExpression, moduleRef} from './source_module'; import { ChangeDetectorJITGenerator } from 'angular2/src/core/change_detection/change_detection_jit_generator'; @@ -15,20 +16,19 @@ import { import {TemplateAst} from './template_ast'; import {Codegen} from 'angular2/src/transform/template_compiler/change_detector_codegen'; - -var IS_DART = !isJsObject({}); +import {IS_DART} from './util'; +import {Injectable} from 'angular2/src/core/di'; const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector"; const UTIL = "ChangeDetectionUtil"; -const JS_CHANGE_DETECTOR_IMPORTS = CONST_EXPR([ - ['angular2/src/core/change_detection/abstract_change_detector', 'acd'], - ['angular2/src/core/change_detection/change_detection_util', 'cdu'] -]); - -const DART_CHANGE_DETECTOR_IMPORTS = - CONST_EXPR([['angular2/src/core/change_detection/pregen_proto_change_detector', '_gen']]); +var ABSTRACT_CHANGE_DETECTOR_MODULE = + moduleRef('angular2/src/core/change_detection/abstract_change_detector'); +var UTIL_MODULE = moduleRef('angular2/src/core/change_detection/change_detection_util'); +var PREGEN_PROTO_CHANGE_DETECTOR_MODULE = + moduleRef('angular2/src/core/change_detection/pregen_proto_change_detector'); +@Injectable() export class ChangeDetectionCompiler { constructor(private _genConfig: ChangeDetectorGenConfig) {} @@ -52,10 +52,9 @@ export class ChangeDetectionCompiler { } compileComponentCodeGen(componentType: TypeMetadata, strategy: ChangeDetectionStrategy, - parsedTemplate: TemplateAst[]): SourceModule { + parsedTemplate: TemplateAst[]): SourceExpression { var changeDetectorDefinitions = createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate); - var imports = IS_DART ? DART_CHANGE_DETECTOR_IMPORTS : JS_CHANGE_DETECTOR_IMPORTS; var factories = []; var sourceParts = changeDetectorDefinitions.map(definition => { var codegen: any; @@ -63,19 +62,20 @@ export class ChangeDetectionCompiler { // suffix // and have the same API for calling them! if (IS_DART) { - codegen = new Codegen(); + codegen = new Codegen(PREGEN_PROTO_CHANGE_DETECTOR_MODULE); var className = definition.id; - codegen.generate(componentType.typeName, className, definition); + codegen.generate(componentType.name, className, definition); factories.push(`(dispatcher) => new ${className}(dispatcher)`); return codegen.toString(); } else { - codegen = new ChangeDetectorJITGenerator(definition, `cdu.${UTIL}`, - `acd.${ABSTRACT_CHANGE_DETECTOR}`); + codegen = new ChangeDetectorJITGenerator( + definition, `${UTIL_MODULE}${UTIL}`, + `${ABSTRACT_CHANGE_DETECTOR_MODULE}${ABSTRACT_CHANGE_DETECTOR}`); factories.push(`function(dispatcher) { return new ${codegen.typeName}(dispatcher); }`); return codegen.generateSource(); } }); - sourceParts.push(`var CHANGE_DETECTORS = [ ${factories.join(',')} ];`); - return new SourceModule(componentType.typeUrl, sourceParts.join('\n'), imports); + var expression = `[ ${factories.join(',')} ]`; + return new SourceExpression(sourceParts, expression); } } diff --git a/modules/angular2/src/compiler/command_compiler.ts b/modules/angular2/src/compiler/command_compiler.ts index 533bc7579d..021c1bf7f7 100644 --- a/modules/angular2/src/compiler/command_compiler.ts +++ b/modules/angular2/src/compiler/command_compiler.ts @@ -25,16 +25,19 @@ import { BoundDirectivePropertyAst, templateVisitAll } from './template_ast'; -import {SourceModule, DirectiveMetadata, TypeMetadata} from './api'; +import {TypeMetadata, NormalizedDirectiveMetadata} from './directive_metadata'; +import {SourceExpression, moduleRef} from './source_module'; + import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {shimHostAttribute, shimContentAttribute} from './style_compiler'; import {escapeSingleQuoteString} from './util'; +import {Injectable} from 'angular2/src/core/di'; -const TEMPLATE_COMMANDS_MODULE = 'angular2/src/core/compiler/template_commands'; -const TEMPLATE_COMMANDS_MODULE_ALIAS = 'tc'; +export var TEMPLATE_COMMANDS_MODULE_REF = moduleRef('angular2/src/core/compiler/template_commands'); +@Injectable() export class CommandCompiler { - compileComponentRuntime(component: DirectiveMetadata, template: TemplateAst[], + compileComponentRuntime(component: NormalizedDirectiveMetadata, template: TemplateAst[], componentTemplateFactory: Function): TemplateCmd[] { var visitor = new CommandBuilderVisitor(new RuntimeCommandFactory(componentTemplateFactory), component); @@ -42,16 +45,13 @@ export class CommandCompiler { return visitor.result; } - compileComponentCodeGen(component: DirectiveMetadata, template: TemplateAst[], - componentTemplateFactory: Function): SourceModule { - var imports: string[][] = [[TEMPLATE_COMMANDS_MODULE, TEMPLATE_COMMANDS_MODULE_ALIAS]]; - var visitor = new CommandBuilderVisitor( - new CodegenCommandFactory(componentTemplateFactory, TEMPLATE_COMMANDS_MODULE_ALIAS, - imports), - component); + compileComponentCodeGen(component: NormalizedDirectiveMetadata, template: TemplateAst[], + componentTemplateFactory: Function): SourceExpression { + var visitor = + new CommandBuilderVisitor(new CodegenCommandFactory(componentTemplateFactory), component); templateVisitAll(visitor, template); - var source = `var COMMANDS = [${visitor.result.join(',')}];`; - return new SourceModule(null, source, imports); + var source = `[${visitor.result.join(',')}]`; + return new SourceExpression([], source); } } @@ -59,22 +59,22 @@ interface CommandFactory { createText(value: string, isBound: boolean, ngContentIndex: number): R; createNgContent(ngContentIndex: number): R; createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: TypeMetadata[], isBound: boolean, - ngContentIndex: number): R; + variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], + isBound: boolean, ngContentIndex: number): R; createEndElement(): R; createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: TypeMetadata[], + variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], nativeShadow: boolean, ngContentIndex: number): R; createEndComponent(): R; createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[], - directives: TypeMetadata[], isMerged: boolean, ngContentIndex: number, - children: R[]): R; + directives: NormalizedDirectiveMetadata[], isMerged: boolean, + ngContentIndex: number, children: R[]): R; } class RuntimeCommandFactory implements CommandFactory { constructor(public componentTemplateFactory: Function) {} - private _mapDirectives(directives: TypeMetadata[]): Type[] { - return directives.map(directive => directive.type); + private _mapDirectives(directives: NormalizedDirectiveMetadata[]): Type[] { + return directives.map(directive => directive.type.runtime); } createText(value: string, isBound: boolean, ngContentIndex: number): TemplateCmd { @@ -82,14 +82,14 @@ class RuntimeCommandFactory implements CommandFactory { } createNgContent(ngContentIndex: number): TemplateCmd { return ngContent(ngContentIndex); } createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: TypeMetadata[], isBound: boolean, - ngContentIndex: number): TemplateCmd { + variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], + isBound: boolean, ngContentIndex: number): TemplateCmd { return beginElement(name, attrNameAndValues, eventNames, variableNameAndValues, this._mapDirectives(directives), isBound, ngContentIndex); } createEndElement(): TemplateCmd { return endElement(); } createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: TypeMetadata[], + variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], nativeShadow: boolean, ngContentIndex: number): TemplateCmd { return beginComponent(name, attrNameAndValues, eventNames, variableNameAndValues, this._mapDirectives(directives), nativeShadow, ngContentIndex, @@ -97,8 +97,8 @@ class RuntimeCommandFactory implements CommandFactory { } createEndComponent(): TemplateCmd { return endComponent(); } createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[], - directives: TypeMetadata[], isMerged: boolean, ngContentIndex: number, - children: TemplateCmd[]): TemplateCmd { + directives: NormalizedDirectiveMetadata[], isMerged: boolean, + ngContentIndex: number, children: TemplateCmd[]): TemplateCmd { return embeddedTemplate(attrNameAndValues, variableNameAndValues, this._mapDirectives(directives), isMerged, ngContentIndex, children); } @@ -109,42 +109,38 @@ function escapeStringArray(data: string[]): string { } class CodegenCommandFactory implements CommandFactory { - constructor(public componentTemplateFactory: Function, public templateCommandsModuleAlias, - public imports: string[][]) {} - - private _escapeDirectives(directives: TypeMetadata[]): string[] { - return directives.map(directiveType => { - var importAlias = `dir${this.imports.length}`; - this.imports.push([directiveType.typeUrl, importAlias]); - return `${importAlias}.${directiveType.typeName}`; - }); - } + constructor(public componentTemplateFactory: Function) {} createText(value: string, isBound: boolean, ngContentIndex: number): string { - return `${this.templateCommandsModuleAlias}.text(${escapeSingleQuoteString(value)}, ${isBound}, ${ngContentIndex})`; + return `${TEMPLATE_COMMANDS_MODULE_REF}text(${escapeSingleQuoteString(value)}, ${isBound}, ${ngContentIndex})`; } createNgContent(ngContentIndex: number): string { - return `${this.templateCommandsModuleAlias}.ngContent(${ngContentIndex})`; + return `${TEMPLATE_COMMANDS_MODULE_REF}ngContent(${ngContentIndex})`; } createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: TypeMetadata[], isBound: boolean, - ngContentIndex: number): string { - return `${this.templateCommandsModuleAlias}.beginElement(${escapeSingleQuoteString(name)}, ${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(eventNames)}, ${escapeStringArray(variableNameAndValues)}, [${this._escapeDirectives(directives).join(',')}], ${isBound}, ${ngContentIndex})`; + variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], + isBound: boolean, ngContentIndex: number): string { + return `${TEMPLATE_COMMANDS_MODULE_REF}beginElement(${escapeSingleQuoteString(name)}, ${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(eventNames)}, ${escapeStringArray(variableNameAndValues)}, [${_escapeDirectives(directives).join(',')}], ${isBound}, ${ngContentIndex})`; } - createEndElement(): string { return `${this.templateCommandsModuleAlias}.endElement()`; } + createEndElement(): string { return `${TEMPLATE_COMMANDS_MODULE_REF}endElement()`; } createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: TypeMetadata[], + variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], nativeShadow: boolean, ngContentIndex: number): string { - return `${this.templateCommandsModuleAlias}.beginComponent(${escapeSingleQuoteString(name)}, ${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(eventNames)}, ${escapeStringArray(variableNameAndValues)}, [${this._escapeDirectives(directives).join(',')}], ${nativeShadow}, ${ngContentIndex}, ${this.componentTemplateFactory(directives[0], this.imports)})`; + return `${TEMPLATE_COMMANDS_MODULE_REF}beginComponent(${escapeSingleQuoteString(name)}, ${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(eventNames)}, ${escapeStringArray(variableNameAndValues)}, [${_escapeDirectives(directives).join(',')}], ${nativeShadow}, ${ngContentIndex}, ${this.componentTemplateFactory(directives[0])})`; } - createEndComponent(): string { return `${this.templateCommandsModuleAlias}.endComponent()`; } + createEndComponent(): string { return `${TEMPLATE_COMMANDS_MODULE_REF}endComponent()`; } createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[], - directives: TypeMetadata[], isMerged: boolean, ngContentIndex: number, - children: string[]): string { - return `${this.templateCommandsModuleAlias}.embeddedTemplate(${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(variableNameAndValues)}, [${this._escapeDirectives(directives).join(',')}], ${isMerged}, ${ngContentIndex}, [${children.join(',')}])`; + directives: NormalizedDirectiveMetadata[], isMerged: boolean, + ngContentIndex: number, children: string[]): string { + return `${TEMPLATE_COMMANDS_MODULE_REF}embeddedTemplate(${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(variableNameAndValues)}, [${_escapeDirectives(directives).join(',')}], ${isMerged}, ${ngContentIndex}, [${children.join(',')}])`; } } +function _escapeDirectives(directives: NormalizedDirectiveMetadata[]): string[] { + return directives.map(directiveType => + `${moduleRef(directiveType.type.moduleId)}${directiveType.type.name}`); +} + function visitAndReturnContext(visitor: TemplateAstVisitor, asts: TemplateAst[], context: any): any { templateVisitAll(visitor, asts, context); @@ -154,18 +150,19 @@ function visitAndReturnContext(visitor: TemplateAstVisitor, asts: TemplateAst[], class CommandBuilderVisitor implements TemplateAstVisitor { result: R[] = []; transitiveNgContentCount: number = 0; - constructor(public commandFactory: CommandFactory, public component: DirectiveMetadata) {} + constructor(public commandFactory: CommandFactory, + public component: NormalizedDirectiveMetadata) {} - private _readAttrNameAndValues(localComponent: DirectiveMetadata, + private _readAttrNameAndValues(localComponent: NormalizedDirectiveMetadata, attrAsts: TemplateAst[]): string[] { var attrNameAndValues: string[] = visitAndReturnContext(this, attrAsts, []); if (isPresent(localComponent) && localComponent.template.encapsulation === ViewEncapsulation.Emulated) { - attrNameAndValues.push(shimHostAttribute(localComponent.type)); + attrNameAndValues.push(shimHostAttribute(localComponent.type.id)); attrNameAndValues.push(''); } if (this.component.template.encapsulation === ViewEncapsulation.Emulated) { - attrNameAndValues.push(shimContentAttribute(this.component.type)); + attrNameAndValues.push(shimContentAttribute(this.component.type.id)); attrNameAndValues.push(''); } return attrNameAndValues; @@ -228,7 +225,7 @@ class CommandBuilderVisitor implements TemplateAstVisitor { return null; } visitDirective(ast: DirectiveAst, directivesAndEventNames: any[][]): any { - directivesAndEventNames[0].push(ast.directive.type); + directivesAndEventNames[0].push(ast.directive); templateVisitAll(this, ast.hostEvents, directivesAndEventNames[1]); return null; } diff --git a/modules/angular2/src/compiler/compiler.ts b/modules/angular2/src/compiler/compiler.ts new file mode 100644 index 0000000000..6f345c24a0 --- /dev/null +++ b/modules/angular2/src/compiler/compiler.ts @@ -0,0 +1,9 @@ +export {TemplateCompiler} from './template_compiler'; +export { + DirectiveMetadata, + TypeMetadata, + TemplateMetadata, + ChangeDetectionMetadata, + INormalizedDirectiveMetadata +} from './directive_metadata'; +export {SourceModule, SourceWithImports} from './source_module'; diff --git a/modules/angular2/src/compiler/api.ts b/modules/angular2/src/compiler/directive_metadata.ts similarity index 55% rename from modules/angular2/src/compiler/api.ts rename to modules/angular2/src/compiler/directive_metadata.ts index 0dd0757700..6883825203 100644 --- a/modules/angular2/src/compiler/api.ts +++ b/modules/angular2/src/compiler/directive_metadata.ts @@ -4,31 +4,31 @@ import { changeDetectionStrategyFromJson } from 'angular2/src/core/change_detection/change_detection'; import {ViewEncapsulation, viewEncapsulationFromJson} from 'angular2/src/core/render/api'; +import {CssSelector} from 'angular2/src/core/render/dom/compiler/selector'; export class TypeMetadata { id: number; - type: Type; - typeName: string; - typeUrl: string; - constructor({id, type, typeName, typeUrl}: - {id?: number, type?: Type, typeName?: string, typeUrl?: string} = {}) { + runtime: Type; + name: string; + moduleId: string; + constructor({id, runtime, name, moduleId}: + {id?: number, runtime?: Type, name?: string, moduleId?: string} = {}) { this.id = id; - this.type = type; - this.typeName = typeName; - this.typeUrl = typeUrl; + this.runtime = runtime; + this.name = name; + this.moduleId = moduleId; } static fromJson(data: StringMap): TypeMetadata { - return new TypeMetadata( - {id: data['id'], type: data['type'], typeName: data['typeName'], typeUrl: data['typeUrl']}); + return new TypeMetadata({id: data['id'], name: data['name'], moduleId: data['moduleId']}); } toJson(): StringMap { return { // Note: Runtime type can't be serialized... 'id': this.id, - 'typeName': this.typeName, - 'typeUrl': this.typeUrl + 'name': this.name, + 'moduleId': this.moduleId }; } } @@ -63,17 +63,17 @@ export class ChangeDetectionMetadata { callOnInit?: boolean } = {}) { this.changeDetection = changeDetection; - this.properties = properties; - this.events = events; - this.hostListeners = hostListeners; - this.hostProperties = hostProperties; - this.callAfterContentInit = callAfterContentInit; - this.callAfterContentChecked = callAfterContentChecked; - this.callAfterViewInit = callAfterViewInit; - this.callAfterViewChecked = callAfterViewChecked; - this.callOnChanges = callOnChanges; - this.callDoCheck = callDoCheck; - this.callOnInit = callOnInit; + this.properties = isPresent(properties) ? properties : []; + this.events = isPresent(events) ? events : []; + this.hostListeners = isPresent(hostListeners) ? hostListeners : {}; + this.hostProperties = isPresent(hostProperties) ? hostProperties : {}; + this.callAfterContentInit = normalizeBool(callAfterContentInit); + this.callAfterContentChecked = normalizeBool(callAfterContentChecked); + this.callAfterViewInit = normalizeBool(callAfterViewInit); + this.callAfterViewChecked = normalizeBool(callAfterViewChecked); + this.callOnChanges = normalizeBool(callOnChanges); + this.callDoCheck = normalizeBool(callDoCheck); + this.callOnInit = normalizeBool(callOnInit); } static fromJson(data: StringMap): ChangeDetectionMetadata { @@ -115,27 +115,79 @@ export class ChangeDetectionMetadata { } export class TemplateMetadata { + encapsulation: ViewEncapsulation; + template: string; + templateUrl: string; + styles: string[]; + styleUrls: string[]; + hostAttributes: StringMap; + constructor({encapsulation, template, templateUrl, styles, styleUrls, hostAttributes}: { + encapsulation?: ViewEncapsulation, + template?: string, + templateUrl?: string, + styles?: string[], + styleUrls?: string[], + hostAttributes?: StringMap + } = {}) { + this.encapsulation = isPresent(encapsulation) ? encapsulation : ViewEncapsulation.None; + this.template = template; + this.templateUrl = templateUrl; + this.styles = isPresent(styles) ? styles : []; + this.styleUrls = isPresent(styleUrls) ? styleUrls : []; + this.hostAttributes = isPresent(hostAttributes) ? hostAttributes : {}; + } +} + + +export class DirectiveMetadata { + type: TypeMetadata; + isComponent: boolean; + dynamicLoadable: boolean; + selector: string; + changeDetection: ChangeDetectionMetadata; + template: TemplateMetadata; + constructor({type, isComponent, dynamicLoadable, selector, changeDetection, template}: { + type?: TypeMetadata, + isComponent?: boolean, + dynamicLoadable?: boolean, + selector?: string, + changeDetection?: ChangeDetectionMetadata, + template?: TemplateMetadata + } = {}) { + this.type = type; + this.isComponent = normalizeBool(isComponent); + this.dynamicLoadable = normalizeBool(dynamicLoadable); + this.selector = selector; + this.changeDetection = changeDetection; + this.template = template; + } +} + +export class NormalizedTemplateMetadata { encapsulation: ViewEncapsulation; template: string; styles: string[]; styleAbsUrls: string[]; ngContentSelectors: string[]; - constructor({encapsulation, template, styles, styleAbsUrls, ngContentSelectors}: { + hostAttributes: StringMap; + constructor({encapsulation, template, styles, styleAbsUrls, ngContentSelectors, hostAttributes}: { encapsulation?: ViewEncapsulation, template?: string, styles?: string[], styleAbsUrls?: string[], - ngContentSelectors?: string[] + ngContentSelectors?: string[], + hostAttributes?: StringMap } = {}) { this.encapsulation = encapsulation; this.template = template; this.styles = styles; this.styleAbsUrls = styleAbsUrls; this.ngContentSelectors = ngContentSelectors; + this.hostAttributes = hostAttributes; } - static fromJson(data: StringMap): TemplateMetadata { - return new TemplateMetadata({ + static fromJson(data: StringMap): NormalizedTemplateMetadata { + return new NormalizedTemplateMetadata({ encapsulation: isPresent(data['encapsulation']) ? viewEncapsulationFromJson(data['encapsulation']) : data['encapsulation'], @@ -143,6 +195,7 @@ export class TemplateMetadata { styles: data['styles'], styleAbsUrls: data['styleAbsUrls'], ngContentSelectors: data['ngContentSelectors'], + hostAttributes: data['hostAttributes'] }); } @@ -154,54 +207,58 @@ export class TemplateMetadata { 'styles': this.styles, 'styleAbsUrls': this.styleAbsUrls, 'ngContentSelectors': this.ngContentSelectors, + 'hostAttributes': this.hostAttributes }; } } +export interface INormalizedDirectiveMetadata {} -export class DirectiveMetadata { +export class NormalizedDirectiveMetadata implements INormalizedDirectiveMetadata { type: TypeMetadata; isComponent: boolean; + dynamicLoadable: boolean; selector: string; - hostAttributes: StringMap; changeDetection: ChangeDetectionMetadata; - template: TemplateMetadata; - constructor({type, isComponent, selector, hostAttributes, changeDetection, template}: { + template: NormalizedTemplateMetadata; + constructor({type, isComponent, dynamicLoadable, selector, changeDetection, template}: { + id?: number, type?: TypeMetadata, isComponent?: boolean, + dynamicLoadable?: boolean, selector?: string, - hostAttributes?: StringMap, changeDetection?: ChangeDetectionMetadata, - template?: TemplateMetadata + template?: NormalizedTemplateMetadata } = {}) { this.type = type; this.isComponent = normalizeBool(isComponent); + this.dynamicLoadable = normalizeBool(dynamicLoadable); this.selector = selector; - this.hostAttributes = hostAttributes; this.changeDetection = changeDetection; this.template = template; } - static fromJson(data: StringMap): DirectiveMetadata { - return new DirectiveMetadata({ - type: isPresent(data['type']) ? TypeMetadata.fromJson(data['type']) : data['type'], + static fromJson(data: StringMap): NormalizedDirectiveMetadata { + return new NormalizedDirectiveMetadata({ isComponent: data['isComponent'], + dynamicLoadable: data['dynamicLoadable'], selector: data['selector'], - hostAttributes: data['hostAttributes'], + type: isPresent(data['type']) ? TypeMetadata.fromJson(data['type']) : data['type'], changeDetection: isPresent(data['changeDetection']) ? ChangeDetectionMetadata.fromJson(data['changeDetection']) : data['changeDetection'], - template: isPresent(data['template']) ? TemplateMetadata.fromJson(data['template']) : - data['template'] + template: + isPresent(data['template']) ? NormalizedTemplateMetadata.fromJson(data['template']) : + data['template'] }); } toJson(): StringMap { return { - 'type': isPresent(this.type) ? this.type.toJson() : this.type, 'isComponent': this.isComponent, + 'dynamicLoadable': this.dynamicLoadable, 'selector': this.selector, - 'hostAttributes': this.hostAttributes, + 'type': isPresent(this.type) ? this.type.toJson() : this.type, 'changeDetection': isPresent(this.changeDetection) ? this.changeDetection.toJson() : this.changeDetection, 'template': isPresent(this.template) ? this.template.toJson() : this.template @@ -209,6 +266,39 @@ export class DirectiveMetadata { } } -export class SourceModule { - constructor(public moduleName: string, public source: string, public imports: string[][]) {} +export function createHostComponentMeta(componentType: TypeMetadata, componentSelector: string): + NormalizedDirectiveMetadata { + var template = CssSelector.parse(componentSelector)[0].getMatchingElementTemplate(); + return new NormalizedDirectiveMetadata({ + type: new TypeMetadata({ + runtime: Object, + id: (componentType.id * -1) - 1, + name: `Host${componentType.name}`, + moduleId: componentType.moduleId + }), + template: new NormalizedTemplateMetadata({ + template: template, + styles: [], + styleAbsUrls: [], + hostAttributes: {}, + ngContentSelectors: [] + }), + changeDetection: new ChangeDetectionMetadata({ + changeDetection: ChangeDetectionStrategy.Default, + properties: [], + events: [], + hostListeners: {}, + hostProperties: {}, + callAfterContentInit: false, + callAfterContentChecked: false, + callAfterViewInit: false, + callAfterViewChecked: false, + callOnChanges: false, + callDoCheck: false, + callOnInit: false + }), + isComponent: true, + dynamicLoadable: false, + selector: '*' + }); } diff --git a/modules/angular2/src/compiler/html_parser.ts b/modules/angular2/src/compiler/html_parser.ts index f5c0276b3a..bac1690ba1 100644 --- a/modules/angular2/src/compiler/html_parser.ts +++ b/modules/angular2/src/compiler/html_parser.ts @@ -17,9 +17,11 @@ import { } from './html_ast'; import {escapeDoubleQuoteString} from './util'; +import {Injectable} from 'angular2/src/core/di'; const NG_NON_BINDABLE = 'ng-non-bindable'; +@Injectable() export class HtmlParser { parse(template: string, sourceInfo: string): HtmlAst[] { var root = DOM.createTemplate(template); diff --git a/modules/angular2/src/compiler/runtime_metadata.ts b/modules/angular2/src/compiler/runtime_metadata.ts new file mode 100644 index 0000000000..ca17cbedc6 --- /dev/null +++ b/modules/angular2/src/compiler/runtime_metadata.ts @@ -0,0 +1,150 @@ +import {resolveForwardRef} from 'angular2/src/core/di'; +import { + Type, + isBlank, + isPresent, + isArray, + stringify, + RegExpWrapper +} from 'angular2/src/core/facade/lang'; +import {BaseException} from 'angular2/src/core/facade/exceptions'; +import {MapWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection'; +import * as cpl from './directive_metadata'; +import * as dirAnn from 'angular2/src/core/metadata/directives'; +import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; +import {ViewResolver} from 'angular2/src/core/compiler/view_resolver'; +import {ViewMetadata} from 'angular2/src/core/metadata/view'; +import {hasLifecycleHook} from 'angular2/src/core/compiler/directive_lifecycle_reflector'; +import {LifecycleHooks} from 'angular2/src/core/compiler/interfaces'; +import {reflector} from 'angular2/src/core/reflection/reflection'; +import {Injectable} from 'angular2/src/core/di'; + +// group 1: "property" from "[property]" +// group 2: "event" from "(event)" +var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g; + +@Injectable() +export class RuntimeMetadataResolver { + private _directiveCounter = 0; + private _cache: Map = new Map(); + + constructor(private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver) {} + + getMetadata(directiveType: Type): cpl.DirectiveMetadata { + var meta = this._cache.get(directiveType); + if (isBlank(meta)) { + var directiveAnnotation = this._directiveResolver.resolve(directiveType); + var moduleId = calcModuleId(directiveType, directiveAnnotation); + var templateMeta = null; + var hostListeners = {}; + var hostProperties = {}; + var hostAttributes = {}; + var changeDetectionStrategy = null; + var dynamicLoadable: boolean = false; + + if (isPresent(directiveAnnotation.host)) { + StringMapWrapper.forEach(directiveAnnotation.host, (value: string, key: string) => { + var matches = RegExpWrapper.firstMatch(HOST_REG_EXP, key); + if (isBlank(matches)) { + hostAttributes[key] = value; + } else if (isPresent(matches[1])) { + hostProperties[matches[1]] = value; + } else if (isPresent(matches[2])) { + hostListeners[matches[2]] = value; + } + }); + } + if (directiveAnnotation instanceof dirAnn.ComponentMetadata) { + var compAnnotation = directiveAnnotation; + var viewAnnotation = this._viewResolver.resolve(directiveType); + templateMeta = new cpl.TemplateMetadata({ + encapsulation: viewAnnotation.encapsulation, + template: viewAnnotation.template, + templateUrl: viewAnnotation.templateUrl, + styles: viewAnnotation.styles, + styleUrls: viewAnnotation.styleUrls, + hostAttributes: hostAttributes + }); + changeDetectionStrategy = compAnnotation.changeDetection; + dynamicLoadable = compAnnotation.dynamicLoadable; + } + meta = new cpl.DirectiveMetadata({ + selector: directiveAnnotation.selector, + isComponent: isPresent(templateMeta), + dynamicLoadable: dynamicLoadable, + type: new cpl.TypeMetadata({ + id: this._directiveCounter++, + name: stringify(directiveType), + moduleId: moduleId, + runtime: directiveType + }), + template: templateMeta, + changeDetection: new cpl.ChangeDetectionMetadata({ + changeDetection: changeDetectionStrategy, + properties: directiveAnnotation.properties, + events: directiveAnnotation.events, + hostListeners: hostListeners, + hostProperties: hostProperties, + callAfterContentInit: hasLifecycleHook(LifecycleHooks.AfterContentInit, directiveType), + callAfterContentChecked: + hasLifecycleHook(LifecycleHooks.AfterContentChecked, directiveType), + callAfterViewInit: hasLifecycleHook(LifecycleHooks.AfterViewInit, directiveType), + callAfterViewChecked: hasLifecycleHook(LifecycleHooks.AfterViewChecked, directiveType), + callOnChanges: hasLifecycleHook(LifecycleHooks.OnChanges, directiveType), + callDoCheck: hasLifecycleHook(LifecycleHooks.DoCheck, directiveType), + callOnInit: hasLifecycleHook(LifecycleHooks.OnInit, directiveType), + }) + }); + this._cache.set(directiveType, meta); + } + return meta; + } + + getViewDirectivesMetadata(component: Type): cpl.DirectiveMetadata[] { + var view = this._viewResolver.resolve(component); + var directives = flattenDirectives(view); + for (var i = 0; i < directives.length; i++) { + if (!isValidDirective(directives[i])) { + throw new BaseException( + `Unexpected directive value '${stringify(directives[i])}' on the View of component '${stringify(component)}'`); + } + } + return removeDuplicatedDirectives(directives.map(type => this.getMetadata(type))); + } +} + +function removeDuplicatedDirectives(directives: cpl.DirectiveMetadata[]): cpl.DirectiveMetadata[] { + var directivesMap: Map = new Map(); + directives.forEach((dirMeta) => { directivesMap.set(dirMeta.type.id, dirMeta); }); + return MapWrapper.values(directivesMap); +} + +function flattenDirectives(view: ViewMetadata): Type[] { + if (isBlank(view.directives)) return []; + var directives = []; + flattenList(view.directives, directives); + return directives; +} + +function flattenList(tree: any[], out: Array): void { + for (var i = 0; i < tree.length; i++) { + var item = resolveForwardRef(tree[i]); + if (isArray(item)) { + flattenList(item, out); + } else { + out.push(item); + } + } +} + +function isValidDirective(value: Type): boolean { + return isPresent(value) && (value instanceof Type); +} + +function calcModuleId(type: Type, directiveAnnotation: dirAnn.DirectiveMetadata): string { + if (isPresent(directiveAnnotation.moduleId)) { + return directiveAnnotation.moduleId; + } else { + return reflector.moduleId(type); + } +} diff --git a/modules/angular2/src/compiler/source_module.ts b/modules/angular2/src/compiler/source_module.ts new file mode 100644 index 0000000000..c02370c15d --- /dev/null +++ b/modules/angular2/src/compiler/source_module.ts @@ -0,0 +1,39 @@ +import {StringWrapper, isBlank} from 'angular2/src/core/facade/lang'; + +var MODULE_REGEXP = /#MODULE\[([^\]]*)\]/g; + +export function moduleRef(moduleId): string { + return `#MODULE[${moduleId}]`; +} + +export class SourceModule { + constructor(public moduleId: string, public source: string) {} + + getSourceWithImports(): SourceWithImports { + var moduleAliases = {}; + var imports: string[][] = []; + var newSource = StringWrapper.replaceAllMapped(this.source, MODULE_REGEXP, (match) => { + var moduleId = match[1]; + var alias = moduleAliases[moduleId]; + if (isBlank(alias)) { + if (moduleId == this.moduleId) { + alias = ''; + } else { + alias = `import${imports.length}`; + imports.push([moduleId, alias]); + } + moduleAliases[moduleId] = alias; + } + return alias.length > 0 ? `${alias}.` : ''; + }); + return new SourceWithImports(newSource, imports); + } +} + +export class SourceExpression { + constructor(public declarations: string[], public expression: string) {} +} + +export class SourceWithImports { + constructor(public source: string, public imports: string[][]) {} +} diff --git a/modules/angular2/src/compiler/style_compiler.ts b/modules/angular2/src/compiler/style_compiler.ts index 874610bb9c..9f17f12a3b 100644 --- a/modules/angular2/src/compiler/style_compiler.ts +++ b/modules/angular2/src/compiler/style_compiler.ts @@ -1,26 +1,35 @@ -import {DirectiveMetadata, SourceModule, TypeMetadata} from './api'; +import {TypeMetadata, NormalizedDirectiveMetadata} from './directive_metadata'; +import {SourceModule, SourceExpression, moduleRef} from './source_module'; import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {XHR} from 'angular2/src/core/render/xhr'; -import {StringWrapper, isJsObject, isBlank} from 'angular2/src/core/facade/lang'; +import {StringWrapper, isBlank} from 'angular2/src/core/facade/lang'; import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async'; import {ShadowCss} from 'angular2/src/core/render/dom/compiler/shadow_css'; import {UrlResolver} from 'angular2/src/core/services/url_resolver'; import {resolveStyleUrls} from './style_url_resolver'; -import {escapeSingleQuoteString} from './util'; +import { + escapeSingleQuoteString, + IS_DART, + codeGenConcatArray, + codeGenMapArray, + codeGenReplaceAll, + codeGenExportVariable +} from './util'; +import {Injectable} from 'angular2/src/core/di'; const COMPONENT_VARIABLE = '%COMP%'; var COMPONENT_REGEX = /%COMP%/g; const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`; const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`; -var IS_DART = !isJsObject({}); +@Injectable() export class StyleCompiler { private _styleCache: Map> = new Map>(); private _shadowCss: ShadowCss = new ShadowCss(); constructor(private _xhr: XHR, private _urlResolver: UrlResolver) {} - compileComponentRuntime(component: DirectiveMetadata): Promise { + compileComponentRuntime(component: NormalizedDirectiveMetadata): Promise { var styles = component.template.styles; var styleAbsUrls = component.template.styleAbsUrls; return this._loadStyles(styles, styleAbsUrls, @@ -29,7 +38,7 @@ export class StyleCompiler { `${component.type.id}`))); } - compileComponentCodeGen(component: DirectiveMetadata): SourceModule { + compileComponentCodeGen(component: NormalizedDirectiveMetadata): SourceExpression { var shim = component.template.encapsulation === ViewEncapsulation.Emulated; var suffix; if (shim) { @@ -39,16 +48,17 @@ export class StyleCompiler { } else { suffix = ''; } - return this._styleCodeGen(`$component.type.typeUrl}.styles`, component.template.styles, - component.template.styleAbsUrls, shim, suffix); + return this._styleCodeGen(component.template.styles, component.template.styleAbsUrls, shim, + suffix); } - compileStylesheetCodeGen(moduleName: string, cssText: string): SourceModule[] { - var styleWithImports = resolveStyleUrls(this._urlResolver, moduleName, cssText); + compileStylesheetCodeGen(moduleId: string, cssText: string): SourceModule[] { + var styleWithImports = resolveStyleUrls(this._urlResolver, moduleId, cssText); return [ - this._styleCodeGen(moduleName, [styleWithImports.style], styleWithImports.styleUrls, false, - ''), - this._styleCodeGen(moduleName, [styleWithImports.style], styleWithImports.styleUrls, true, '') + this._styleModule(moduleId, false, this._styleCodeGen([styleWithImports.style], + styleWithImports.styleUrls, false, '')), + this._styleModule(moduleId, true, this._styleCodeGen([styleWithImports.style], + styleWithImports.styleUrls, true, '')) ]; } @@ -74,55 +84,41 @@ export class StyleCompiler { }); } - private _styleCodeGen(moduleName: string, plainStyles: string[], absUrls: string[], shim: boolean, - suffix: string): SourceModule { - var imports: string[][] = []; - var moduleSource = `var STYLES = (`; - moduleSource += + private _styleCodeGen(plainStyles: string[], absUrls: string[], shim: boolean, + suffix: string): SourceExpression { + var expressionSource = `(`; + expressionSource += `[${plainStyles.map( plainStyle => escapeSingleQuoteString(this._shimIfNeeded(plainStyle, shim)) ).join(',')}]`; for (var i = 0; i < absUrls.length; i++) { - var url = absUrls[i]; - var moduleAlias = `import${i}`; - imports.push([this._shimModuleName(url, shim), moduleAlias]); - moduleSource += `${codeGenConcatArray(moduleAlias+'.STYLES')}`; + var moduleId = this._shimModuleIdIfNeeded(absUrls[i], shim); + expressionSource += codeGenConcatArray(`${moduleRef(moduleId)}STYLES`); } - moduleSource += `)${suffix};`; - return new SourceModule(this._shimModuleName(moduleName, shim), moduleSource, imports); + expressionSource += `)${suffix}`; + return new SourceExpression([], expressionSource); + } + + private _styleModule(moduleId: string, shim: boolean, + expression: SourceExpression): SourceModule { + var moduleSource = ` + ${expression.declarations.join('\n')} + ${codeGenExportVariable('STYLES')}${expression.expression}; + `; + return new SourceModule(this._shimModuleIdIfNeeded(moduleId, shim), moduleSource); } private _shimIfNeeded(style: string, shim: boolean): string { return shim ? this._shadowCss.shimCssText(style, CONTENT_ATTR, HOST_ATTR) : style; } - private _shimModuleName(originalUrl: string, shim: boolean): string { - return shim ? `${originalUrl}.shim` : originalUrl; + private _shimModuleIdIfNeeded(moduleId: string, shim: boolean): string { + return shim ? `${moduleId}.shim` : moduleId; } } -export function shimContentAttribute(component: TypeMetadata): string { - return StringWrapper.replaceAll(CONTENT_ATTR, COMPONENT_REGEX, `${component.id}`); +export function shimContentAttribute(componentId: number): string { + return StringWrapper.replaceAll(CONTENT_ATTR, COMPONENT_REGEX, `${componentId}`); } -export function shimHostAttribute(component: TypeMetadata): string { - return StringWrapper.replaceAll(HOST_ATTR, COMPONENT_REGEX, `${component.id}`); +export function shimHostAttribute(componentId: number): string { + return StringWrapper.replaceAll(HOST_ATTR, COMPONENT_REGEX, `${componentId}`); } - -function codeGenConcatArray(expression: string): string { - return `${IS_DART ? '..addAll' : '.concat'}(${expression})`; -} - -function codeGenMapArray(argNames: string[], callback: string): string { - if (IS_DART) { - return `.map( (${argNames.join(',')}) => ${callback} ).toList()`; - } else { - return `.map(function(${argNames.join(',')}) { return ${callback}; })`; - } -} - -function codeGenReplaceAll(pattern: string, value: string): string { - if (IS_DART) { - return `.replaceAll('${pattern}', '${value}')`; - } else { - return `.replace(/${pattern}/g, '${value}')`; - } -} \ No newline at end of file diff --git a/modules/angular2/src/compiler/template_ast.ts b/modules/angular2/src/compiler/template_ast.ts index 7f293383af..f8ad04ed06 100644 --- a/modules/angular2/src/compiler/template_ast.ts +++ b/modules/angular2/src/compiler/template_ast.ts @@ -1,6 +1,6 @@ import {AST} from 'angular2/src/core/change_detection/change_detection'; import {isPresent} from 'angular2/src/core/facade/lang'; -import {DirectiveMetadata} from './api'; +import {NormalizedDirectiveMetadata} from './directive_metadata'; export interface TemplateAst { sourceInfo: string; @@ -69,7 +69,7 @@ export class ElementAst implements TemplateAst { this.directives.length > 0); } - getComponent(): DirectiveMetadata { + getComponent(): NormalizedDirectiveMetadata { return this.directives.length > 0 && this.directives[0].directive.isComponent ? this.directives[0].directive : null; @@ -94,7 +94,8 @@ export class BoundDirectivePropertyAst implements TemplateAst { } export class DirectiveAst implements TemplateAst { - constructor(public directive: DirectiveMetadata, public properties: BoundDirectivePropertyAst[], + constructor(public directive: NormalizedDirectiveMetadata, + public properties: BoundDirectivePropertyAst[], public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[], public sourceInfo: string) {} visit(visitor: TemplateAstVisitor, context: any): any { diff --git a/modules/angular2/src/compiler/template_compiler.ts b/modules/angular2/src/compiler/template_compiler.ts new file mode 100644 index 0000000000..62e5aabfbe --- /dev/null +++ b/modules/angular2/src/compiler/template_compiler.ts @@ -0,0 +1,205 @@ +import {Type, Json, isBlank, stringify} from 'angular2/src/core/facade/lang'; +import {BaseException} from 'angular2/src/core/facade/exceptions'; +import {ListWrapper, SetWrapper} from 'angular2/src/core/facade/collection'; +import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async'; +import {CompiledTemplate, TemplateCmd} from 'angular2/src/core/compiler/template_commands'; +import { + createHostComponentMeta, + DirectiveMetadata, + INormalizedDirectiveMetadata, + NormalizedDirectiveMetadata, + TypeMetadata, + ChangeDetectionMetadata, + NormalizedTemplateMetadata +} from './directive_metadata'; +import {TemplateAst} from './template_ast'; +import {Injectable} from 'angular2/src/core/di'; +import {SourceModule, moduleRef} from './source_module'; +import {ChangeDetectionCompiler} from './change_detector_compiler'; +import {StyleCompiler} from './style_compiler'; +import {CommandCompiler} from './command_compiler'; +import {TemplateParser} from './template_parser'; +import {TemplateNormalizer} from './template_normalizer'; +import {RuntimeMetadataResolver} from './runtime_metadata'; + +import {TEMPLATE_COMMANDS_MODULE_REF} from './command_compiler'; +import {IS_DART, codeGenExportVariable, escapeSingleQuoteString, codeGenValueFn} from './util'; + +@Injectable() +export class TemplateCompiler { + private _compiledTemplateCache: Map = new Map(); + private _compiledTemplateDone: Map> = new Map(); + + constructor(private _runtimeMetadataResolver: RuntimeMetadataResolver, + private _templateNormalizer: TemplateNormalizer, + private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, + private _commandCompiler: CommandCompiler, + private _cdCompiler: ChangeDetectionCompiler) {} + + normalizeDirective(directive: DirectiveMetadata): Promise { + var normalizedTemplatePromise; + if (directive.isComponent) { + normalizedTemplatePromise = + this._templateNormalizer.normalizeTemplate(directive.type, directive.template); + } else { + normalizedTemplatePromise = PromiseWrapper.resolve(null); + } + return normalizedTemplatePromise.then( + (normalizedTemplate) => new NormalizedDirectiveMetadata({ + selector: directive.selector, + dynamicLoadable: directive.dynamicLoadable, + isComponent: directive.isComponent, + type: directive.type, + changeDetection: directive.changeDetection, template: normalizedTemplate + })); + } + + serializeTemplateMetadata(metadata: INormalizedDirectiveMetadata): string { + return Json.stringify((metadata).toJson()); + } + + deserializeTemplateMetadata(data: string): INormalizedDirectiveMetadata { + return NormalizedDirectiveMetadata.fromJson(Json.parse(data)); + } + + compileHostComponentRuntime(type: Type): Promise { + var compMeta: DirectiveMetadata = this._runtimeMetadataResolver.getMetadata(type); + if (isBlank(compMeta) || !compMeta.isComponent || !compMeta.dynamicLoadable) { + throw new BaseException( + `Could not compile '${stringify(type)}' because it is not dynamically loadable.`); + } + var hostMeta: NormalizedDirectiveMetadata = + createHostComponentMeta(compMeta.type, compMeta.selector); + this._compileComponentRuntime(hostMeta, [compMeta], new Set()); + return this._compiledTemplateDone.get(hostMeta.type.id); + } + + private _compileComponentRuntime(compMeta: NormalizedDirectiveMetadata, + viewDirectives: DirectiveMetadata[], + compilingComponentIds: Set): CompiledTemplate { + var compiledTemplate = this._compiledTemplateCache.get(compMeta.type.id); + var done = this._compiledTemplateDone.get(compMeta.type.id); + if (isBlank(compiledTemplate)) { + var styles; + var changeDetectorFactories; + var commands; + compiledTemplate = + new CompiledTemplate(compMeta.type.id, () => [changeDetectorFactories, commands, styles]); + this._compiledTemplateCache.set(compMeta.type.id, compiledTemplate); + compilingComponentIds.add(compMeta.type.id); + done = + PromiseWrapper.all([this._styleCompiler.compileComponentRuntime(compMeta)].concat( + viewDirectives.map(dirMeta => this.normalizeDirective(dirMeta)))) + .then((stylesAndNormalizedViewDirMetas: any[]) => { + var childPromises = []; + var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1); + var parsedTemplate = this._templateParser.parse( + compMeta.template.template, normalizedViewDirMetas, compMeta.type.name); + + changeDetectorFactories = this._cdCompiler.compileComponentRuntime( + compMeta.type, compMeta.changeDetection.changeDetection, parsedTemplate); + styles = stylesAndNormalizedViewDirMetas[0]; + commands = this._compileCommandsRuntime(compMeta, parsedTemplate, + compilingComponentIds, childPromises); + return PromiseWrapper.all(childPromises); + }) + .then((_) => { + SetWrapper.delete(compilingComponentIds, compMeta.type.id); + return compiledTemplate; + }); + this._compiledTemplateDone.set(compMeta.type.id, done); + } + return compiledTemplate; + } + + private _compileCommandsRuntime(compMeta: NormalizedDirectiveMetadata, + parsedTemplate: TemplateAst[], compilingComponentIds: Set, + childPromises: Promise[]): TemplateCmd[] { + return this._commandCompiler.compileComponentRuntime( + compMeta, parsedTemplate, (childComponentDir: NormalizedDirectiveMetadata) => { + var childViewDirectives: DirectiveMetadata[] = + this._runtimeMetadataResolver.getViewDirectivesMetadata( + childComponentDir.type.runtime); + var childIsRecursive = SetWrapper.has(compilingComponentIds, childComponentDir.type.id); + var childTemplate = this._compileComponentRuntime(childComponentDir, childViewDirectives, + compilingComponentIds); + if (!childIsRecursive) { + // Only wait for a child if it is not a cycle + childPromises.push(this._compiledTemplateDone.get(childComponentDir.type.id)); + } + return childTemplate; + }); + } + + compileTemplatesCodeGen(moduleId: string, + components: NormalizedComponentWithViewDirectives[]): SourceModule { + var declarations = []; + var templateArguments = []; + var componentMetas: NormalizedDirectiveMetadata[] = []; + components.forEach(componentWithDirs => { + var compMeta = componentWithDirs.component; + componentMetas.push(compMeta); + this._processTemplateCodeGen(compMeta, + componentWithDirs.directives, + declarations, templateArguments); + if (compMeta.dynamicLoadable) { + var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector); + componentMetas.push(hostMeta); + this._processTemplateCodeGen(hostMeta, [compMeta], declarations, templateArguments); + } + }); + ListWrapper.forEachWithIndex(componentMetas, (compMeta: NormalizedDirectiveMetadata, + index: number) => { + var templateDataFn = codeGenValueFn([], `[${templateArguments[index].join(',')}]`); + declarations.push( + `${codeGenExportVariable(templateVariableName(compMeta.type))}new ${TEMPLATE_COMMANDS_MODULE_REF}CompiledTemplate(${compMeta.type.id},${templateDataFn});`); + }); + return new SourceModule(`${templateModuleName(moduleId)}`, declarations.join('\n')); + } + + compileStylesheetCodeGen(moduleId: string, cssText: string): SourceModule[] { + return this._styleCompiler.compileStylesheetCodeGen(moduleId, cssText); + } + + private _processTemplateCodeGen(compMeta: NormalizedDirectiveMetadata, + directives: NormalizedDirectiveMetadata[], + targetDeclarations: string[], targetTemplateArguments: any[][]) { + var styleExpr = this._styleCompiler.compileComponentCodeGen(compMeta); + var parsedTemplate = + this._templateParser.parse(compMeta.template.template, directives, compMeta.type.name); + var changeDetectorsExpr = this._cdCompiler.compileComponentCodeGen( + compMeta.type, compMeta.changeDetection.changeDetection, parsedTemplate); + var commandsExpr = this._commandCompiler.compileComponentCodeGen( + compMeta, parsedTemplate, codeGenComponentTemplateFactory); + + addAll(styleExpr.declarations, targetDeclarations); + addAll(changeDetectorsExpr.declarations, targetDeclarations); + addAll(commandsExpr.declarations, targetDeclarations); + + targetTemplateArguments.push( + [changeDetectorsExpr.expression, commandsExpr.expression, styleExpr.expression]); + } +} + +export class NormalizedComponentWithViewDirectives { + constructor(public component: INormalizedDirectiveMetadata, + public directives: INormalizedDirectiveMetadata[]) {} +} + +function templateVariableName(type: TypeMetadata): string { + return `${type.name}Template`; +} + +function templateModuleName(moduleId: string): string { + return `${moduleId}.template`; +} + +function addAll(source: any[], target: any[]) { + for (var i = 0; i < source.length; i++) { + target.push(source[i]); + } +} + +function codeGenComponentTemplateFactory(nestedCompType: NormalizedDirectiveMetadata): string { + return `${moduleRef(templateModuleName(nestedCompType.type.moduleId))}${templateVariableName(nestedCompType.type)}`; +} diff --git a/modules/angular2/src/compiler/template_loader.ts b/modules/angular2/src/compiler/template_normalizer.ts similarity index 69% rename from modules/angular2/src/compiler/template_loader.ts rename to modules/angular2/src/compiler/template_normalizer.ts index c9d28278d6..02a41827d0 100644 --- a/modules/angular2/src/compiler/template_loader.ts +++ b/modules/angular2/src/compiler/template_normalizer.ts @@ -1,11 +1,16 @@ -import {TypeMetadata, TemplateMetadata} from './api'; -import {ViewEncapsulation} from 'angular2/src/core/render/api'; +import { + TypeMetadata, + TemplateMetadata, + NormalizedDirectiveMetadata, + NormalizedTemplateMetadata +} from './directive_metadata'; import {isPresent, isBlank} from 'angular2/src/core/facade/lang'; import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async'; import {XHR} from 'angular2/src/core/render/xhr'; import {UrlResolver} from 'angular2/src/core/services/url_resolver'; import {resolveStyleUrls} from './style_url_resolver'; +import {Injectable} from 'angular2/src/core/di'; import { HtmlAstVisitor, @@ -25,43 +30,43 @@ const LINK_STYLE_HREF_ATTR = 'href'; const LINK_STYLE_REL_VALUE = 'stylesheet'; const STYLE_ELEMENT = 'style'; -export class TemplateLoader { +@Injectable() +export class TemplateNormalizer { constructor(private _xhr: XHR, private _urlResolver: UrlResolver, private _domParser: HtmlParser) {} - loadTemplate(directiveType: TypeMetadata, encapsulation: ViewEncapsulation, template: string, - templateUrl: string, styles: string[], - styleUrls: string[]): Promise { - if (isPresent(template)) { - return PromiseWrapper.resolve(this.createTemplateFromString( - directiveType, encapsulation, template, directiveType.typeUrl, styles, styleUrls)); + normalizeTemplate(directiveType: TypeMetadata, + template: TemplateMetadata): Promise { + if (isPresent(template.template)) { + return PromiseWrapper.resolve(this.normalizeLoadedTemplate( + directiveType, template, template.template, directiveType.moduleId)); } else { - var sourceAbsUrl = this._urlResolver.resolve(directiveType.typeUrl, templateUrl); + var sourceAbsUrl = this._urlResolver.resolve(directiveType.moduleId, template.templateUrl); return this._xhr.get(sourceAbsUrl) - .then(templateContent => - this.createTemplateFromString(directiveType, encapsulation, templateContent, - sourceAbsUrl, styles, styleUrls)); + .then(templateContent => this.normalizeLoadedTemplate(directiveType, template, + templateContent, sourceAbsUrl)); } } - createTemplateFromString(directiveType: TypeMetadata, encapsulation: ViewEncapsulation, - template: string, templateSourceUrl: string, styles: string[], - styleUrls: string[]): TemplateMetadata { - var domNodes = this._domParser.parse(template, directiveType.typeName); + normalizeLoadedTemplate(directiveType: TypeMetadata, templateMeta: TemplateMetadata, + template: string, templateAbsUrl: string): NormalizedTemplateMetadata { + var domNodes = this._domParser.parse(template, directiveType.name); var visitor = new TemplatePreparseVisitor(); var remainingNodes = htmlVisitAll(visitor, domNodes); - var allStyles = styles.concat(visitor.styles); - var allStyleUrls = styleUrls.concat(visitor.styleUrls); - var allResolvedStyles = allStyles.map(style => { - var styleWithImports = resolveStyleUrls(this._urlResolver, templateSourceUrl, style); - styleWithImports.styleUrls.forEach(styleUrl => allStyleUrls.push(styleUrl)); - return styleWithImports.style; - }); + var allStyles = templateMeta.styles.concat(visitor.styles); var allStyleAbsUrls = - allStyleUrls.map(styleUrl => this._urlResolver.resolve(templateSourceUrl, styleUrl)); - return new TemplateMetadata({ - encapsulation: encapsulation, + visitor.styleUrls.map(url => this._urlResolver.resolve(templateAbsUrl, url)) + .concat(templateMeta.styleUrls.map( + url => this._urlResolver.resolve(directiveType.moduleId, url))); + + var allResolvedStyles = allStyles.map(style => { + var styleWithImports = resolveStyleUrls(this._urlResolver, templateAbsUrl, style); + styleWithImports.styleUrls.forEach(styleUrl => allStyleAbsUrls.push(styleUrl)); + return styleWithImports.style; + }); + return new NormalizedTemplateMetadata({ + encapsulation: templateMeta.encapsulation, template: this._domParser.unparse(remainingNodes), styles: allResolvedStyles, styleAbsUrls: allStyleAbsUrls, diff --git a/modules/angular2/src/compiler/template_parser.ts b/modules/angular2/src/compiler/template_parser.ts index 8e3ccab3b4..1695169068 100644 --- a/modules/angular2/src/compiler/template_parser.ts +++ b/modules/angular2/src/compiler/template_parser.ts @@ -8,11 +8,13 @@ import { assertionsEnabled, isBlank } from 'angular2/src/core/facade/lang'; +import {Injectable} from 'angular2/src/core/di'; import {BaseException} from 'angular2/src/core/facade/exceptions'; import {Parser, AST, ASTWithSource} from 'angular2/src/core/change_detection/change_detection'; import {TemplateBinding} from 'angular2/src/core/change_detection/parser/ast'; +import {NormalizedDirectiveMetadata} from './directive_metadata'; +import {HtmlParser} from './html_parser'; -import {DirectiveMetadata, TemplateMetadata} from './api'; import { ElementAst, BoundElementPropertyAst, @@ -68,12 +70,16 @@ const STYLE_PREFIX = 'style'; var TEXT_CSS_SELECTOR = CssSelector.parse('*')[0]; +@Injectable() export class TemplateParser { - constructor(private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry) {} + constructor(private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry, + private _htmlParser: HtmlParser) {} - parse(domNodes: HtmlAst[], directives: DirectiveMetadata[]): TemplateAst[] { + parse(template: string, directives: NormalizedDirectiveMetadata[], + sourceInfo: string): TemplateAst[] { var parseVisitor = new TemplateParseVisitor(directives, this._exprParser, this._schemaRegistry); - var result = htmlVisitAll(parseVisitor, domNodes, EMPTY_COMPONENT); + var result = + htmlVisitAll(parseVisitor, this._htmlParser.parse(template, sourceInfo), EMPTY_COMPONENT); if (parseVisitor.errors.length > 0) { var errorString = parseVisitor.errors.join('\n'); throw new BaseException(`Template parse errors:\n${errorString}`); @@ -85,7 +91,7 @@ export class TemplateParser { class TemplateParseVisitor implements HtmlAstVisitor { selectorMatcher: SelectorMatcher; errors: string[] = []; - constructor(directives: DirectiveMetadata[], private _exprParser: Parser, + constructor(directives: NormalizedDirectiveMetadata[], private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry) { this.selectorMatcher = new SelectorMatcher(); directives.forEach(directive => { @@ -371,31 +377,32 @@ class TemplateParseVisitor implements HtmlAstVisitor { } private _parseDirectives(selectorMatcher: SelectorMatcher, - elementCssSelector: CssSelector): DirectiveMetadata[] { + elementCssSelector: CssSelector): NormalizedDirectiveMetadata[] { var directives = []; selectorMatcher.match(elementCssSelector, (selector, directive) => { directives.push(directive); }); // Need to sort the directives so that we get consistent results throughout, // as selectorMatcher uses Maps inside. // Also need to make components the first directive in the array - ListWrapper.sort(directives, (dir1: DirectiveMetadata, dir2: DirectiveMetadata) => { - var dir1Comp = dir1.isComponent; - var dir2Comp = dir2.isComponent; - if (dir1Comp && !dir2Comp) { - return -1; - } else if (!dir1Comp && dir2Comp) { - return 1; - } else { - return StringWrapper.compare(dir1.type.typeName, dir2.type.typeName); - } - }); + ListWrapper.sort(directives, + (dir1: NormalizedDirectiveMetadata, dir2: NormalizedDirectiveMetadata) => { + var dir1Comp = dir1.isComponent; + var dir2Comp = dir2.isComponent; + if (dir1Comp && !dir2Comp) { + return -1; + } else if (!dir1Comp && dir2Comp) { + return 1; + } else { + return StringWrapper.compare(dir1.type.name, dir2.type.name); + } + }); return directives; } - private _createDirectiveAsts(elementName: string, directives: DirectiveMetadata[], + private _createDirectiveAsts(elementName: string, directives: NormalizedDirectiveMetadata[], props: BoundElementOrDirectiveProperty[], sourceInfo: string): DirectiveAst[] { - return directives.map((directive: DirectiveMetadata) => { + return directives.map((directive: NormalizedDirectiveMetadata) => { var hostProperties: BoundElementPropertyAst[] = []; var hostEvents: BoundEventAst[] = []; var directiveProperties: BoundDirectivePropertyAst[] = []; @@ -510,7 +517,7 @@ class TemplateParseVisitor implements HtmlAstVisitor { private _findComponentDirectiveNames(directives: DirectiveAst[]): string[] { var componentTypeNames: string[] = []; directives.forEach(directive => { - var typeName = directive.directive.type.typeName; + var typeName = directive.directive.type.name; if (directive.directive.isComponent) { componentTypeNames.push(typeName); } diff --git a/modules/angular2/src/compiler/util.ts b/modules/angular2/src/compiler/util.ts index 7205b543eb..e6e81f0e51 100644 --- a/modules/angular2/src/compiler/util.ts +++ b/modules/angular2/src/compiler/util.ts @@ -1,10 +1,12 @@ -import {StringWrapper, isBlank} from 'angular2/src/core/facade/lang'; +import {StringWrapper, isBlank, isJsObject} from 'angular2/src/core/facade/lang'; var CAMEL_CASE_REGEXP = /([A-Z])/g; var DASH_CASE_REGEXP = /-([a-z])/g; var SINGLE_QUOTE_ESCAPE_STRING_RE = /'|\\|\n/g; var DOUBLE_QUOTE_ESCAPE_STRING_RE = /"|\\|\n/g; +export var IS_DART = !isJsObject({}); + export function camelCaseToDashCase(input: string): string { return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP, (m) => { return '-' + m[1].toLowerCase(); }); @@ -38,3 +40,35 @@ function escapeString(input: string, re: RegExp): string { } }); } + +export function codeGenExportVariable(name: string): string { + return IS_DART ? `var ${name} = ` : `var ${name} = exports['${name}'] = `; +} + +export function codeGenConcatArray(expression: string): string { + return `${IS_DART ? '..addAll' : '.concat'}(${expression})`; +} + +export function codeGenMapArray(argNames: string[], callback: string): string { + if (IS_DART) { + return `.map( (${argNames.join(',')}) => ${callback} ).toList()`; + } else { + return `.map(function(${argNames.join(',')}) { return ${callback}; })`; + } +} + +export function codeGenReplaceAll(pattern: string, value: string): string { + if (IS_DART) { + return `.replaceAll('${pattern}', '${value}')`; + } else { + return `.replace(/${pattern}/g, '${value}')`; + } +} + +export function codeGenValueFn(params: string[], value: string): string { + if (IS_DART) { + return `(${params.join(',')}) => ${value}`; + } else { + return `function(${params.join(',')}) { return ${value}; }`; + } +} diff --git a/modules/angular2/src/core/compiler/directive_resolver.ts b/modules/angular2/src/core/compiler/directive_resolver.ts index 3a3daf5e25..854143c654 100644 --- a/modules/angular2/src/core/compiler/directive_resolver.ts +++ b/modules/angular2/src/core/compiler/directive_resolver.ts @@ -93,8 +93,11 @@ export class DirectiveResolver { properties: mergedProperties, events: mergedEvents, host: mergedHost, + dynamicLoadable: dm.dynamicLoadable, + compiledHostTemplate: dm.compiledHostTemplate, bindings: dm.bindings, exportAs: dm.exportAs, + moduleId: dm.moduleId, compileChildren: dm.compileChildren, changeDetection: dm.changeDetection, viewBindings: dm.viewBindings @@ -108,6 +111,7 @@ export class DirectiveResolver { host: mergedHost, bindings: dm.bindings, exportAs: dm.exportAs, + moduleId: dm.moduleId, compileChildren: dm.compileChildren }); } diff --git a/modules/angular2/src/core/compiler/template_commands.ts b/modules/angular2/src/core/compiler/template_commands.ts index 188c101f05..4a968e98b6 100644 --- a/modules/angular2/src/core/compiler/template_commands.ts +++ b/modules/angular2/src/core/compiler/template_commands.ts @@ -1,4 +1,4 @@ -import {Type, CONST_EXPR, isPresent} from 'angular2/src/core/facade/lang'; +import {Type, CONST_EXPR, isPresent, isBlank} from 'angular2/src/core/facade/lang'; import { RenderTemplateCmd, RenderCommandVisitor, @@ -10,7 +10,35 @@ import { } from 'angular2/src/core/render/render'; export class CompiledTemplate { - constructor(public id: string, public commands: TemplateCmd[]) {} + private _changeDetectorFactories: Function[] = null; + private _styles: string[] = null; + private _commands: TemplateCmd[] = null; + // Note: paramGetter is a function so that we can have cycles between templates! + constructor(public id: number, private _paramGetter: Function) {} + + private _init() { + if (isBlank(this._commands)) { + var params = this._paramGetter(); + this._changeDetectorFactories = params[0]; + this._commands = params[1]; + this._styles = params[2]; + } + } + + get changeDetectorFactories(): Function[] { + this._init(); + return this._changeDetectorFactories; + } + + get styles(): string[] { + this._init(); + return this._styles; + } + + get commands(): TemplateCmd[] { + this._init(); + return this._commands; + } } const EMPTY_ARR = CONST_EXPR([]); @@ -73,14 +101,14 @@ export function endElement(): TemplateCmd { export class BeginComponentCmd implements TemplateCmd, IBeginElementCmd, RenderBeginComponentCmd { isBound: boolean = true; - templateId: string; + templateId: number; component: Type; constructor(public name: string, public attrNameAndValues: string[], public eventNames: string[], public variableNameAndValues: string[], public directives: Type[], public nativeShadow: boolean, public ngContentIndex: number, public template: CompiledTemplate) { this.component = directives[0]; - this.templateId = isPresent(template) ? template.id : null; + this.templateId = template.id; } visit(visitor: CommandVisitor, context: any): any { return visitor.visitBeginComponent(this, context); diff --git a/modules/angular2/src/core/compiler/view_resolver.ts b/modules/angular2/src/core/compiler/view_resolver.ts index d268aeb446..0fa4bec87a 100644 --- a/modules/angular2/src/core/compiler/view_resolver.ts +++ b/modules/angular2/src/core/compiler/view_resolver.ts @@ -10,7 +10,7 @@ import {reflector} from 'angular2/src/core/reflection/reflection'; @Injectable() export class ViewResolver { - _cache: Map = new Map(); + _cache: Map = new Map(); resolve(component: Type): ViewMetadata { var view = this._cache.get(component); diff --git a/modules/angular2/src/core/metadata.dart b/modules/angular2/src/core/metadata.dart index 20c366f5bc..4afc466aef 100644 --- a/modules/angular2/src/core/metadata.dart +++ b/modules/angular2/src/core/metadata.dart @@ -1,14 +1,14 @@ library angular2.src.core.metadata; -import "package:angular2/src/core/facade/collection.dart" show List; +import 'package:angular2/src/core/facade/collection.dart' show List; import 'package:angular2/src/core/change_detection/change_detection.dart'; -import "./metadata/di.dart"; -import "./metadata/directives.dart"; -import "./metadata/view.dart"; +import './metadata/di.dart'; +import './metadata/directives.dart'; +import './metadata/view.dart'; -export "./metadata/di.dart"; -export "./metadata/directives.dart"; -export "./metadata/view.dart"; +export './metadata/di.dart'; +export './metadata/directives.dart'; +export './metadata/view.dart'; /** * See: [DirectiveMetadata] for docs. @@ -16,7 +16,7 @@ export "./metadata/view.dart"; class Directive extends DirectiveMetadata { const Directive({String selector, List properties, List events, Map host, - List bindings, String exportAs, + List bindings, String exportAs, String moduleId, bool compileChildren: true}) : super( selector: selector, @@ -25,6 +25,7 @@ class Directive extends DirectiveMetadata { host: host, bindings: bindings, exportAs: exportAs, + moduleId: moduleId, compileChildren: compileChildren); } @@ -33,16 +34,18 @@ class Directive extends DirectiveMetadata { */ class Component extends ComponentMetadata { const Component({String selector, List properties, - List events, Map host, - List bindings, String exportAs, + List events, Map host, bool dynamicLoadable, + List bindings, String exportAs, String moduleId, bool compileChildren, List viewBindings, ChangeDetectionStrategy changeDetection}) : super( selector: selector, properties: properties, events: events, host: host, + dynamicLoadable: dynamicLoadable, bindings: bindings, exportAs: exportAs, + moduleId: moduleId, compileChildren: compileChildren, viewBindings: viewBindings, changeDetection: changeDetection); diff --git a/modules/angular2/src/core/metadata.ts b/modules/angular2/src/core/metadata.ts index 5bcffd7eef..05fa41ff1d 100644 --- a/modules/angular2/src/core/metadata.ts +++ b/modules/angular2/src/core/metadata.ts @@ -139,11 +139,11 @@ export interface ViewDecorator extends TypeDecorator { export interface DirectiveFactory { (obj: { selector?: string, properties?: string[], events?: string[], host?: StringMap, - bindings?: any[], exportAs?: string, compileChildren?: boolean; + bindings?: any[], exportAs?: string, moduleId?: string, compileChildren?: boolean; }): DirectiveDecorator; new (obj: { selector?: string, properties?: string[], events?: string[], host?: StringMap, - bindings?: any[], exportAs?: string, compileChildren?: boolean; + bindings?: any[], exportAs?: string, moduleId?: string, compileChildren?: boolean; }): DirectiveMetadata; } @@ -196,8 +196,10 @@ export interface ComponentFactory { properties?: string[], events?: string[], host?: StringMap, + dynamicLoadable?: boolean, bindings?: any[], exportAs?: string, + moduleId?: string, compileChildren?: boolean, viewBindings?: any[], changeDetection?: ChangeDetectionStrategy, @@ -207,8 +209,10 @@ export interface ComponentFactory { properties?: string[], events?: string[], host?: StringMap, + dynamicLoadable?: boolean, bindings?: any[], exportAs?: string, + moduleId?: string, compileChildren?: boolean, viewBindings?: any[], changeDetection?: ChangeDetectionStrategy, diff --git a/modules/angular2/src/core/metadata/directives.ts b/modules/angular2/src/core/metadata/directives.ts index a7fb8ade80..70145a0b2a 100644 --- a/modules/angular2/src/core/metadata/directives.ts +++ b/modules/angular2/src/core/metadata/directives.ts @@ -1,4 +1,4 @@ -import {isPresent, CONST, CONST_EXPR} from 'angular2/src/core/facade/lang'; +import {isPresent, CONST, CONST_EXPR, Type} from 'angular2/src/core/facade/lang'; import {InjectableMetadata} from 'angular2/src/core/di/metadata'; import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection'; @@ -699,8 +699,29 @@ export class DirectiveMetadata extends InjectableMetadata { */ exportAs: string; + /** + * The module id of the module that contains the directive. + * Needed to be able to resolve relative urls for templates and styles. + * In Dart, this can be determined automatically and does not need to be set. + * In CommonJS, this can always be set to `module.id`. + * + * ## Simple Example + * + * ``` + * @Directive({ + * selector: 'someDir', + * moduleId: module.id + * }) + * class SomeDir { + * } + * + * ``` + */ + moduleId: string; + constructor({ - selector, properties, events, host, bindings, exportAs, compileChildren = true, + selector, properties, events, host, bindings, exportAs, moduleId, + compileChildren = true, }: { selector?: string, properties?: string[], @@ -708,6 +729,7 @@ export class DirectiveMetadata extends InjectableMetadata { host?: StringMap, bindings?: any[], exportAs?: string, + moduleId?: string, compileChildren?: boolean, } = {}) { super(); @@ -716,6 +738,7 @@ export class DirectiveMetadata extends InjectableMetadata { this.events = events; this.host = host; this.exportAs = exportAs; + this.moduleId = moduleId; this.compileChildren = compileChildren; this.bindings = bindings; } @@ -764,6 +787,34 @@ export class DirectiveMetadata extends InjectableMetadata { */ @CONST() export class ComponentMetadata extends DirectiveMetadata { + /** + * Declare that this component can be programatically loaded. + * Every component that is used in bootstrap, routing, ... has to be + * annotated with this. + * + * ## Example + * + * ``` + * @Component({ + * selector: 'root', + * dynamicLoadable: true + * }) + * @View({ + * template: 'hello world!' + * }) + * class RootComponent { + * } + * ``` + */ + dynamicLoadable: boolean; + + + /** + * Used by build tools to store the compiled template. + * Not intended to be used by a user. + */ + compiledHostTemplate: /* CompiledTemplate */ any; + /** * Defines the used change detection strategy. * @@ -817,14 +868,18 @@ export class ComponentMetadata extends DirectiveMetadata { */ viewBindings: any[]; - constructor({selector, properties, events, host, exportAs, bindings, viewBindings, - changeDetection = ChangeDetectionStrategy.Default, compileChildren = true}: { + constructor({selector, properties, events, host, dynamicLoadable, compiledHostTemplate, exportAs, + moduleId, bindings, viewBindings, changeDetection = ChangeDetectionStrategy.Default, + compileChildren = true}: { selector?: string, properties?: string[], events?: string[], host?: StringMap, + dynamicLoadable?: boolean, + compiledHostTemplate?: any, bindings?: any[], exportAs?: string, + moduleId?: string, compileChildren?: boolean, viewBindings?: any[], changeDetection?: ChangeDetectionStrategy, @@ -835,12 +890,15 @@ export class ComponentMetadata extends DirectiveMetadata { events: events, host: host, exportAs: exportAs, + moduleId: moduleId, bindings: bindings, compileChildren: compileChildren }); this.changeDetection = changeDetection; this.viewBindings = viewBindings; + this.dynamicLoadable = dynamicLoadable; + this.compiledHostTemplate = compiledHostTemplate; } } diff --git a/modules/angular2/src/core/reflection/platform_reflection_capabilities.ts b/modules/angular2/src/core/reflection/platform_reflection_capabilities.ts index 36b196d391..09eb9d629d 100644 --- a/modules/angular2/src/core/reflection/platform_reflection_capabilities.ts +++ b/modules/angular2/src/core/reflection/platform_reflection_capabilities.ts @@ -11,5 +11,8 @@ export interface PlatformReflectionCapabilities { getter(name: string): GetterFn; setter(name: string): SetterFn; method(name: string): MethodFn; + // TODO(tbosch): remove this method after the new compiler is done + // (and ComponentUrlMapper as well). importUri(type: Type): string; + moduleId(type: Type): string; } diff --git a/modules/angular2/src/core/reflection/reflection.dart b/modules/angular2/src/core/reflection/reflection.dart index 6a7d1bd802..a81248ad3c 100644 --- a/modules/angular2/src/core/reflection/reflection.dart +++ b/modules/angular2/src/core/reflection/reflection.dart @@ -44,6 +44,8 @@ class NoReflectionCapabilities implements PlatformReflectionCapabilities { } String importUri(Type type) => './'; + + String moduleId(Type type) => null; } final Reflector reflector = new Reflector(new NoReflectionCapabilities()); diff --git a/modules/angular2/src/core/reflection/reflection_capabilities.dart b/modules/angular2/src/core/reflection/reflection_capabilities.dart index 0d77f3fadb..fe9211466d 100644 --- a/modules/angular2/src/core/reflection/reflection_capabilities.dart +++ b/modules/angular2/src/core/reflection/reflection_capabilities.dart @@ -5,6 +5,8 @@ import 'types.dart'; import 'dart:mirrors'; import 'platform_reflection_capabilities.dart'; +var DOT_REGEX = new RegExp('\\.'); + class ReflectionCapabilities implements PlatformReflectionCapabilities { ReflectionCapabilities([metadataReader]) {} @@ -315,4 +317,8 @@ class ReflectionCapabilities implements PlatformReflectionCapabilities { String importUri(Type type) { return '${(reflectClass(type).owner as LibraryMirror).uri}'; } + + String moduleId(Type type) { + return '${MirrorSystem.getName((reflectClass(type).owner as LibraryMirror).qualifiedName).replaceAll(DOT_REGEX, "/")}'; + } } diff --git a/modules/angular2/src/core/reflection/reflection_capabilities.ts b/modules/angular2/src/core/reflection/reflection_capabilities.ts index c27dfc94fc..7d249432ef 100644 --- a/modules/angular2/src/core/reflection/reflection_capabilities.ts +++ b/modules/angular2/src/core/reflection/reflection_capabilities.ts @@ -168,4 +168,6 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities { // There is not a concept of import uri in Js, but this is useful in developing Dart applications. importUri(type: Type): string { return './'; } + + moduleId(type: Type): string { return null; } } diff --git a/modules/angular2/src/core/reflection/reflector.ts b/modules/angular2/src/core/reflection/reflector.ts index 54c5258d4a..b58bda97ab 100644 --- a/modules/angular2/src/core/reflection/reflector.ts +++ b/modules/angular2/src/core/reflection/reflector.ts @@ -156,6 +156,8 @@ export class Reflector { _containsReflectionInfo(typeOrFunc) { return this._injectableInfo.has(typeOrFunc); } importUri(type: Type): string { return this.reflectionCapabilities.importUri(type); } + + moduleId(type: Type): string { return this.reflectionCapabilities.moduleId(type); } } function _mergeMaps(target: Map, config: StringMap): void { diff --git a/modules/angular2/src/core/render/api.ts b/modules/angular2/src/core/render/api.ts index 809f047a09..bb39861c56 100644 --- a/modules/angular2/src/core/render/api.ts +++ b/modules/angular2/src/core/render/api.ts @@ -409,7 +409,7 @@ export interface RenderBeginElementCmd extends RenderBeginCmd { export interface RenderBeginComponentCmd extends RenderBeginElementCmd { nativeShadow: boolean; - templateId: string; + templateId: number; } export interface RenderEmbeddedTemplateCmd extends RenderBeginElementCmd { diff --git a/modules/angular2/src/mock/directive_resolver_mock.ts b/modules/angular2/src/mock/directive_resolver_mock.ts index 948f3e4ac0..499aa09d96 100644 --- a/modules/angular2/src/mock/directive_resolver_mock.ts +++ b/modules/angular2/src/mock/directive_resolver_mock.ts @@ -29,8 +29,11 @@ export class MockDirectiveResolver extends DirectiveResolver { properties: dm.properties, events: dm.events, host: dm.host, + dynamicLoadable: dm.dynamicLoadable, + compiledHostTemplate: dm.compiledHostTemplate, bindings: bindings, exportAs: dm.exportAs, + moduleId: dm.moduleId, compileChildren: dm.compileChildren, changeDetection: dm.changeDetection, viewBindings: viewBindings @@ -44,6 +47,7 @@ export class MockDirectiveResolver extends DirectiveResolver { host: dm.host, bindings: bindings, exportAs: dm.exportAs, + moduleId: dm.moduleId, compileChildren: dm.compileChildren }); } diff --git a/modules/angular2/src/transform/template_compiler/change_detector_codegen.ts b/modules/angular2/src/transform/template_compiler/change_detector_codegen.ts index 03d340e5d6..82a345e755 100644 --- a/modules/angular2/src/transform/template_compiler/change_detector_codegen.ts +++ b/modules/angular2/src/transform/template_compiler/change_detector_codegen.ts @@ -7,6 +7,7 @@ import { // TODO(tbosch): Move the corresponding code into angular2/src/compiler once // the new compiler is done. export class Codegen { + constructor(moduleAlias: string) {} generate(typeName: string, changeDetectorTypeName: string, def: ChangeDetectorDefinition): void { throw "Not implemented in JS"; } diff --git a/modules/angular2/test/compiler/change_definition_factory_spec.ts b/modules/angular2/test/compiler/change_definition_factory_spec.ts index 4c15b665fa..3c54b43e55 100644 --- a/modules/angular2/test/compiler/change_definition_factory_spec.ts +++ b/modules/angular2/test/compiler/change_definition_factory_spec.ts @@ -9,12 +9,15 @@ import { inject, it, xit, - TestComponentBuilder + TestComponentBuilder, + beforeEachBindings } from 'angular2/test_lib'; import {MapWrapper} from 'angular2/src/core/facade/collection'; -import {HtmlParser} from 'angular2/src/compiler/html_parser'; -import {DirectiveMetadata, TypeMetadata, ChangeDetectionMetadata} from 'angular2/src/compiler/api'; -import {MockSchemaRegistry} from './template_parser_spec'; +import { + NormalizedDirectiveMetadata, + TypeMetadata, + ChangeDetectionMetadata +} from 'angular2/src/compiler/directive_metadata'; import {TemplateParser} from 'angular2/src/compiler/template_parser'; import { Parser, @@ -33,9 +36,12 @@ import {Pipes} from 'angular2/src/core/change_detection/pipes'; import {createChangeDetectorDefinitions} from 'angular2/src/compiler/change_definition_factory'; import {TestContext, TestDirective, TestDispatcher, TestPipes} from './change_detector_mocks'; +import {TEST_BINDINGS} from './test_bindings'; + export function main() { describe('ChangeDefinitionFactory', () => { - var domParser: HtmlParser; + beforeEachBindings(() => TEST_BINDINGS); + var parser: TemplateParser; var dispatcher: TestDispatcher; var context: TestContext; @@ -44,26 +50,23 @@ export function main() { var pipes: Pipes; var eventLocals: Locals; - beforeEach(() => { - domParser = new HtmlParser(); - parser = new TemplateParser( - new Parser(new Lexer()), - new MockSchemaRegistry({'invalidProp': false}, {'mappedAttr': 'mappedProp'})); + beforeEach(inject([TemplateParser], (_templateParser) => { + parser = _templateParser; context = new TestContext(); directive = new TestDirective(); dispatcher = new TestDispatcher([directive], []); locals = new Locals(null, MapWrapper.createFromStringMap({'someVar': null})); eventLocals = new Locals(null, MapWrapper.createFromStringMap({'$event': null})); pipes = new TestPipes(); - }); + })); - function createChangeDetector(template: string, directives: DirectiveMetadata[], + function createChangeDetector(template: string, directives: NormalizedDirectiveMetadata[], protoViewIndex: number = 0): ChangeDetector { var protoChangeDetectors = - createChangeDetectorDefinitions( - new TypeMetadata({typeName: 'SomeComp'}), ChangeDetectionStrategy.Default, - new ChangeDetectorGenConfig(true, true, false, false), - parser.parse(domParser.parse(template, 'TestComp'), directives)) + createChangeDetectorDefinitions(new TypeMetadata({name: 'SomeComp'}), + ChangeDetectionStrategy.Default, + new ChangeDetectorGenConfig(true, true, false, false), + parser.parse(template, directives, 'TestComp')) .map(definition => new DynamicProtoChangeDetector(definition)); var changeDetector = protoChangeDetectors[protoViewIndex].instantiate(dispatcher); changeDetector.hydrate(context, locals, dispatcher, pipes); @@ -103,8 +106,8 @@ export function main() { }); it('should write directive properties', () => { - var dirMeta = new DirectiveMetadata({ - type: new TypeMetadata({typeName: 'SomeDir'}), + var dirMeta = new NormalizedDirectiveMetadata({ + type: new TypeMetadata({name: 'SomeDir'}), selector: 'div', changeDetection: new ChangeDetectionMetadata({properties: ['dirProp']}) }); @@ -117,8 +120,8 @@ export function main() { }); it('should watch directive host properties', () => { - var dirMeta = new DirectiveMetadata({ - type: new TypeMetadata({typeName: 'SomeDir'}), + var dirMeta = new NormalizedDirectiveMetadata({ + type: new TypeMetadata({name: 'SomeDir'}), selector: 'div', changeDetection: new ChangeDetectionMetadata({hostProperties: {'elProp': 'dirProp'}}) }); @@ -131,8 +134,8 @@ export function main() { }); it('should handle directive events', () => { - var dirMeta = new DirectiveMetadata({ - type: new TypeMetadata({typeName: 'SomeDir'}), + var dirMeta = new NormalizedDirectiveMetadata({ + type: new TypeMetadata({name: 'SomeDir'}), selector: 'div', changeDetection: new ChangeDetectionMetadata({hostListeners: {'click': 'onEvent($event)'}}) diff --git a/modules/angular2/test/compiler/change_detector_compiler_spec.ts b/modules/angular2/test/compiler/change_detector_compiler_spec.ts index 1b7e55234d..3ad15de32f 100644 --- a/modules/angular2/test/compiler/change_detector_compiler_spec.ts +++ b/modules/angular2/test/compiler/change_detector_compiler_spec.ts @@ -9,9 +9,10 @@ import { beforeEach, afterEach, AsyncTestCompleter, - inject + inject, + beforeEachBindings } from 'angular2/test_lib'; -import {IS_DART} from '../platform'; +import {bind} from 'angular2/src/core/di'; import {CONST_EXPR} from 'angular2/src/core/facade/lang'; import {MapWrapper} from 'angular2/src/core/facade/collection'; @@ -19,21 +20,16 @@ import {Promise} from 'angular2/src/core/facade/async'; import {ChangeDetectionCompiler} from 'angular2/src/compiler/change_detector_compiler'; -import {HtmlParser} from 'angular2/src/compiler/html_parser'; import { - DirectiveMetadata, + NormalizedDirectiveMetadata, TypeMetadata, - ChangeDetectionMetadata, - SourceModule -} from 'angular2/src/compiler/api'; - -import {MockSchemaRegistry} from './template_parser_spec'; + ChangeDetectionMetadata +} from 'angular2/src/compiler/directive_metadata'; +import {SourceModule, SourceExpression, moduleRef} from 'angular2/src/compiler/source_module'; import {TemplateParser} from 'angular2/src/compiler/template_parser'; import { - Parser, - Lexer, ChangeDetectorGenConfig, ChangeDetectionStrategy, ChangeDispatcher, @@ -45,61 +41,76 @@ import { import {evalModule} from './eval_module'; +import {TEST_BINDINGS} from './test_bindings'; import {TestContext, TestDispatcher, TestPipes} from './change_detector_mocks'; +import {codeGenValueFn, codeGenExportVariable} from 'angular2/src/compiler/util'; // Attention: These module names have to correspond to real modules! -const MODULE_NAME = 'angular2/test/compiler/change_detector_compiler_spec'; +const THIS_MODULE = 'angular2/test/compiler/change_detector_compiler_spec'; +var THIS_MODULE_REF = moduleRef(THIS_MODULE); export function main() { describe('ChangeDetectorCompiler', () => { - var domParser: HtmlParser; + beforeEachBindings(() => TEST_BINDINGS); + var parser: TemplateParser; + var compiler: ChangeDetectionCompiler; - function createCompiler(useJit: boolean): ChangeDetectionCompiler { - return new ChangeDetectionCompiler(new ChangeDetectorGenConfig(true, true, false, useJit)); - } - - beforeEach(() => { - domParser = new HtmlParser(); - parser = new TemplateParser( - new Parser(new Lexer()), - new MockSchemaRegistry({'invalidProp': false}, {'mappedAttr': 'mappedProp'})); - }); + beforeEach(inject([TemplateParser, ChangeDetectionCompiler], (_parser, _compiler) => { + parser = _parser; + compiler = _compiler; + })); describe('compileComponentRuntime', () => { function detectChanges(compiler: ChangeDetectionCompiler, template: string, - directives: DirectiveMetadata[] = CONST_EXPR([])): string[] { - var type = new TypeMetadata({typeName: 'SomeComp'}); - var parsedTemplate = parser.parse(domParser.parse(template, 'TestComp'), directives); + directives: NormalizedDirectiveMetadata[] = CONST_EXPR([])): string[] { + var type = new TypeMetadata({name: 'SomeComp'}); + var parsedTemplate = parser.parse(template, directives, 'TestComp'); var factories = compiler.compileComponentRuntime(type, ChangeDetectionStrategy.Default, parsedTemplate); return testChangeDetector(factories[0]); } - it('should watch element properties (no jit)', () => { - expect(detectChanges(createCompiler(false), '
')) - .toEqual(['elementProperty(elProp)=someValue']); + describe('no jit', () => { + beforeEachBindings(() => [ + bind(ChangeDetectorGenConfig) + .toValue(new ChangeDetectorGenConfig(true, true, false, false)) + ]); + it('should watch element properties', () => { + expect(detectChanges(compiler, '
')) + .toEqual(['elementProperty(elProp)=someValue']); + }); }); - it('should watch element properties (jit)', () => { - expect(detectChanges(createCompiler(true), '
')) - .toEqual(['elementProperty(elProp)=someValue']); + describe('jit', () => { + beforeEachBindings(() => [ + bind(ChangeDetectorGenConfig) + .toValue(new ChangeDetectorGenConfig(true, true, false, true)) + ]); + it('should watch element properties', () => { + expect(detectChanges(compiler, '
')) + .toEqual(['elementProperty(elProp)=someValue']); + }); + }); + + }); describe('compileComponentCodeGen', () => { function detectChanges(compiler: ChangeDetectionCompiler, template: string, - directives: DirectiveMetadata[] = CONST_EXPR([])): Promise { - var type = new TypeMetadata({typeName: 'SomeComp'}); - var parsedTemplate = parser.parse(domParser.parse(template, 'TestComp'), directives); - var sourceModule = + directives: NormalizedDirectiveMetadata[] = CONST_EXPR([])): + Promise { + var type = new TypeMetadata({name: 'SomeComp'}); + var parsedTemplate = parser.parse(template, directives, 'TestComp'); + var sourceExpression = compiler.compileComponentCodeGen(type, ChangeDetectionStrategy.Default, parsedTemplate); - var testableModule = createTestableModule(sourceModule, 0); + var testableModule = createTestableModule(sourceExpression, 0).getSourceWithImports(); return evalModule(testableModule.source, testableModule.imports, null); } it('should watch element properties', inject([AsyncTestCompleter], (async) => { - detectChanges(createCompiler(true), '
') + detectChanges(compiler, '
') .then((value) => { expect(value).toEqual(['elementProperty(elProp)=someValue']); async.done(); @@ -111,19 +122,12 @@ export function main() { }); } - -function createTestableModule(sourceModule: SourceModule, changeDetectorIndex: number): - SourceModule { - var testableSource; - var testableImports = [[MODULE_NAME, 'mocks']].concat(sourceModule.imports); - if (IS_DART) { - testableSource = `${sourceModule.source} - run(_) { return mocks.testChangeDetector(CHANGE_DETECTORS[${changeDetectorIndex}]); }`; - } else { - testableSource = `${sourceModule.source} - exports.run = function(_) { return mocks.testChangeDetector(CHANGE_DETECTORS[${changeDetectorIndex}]); }`; - } - return new SourceModule(null, testableSource, testableImports); +function createTestableModule(source: SourceExpression, changeDetectorIndex: number): SourceModule { + var resultExpression = + `${THIS_MODULE_REF}testChangeDetector((${source.expression})[${changeDetectorIndex}])`; + var testableSource = `${source.declarations.join('\n')} + ${codeGenExportVariable('run')}${codeGenValueFn(['_'], resultExpression)};`; + return new SourceModule(null, testableSource); } export function testChangeDetector(changeDetectorFactory: Function): string[] { diff --git a/modules/angular2/test/compiler/command_compiler_spec.ts b/modules/angular2/test/compiler/command_compiler_spec.ts index 810b248801..4c24b1d3f5 100644 --- a/modules/angular2/test/compiler/command_compiler_spec.ts +++ b/modules/angular2/test/compiler/command_compiler_spec.ts @@ -9,16 +9,13 @@ import { beforeEach, afterEach, AsyncTestCompleter, - inject + inject, + beforeEachBindings } from 'angular2/test_lib'; -import {IS_DART} from '../platform'; import {CONST_EXPR, stringify, isType, Type, isBlank} from 'angular2/src/core/facade/lang'; import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async'; -import {HtmlParser} from 'angular2/src/compiler/html_parser'; import {TemplateParser} from 'angular2/src/compiler/template_parser'; -import {MockSchemaRegistry} from './template_parser_spec'; -import {Parser, Lexer} from 'angular2/src/core/change_detection/change_detection'; import { CommandVisitor, TextCmd, @@ -32,14 +29,19 @@ import { } from 'angular2/src/core/compiler/template_commands'; import {CommandCompiler} from 'angular2/src/compiler/command_compiler'; import { - DirectiveMetadata, + NormalizedDirectiveMetadata, TypeMetadata, - TemplateMetadata, - SourceModule -} from 'angular2/src/compiler/api'; + NormalizedTemplateMetadata +} from 'angular2/src/compiler/directive_metadata'; +import {SourceModule, SourceExpression, moduleRef} from 'angular2/src/compiler/source_module'; import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {evalModule} from './eval_module'; -import {escapeSingleQuoteString} from 'angular2/src/compiler/util'; +import { + escapeSingleQuoteString, + codeGenValueFn, + codeGenExportVariable +} from 'angular2/src/compiler/util'; +import {TEST_BINDINGS} from './test_bindings'; const BEGIN_ELEMENT = 'BEGIN_ELEMENT'; const END_ELEMENT = 'END_ELEMENT'; @@ -50,8 +52,9 @@ const NG_CONTENT = 'NG_CONTENT'; const EMBEDDED_TEMPLATE = 'EMBEDDED_TEMPLATE'; // Attention: These module names have to correspond to real modules! -const MODULE_NAME = 'angular2/test/compiler/command_compiler_spec'; -const TEMPLATE_COMMANDS_MODULE_NAME = 'angular2/src/core/compiler/template_commands'; +const THIS_MODULE_NAME = 'angular2/test/compiler/command_compiler_spec'; +var THIS_MODULE_REF = moduleRef(THIS_MODULE_NAME); +var TEMPLATE_COMMANDS_MODULE_REF = moduleRef('angular2/src/core/compiler/template_commands'); // Attention: read by eval! export class RootComp {} @@ -59,27 +62,26 @@ export class SomeDir {} export class AComp {} var RootCompTypeMeta = - new TypeMetadata({typeName: 'RootComp', id: 1, type: RootComp, typeUrl: MODULE_NAME}); + new TypeMetadata({id: 1, name: 'RootComp', runtime: RootComp, moduleId: THIS_MODULE_NAME}); var SomeDirTypeMeta = - new TypeMetadata({typeName: 'SomeDir', id: 2, type: SomeDir, typeUrl: MODULE_NAME}); -var ACompTypeMeta = new TypeMetadata({typeName: 'AComp', id: 3, type: AComp, typeUrl: MODULE_NAME}); + new TypeMetadata({id: 2, name: 'SomeDir', runtime: SomeDir, moduleId: THIS_MODULE_NAME}); +var ACompTypeMeta = + new TypeMetadata({id: 3, name: 'AComp', runtime: AComp, moduleId: THIS_MODULE_NAME}); -var NESTED_COMPONENT = new CompiledTemplate('someNestedComponentId', []); +var NESTED_COMPONENT = new CompiledTemplate(45, () => []); export function main() { describe('CommandCompiler', () => { - var domParser: HtmlParser; + beforeEachBindings(() => TEST_BINDINGS); + var parser: TemplateParser; var commandCompiler: CommandCompiler; var componentTemplateFactory: Function; - beforeEach(() => { - domParser = new HtmlParser(); - parser = new TemplateParser( - new Parser(new Lexer()), - new MockSchemaRegistry({'invalidProp': false}, {'mappedAttr': 'mappedProp'})); - commandCompiler = new CommandCompiler(); - }); + beforeEach(inject([TemplateParser, CommandCompiler], (_templateParser, _commandCompiler) => { + parser = _templateParser; + commandCompiler = _commandCompiler; + })); function createComp({type, selector, template, encapsulation, ngContentSelectors}: { type?: TypeMetadata, @@ -87,7 +89,7 @@ export function main() { template?: string, encapsulation?: ViewEncapsulation, ngContentSelectors?: string[] - }): DirectiveMetadata { + }): NormalizedDirectiveMetadata { if (isBlank(encapsulation)) { encapsulation = ViewEncapsulation.None; } @@ -100,11 +102,11 @@ export function main() { if (isBlank(template)) { template = ''; } - return new DirectiveMetadata({ + return new NormalizedDirectiveMetadata({ selector: selector, isComponent: true, type: type, - template: new TemplateMetadata({ + template: new NormalizedTemplateMetadata({ template: template, ngContentSelectors: ngContentSelectors, encapsulation: encapsulation @@ -112,8 +114,8 @@ export function main() { }); } - function createDirective(type: TypeMetadata, selector: string): DirectiveMetadata { - return new DirectiveMetadata({selector: selector, isComponent: false, type: type}); + function createDirective(type: TypeMetadata, selector: string): NormalizedDirectiveMetadata { + return new NormalizedDirectiveMetadata({selector: selector, isComponent: false, type: type}); } @@ -229,7 +231,7 @@ export function main() { ['ACompType'], false, null, - 'AComp' + 3 ], [END_COMPONENT] ]); @@ -258,7 +260,7 @@ export function main() { ['ACompType'], false, null, - 'AComp' + 3 ], [END_COMPONENT] ]); @@ -273,7 +275,7 @@ export function main() { run(rootComp, [comp]) .then((data) => { expect(data).toEqual([ - [BEGIN_COMPONENT, 'a', [], [], [], ['ACompType'], true, null, 'AComp'], + [BEGIN_COMPONENT, 'a', [], [], [], ['ACompType'], true, null, 3], [END_COMPONENT] ]); async.done(); @@ -287,7 +289,7 @@ export function main() { run(rootComp, [comp]) .then((data) => { expect(data).toEqual([ - [BEGIN_COMPONENT, 'a', [], [], [], ['ACompType'], false, null, 'AComp'], + [BEGIN_COMPONENT, 'a', [], [], [], ['ACompType'], false, null, 3], [TEXT, 't', false, 0], [END_COMPONENT] ]); @@ -362,15 +364,15 @@ export function main() { describe('compileComponentRuntime', () => { beforeEach(() => { - componentTemplateFactory = (directiveType: TypeMetadata) => { - return new CompiledTemplate(directiveType.typeName, []); + componentTemplateFactory = (directive: NormalizedDirectiveMetadata) => { + return new CompiledTemplate(directive.type.id, () => []); }; }); - function run(component: DirectiveMetadata, directives: DirectiveMetadata[]): - Promise { - var parsedTemplate = parser.parse( - domParser.parse(component.template.template, component.type.typeName), directives); + function run(component: NormalizedDirectiveMetadata, + directives: NormalizedDirectiveMetadata[]): Promise { + var parsedTemplate = + parser.parse(component.template.template, directives, component.type.name); var commands = commandCompiler.compileComponentRuntime(component, parsedTemplate, componentTemplateFactory); return PromiseWrapper.resolve(humanize(commands)); @@ -382,19 +384,18 @@ export function main() { describe('compileComponentCodeGen', () => { beforeEach(() => { - componentTemplateFactory = (directiveType: TypeMetadata, imports: string[][]) => { - imports.push([TEMPLATE_COMMANDS_MODULE_NAME, 'tcm']); - return `new tcm.CompiledTemplate(${escapeSingleQuoteString(directiveType.typeName)}, [])`; + componentTemplateFactory = (directive: NormalizedDirectiveMetadata) => { + return `new ${TEMPLATE_COMMANDS_MODULE_REF}CompiledTemplate(${directive.type.id}, ${codeGenValueFn([], '{}')})`; }; }); - function run(component: DirectiveMetadata, directives: DirectiveMetadata[]): - Promise { - var parsedTemplate = parser.parse( - domParser.parse(component.template.template, component.type.typeName), directives); + function run(component: NormalizedDirectiveMetadata, + directives: NormalizedDirectiveMetadata[]): Promise { + var parsedTemplate = + parser.parse(component.template.template, directives, component.type.name); var sourceModule = commandCompiler.compileComponentCodeGen(component, parsedTemplate, componentTemplateFactory); - var testableModule = createTestableModule(sourceModule); + var testableModule = createTestableModule(sourceModule).getSourceWithImports(); return evalModule(testableModule.source, testableModule.imports, null); } @@ -453,6 +454,7 @@ class CommandHumanizer implements CommandVisitor { cmd.directives.map(checkAndStringifyType), cmd.nativeShadow, cmd.ngContentIndex, + // TODO humanizeTemplate(cmd.template) cmd.template.id ]); return null; @@ -475,15 +477,9 @@ class CommandHumanizer implements CommandVisitor { } } -function createTestableModule(sourceModule: SourceModule): SourceModule { - var testableSource; - var testableImports = [[MODULE_NAME, 'mocks']].concat(sourceModule.imports); - if (IS_DART) { - testableSource = `${sourceModule.source} - run(_) { return mocks.humanize(COMMANDS); }`; - } else { - testableSource = `${sourceModule.source} - exports.run = function(_) { return mocks.humanize(COMMANDS); }`; - } - return new SourceModule(null, testableSource, testableImports); +function createTestableModule(source: SourceExpression): SourceModule { + var resultExpression = `${THIS_MODULE_REF}humanize(${source.expression})`; + var testableSource = `${source.declarations.join('\n')} + ${codeGenExportVariable('run')}${codeGenValueFn(['_'], resultExpression)};`; + return new SourceModule(null, testableSource); } diff --git a/modules/angular2/test/compiler/api_spec.ts b/modules/angular2/test/compiler/directive_metadata_spec.ts similarity index 71% rename from modules/angular2/test/compiler/api_spec.ts rename to modules/angular2/test/compiler/directive_metadata_spec.ts index 5c9556ff80..db8767e75b 100644 --- a/modules/angular2/test/compiler/api_spec.ts +++ b/modules/angular2/test/compiler/directive_metadata_spec.ts @@ -13,28 +13,29 @@ import { } from 'angular2/test_lib'; import { - DirectiveMetadata, + NormalizedDirectiveMetadata, TypeMetadata, - TemplateMetadata, + NormalizedTemplateMetadata, ChangeDetectionMetadata -} from 'angular2/src/compiler/api'; +} from 'angular2/src/compiler/directive_metadata'; import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection'; export function main() { - describe('Compiler api', () => { + describe('DirectiveMetadata', () => { var fullTypeMeta: TypeMetadata; - var fullTemplateMeta: TemplateMetadata; + var fullTemplateMeta: NormalizedTemplateMetadata; var fullChangeDetectionMeta: ChangeDetectionMetadata; - var fullDirectiveMeta: DirectiveMetadata; + var fullDirectiveMeta: NormalizedDirectiveMetadata; beforeEach(() => { - fullTypeMeta = new TypeMetadata({id: 23, typeName: 'SomeType', typeUrl: 'someUrl'}); - fullTemplateMeta = new TemplateMetadata({ + fullTypeMeta = new TypeMetadata({id: 23, name: 'SomeType', moduleId: 'someUrl'}); + fullTemplateMeta = new NormalizedTemplateMetadata({ encapsulation: ViewEncapsulation.Emulated, template: '', styles: ['someStyle'], styleAbsUrls: ['someStyleUrl'], + hostAttributes: {'attr1': 'attrValue2'}, ngContentSelectors: ['*'] }); fullChangeDetectionMeta = new ChangeDetectionMetadata({ @@ -51,10 +52,10 @@ export function main() { callDoCheck: true, callOnInit: true }); - fullDirectiveMeta = new DirectiveMetadata({ + fullDirectiveMeta = new NormalizedDirectiveMetadata({ selector: 'someSelector', isComponent: true, - hostAttributes: {'attr1': 'attrValue2'}, + dynamicLoadable: true, type: fullTypeMeta, template: fullTemplateMeta, changeDetection: fullChangeDetectionMeta, }); @@ -63,12 +64,13 @@ export function main() { describe('DirectiveMetadata', () => { it('should serialize with full data', () => { - expect(DirectiveMetadata.fromJson(fullDirectiveMeta.toJson())).toEqual(fullDirectiveMeta); + expect(NormalizedDirectiveMetadata.fromJson(fullDirectiveMeta.toJson())) + .toEqual(fullDirectiveMeta); }); it('should serialize with no data', () => { - var empty = new DirectiveMetadata(); - expect(DirectiveMetadata.fromJson(empty.toJson())).toEqual(empty); + var empty = new NormalizedDirectiveMetadata(); + expect(NormalizedDirectiveMetadata.fromJson(empty.toJson())).toEqual(empty); }); }); @@ -84,12 +86,13 @@ export function main() { describe('TemplateMetadata', () => { it('should serialize with full data', () => { - expect(TemplateMetadata.fromJson(fullTemplateMeta.toJson())).toEqual(fullTemplateMeta); + expect(NormalizedTemplateMetadata.fromJson(fullTemplateMeta.toJson())) + .toEqual(fullTemplateMeta); }); it('should serialize with no data', () => { - var empty = new TemplateMetadata(); - expect(TemplateMetadata.fromJson(empty.toJson())).toEqual(empty); + var empty = new NormalizedTemplateMetadata(); + expect(NormalizedTemplateMetadata.fromJson(empty.toJson())).toEqual(empty); }); }); diff --git a/modules/angular2/test/compiler/eval_module.dart b/modules/angular2/test/compiler/eval_module.dart index d2bf8b920d..35e51ce59f 100644 --- a/modules/angular2/test/compiler/eval_module.dart +++ b/modules/angular2/test/compiler/eval_module.dart @@ -7,31 +7,27 @@ Uri toDartDataUri(String source) { } createIsolateSource(String moduleSource, List> moduleImports) { - var moduleSourceParts = []; + var moduleSourceParts = ['import "dart:isolate";']; moduleImports.forEach((sourceImport) { String modName = sourceImport[0]; String modAlias = sourceImport[1]; moduleSourceParts.add("import 'package:${modName}.dart' as ${modAlias};"); }); - moduleSourceParts.add(moduleSource); - - return """ -import "dart:isolate"; -import "${toDartDataUri(moduleSourceParts.join('\n'))}" as mut; - + moduleSourceParts.add(moduleSource); + moduleSourceParts.add(""" main(List args, SendPort replyPort) { - replyPort.send(mut.run(args)); + replyPort.send(run(args)); +} +"""); + return moduleSourceParts.join('\n'); } -"""; - -} var timeStamp = new DateTime.now().millisecondsSinceEpoch; dynamic callModule(dynamic data) { return data.map( (a) => a+1); } -evalModule(String moduleSource, List> moduleImports, List args) { - String source = createIsolateSource(moduleSource, moduleImports); +evalModule(String moduleSource, List> imports, List args) { + String source = createIsolateSource(moduleSource, imports); Completer completer = new Completer(); RawReceivePort receivePort; receivePort = new RawReceivePort( (message) { @@ -43,12 +39,12 @@ evalModule(String moduleSource, List> moduleImports, List args) { // With this, spawning multiple isolates gets faster as Darts does not // reload the files from the server. var packageRoot = Uri.parse('/packages_${timeStamp}'); - return Isolate.spawnUri(toDartDataUri(source), args, receivePort.sendPort, packageRoot: packageRoot).then( (isolate) { + return Isolate.spawnUri(toDartDataUri(source), args, receivePort.sendPort, packageRoot: packageRoot).then( (isolate) { RawReceivePort errorPort; errorPort = new RawReceivePort( (message) { completer.completeError(message); }); isolate.addErrorListener(errorPort.sendPort); - return completer.future; + return completer.future; }); } diff --git a/modules/angular2/test/compiler/eval_module.ts b/modules/angular2/test/compiler/eval_module.ts index 98bda86bb5..e514377751 100644 --- a/modules/angular2/test/compiler/eval_module.ts +++ b/modules/angular2/test/compiler/eval_module.ts @@ -1,18 +1,17 @@ import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async'; -import {isPresent, global} from 'angular2/src/core/facade/lang'; +import {isPresent, global, StringWrapper} from 'angular2/src/core/facade/lang'; var evalCounter = 0; -function nextModuleName() { +function nextModuleId() { return `evalScript${evalCounter++}`; } -export function evalModule(moduleSource: string, moduleImports: string[][], args: any[]): - Promise { - var moduleName = nextModuleName(); +export function evalModule(moduleSource: string, imports: string[][], args: any[]): Promise { + var moduleId = nextModuleId(); var moduleSourceWithImports = []; var importModuleNames = []; - moduleImports.forEach(sourceImport => { + imports.forEach(sourceImport => { var modName = sourceImport[0]; var modAlias = sourceImport[1]; importModuleNames.push(modName); @@ -28,8 +27,8 @@ export function evalModule(moduleSource: string, moduleImports: string[][], args var moduleBody = new Function('require', 'exports', 'module', moduleSourceWithImports.join('\n')); var System = global['System']; if (isPresent(System) && isPresent(System.registerDynamic)) { - System.registerDynamic(moduleName, importModuleNames, false, moduleBody); - return >System.import(moduleName).then((module) => module.run(args)); + System.registerDynamic(moduleId, importModuleNames, false, moduleBody); + return >System.import(moduleId).then((module) => module.run(args)); } else { var exports = {}; moduleBody(require, exports, {}); diff --git a/modules/angular2/test/compiler/eval_module_spec.ts b/modules/angular2/test/compiler/eval_module_spec.ts index 902d3386df..223ab39567 100644 --- a/modules/angular2/test/compiler/eval_module_spec.ts +++ b/modules/angular2/test/compiler/eval_module_spec.ts @@ -19,14 +19,14 @@ import {evalModule} from './eval_module'; // when evaling the test module! export var TEST_VALUE = 23; +const THIS_MODULE = 'angular2/test/compiler/eval_module_spec'; + export function main() { describe('evalModule', () => { it('should call the "run" function and allow to use imports', inject([AsyncTestCompleter], (async) => { var moduleSource = IS_DART ? testDartModule : testJsModule; - var imports = [['angular2/test/compiler/eval_module_spec', 'testMod']]; - - evalModule(moduleSource, imports, [1]) + evalModule(moduleSource, [[THIS_MODULE, 'tst']], [1]) .then((value) => { expect(value).toEqual([1, 23]); async.done(); @@ -36,15 +36,15 @@ export function main() { } var testDartModule = ` - run(data) { - data.add(testMod.TEST_VALUE); + run(data) { + data.add(tst.TEST_VALUE); return data; } `; var testJsModule = ` exports.run = function(data) { - data.push(testMod.TEST_VALUE); + data.push(tst.TEST_VALUE); return data; } `; \ No newline at end of file diff --git a/modules/angular2/test/compiler/runtime_metadata_spec.ts b/modules/angular2/test/compiler/runtime_metadata_spec.ts new file mode 100644 index 0000000000..e802956812 --- /dev/null +++ b/modules/angular2/test/compiler/runtime_metadata_spec.ts @@ -0,0 +1,131 @@ +import { + ddescribe, + describe, + xdescribe, + it, + iit, + xit, + expect, + beforeEach, + afterEach, + AsyncTestCompleter, + inject, + beforeEachBindings +} from 'angular2/test_lib'; + +import {stringify} from 'angular2/src/core/facade/lang'; +import {RuntimeMetadataResolver} from 'angular2/src/compiler/runtime_metadata'; +import { + Component, + View, + Directive, + ViewEncapsulation, + ChangeDetectionStrategy, + OnChanges, + OnInit, + DoCheck, + OnDestroy, + AfterContentInit, + AfterContentChecked, + AfterViewInit, + AfterViewChecked +} from 'angular2/core'; + +import {TEST_BINDINGS} from './test_bindings'; +import {IS_DART} from '../platform'; + +export function main() { + describe('RuntimeMetadataResolver', () => { + beforeEachBindings(() => TEST_BINDINGS); + + describe('getMetadata', () => { + it('should read metadata', + inject([RuntimeMetadataResolver], (resolver: RuntimeMetadataResolver) => { + var meta = resolver.getMetadata(ComponentWithEverything); + expect(meta.selector).toEqual('someSelector'); + expect(meta.isComponent).toBe(true); + expect(meta.dynamicLoadable).toBe(true); + expect(meta.type.runtime).toBe(ComponentWithEverything); + expect(meta.type.name).toEqual(stringify(ComponentWithEverything)); + expect(meta.type.moduleId).toEqual('someModuleId'); + expect(meta.changeDetection.callAfterContentChecked).toBe(true); + expect(meta.changeDetection.callAfterContentInit).toBe(true); + expect(meta.changeDetection.callAfterViewChecked).toBe(true); + expect(meta.changeDetection.callAfterViewInit).toBe(true); + expect(meta.changeDetection.callDoCheck).toBe(true); + expect(meta.changeDetection.callOnChanges).toBe(true); + expect(meta.changeDetection.callOnInit).toBe(true); + expect(meta.changeDetection.changeDetection).toBe(ChangeDetectionStrategy.CheckAlways); + expect(meta.changeDetection.properties).toEqual(['someProp']); + expect(meta.changeDetection.events).toEqual(['someEvent']); + expect(meta.changeDetection.hostListeners) + .toEqual({'someHostListener': 'someHostListenerExpr'}); + expect(meta.changeDetection.hostProperties) + .toEqual({'someHostProp': 'someHostPropExpr'}); + expect(meta.template.encapsulation).toBe(ViewEncapsulation.Emulated); + expect(meta.template.hostAttributes).toEqual({'someHostAttr': 'someHostAttrValue'}); + expect(meta.template.styles).toEqual(['someStyle']); + expect(meta.template.styleUrls).toEqual(['someStyleUrl']); + expect(meta.template.template).toEqual('someTemplate'); + expect(meta.template.templateUrl).toEqual('someTemplateUrl'); + })); + + it('should use the moduleId from the reflector if none is given', + inject([RuntimeMetadataResolver], (resolver: RuntimeMetadataResolver) => { + var expectedValue = IS_DART ? 'angular2/test/compiler/runtime_metadata_spec' : null; + expect(resolver.getMetadata(DirectiveWithoutModuleId).type.moduleId) + .toEqual(expectedValue); + })); + }); + + describe('getViewDirectivesMetadata', () => { + + it('should return the directive metadatas', + inject([RuntimeMetadataResolver], (resolver: RuntimeMetadataResolver) => { + expect(resolver.getViewDirectivesMetadata(ComponentWithEverything)) + .toEqual([resolver.getMetadata(DirectiveWithoutModuleId)]); + })); + }); + + }); +} + + + +@Directive({selector: 'someSelector'}) +class DirectiveWithoutModuleId { +} + +@Component({ + selector: 'someSelector', + properties: ['someProp'], + events: ['someEvent'], + host: { + '[someHostProp]': 'someHostPropExpr', + '(someHostListener)': 'someHostListenerExpr', + 'someHostAttr': 'someHostAttrValue' + }, + dynamicLoadable: true, + moduleId: 'someModuleId', + changeDetection: ChangeDetectionStrategy.CheckAlways +}) +@View({ + template: 'someTemplate', + templateUrl: 'someTemplateUrl', + encapsulation: ViewEncapsulation.Emulated, + styles: ['someStyle'], + styleUrls: ['someStyleUrl'], + directives: [DirectiveWithoutModuleId] +}) +class ComponentWithEverything implements OnChanges, + OnInit, DoCheck, OnDestroy, AfterContentInit, AfterContentChecked, AfterViewInit, + AfterViewChecked { + onChanges(changes: StringMap): void {} + onInit(): void {} + doCheck(): void {} + onDestroy(): void {} + afterContentInit(): void {} + afterContentChecked(): void {} + afterViewInit(): void {} + afterViewChecked(): void {} +} diff --git a/modules/angular2/test/compiler/schema_registry_mock.ts b/modules/angular2/test/compiler/schema_registry_mock.ts new file mode 100644 index 0000000000..ef661f67dc --- /dev/null +++ b/modules/angular2/test/compiler/schema_registry_mock.ts @@ -0,0 +1,17 @@ +import {ElementSchemaRegistry} from 'angular2/src/core/render/dom/schema/element_schema_registry'; +import {StringMap} from 'angular2/src/core/facade/collection'; +import {isPresent} from 'angular2/src/core/facade/lang'; + +export class MockSchemaRegistry implements ElementSchemaRegistry { + constructor(public existingProperties: StringMap, + public attrPropMapping: StringMap) {} + hasProperty(tagName: string, property: string): boolean { + var result = this.existingProperties[property]; + return isPresent(result) ? result : true; + } + + getMappedPropName(attrName: string): string { + var result = this.attrPropMapping[attrName]; + return isPresent(result) ? result : attrName; + } +} \ No newline at end of file diff --git a/modules/angular2/test/compiler/source_module_spec.ts b/modules/angular2/test/compiler/source_module_spec.ts new file mode 100644 index 0000000000..11ac472969 --- /dev/null +++ b/modules/angular2/test/compiler/source_module_spec.ts @@ -0,0 +1,43 @@ +import { + AsyncTestCompleter, + beforeEach, + ddescribe, + describe, + el, + expect, + iit, + inject, + it, + xit, + TestComponentBuilder +} from 'angular2/test_lib'; + +import {SourceModule, moduleRef} from 'angular2/src/compiler/source_module'; + +export function main() { + describe('SourceModule', () => { + describe('getSourceWithImports', () => { + it('should generate named imports for modules', () => { + var sourceWithImports = + new SourceModule('some/moda', `${moduleRef('some/modb')}A`).getSourceWithImports(); + expect(sourceWithImports.source).toEqual('import0.A'); + expect(sourceWithImports.imports).toEqual([['some/modb', 'import0']]); + }); + + it('should dedupe imports', () => { + var sourceWithImports = + new SourceModule('some/moda', `${moduleRef('some/modb')}A + ${moduleRef('some/modb')}B`) + .getSourceWithImports(); + expect(sourceWithImports.source).toEqual('import0.A + import0.B'); + expect(sourceWithImports.imports).toEqual([['some/modb', 'import0']]); + }); + + it('should not use an import for the moduleId of the SourceModule', () => { + var sourceWithImports = + new SourceModule('some/moda', `${moduleRef('some/moda')}A`).getSourceWithImports(); + expect(sourceWithImports.source).toEqual('A'); + expect(sourceWithImports.imports).toEqual([]); + }); + }); + }); +} diff --git a/modules/angular2/test/compiler/style_compiler_spec.ts b/modules/angular2/test/compiler/style_compiler_spec.ts index d961f2ef1d..943b4c2a4f 100644 --- a/modules/angular2/test/compiler/style_compiler_spec.ts +++ b/modules/angular2/test/compiler/style_compiler_spec.ts @@ -9,19 +9,27 @@ import { beforeEach, afterEach, AsyncTestCompleter, - inject + inject, + beforeEachBindings } from 'angular2/test_lib'; -import {IS_DART} from '../platform'; +import {bind} from 'angular2/src/core/di'; import {SpyXHR} from '../core/spies'; +import {XHR} from 'angular2/src/core/render/xhr'; import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions'; import {CONST_EXPR, isPresent, StringWrapper} from 'angular2/src/core/facade/lang'; import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async'; import {evalModule} from './eval_module'; import {StyleCompiler} from 'angular2/src/compiler/style_compiler'; -import {UrlResolver} from 'angular2/src/core/services/url_resolver'; -import {DirectiveMetadata, TemplateMetadata, TypeMetadata} from 'angular2/src/compiler/api'; +import { + NormalizedDirectiveMetadata, + NormalizedTemplateMetadata, + TypeMetadata +} from 'angular2/src/compiler/directive_metadata'; +import {SourceExpression, SourceModule} from 'angular2/src/compiler/source_module'; import {ViewEncapsulation} from 'angular2/src/core/render/api'; +import {TEST_BINDINGS} from './test_bindings'; +import {codeGenValueFn, codeGenExportVariable} from 'angular2/src/compiler/util'; // Attention: These module names have to correspond to real modules! const MODULE_NAME = 'angular2/test/compiler/style_compiler_spec'; @@ -33,19 +41,21 @@ const IMPORT_ABS_MODULE_NAME_WITH_IMPORT = export function main() { describe('StyleCompiler', () => { - var compiler: StyleCompiler; - var xhr; - - beforeEach(() => { + var xhr: SpyXHR; + beforeEachBindings(() => { xhr = new SpyXHR(); - compiler = new StyleCompiler(xhr, new UrlResolver()); + return [TEST_BINDINGS, bind(XHR).toValue(xhr)]; }); + var compiler: StyleCompiler; + + beforeEach(inject([StyleCompiler], (_compiler) => { compiler = _compiler; })); + function comp(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation): - DirectiveMetadata { - return new DirectiveMetadata({ - type: new TypeMetadata({id: 23, typeUrl: 'someUrl'}), - template: new TemplateMetadata( + NormalizedDirectiveMetadata { + return new NormalizedDirectiveMetadata({ + type: new TypeMetadata({id: 23, moduleId: 'someUrl'}), + template: new NormalizedTemplateMetadata( {styles: styles, styleAbsUrls: styleAbsUrls, encapsulation: encapsulation}) }); } @@ -117,9 +127,10 @@ export function main() { function runTest(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation, expectedStyles: string[]) { return inject([AsyncTestCompleter], (async) => { - var sourceModule = + var sourceExpression = compiler.compileComponentCodeGen(comp(styles, styleAbsUrls, encapsulation)); - evalModule(testableModule(sourceModule.source), sourceModule.imports, null) + var sourceWithImports = testableExpression(sourceExpression).getSourceWithImports(); + evalModule(sourceWithImports.source, sourceWithImports.imports, null) .then((value) => { compareStyles(value, expectedStyles); async.done(); @@ -163,9 +174,12 @@ export function main() { function runTest(style: string, expectedStyles: string[], expectedShimStyles: string[]) { return inject([AsyncTestCompleter], (async) => { var sourceModules = compiler.compileStylesheetCodeGen(MODULE_NAME, style); - PromiseWrapper.all(sourceModules.map(sourceModule => - evalModule(testableModule(sourceModule.source), - sourceModule.imports, null))) + PromiseWrapper.all(sourceModules.map(sourceModule => { + var sourceWithImports = + testableModule(sourceModule).getSourceWithImports(); + return evalModule(sourceWithImports.source, sourceWithImports.imports, + null); + })) .then((values) => { compareStyles(values[0], expectedStyles); compareStyles(values[1], expectedShimStyles); @@ -188,16 +202,17 @@ export function main() { }); } -function testableModule(sourceModule: string) { - if (IS_DART) { - return `${sourceModule} - run(_) { return STYLES; } -`; - } else { - return `${sourceModule} - exports.run = function(_) { return STYLES; }; -`; - } + +function testableExpression(source: SourceExpression): SourceModule { + var testableSource = `${source.declarations.join('\n')} + ${codeGenExportVariable('run')}${codeGenValueFn(['_'], source.expression)};`; + return new SourceModule(null, testableSource); +} + +function testableModule(sourceModule: SourceModule): SourceModule { + var testableSource = `${sourceModule.source} + ${codeGenExportVariable('run')}${codeGenValueFn(['_'], 'STYLES')};`; + return new SourceModule(sourceModule.moduleId, testableSource); } // Needed for Android browsers which add an extra space at the end of some lines diff --git a/modules/angular2/test/compiler/template_compiler_spec.ts b/modules/angular2/test/compiler/template_compiler_spec.ts new file mode 100644 index 0000000000..200be384f4 --- /dev/null +++ b/modules/angular2/test/compiler/template_compiler_spec.ts @@ -0,0 +1,334 @@ +import { + ddescribe, + describe, + xdescribe, + it, + iit, + xit, + expect, + beforeEach, + afterEach, + AsyncTestCompleter, + inject, + beforeEachBindings +} from 'angular2/test_lib'; + +import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async'; +import {Type, isPresent, isBlank, stringify, isString} from 'angular2/src/core/facade/lang'; +import {MapWrapper, SetWrapper, ListWrapper} from 'angular2/src/core/facade/collection'; +import {RuntimeMetadataResolver} from 'angular2/src/compiler/runtime_metadata'; +import { + TemplateCompiler, + NormalizedComponentWithViewDirectives +} from 'angular2/src/compiler/template_compiler'; +import { + DirectiveMetadata, + NormalizedDirectiveMetadata, + INormalizedDirectiveMetadata +} from 'angular2/src/compiler/directive_metadata'; +import {evalModule} from './eval_module'; +import {SourceModule, moduleRef} from 'angular2/src/compiler/source_module'; +import {XHR} from 'angular2/src/core/render/xhr'; +import {MockXHR} from 'angular2/src/core/render/xhr_mock'; + +import {Locals} from 'angular2/src/core/change_detection/change_detection'; + +import { + CommandVisitor, + TextCmd, + NgContentCmd, + BeginElementCmd, + BeginComponentCmd, + EmbeddedTemplateCmd, + TemplateCmd, + visitAllCommands, + CompiledTemplate +} from 'angular2/src/core/compiler/template_commands'; + +import {Component, View, Directive} from 'angular2/core'; + +import {TEST_BINDINGS} from './test_bindings'; +import {TestContext, TestDispatcher, TestPipes} from './change_detector_mocks'; +import {codeGenValueFn, codeGenExportVariable} from 'angular2/src/compiler/util'; + +// Attention: This path has to point to this test file! +const THIS_MODULE = 'angular2/test/compiler/template_compiler_spec'; +var THIS_MODULE_REF = moduleRef(THIS_MODULE); + +export function main() { + describe('TemplateCompiler', () => { + var compiler: TemplateCompiler; + var runtimeMetadataResolver: RuntimeMetadataResolver; + + beforeEachBindings(() => TEST_BINDINGS); + beforeEach(inject([TemplateCompiler, RuntimeMetadataResolver], + (_compiler, _runtimeMetadataResolver) => { + compiler = _compiler; + runtimeMetadataResolver = _runtimeMetadataResolver; + })); + + describe('compile templates', () => { + + function runTests(compile) { + it('should compile host components', inject([AsyncTestCompleter], (async) => { + compile([CompWithBindingsAndStyles]) + .then((humanizedTemplate) => { + expect(humanizedTemplate['styles']).toEqual([]); + expect(humanizedTemplate['commands'][0]).toEqual(''); + expect(humanizedTemplate['cd']).toEqual(['elementProperty(title)=someDirValue']); + + async.done(); + }); + })); + + it('should compile nested components', inject([AsyncTestCompleter], (async) => { + compile([CompWithBindingsAndStyles]) + .then((humanizedTemplate) => { + var nestedTemplate = humanizedTemplate['commands'][1]; + expect(nestedTemplate['styles']).toEqual(['div {color: red}']); + expect(nestedTemplate['commands'][0]).toEqual(''); + expect(nestedTemplate['cd']).toEqual(['elementProperty(href)=someCtxValue']); + + async.done(); + }); + })); + + it('should compile recursive components', inject([AsyncTestCompleter], (async) => { + compile([TreeComp]) + .then((humanizedTemplate) => { + expect(humanizedTemplate['commands'][0]).toEqual(''); + expect(humanizedTemplate['commands'][1]['commands'][0]).toEqual(''); + expect(humanizedTemplate['commands'][1]['commands'][1]['commands'][0]) + .toEqual(''); + + async.done(); + }); + })); + } + + describe('compileHostComponentRuntime', () => { + function compile(components: Type[]): Promise { + return compiler.compileHostComponentRuntime(components[0]).then(humanizeTemplate); + } + + runTests(compile); + + it('should cache components', inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => { + // we expect only one request! + xhr.expect('angular2/test/compiler/compUrl.html', ''); + PromiseWrapper.all([ + compiler.compileHostComponentRuntime(CompWithTemplateUrl), + compiler.compileHostComponentRuntime(CompWithTemplateUrl) + ]) + .then((humanizedTemplates) => { + expect(humanizedTemplates[0]).toEqual(humanizedTemplates[1]); + async.done(); + }); + xhr.flush(); + })); + + it('should only allow dynamic loadable components', () => { + expect(() => compiler.compileHostComponentRuntime(PlainDirective)) + .toThrowError( + `Could not compile '${stringify(PlainDirective)}' because it is not dynamically loadable.`); + expect(() => compiler.compileHostComponentRuntime(CompWithoutHost)) + .toThrowError( + `Could not compile '${stringify(CompWithoutHost)}' because it is not dynamically loadable.`); + }); + + }); + + describe('compileTemplatesCodeGen', () => { + function normalizeComponent(component: Type): + Promise { + var compAndViewDirMetas = [runtimeMetadataResolver.getMetadata(component)].concat( + runtimeMetadataResolver.getViewDirectivesMetadata(component)); + return PromiseWrapper.all(compAndViewDirMetas.map(meta => + compiler.normalizeDirective(meta))) + .then((normalizedCompAndViewDirMetas: NormalizedDirectiveMetadata[]) => + new NormalizedComponentWithViewDirectives( + normalizedCompAndViewDirMetas[0], + normalizedCompAndViewDirMetas.slice(1))); + } + + function compile(components: Type[]): Promise { + return PromiseWrapper.all(components.map(normalizeComponent)) + .then((normalizedCompWithViewDirMetas: NormalizedComponentWithViewDirectives[]) => { + var sourceModule = + compiler.compileTemplatesCodeGen(THIS_MODULE, normalizedCompWithViewDirMetas); + var sourceWithImports = + testableTemplateModule(sourceModule, + normalizedCompWithViewDirMetas[0].component) + .getSourceWithImports(); + return evalModule(sourceWithImports.source, sourceWithImports.imports, null); + }); + } + + runTests(compile); + }); + + }); + + describe('serializeTemplateMetadata and deserializeTemplateMetadata', () => { + it('should serialize and deserialize', inject([AsyncTestCompleter], (async) => { + compiler.normalizeDirective( + runtimeMetadataResolver.getMetadata(CompWithBindingsAndStyles)) + .then((meta: NormalizedDirectiveMetadata) => { + var json = compiler.serializeTemplateMetadata(meta); + expect(isString(json)).toBe(true); + // Note: serializing will clear our the runtime type! + var clonedMeta = + compiler.deserializeTemplateMetadata(json); + expect(meta.changeDetection).toEqual(clonedMeta.changeDetection); + expect(meta.template).toEqual(clonedMeta.template); + expect(meta.selector).toEqual(clonedMeta.selector); + expect(meta.type.name).toEqual(clonedMeta.type.name); + async.done(); + }); + })); + }); + + describe('normalizeDirective', () => { + it('should normalize the template', + inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => { + xhr.expect('angular2/test/compiler/compUrl.html', 'loadedTemplate'); + compiler.normalizeDirective(runtimeMetadataResolver.getMetadata(CompWithTemplateUrl)) + .then((meta: NormalizedDirectiveMetadata) => { + expect(meta.template.template).toEqual('loadedTemplate'); + async.done(); + }); + xhr.flush(); + })); + + it('should copy all the other fields', inject([AsyncTestCompleter], (async) => { + var meta = runtimeMetadataResolver.getMetadata(CompWithBindingsAndStyles); + compiler.normalizeDirective(meta).then((normMeta: NormalizedDirectiveMetadata) => { + expect(normMeta.selector).toEqual(meta.selector); + expect(normMeta.dynamicLoadable).toEqual(meta.dynamicLoadable); + expect(normMeta.isComponent).toEqual(meta.isComponent); + expect(normMeta.type).toEqual(meta.type); + expect(normMeta.changeDetection).toEqual(meta.changeDetection); + async.done(); + }); + })); + }); + + describe('compileStylesheetCodeGen', () => { + it('should compile stylesheets into code', inject([AsyncTestCompleter], (async) => { + var cssText = 'div {color: red}'; + var sourceModule = compiler.compileStylesheetCodeGen('someModuleId', cssText)[0]; + var sourceWithImports = testableStylesModule(sourceModule).getSourceWithImports(); + evalModule(sourceWithImports.source, sourceWithImports.imports, null) + .then(loadedCssText => { + expect(loadedCssText).toEqual([cssText]); + async.done(); + }); + + })); + }); + }); +} + +@Component({ + selector: 'comp-a', + dynamicLoadable: true, + host: {'[title]': 'someProp'}, + moduleId: THIS_MODULE +}) +@View({template: '', styles: ['div {color: red}']}) +class CompWithBindingsAndStyles { +} + +@Component({selector: 'tree', dynamicLoadable: true, moduleId: THIS_MODULE}) +@View({template: '', directives: [TreeComp]}) +class TreeComp { +} + +@Component({selector: 'comp-url', dynamicLoadable: true, moduleId: THIS_MODULE}) +@View({templateUrl: 'compUrl.html'}) +class CompWithTemplateUrl { +} + +@Directive({selector: 'plain', moduleId: THIS_MODULE}) +class PlainDirective { +} + +@Component({selector: 'comp', moduleId: THIS_MODULE}) +@View({template: ''}) +class CompWithoutHost { +} + +function testableTemplateModule(sourceModule: SourceModule, comp: INormalizedDirectiveMetadata): + SourceModule { + var normComp = comp; + var resultExpression = `${THIS_MODULE_REF}humanizeTemplate(Host${normComp.type.name}Template)`; + var testableSource = `${sourceModule.source} + ${codeGenExportVariable('run')}${codeGenValueFn(['_'], resultExpression)};`; + return new SourceModule(sourceModule.moduleId, testableSource); +} + +function testableStylesModule(sourceModule: SourceModule): SourceModule { + var testableSource = `${sourceModule.source} + ${codeGenExportVariable('run')}${codeGenValueFn(['_'], 'STYLES')};`; + return new SourceModule(sourceModule.moduleId, testableSource); +} + +// Attention: read by eval! +export function humanizeTemplate(template: CompiledTemplate, + humanizedTemplates: Map> = null): + StringMap { + if (isBlank(humanizedTemplates)) { + humanizedTemplates = new Map(); + } + var result = humanizedTemplates.get(template.id); + if (isPresent(result)) { + return result; + } + var commands = []; + result = { + 'styles': template.styles, + 'commands': commands, + 'cd': testChangeDetector(template.changeDetectorFactories[0]) + }; + humanizedTemplates.set(template.id, result); + visitAllCommands(new CommandHumanizer(commands, humanizedTemplates), template.commands); + return result; +} + + +function testChangeDetector(changeDetectorFactory: Function): string[] { + var ctx = new TestContext(); + ctx.someProp = 'someCtxValue'; + var dir1 = new TestContext(); + dir1.someProp = 'someDirValue'; + + var dispatcher = new TestDispatcher([dir1], []); + var cd = changeDetectorFactory(dispatcher); + var locals = new Locals(null, MapWrapper.createFromStringMap({'someVar': null})); + cd.hydrate(ctx, locals, dispatcher, new TestPipes()); + cd.detectChanges(); + return dispatcher.log; +} + + +class CommandHumanizer implements CommandVisitor { + constructor(private result: any[], + private humanizedTemplates: Map>) {} + visitText(cmd: TextCmd, context: any): any { return null; } + visitNgContent(cmd: NgContentCmd, context: any): any { return null; } + visitBeginElement(cmd: BeginElementCmd, context: any): any { + this.result.push(`<${cmd.name}>`); + return null; + } + visitEndElement(context: any): any { + this.result.push(''); + return null; + } + visitBeginComponent(cmd: BeginComponentCmd, context: any): any { + this.result.push(`<${cmd.name}>`); + this.result.push(humanizeTemplate(cmd.template, this.humanizedTemplates)); + return null; + } + visitEndComponent(context: any): any { return this.visitEndElement(context); } + visitEmbeddedTemplate(cmd: EmbeddedTemplateCmd, context: any): any { return null; } +} diff --git a/modules/angular2/test/compiler/template_loader_spec.ts b/modules/angular2/test/compiler/template_loader_spec.ts deleted file mode 100644 index 94a648e01d..0000000000 --- a/modules/angular2/test/compiler/template_loader_spec.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { - AsyncTestCompleter, - beforeEach, - ddescribe, - describe, - el, - expect, - iit, - inject, - it, - xit, - TestComponentBuilder -} from 'angular2/test_lib'; - -import {HtmlParser} from 'angular2/src/compiler/html_parser'; -import {TypeMetadata, TemplateMetadata} from 'angular2/src/compiler/api'; -import {ViewEncapsulation} from 'angular2/src/core/render/api'; - -import {TemplateLoader} from 'angular2/src/compiler/template_loader'; -import {UrlResolver} from 'angular2/src/core/services/url_resolver'; -import {XHR} from 'angular2/src/core/render/xhr'; -import {MockXHR} from 'angular2/src/core/render/xhr_mock'; - -export function main() { - describe('TemplateLoader', () => { - var loader: TemplateLoader; - var dirType: TypeMetadata; - var xhr: MockXHR; - var htmlParser: HtmlParser; - - beforeEach(inject([XHR], (mockXhr) => { - xhr = mockXhr; - htmlParser = new HtmlParser(); - loader = new TemplateLoader(xhr, new UrlResolver(), htmlParser); - dirType = new TypeMetadata({typeUrl: 'http://sometypeurl', typeName: 'SomeComp'}); - })); - - describe('loadTemplate', () => { - describe('inline template', () => { - it('should store the template', inject([AsyncTestCompleter], (async) => { - loader.loadTemplate(dirType, null, 'a', null, [], ['test.css']) - .then((template: TemplateMetadata) => { - expect(template.template).toEqual('a'); - async.done(); - }); - })); - - it('should resolve styles against the typeUrl', inject([AsyncTestCompleter], (async) => { - loader.loadTemplate(dirType, null, 'a', null, [], ['test.css']) - .then((template: TemplateMetadata) => { - expect(template.styleAbsUrls).toEqual(['http://sometypeurl/test.css']); - async.done(); - }); - })); - }); - - describe('templateUrl', () => { - - it('should load a template from a url that is resolved against typeUrl', - inject([AsyncTestCompleter], (async) => { - xhr.expect('http://sometypeurl/sometplurl', 'a'); - loader.loadTemplate(dirType, null, null, 'sometplurl', [], ['test.css']) - .then((template: TemplateMetadata) => { - expect(template.template).toEqual('a'); - async.done(); - }); - xhr.flush(); - })); - - it('should resolve styles against the templateUrl', - inject([AsyncTestCompleter], (async) => { - xhr.expect('http://sometypeurl/tpl/sometplurl', 'a'); - loader.loadTemplate(dirType, null, null, 'tpl/sometplurl', [], ['test.css']) - .then((template: TemplateMetadata) => { - expect(template.styleAbsUrls).toEqual(['http://sometypeurl/tpl/test.css']); - async.done(); - }); - xhr.flush(); - })); - - }); - - }); - - describe('loadTemplateFromString', () => { - it('should store the viewEncapsulationin the result', () => { - var viewEncapsulation = ViewEncapsulation.Native; - var template = loader.createTemplateFromString(dirType, viewEncapsulation, '', - 'http://someurl/', [], []); - expect(template.encapsulation).toBe(viewEncapsulation); - }); - - it('should keep the template as html', () => { - var template = - loader.createTemplateFromString(dirType, null, 'a', 'http://someurl/', [], []); - expect(template.template).toEqual('a') - }); - - it('should collect and keep ngContent', () => { - var template = loader.createTemplateFromString( - dirType, null, '', 'http://someurl/', [], []); - expect(template.ngContentSelectors).toEqual(['a']); - expect(template.template).toEqual(''); - }); - - it('should normalize ngContent wildcard selector', () => { - var template = loader.createTemplateFromString( - dirType, null, - '', - 'http://someurl/', [], []); - expect(template.ngContentSelectors).toEqual(['*', '*', '*']); - }); - - it('should collect and remove top level styles in the template', () => { - var template = loader.createTemplateFromString(dirType, null, '', - 'http://someurl/', [], []); - expect(template.styles).toEqual(['a']); - expect(template.template).toEqual(''); - }); - - it('should collect and remove styles inside in elements', () => { - var template = loader.createTemplateFromString(dirType, null, '
', - 'http://someurl/', [], []); - expect(template.styles).toEqual(['a']); - expect(template.template).toEqual('
'); - }); - - it('should collect and remove styleUrls in the template', () => { - var template = loader.createTemplateFromString( - dirType, null, '', 'http://someurl/', [], []); - expect(template.styleAbsUrls).toEqual(['http://someurl/aUrl']); - expect(template.template).toEqual(''); - }); - - it('should collect and remove styleUrls in elements', () => { - var template = loader.createTemplateFromString( - dirType, null, '
', 'http://someurl/', [], - []); - expect(template.styleAbsUrls).toEqual(['http://someurl/aUrl']); - expect(template.template).toEqual('
'); - }); - - it('should keep link elements with non stylesheet rel attribute', () => { - var template = loader.createTemplateFromString( - dirType, null, '', 'http://someurl/', [], []); - expect(template.styleAbsUrls).toEqual([]); - expect(template.template).toEqual(''); - }); - - it('should extract @import style urls into styleAbsUrl', () => { - var template = loader.createTemplateFromString(dirType, null, '', 'http://someurl', - ['@import "test.css";'], []); - expect(template.styles).toEqual(['']); - expect(template.styleAbsUrls).toEqual(['http://someurl/test.css']); - }); - - it('should resolve relative urls in inline styles', () => { - var template = - loader.createTemplateFromString(dirType, null, '', 'http://someurl', - ['.foo{background-image: url(\'double.jpg\');'], []); - expect(template.styles) - .toEqual(['.foo{background-image: url(\'http://someurl/double.jpg\');']); - }); - - it('should resolve relative style urls in styleUrls', () => { - var template = - loader.createTemplateFromString(dirType, null, '', 'http://someurl', [], ['test.css']); - expect(template.styles).toEqual([]); - expect(template.styleAbsUrls).toEqual(['http://someurl/test.css']); - }); - - }); - - - }); -} diff --git a/modules/angular2/test/compiler/template_normalizer_spec.ts b/modules/angular2/test/compiler/template_normalizer_spec.ts new file mode 100644 index 0000000000..66a22a2f0a --- /dev/null +++ b/modules/angular2/test/compiler/template_normalizer_spec.ts @@ -0,0 +1,268 @@ +import { + AsyncTestCompleter, + beforeEach, + ddescribe, + describe, + el, + expect, + iit, + inject, + it, + xit, + TestComponentBuilder, + beforeEachBindings +} from 'angular2/test_lib'; + +import { + TypeMetadata, + NormalizedTemplateMetadata, + TemplateMetadata +} from 'angular2/src/compiler/directive_metadata'; +import {ViewEncapsulation} from 'angular2/src/core/render/api'; + +import {TemplateNormalizer} from 'angular2/src/compiler/template_normalizer'; +import {XHR} from 'angular2/src/core/render/xhr'; +import {MockXHR} from 'angular2/src/core/render/xhr_mock'; +import {TEST_BINDINGS} from './test_bindings'; + +export function main() { + describe('TemplateNormalizer', () => { + var dirType: TypeMetadata; + + beforeEachBindings(() => TEST_BINDINGS); + + beforeEach( + () => { dirType = new TypeMetadata({moduleId: 'some/module/id', name: 'SomeComp'}); }); + + describe('loadTemplate', () => { + describe('inline template', () => { + it('should store the template', + inject([AsyncTestCompleter, TemplateNormalizer], + (async, normalizer: TemplateNormalizer) => { + normalizer.normalizeTemplate(dirType, new TemplateMetadata({ + encapsulation: null, + template: 'a', + templateUrl: null, + styles: [], + styleUrls: ['test.css'] + })) + .then((template: NormalizedTemplateMetadata) => { + expect(template.template).toEqual('a'); + async.done(); + }); + })); + + it('should resolve styles on the annotation against the moduleId', + inject([AsyncTestCompleter, TemplateNormalizer], + (async, normalizer: TemplateNormalizer) => { + normalizer.normalizeTemplate(dirType, new TemplateMetadata({ + encapsulation: null, + template: '', + templateUrl: null, + styles: [], + styleUrls: ['test.css'] + })) + .then((template: NormalizedTemplateMetadata) => { + expect(template.styleAbsUrls).toEqual(['some/module/test.css']); + async.done(); + }); + })); + + it('should resolve styles in the template against the moduleId', + inject([AsyncTestCompleter, TemplateNormalizer], + (async, normalizer: TemplateNormalizer) => { + normalizer.normalizeTemplate(dirType, new TemplateMetadata({ + encapsulation: null, + template: '', + templateUrl: null, + styles: [], + styleUrls: [] + })) + .then((template: NormalizedTemplateMetadata) => { + expect(template.styleAbsUrls).toEqual(['some/module/test.css']); + async.done(); + }); + })); + }); + + describe('templateUrl', () => { + + it('should load a template from a url that is resolved against moduleId', + inject([AsyncTestCompleter, TemplateNormalizer, XHR], + (async, normalizer: TemplateNormalizer, xhr: MockXHR) => { + xhr.expect('some/module/sometplurl', 'a'); + normalizer.normalizeTemplate(dirType, new TemplateMetadata({ + encapsulation: null, + template: null, + templateUrl: 'sometplurl', + styles: [], + styleUrls: ['test.css'] + })) + .then((template: NormalizedTemplateMetadata) => { + expect(template.template).toEqual('a'); + async.done(); + }); + xhr.flush(); + })); + + it('should resolve styles on the annotation against the moduleId', + inject([AsyncTestCompleter, TemplateNormalizer, XHR], + (async, normalizer: TemplateNormalizer, xhr: MockXHR) => { + xhr.expect('some/module/tpl/sometplurl', ''); + normalizer.normalizeTemplate(dirType, new TemplateMetadata({ + encapsulation: null, + template: null, + templateUrl: 'tpl/sometplurl', + styles: [], + styleUrls: ['test.css'] + })) + .then((template: NormalizedTemplateMetadata) => { + expect(template.styleAbsUrls).toEqual(['some/module/test.css']); + async.done(); + }); + xhr.flush(); + })); + + it('should resolve styles in the template against the templateUrl', + inject([AsyncTestCompleter, TemplateNormalizer, XHR], + (async, normalizer: TemplateNormalizer, xhr: MockXHR) => { + xhr.expect('some/module/tpl/sometplurl', ''); + normalizer.normalizeTemplate(dirType, new TemplateMetadata({ + encapsulation: null, + template: null, + templateUrl: 'tpl/sometplurl', + styles: [], + styleUrls: [] + })) + .then((template: NormalizedTemplateMetadata) => { + expect(template.styleAbsUrls).toEqual(['some/module/tpl/test.css']); + async.done(); + }); + xhr.flush(); + })); + + }); + + }); + + describe('normalizeLoadedTemplate', () => { + it('should store the viewEncapsulationin the result', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + + var viewEncapsulation = ViewEncapsulation.Native; + var template = normalizer.normalizeLoadedTemplate( + dirType, + new TemplateMetadata({encapsulation: viewEncapsulation, styles: [], styleUrls: []}), + '', 'some/module/'); + expect(template.encapsulation).toBe(viewEncapsulation); + })); + + it('should keep the template as html', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + var template = normalizer.normalizeLoadedTemplate( + dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), 'a', + 'some/module/'); + expect(template.template).toEqual('a') + })); + + it('should collect and keep ngContent', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + var template = normalizer.normalizeLoadedTemplate( + dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), + '', 'some/module/'); + expect(template.ngContentSelectors).toEqual(['a']); + expect(template.template).toEqual(''); + })); + + it('should normalize ngContent wildcard selector', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + var template = normalizer.normalizeLoadedTemplate( + dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), + '', + 'some/module/'); + expect(template.ngContentSelectors).toEqual(['*', '*', '*']); + })); + + it('should collect and remove top level styles in the template', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + var template = normalizer.normalizeLoadedTemplate( + dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), + '', 'some/module/'); + expect(template.styles).toEqual(['a']); + expect(template.template).toEqual(''); + })); + + it('should collect and remove styles inside in elements', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + var template = normalizer.normalizeLoadedTemplate( + dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), + '
', 'some/module/'); + expect(template.styles).toEqual(['a']); + expect(template.template).toEqual('
'); + })); + + it('should collect and remove styleUrls in the template', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + var template = normalizer.normalizeLoadedTemplate( + dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), + '', 'some/module/'); + expect(template.styleAbsUrls).toEqual(['some/module/aUrl']); + expect(template.template).toEqual(''); + })); + + it('should collect and remove styleUrls in elements', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + var template = normalizer.normalizeLoadedTemplate( + dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), + '
', 'some/module/'); + expect(template.styleAbsUrls).toEqual(['some/module/aUrl']); + expect(template.template).toEqual('
'); + })); + + it('should keep link elements with non stylesheet rel attribute', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + var template = normalizer.normalizeLoadedTemplate( + dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), + '', 'some/module/'); + expect(template.styleAbsUrls).toEqual([]); + expect(template.template).toEqual(''); + })); + + it('should extract @import style urls into styleAbsUrl', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + var template = normalizer.normalizeLoadedTemplate( + dirType, new TemplateMetadata( + {encapsulation: null, styles: ['@import "test.css";'], styleUrls: []}), + '', 'some/module/id'); + expect(template.styles).toEqual(['']); + expect(template.styleAbsUrls).toEqual(['some/module/test.css']); + })); + + it('should resolve relative urls in inline styles', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + var template = normalizer.normalizeLoadedTemplate( + dirType, new TemplateMetadata({ + encapsulation: null, + styles: ['.foo{background-image: url(\'double.jpg\');'], + styleUrls: [] + }), + '', 'some/module/id'); + expect(template.styles) + .toEqual(['.foo{background-image: url(\'some/module/double.jpg\');']); + })); + + it('should resolve relative style urls in styleUrls', + inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { + var template = normalizer.normalizeLoadedTemplate( + dirType, + new TemplateMetadata({encapsulation: null, styles: [], styleUrls: ['test.css']}), '', + 'some/module/id'); + expect(template.styles).toEqual([]); + expect(template.styleAbsUrls).toEqual(['some/module/test.css']); + })); + + }); + + + }); +} diff --git a/modules/angular2/test/compiler/template_parser_spec.ts b/modules/angular2/test/compiler/template_parser_spec.ts index 96c0f638f4..35d6ba90da 100644 --- a/modules/angular2/test/compiler/template_parser_spec.ts +++ b/modules/angular2/test/compiler/template_parser_spec.ts @@ -1,15 +1,26 @@ -import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; - -import {isPresent} from 'angular2/src/core/facade/lang'; -import {Parser, Lexer} from 'angular2/src/core/change_detection/change_detection'; -import {TemplateParser, splitClasses} from 'angular2/src/compiler/template_parser'; -import {HtmlParser} from 'angular2/src/compiler/html_parser'; import { - DirectiveMetadata, + ddescribe, + describe, + it, + iit, + xit, + expect, + beforeEach, + afterEach, + inject, + beforeEachBindings +} from 'angular2/test_lib'; +import {bind} from 'angular2/src/core/di'; + +import {TEST_BINDINGS} from './test_bindings'; +import {isPresent} from 'angular2/src/core/facade/lang'; +import {TemplateParser, splitClasses} from 'angular2/src/compiler/template_parser'; +import { + NormalizedDirectiveMetadata, TypeMetadata, ChangeDetectionMetadata, - TemplateMetadata -} from 'angular2/src/compiler/api'; + NormalizedTemplateMetadata +} from 'angular2/src/compiler/directive_metadata'; import { templateVisitAll, TemplateAstVisitor, @@ -29,6 +40,7 @@ import { } from 'angular2/src/compiler/template_ast'; import {ElementSchemaRegistry} from 'angular2/src/core/render/dom/schema/element_schema_registry'; +import {MockSchemaRegistry} from './schema_registry_mock'; import {Unparser} from '../core/change_detection/parser/unparser'; @@ -36,24 +48,26 @@ var expressionUnparser = new Unparser(); export function main() { describe('TemplateParser', () => { - var domParser: HtmlParser; + beforeEachBindings(() => [ + TEST_BINDINGS, + bind(ElementSchemaRegistry) + .toValue(new MockSchemaRegistry({'invalidProp': false}, {'mappedAttr': 'mappedProp'})) + ]); + var parser: TemplateParser; var ngIf; - beforeEach(() => { - domParser = new HtmlParser(); - parser = new TemplateParser( - new Parser(new Lexer()), - new MockSchemaRegistry({'invalidProp': false}, {'mappedAttr': 'mappedProp'})); - ngIf = new DirectiveMetadata({ + beforeEach(inject([TemplateParser], (_parser) => { + parser = _parser; + ngIf = new NormalizedDirectiveMetadata({ selector: '[ng-if]', - type: new TypeMetadata({typeName: 'NgIf'}), + type: new TypeMetadata({name: 'NgIf'}), changeDetection: new ChangeDetectionMetadata({properties: ['ngIf']}) }); - }); + })); - function parse(template: string, directives: DirectiveMetadata[]): TemplateAst[] { - return parser.parse(domParser.parse(template, 'TestComp'), directives); + function parse(template: string, directives: NormalizedDirectiveMetadata[]): TemplateAst[] { + return parser.parse(template, directives, 'TestComp'); } describe('parse', () => { @@ -341,16 +355,16 @@ export function main() { }); describe('directives', () => { - it('should locate directives ordered by typeName and components first', () => { - var dirA = new DirectiveMetadata( - {selector: '[a=b]', type: new TypeMetadata({typeName: 'DirA'})}); - var dirB = - new DirectiveMetadata({selector: '[a]', type: new TypeMetadata({typeName: 'DirB'})}); - var comp = new DirectiveMetadata({ + it('should locate directives ordered by name and components first', () => { + var dirA = new NormalizedDirectiveMetadata( + {selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})}); + var dirB = new NormalizedDirectiveMetadata( + {selector: '[a]', type: new TypeMetadata({name: 'DirB'})}); + var comp = new NormalizedDirectiveMetadata({ selector: 'div', isComponent: true, - type: new TypeMetadata({typeName: 'ZComp'}), - template: new TemplateMetadata({ngContentSelectors: []}) + type: new TypeMetadata({name: 'ZComp'}), + template: new NormalizedTemplateMetadata({ngContentSelectors: []}) }); expect(humanizeTemplateAsts(parse('
', [dirB, dirA, comp]))) .toEqual([ @@ -363,10 +377,10 @@ export function main() { }); it('should locate directives in property bindings', () => { - var dirA = new DirectiveMetadata( - {selector: '[a=b]', type: new TypeMetadata({typeName: 'DirA'})}); - var dirB = - new DirectiveMetadata({selector: '[b]', type: new TypeMetadata({typeName: 'DirB'})}); + var dirA = new NormalizedDirectiveMetadata( + {selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})}); + var dirB = new NormalizedDirectiveMetadata( + {selector: '[b]', type: new TypeMetadata({name: 'DirB'})}); expect(humanizeTemplateAsts(parse('
', [dirA, dirB]))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], @@ -383,10 +397,10 @@ export function main() { }); it('should locate directives in variable bindings', () => { - var dirA = new DirectiveMetadata( - {selector: '[a=b]', type: new TypeMetadata({typeName: 'DirA'})}); - var dirB = - new DirectiveMetadata({selector: '[b]', type: new TypeMetadata({typeName: 'DirB'})}); + var dirA = new NormalizedDirectiveMetadata( + {selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})}); + var dirB = new NormalizedDirectiveMetadata( + {selector: '[b]', type: new TypeMetadata({name: 'DirB'})}); expect(humanizeTemplateAsts(parse('
', [dirA, dirB]))) .toEqual([ [ElementAst, 'div', 'TestComp > div:nth-child(0)'], @@ -396,9 +410,9 @@ export function main() { }); it('should parse directive host properties', () => { - var dirA = new DirectiveMetadata({ + var dirA = new NormalizedDirectiveMetadata({ selector: 'div', - type: new TypeMetadata({typeName: 'DirA'}), + type: new TypeMetadata({name: 'DirA'}), changeDetection: new ChangeDetectionMetadata({hostProperties: {'a': 'expr'}}) }); expect(humanizeTemplateAsts(parse('
', [dirA]))) @@ -417,9 +431,9 @@ export function main() { }); it('should parse directive host listeners', () => { - var dirA = new DirectiveMetadata({ + var dirA = new NormalizedDirectiveMetadata({ selector: 'div', - type: new TypeMetadata({typeName: 'DirA'}), + type: new TypeMetadata({name: 'DirA'}), changeDetection: new ChangeDetectionMetadata({hostListeners: {'a': 'expr'}}) }); expect(humanizeTemplateAsts(parse('
', [dirA]))) @@ -431,9 +445,9 @@ export function main() { }); it('should parse directive properties', () => { - var dirA = new DirectiveMetadata({ + var dirA = new NormalizedDirectiveMetadata({ selector: 'div', - type: new TypeMetadata({typeName: 'DirA'}), + type: new TypeMetadata({name: 'DirA'}), changeDetection: new ChangeDetectionMetadata({properties: ['aProp']}) }); expect(humanizeTemplateAsts(parse('
', [dirA]))) @@ -450,9 +464,9 @@ export function main() { }); it('should parse renamed directive properties', () => { - var dirA = new DirectiveMetadata({ + var dirA = new NormalizedDirectiveMetadata({ selector: 'div', - type: new TypeMetadata({typeName: 'DirA'}), + type: new TypeMetadata({name: 'DirA'}), changeDetection: new ChangeDetectionMetadata({properties: ['b:a']}) }); expect(humanizeTemplateAsts(parse('
', [dirA]))) @@ -464,9 +478,9 @@ export function main() { }); it('should parse literal directive properties', () => { - var dirA = new DirectiveMetadata({ + var dirA = new NormalizedDirectiveMetadata({ selector: 'div', - type: new TypeMetadata({typeName: 'DirA'}), + type: new TypeMetadata({name: 'DirA'}), changeDetection: new ChangeDetectionMetadata({properties: ['a']}) }); expect(humanizeTemplateAsts(parse('
', [dirA]))) @@ -484,9 +498,9 @@ export function main() { }); it('should support optional directive properties', () => { - var dirA = new DirectiveMetadata({ + var dirA = new NormalizedDirectiveMetadata({ selector: 'div', - type: new TypeMetadata({typeName: 'DirA'}), + type: new TypeMetadata({name: 'DirA'}), changeDetection: new ChangeDetectionMetadata({properties: ['a']}) }); expect(humanizeTemplateAsts(parse('
', [dirA]))) @@ -549,13 +563,13 @@ export function main() { describe('directives', () => { it('should locate directives in property bindings', () => { - var dirA = new DirectiveMetadata({ + var dirA = new NormalizedDirectiveMetadata({ selector: '[a=b]', - type: new TypeMetadata({typeName: 'DirA'}), + type: new TypeMetadata({name: 'DirA'}), changeDetection: new ChangeDetectionMetadata({properties: ['a']}) }); - var dirB = new DirectiveMetadata( - {selector: '[b]', type: new TypeMetadata({typeName: 'DirB'})}); + var dirB = new NormalizedDirectiveMetadata( + {selector: '[b]', type: new TypeMetadata({name: 'DirB'})}); expect(humanizeTemplateAsts(parse('
', [dirA, dirB]))) .toEqual([ [EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'], @@ -573,10 +587,10 @@ export function main() { }); it('should locate directives in variable bindings', () => { - var dirA = new DirectiveMetadata( - {selector: '[a=b]', type: new TypeMetadata({typeName: 'DirA'})}); - var dirB = new DirectiveMetadata( - {selector: '[b]', type: new TypeMetadata({typeName: 'DirB'})}); + var dirA = new NormalizedDirectiveMetadata( + {selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})}); + var dirB = new NormalizedDirectiveMetadata( + {selector: '[b]', type: new TypeMetadata({name: 'DirB'})}); expect(humanizeTemplateAsts(parse('
', [dirA, dirB]))) .toEqual([ [EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'], @@ -609,12 +623,13 @@ export function main() { }); describe('content projection', () => { - function createComp(selector: string, ngContentSelectors: string[]): DirectiveMetadata { - return new DirectiveMetadata({ + function createComp(selector: string, ngContentSelectors: string[]): + NormalizedDirectiveMetadata { + return new NormalizedDirectiveMetadata({ selector: selector, isComponent: true, - type: new TypeMetadata({typeName: 'SomeComp'}), - template: new TemplateMetadata({ngContentSelectors: ngContentSelectors}) + type: new TypeMetadata({name: 'SomeComp'}), + template: new NormalizedTemplateMetadata({ngContentSelectors: ngContentSelectors}) }) } @@ -710,26 +725,26 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp > div:nth-ch it('should not throw on invalid property names if the property is used by a directive', () => { - var dirA = new DirectiveMetadata({ + var dirA = new NormalizedDirectiveMetadata({ selector: 'div', - type: new TypeMetadata({typeName: 'DirA'}), + type: new TypeMetadata({name: 'DirA'}), changeDetection: new ChangeDetectionMetadata({properties: ['invalidProp']}) }); expect(() => parse('
', [dirA])).not.toThrow(); }); it('should not allow more than 1 component per element', () => { - var dirA = new DirectiveMetadata({ + var dirA = new NormalizedDirectiveMetadata({ selector: 'div', isComponent: true, - type: new TypeMetadata({typeName: 'DirA'}), - template: new TemplateMetadata({ngContentSelectors: []}) + type: new TypeMetadata({name: 'DirA'}), + template: new NormalizedTemplateMetadata({ngContentSelectors: []}) }); - var dirB = new DirectiveMetadata({ + var dirB = new NormalizedDirectiveMetadata({ selector: 'div', isComponent: true, - type: new TypeMetadata({typeName: 'DirB'}), - template: new TemplateMetadata({ngContentSelectors: []}) + type: new TypeMetadata({name: 'DirB'}), + template: new NormalizedTemplateMetadata({ngContentSelectors: []}) }); expect(() => parse('
', [dirB, dirA])).toThrowError(`Template parse errors: More than one component: DirA,DirB in TestComp > div:nth-child(0)`); @@ -737,11 +752,11 @@ More than one component: DirA,DirB in TestComp > div:nth-child(0)`); it('should not allow components or element nor event bindings on explicit embedded templates', () => { - var dirA = new DirectiveMetadata({ + var dirA = new NormalizedDirectiveMetadata({ selector: '[a]', isComponent: true, - type: new TypeMetadata({typeName: 'DirA'}), - template: new TemplateMetadata({ngContentSelectors: []}) + type: new TypeMetadata({name: 'DirA'}), + template: new NormalizedTemplateMetadata({ngContentSelectors: []}) }); expect(() => parse('', [dirA])) .toThrowError(`Template parse errors: @@ -751,11 +766,11 @@ Event binding e on an embedded template in TestComp > template:nth-child(0)[(e)= }); it('should not allow components or element bindings on inline embedded templates', () => { - var dirA = new DirectiveMetadata({ + var dirA = new NormalizedDirectiveMetadata({ selector: '[a]', isComponent: true, - type: new TypeMetadata({typeName: 'DirA'}), - template: new TemplateMetadata({ngContentSelectors: []}) + type: new TypeMetadata({name: 'DirA'}), + template: new NormalizedTemplateMetadata({ngContentSelectors: []}) }); expect(() => parse('
', [dirA])).toThrowError(`Template parse errors: Components on an embedded template: DirA in TestComp > div:nth-child(0) @@ -887,17 +902,3 @@ class TemplateContentProjectionHumanizer implements TemplateAstVisitor { visitDirective(ast: DirectiveAst, context: any): any { return null; } visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; } } - -export class MockSchemaRegistry implements ElementSchemaRegistry { - constructor(public existingProperties: StringMap, - public attrPropMapping: StringMap) {} - hasProperty(tagName: string, property: string): boolean { - var result = this.existingProperties[property]; - return isPresent(result) ? result : true; - } - - getMappedPropName(attrName: string): string { - var result = this.attrPropMapping[attrName]; - return isPresent(result) ? result : attrName; - } -} \ No newline at end of file diff --git a/modules/angular2/test/compiler/test_bindings.ts b/modules/angular2/test/compiler/test_bindings.ts new file mode 100644 index 0000000000..abb8f2f32f --- /dev/null +++ b/modules/angular2/test/compiler/test_bindings.ts @@ -0,0 +1,26 @@ +import {bind, Binding} from 'angular2/src/core/di'; +import {TemplateParser} from 'angular2/src/compiler/template_parser'; +import {HtmlParser} from 'angular2/src/compiler/html_parser'; +import {TemplateNormalizer} from 'angular2/src/compiler/template_normalizer'; +import {RuntimeMetadataResolver} from 'angular2/src/compiler/runtime_metadata'; +import {ChangeDetectionCompiler} from 'angular2/src/compiler/change_detector_compiler'; +import {StyleCompiler} from 'angular2/src/compiler/style_compiler'; +import {CommandCompiler} from 'angular2/src/compiler/command_compiler'; +import {TemplateCompiler} from 'angular2/src/compiler/template_compiler'; +import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection'; +import {MockSchemaRegistry} from './schema_registry_mock'; +import {ElementSchemaRegistry} from 'angular2/src/core/render/dom/schema/element_schema_registry'; + +// TODO(tbosch): move this into test_injector once the new compiler pipeline is used fully +export var TEST_BINDINGS = [ + HtmlParser, + TemplateParser, + TemplateNormalizer, + RuntimeMetadataResolver, + StyleCompiler, + CommandCompiler, + ChangeDetectionCompiler, + bind(ChangeDetectorGenConfig).toValue(new ChangeDetectorGenConfig(true, true, false, false)), + TemplateCompiler, + bind(ElementSchemaRegistry).toValue(new MockSchemaRegistry({}, {})) +]; diff --git a/modules/angular2/test/core/reflection/reflector_spec.ts b/modules/angular2/test/core/reflection/reflector_spec.ts index 26adf7e325..273b9e77cd 100644 --- a/modules/angular2/test/core/reflection/reflector_spec.ts +++ b/modules/angular2/test/core/reflection/reflector_spec.ts @@ -242,6 +242,20 @@ export function main() { expect(reflector.method("abc")("anything", ["fake"])).toEqual(['fake']); }); }); + + if (IS_DART) { + describe("moduleId", () => { + it("should return the moduleId for a type", () => { + expect(reflector.moduleId(TestObjWith00Args)) + .toEqual('angular2/test/core/reflection/reflector_spec'); + }); + + it("should return an empty array otherwise", () => { + var p = reflector.interfaces(ClassWithDecorators); + expect(p).toEqual([]); + }); + }); + } }); } diff --git a/modules_dart/transform/lib/src/transform/template_compiler/change_detector_codegen.dart b/modules_dart/transform/lib/src/transform/template_compiler/change_detector_codegen.dart index a805162a0c..5b6fcd46c3 100644 --- a/modules_dart/transform/lib/src/transform/template_compiler/change_detector_codegen.dart +++ b/modules_dart/transform/lib/src/transform/template_compiler/change_detector_codegen.dart @@ -28,6 +28,11 @@ class Codegen { /// The names of already generated classes. final Set _names = new Set(); + /// The module prefix for pregen_proto_change_detector + final String _genPrefix; + + Codegen([this._genPrefix = _GEN_PREFIX_WITH_DOT]); + /// Generates a change detector class with name `changeDetectorTypeName`, /// which must not conflict with other generated classes in the same /// `.ng_deps.dart` file. The change detector is used to detect changes in @@ -40,7 +45,7 @@ class Codegen { 'conflicts with an earlier generated change detector class.'); } _names.add(changeDetectorTypeName); - new _CodegenState(typeName, changeDetectorTypeName, def) + new _CodegenState(_genPrefix, typeName, changeDetectorTypeName, def) .._writeToBuf(_buf) .._writeInitToBuf(_initBuf); } @@ -86,9 +91,13 @@ class _CodegenState { final List _propertyBindingTargets; String get _changeDetectionStrategyAsCode => - _changeDetectionStrategy == null ? 'null' : '${_GEN_PREFIX}.${_changeDetectionStrategy}'; + _changeDetectionStrategy == null ? 'null' : '${_genPrefix}${_changeDetectionStrategy}'; + + /// The module prefix for pregen_proto_change_detector + final String _genPrefix; _CodegenState._( + this._genPrefix, this._changeDetectorDefId, this._contextTypeName, this._changeDetectorTypeName, @@ -101,15 +110,16 @@ class _CodegenState { this._names, this._genConfig); - factory _CodegenState(String typeName, String changeDetectorTypeName, + factory _CodegenState(String genPrefix, String typeName, String changeDetectorTypeName, ChangeDetectorDefinition def) { var protoRecords = createPropertyRecords(def); var eventBindings = createEventRecords(def); var propertyBindingTargets = def.bindingRecords.map((b) => b.target).toList(); - var names = new CodegenNameUtil(protoRecords, eventBindings, def.directiveRecords, _UTIL); - var logic = new CodegenLogicUtil(names, _UTIL, def.strategy); + var names = new CodegenNameUtil(protoRecords, eventBindings, def.directiveRecords, '$genPrefix$_UTIL'); + var logic = new CodegenLogicUtil(names, '$genPrefix$_UTIL', def.strategy); return new _CodegenState._( + genPrefix, def.id, typeName, changeDetectorTypeName, @@ -125,7 +135,7 @@ class _CodegenState { void _writeToBuf(StringBuffer buf) { buf.write('''\n - class $_changeDetectorTypeName extends $_BASE_CLASS<$_contextTypeName> { + class $_changeDetectorTypeName extends ${_genPrefix}$_BASE_CLASS<$_contextTypeName> { ${_genDeclareFields()} $_changeDetectorTypeName(dispatcher) @@ -161,10 +171,10 @@ class _CodegenState { ${_genDirectiveIndices()}; - static $_GEN_PREFIX.ProtoChangeDetector + static ${_genPrefix}ProtoChangeDetector $PROTO_CHANGE_DETECTOR_FACTORY_METHOD( - $_GEN_PREFIX.ChangeDetectorDefinition def) { - return new $_GEN_PREFIX.PregenProtoChangeDetector( + ${_genPrefix}ChangeDetectorDefinition def) { + return new ${_genPrefix}PregenProtoChangeDetector( (a) => new $_changeDetectorTypeName(a), def); } @@ -233,7 +243,7 @@ class _CodegenState { void _writeInitToBuf(StringBuffer buf) { buf.write(''' - $_GEN_PREFIX.preGeneratedProtoDetectors['$_changeDetectorDefId'] = + ${_genPrefix}preGeneratedProtoDetectors['$_changeDetectorDefId'] = $_changeDetectorTypeName.newProtoChangeDetector; '''); } @@ -336,7 +346,7 @@ class _CodegenState { var pipeType = r.name; var init = ''' - if ($_IDENTICAL_CHECK_FN($pipe, $_UTIL.uninitialized)) { + if (${_genPrefix}$_IDENTICAL_CHECK_FN($pipe, ${_genPrefix}$_UTIL.uninitialized)) { $pipe = ${_names.getPipesAccessorName()}.get('$pipeType'); } '''; @@ -350,8 +360,8 @@ class _CodegenState { var condition = '''!${pipe}.pure || (${contexOrArgCheck.join(" || ")})'''; var check = ''' - if ($_NOT_IDENTICAL_CHECK_FN($oldValue, $newValue)) { - $newValue = $_UTIL.unwrapValue($newValue); + if (${_genPrefix}$_NOT_IDENTICAL_CHECK_FN($oldValue, $newValue)) { + $newValue = ${_genPrefix}$_UTIL.unwrapValue($newValue); ${_genChangeMarker(r)} ${_genUpdateDirectiveOrElement(r)} ${_genAddToChanges(r)} @@ -376,7 +386,7 @@ class _CodegenState { '''; var check = ''' - if ($_NOT_IDENTICAL_CHECK_FN($newValue, $oldValue)) { + if (${_genPrefix}$_NOT_IDENTICAL_CHECK_FN($newValue, $oldValue)) { ${_genChangeMarker(r)} ${_genUpdateDirectiveOrElement(r)} ${_genAddToChanges(r)} @@ -507,13 +517,14 @@ class _CodegenState { const PROTO_CHANGE_DETECTOR_FACTORY_METHOD = 'newProtoChangeDetector'; -const _BASE_CLASS = '$_GEN_PREFIX.AbstractChangeDetector'; +const _BASE_CLASS = 'AbstractChangeDetector'; const _CHANGES_LOCAL = 'changes'; const _GEN_PREFIX = '_gen'; +const _GEN_PREFIX_WITH_DOT = _GEN_PREFIX + '.'; const _GEN_RECORDS_METHOD_NAME = '_createRecords'; -const _IDENTICAL_CHECK_FN = '$_GEN_PREFIX.looseIdentical'; -const _NOT_IDENTICAL_CHECK_FN = '$_GEN_PREFIX.looseNotIdentical'; +const _IDENTICAL_CHECK_FN = 'looseIdentical'; +const _NOT_IDENTICAL_CHECK_FN = 'looseNotIdentical'; const _IS_CHANGED_LOCAL = 'isChanged'; const _PREGEN_PROTO_CHANGE_DETECTOR_IMPORT = 'package:angular2/src/core/change_detection/pregen_proto_change_detector.dart'; -const _UTIL = '$_GEN_PREFIX.ChangeDetectionUtil'; +const _UTIL = 'ChangeDetectionUtil'; diff --git a/modules_dart/transform/lib/src/transform/template_compiler/reflection/reflection_capabilities.dart b/modules_dart/transform/lib/src/transform/template_compiler/reflection/reflection_capabilities.dart index 76fd1ab53e..a7d8708de1 100644 --- a/modules_dart/transform/lib/src/transform/template_compiler/reflection/reflection_capabilities.dart +++ b/modules_dart/transform/lib/src/transform/template_compiler/reflection/reflection_capabilities.dart @@ -29,6 +29,8 @@ class NullReflectionCapabilities implements ReflectionCapabilities { MethodFn method(String name) => _nullMethod; String importUri(Type type) => './'; + + String moduleId(Type type) => null; } _nullGetter(Object p) => null;