import { IS_DART, Type, Json, isBlank, isPresent, stringify, evalExpression } from 'angular2/src/facade/lang'; import {BaseException} from 'angular2/src/facade/exceptions'; import { ListWrapper, SetWrapper, MapWrapper, StringMapWrapper } from 'angular2/src/facade/collection'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import { createHostComponentMeta, CompileDirectiveMetadata, CompileTypeMetadata, CompileTemplateMetadata, CompilePipeMetadata, CompileMetadataWithType } from './directive_metadata'; import { TemplateAst, TemplateAstVisitor, NgContentAst, EmbeddedTemplateAst, ElementAst, VariableAst, BoundEventAst, BoundElementPropertyAst, AttrAst, BoundTextAst, TextAst, DirectiveAst, BoundDirectivePropertyAst, templateVisitAll } from './template_ast'; import {Injectable} from 'angular2/src/core/di'; import {SourceModule, moduleRef, SourceExpression} from './source_module'; import {ChangeDetectionCompiler, CHANGE_DETECTION_JIT_IMPORTS} from './change_detector_compiler'; import {StyleCompiler} from './style_compiler'; import {ViewCompiler, VIEW_JIT_IMPORTS} from './view_compiler'; import { ProtoViewCompiler, APP_VIEW_MODULE_REF, CompileProtoView, PROTO_VIEW_JIT_IMPORTS } from './proto_view_compiler'; import {TemplateParser, PipeCollector} from './template_parser'; import {TemplateNormalizer} from './template_normalizer'; import {RuntimeMetadataResolver} from './runtime_metadata'; import {HostViewFactory} from 'angular2/src/core/linker/view'; import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection'; import {ResolvedMetadataCache} from 'angular2/src/core/linker/resolved_metadata_cache'; import { codeGenExportVariable, escapeSingleQuoteString, codeGenValueFn, MODULE_SUFFIX, addAll, Expression } from './util'; export var METADATA_CACHE_MODULE_REF = moduleRef('package:angular2/src/core/linker/resolved_metadata_cache' + MODULE_SUFFIX); /** * An internal module of the Angular compiler that begins with component types, * extracts templates, and eventually produces a compiled version of the component * ready for linking into an application. */ @Injectable() export class TemplateCompiler { private _hostCacheKeys = new Map(); private _compiledTemplateCache = new Map(); private _compiledTemplateDone = new Map>(); constructor(private _runtimeMetadataResolver: RuntimeMetadataResolver, private _templateNormalizer: TemplateNormalizer, private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, private _cdCompiler: ChangeDetectionCompiler, private _protoViewCompiler: ProtoViewCompiler, private _viewCompiler: ViewCompiler, private _resolvedMetadataCache: ResolvedMetadataCache, private _genConfig: ChangeDetectorGenConfig) {} normalizeDirectiveMetadata(directive: CompileDirectiveMetadata): Promise { if (!directive.isComponent) { // For non components there is nothing to be normalized yet. return PromiseWrapper.resolve(directive); } return this._templateNormalizer.normalizeTemplate(directive.type, directive.template) .then((normalizedTemplate: CompileTemplateMetadata) => new CompileDirectiveMetadata({ type: directive.type, isComponent: directive.isComponent, dynamicLoadable: directive.dynamicLoadable, selector: directive.selector, exportAs: directive.exportAs, changeDetection: directive.changeDetection, inputs: directive.inputs, outputs: directive.outputs, hostListeners: directive.hostListeners, hostProperties: directive.hostProperties, hostAttributes: directive.hostAttributes, lifecycleHooks: directive.lifecycleHooks, template: normalizedTemplate })); } compileHostComponentRuntime(type: Type): Promise { var compMeta: CompileDirectiveMetadata = this._runtimeMetadataResolver.getDirectiveMetadata(type); var hostCacheKey = this._hostCacheKeys.get(type); if (isBlank(hostCacheKey)) { hostCacheKey = new Object(); this._hostCacheKeys.set(type, hostCacheKey); assertComponent(compMeta); var hostMeta: CompileDirectiveMetadata = createHostComponentMeta(compMeta.type, compMeta.selector); this._compileComponentRuntime(hostCacheKey, hostMeta, [compMeta], [], []); } return this._compiledTemplateDone.get(hostCacheKey) .then((compiledTemplate: CompiledTemplate) => new HostViewFactory(compMeta.selector, compiledTemplate.viewFactory)); } clearCache() { this._styleCompiler.clearCache(); this._compiledTemplateCache.clear(); this._compiledTemplateDone.clear(); this._hostCacheKeys.clear(); } compileTemplatesCodeGen(components: NormalizedComponentWithViewDirectives[]): SourceModule { if (components.length === 0) { throw new BaseException('No components given'); } var declarations = []; components.forEach(componentWithDirs => { var compMeta = componentWithDirs.component; assertComponent(compMeta); this._compileComponentCodeGen(compMeta, componentWithDirs.directives, componentWithDirs.pipes, declarations); if (compMeta.dynamicLoadable) { var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector); var viewFactoryExpression = this._compileComponentCodeGen(hostMeta, [compMeta], [], declarations); var constructionKeyword = IS_DART ? 'const' : 'new'; var compiledTemplateExpr = `${constructionKeyword} ${APP_VIEW_MODULE_REF}HostViewFactory('${compMeta.selector}',${viewFactoryExpression})`; var varName = codeGenHostViewFactoryName(compMeta.type); declarations.push(`${codeGenExportVariable(varName)}${compiledTemplateExpr};`); } }); var moduleUrl = components[0].component.type.moduleUrl; return new SourceModule(`${templateModuleUrl(moduleUrl)}`, declarations.join('\n')); } compileStylesheetCodeGen(stylesheetUrl: string, cssText: string): SourceModule[] { return this._styleCompiler.compileStylesheetCodeGen(stylesheetUrl, cssText); } private _compileComponentRuntime(cacheKey: any, compMeta: CompileDirectiveMetadata, viewDirectives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[], compilingComponentsPath: any[]): CompiledTemplate { let uniqViewDirectives = removeDuplicates(viewDirectives); let uniqViewPipes = removeDuplicates(pipes); var compiledTemplate = this._compiledTemplateCache.get(cacheKey); var done = this._compiledTemplateDone.get(cacheKey); if (isBlank(compiledTemplate)) { compiledTemplate = new CompiledTemplate(); this._compiledTemplateCache.set(cacheKey, compiledTemplate); done = PromiseWrapper .all([this._styleCompiler.compileComponentRuntime(compMeta.template)].concat( uniqViewDirectives.map(dirMeta => this.normalizeDirectiveMetadata(dirMeta)))) .then((stylesAndNormalizedViewDirMetas: any[]) => { var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1); var styles = stylesAndNormalizedViewDirMetas[0]; var parsedTemplate = this._templateParser.parse( compMeta.template.template, normalizedViewDirMetas, uniqViewPipes, compMeta.type.name); var childPromises = []; var usedDirectives = DirectiveCollector.findUsedDirectives(parsedTemplate); usedDirectives.components.forEach( component => this._compileNestedComponentRuntime( component, compilingComponentsPath, childPromises)); return PromiseWrapper.all(childPromises) .then((_) => { var filteredPipes = filterPipes(parsedTemplate, uniqViewPipes); compiledTemplate.init(this._createViewFactoryRuntime( compMeta, parsedTemplate, usedDirectives.directives, styles, filteredPipes)); return compiledTemplate; }); }); this._compiledTemplateDone.set(cacheKey, done); } return compiledTemplate; } private _compileNestedComponentRuntime(childComponentDir: CompileDirectiveMetadata, parentCompilingComponentsPath: any[], childPromises: Promise[]) { var compilingComponentsPath = ListWrapper.clone(parentCompilingComponentsPath); var childCacheKey = childComponentDir.type.runtime; var childViewDirectives: CompileDirectiveMetadata[] = this._runtimeMetadataResolver.getViewDirectivesMetadata(childComponentDir.type.runtime); var childViewPipes: CompilePipeMetadata[] = this._runtimeMetadataResolver.getViewPipesMetadata(childComponentDir.type.runtime); var childIsRecursive = ListWrapper.contains(compilingComponentsPath, childCacheKey); compilingComponentsPath.push(childCacheKey); this._compileComponentRuntime(childCacheKey, childComponentDir, childViewDirectives, childViewPipes, compilingComponentsPath); if (!childIsRecursive) { // Only wait for a child if it is not a cycle childPromises.push(this._compiledTemplateDone.get(childCacheKey)); } } private _createViewFactoryRuntime(compMeta: CompileDirectiveMetadata, parsedTemplate: TemplateAst[], directives: CompileDirectiveMetadata[], styles: string[], pipes: CompilePipeMetadata[]): Function { if (IS_DART || !this._genConfig.useJit) { var changeDetectorFactories = this._cdCompiler.compileComponentRuntime( compMeta.type, compMeta.changeDetection, parsedTemplate); var protoViews = this._protoViewCompiler.compileProtoViewRuntime( this._resolvedMetadataCache, compMeta, parsedTemplate, pipes); return this._viewCompiler.compileComponentRuntime( compMeta, parsedTemplate, styles, protoViews.protoViews, changeDetectorFactories, (compMeta) => this._getNestedComponentViewFactory(compMeta)); } else { var declarations = []; var viewFactoryExpr = this._createViewFactoryCodeGen('resolvedMetadataCache', compMeta, new SourceExpression([], 'styles'), parsedTemplate, pipes, declarations); var vars: {[key: string]: any} = {'exports': {}, 'styles': styles, 'resolvedMetadataCache': this._resolvedMetadataCache}; directives.forEach(dirMeta => { vars[dirMeta.type.name] = dirMeta.type.runtime; if (dirMeta.isComponent && dirMeta.type.runtime !== compMeta.type.runtime) { vars[`viewFactory_${dirMeta.type.name}0`] = this._getNestedComponentViewFactory(dirMeta); } }); pipes.forEach(pipeMeta => vars[pipeMeta.type.name] = pipeMeta.type.runtime); var declarationsWithoutImports = SourceModule.getSourceWithoutImports(declarations.join('\n')); return evalExpression( `viewFactory_${compMeta.type.name}`, viewFactoryExpr, declarationsWithoutImports, mergeStringMaps( [vars, CHANGE_DETECTION_JIT_IMPORTS, PROTO_VIEW_JIT_IMPORTS, VIEW_JIT_IMPORTS])); } } private _getNestedComponentViewFactory(compMeta: CompileDirectiveMetadata): Function { return this._compiledTemplateCache.get(compMeta.type.runtime).viewFactory; } private _compileComponentCodeGen(compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[], targetDeclarations: string[]): string { let uniqueDirectives = removeDuplicates(directives); let uniqPipes = removeDuplicates(pipes); var styleExpr = this._styleCompiler.compileComponentCodeGen(compMeta.template); var parsedTemplate = this._templateParser.parse(compMeta.template.template, uniqueDirectives, uniqPipes, compMeta.type.name); var filteredPipes = filterPipes(parsedTemplate, uniqPipes); return this._createViewFactoryCodeGen( `${METADATA_CACHE_MODULE_REF}CODEGEN_RESOLVED_METADATA_CACHE`, compMeta, styleExpr, parsedTemplate, filteredPipes, targetDeclarations); } private _createViewFactoryCodeGen(resolvedMetadataCacheExpr: string, compMeta: CompileDirectiveMetadata, styleExpr: SourceExpression, parsedTemplate: TemplateAst[], pipes: CompilePipeMetadata[], targetDeclarations: string[]): string { var changeDetectorsExprs = this._cdCompiler.compileComponentCodeGen( compMeta.type, compMeta.changeDetection, parsedTemplate); var protoViewExprs = this._protoViewCompiler.compileProtoViewCodeGen( new Expression(resolvedMetadataCacheExpr), compMeta, parsedTemplate, pipes); var viewFactoryExpr = this._viewCompiler.compileComponentCodeGen( compMeta, parsedTemplate, styleExpr, protoViewExprs.protoViews, changeDetectorsExprs, codeGenComponentViewFactoryName); addAll(changeDetectorsExprs.declarations, targetDeclarations); addAll(protoViewExprs.declarations, targetDeclarations); addAll(viewFactoryExpr.declarations, targetDeclarations); return viewFactoryExpr.expression; } } export class NormalizedComponentWithViewDirectives { constructor(public component: CompileDirectiveMetadata, public directives: CompileDirectiveMetadata[], public pipes: CompilePipeMetadata[]) {} } class CompiledTemplate { viewFactory: Function = null; init(viewFactory: Function) { this.viewFactory = viewFactory; } } function assertComponent(meta: CompileDirectiveMetadata) { if (!meta.isComponent) { throw new BaseException(`Could not compile '${meta.type.name}' because it is not a component.`); } } function templateModuleUrl(moduleUrl: string): string { var urlWithoutSuffix = moduleUrl.substring(0, moduleUrl.length - MODULE_SUFFIX.length); return `${urlWithoutSuffix}.template${MODULE_SUFFIX}`; } function codeGenHostViewFactoryName(type: CompileTypeMetadata): string { return `hostViewFactory_${type.name}`; } function codeGenComponentViewFactoryName(nestedCompType: CompileDirectiveMetadata): string { return `${moduleRef(templateModuleUrl(nestedCompType.type.moduleUrl))}viewFactory_${nestedCompType.type.name}0`; } function mergeStringMaps(maps: Array<{[key: string]: any}>): {[key: string]: any} { var result = {}; maps.forEach( (map) => { StringMapWrapper.forEach(map, (value, key) => { result[key] = value; }); }); return result; } function removeDuplicates(items: CompileMetadataWithType[]): CompileMetadataWithType[] { let res = []; items.forEach(item => { let hasMatch = res.filter(r => r.type.name == item.type.name && r.type.moduleUrl == item.type.moduleUrl && r.type.runtime == item.type.runtime) .length > 0; if (!hasMatch) { res.push(item); } }); return res; } class DirectiveCollector implements TemplateAstVisitor { static findUsedDirectives(parsedTemplate: TemplateAst[]): DirectiveCollector { var collector = new DirectiveCollector(); templateVisitAll(collector, parsedTemplate); return collector; } directives: CompileDirectiveMetadata[] = []; components: CompileDirectiveMetadata[] = []; visitBoundText(ast: BoundTextAst, context: any): any { return null; } visitText(ast: TextAst, context: any): any { return null; } visitNgContent(ast: NgContentAst, context: any): any { return null; } visitElement(ast: ElementAst, context: any): any { templateVisitAll(this, ast.directives); templateVisitAll(this, ast.children); return null; } visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { templateVisitAll(this, ast.directives); templateVisitAll(this, ast.children); return null; } visitVariable(ast: VariableAst, ctx: any): any { return null; } visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any { return null; } visitDirective(ast: DirectiveAst, ctx: any): any { if (ast.directive.isComponent) { this.components.push(ast.directive); } this.directives.push(ast.directive); return null; } visitEvent(ast: BoundEventAst, eventTargetAndNames: Map): any { return null; } visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; } visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; } } function filterPipes(template: TemplateAst[], allPipes: CompilePipeMetadata[]): CompilePipeMetadata[] { var visitor = new PipeVisitor(); templateVisitAll(visitor, template); return allPipes.filter((pipeMeta) => SetWrapper.has(visitor.collector.pipes, pipeMeta.name)); } class PipeVisitor implements TemplateAstVisitor { collector: PipeCollector = new PipeCollector(); visitBoundText(ast: BoundTextAst, context: any): any { ast.value.visit(this.collector); return null; } visitText(ast: TextAst, context: any): any { return null; } visitNgContent(ast: NgContentAst, context: any): any { return null; } visitElement(ast: ElementAst, context: any): any { templateVisitAll(this, ast.inputs); templateVisitAll(this, ast.outputs); templateVisitAll(this, ast.directives); templateVisitAll(this, ast.children); return null; } visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { templateVisitAll(this, ast.outputs); templateVisitAll(this, ast.directives); templateVisitAll(this, ast.children); return null; } visitVariable(ast: VariableAst, ctx: any): any { return null; } visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any { return null; } visitDirective(ast: DirectiveAst, ctx: any): any { templateVisitAll(this, ast.inputs); templateVisitAll(this, ast.hostEvents); templateVisitAll(this, ast.hostProperties); return null; } visitEvent(ast: BoundEventAst, eventTargetAndNames: Map): any { ast.handler.visit(this.collector); return null; } visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { ast.value.visit(this.collector); return null; } visitElementProperty(ast: BoundElementPropertyAst, context: any): any { ast.value.visit(this.collector); return null; } }