import {ComponentFactory, ComponentResolver, Injectable} from '@angular/core'; import {BaseException} from '../src/facade/exceptions'; import {IS_DART, Type, isBlank, isString} from '../src/facade/lang'; import {ListWrapper,} from '../src/facade/collection'; import {PromiseWrapper} from '../src/facade/async'; import {createHostComponentMeta, CompileDirectiveMetadata, CompilePipeMetadata, CompileIdentifierMetadata} from './compile_metadata'; import {TemplateAst,} from './template_ast'; import {StyleCompiler, StylesCompileDependency, StylesCompileResult} from './style_compiler'; import {ViewCompiler} from './view_compiler/view_compiler'; import {TemplateParser} from './template_parser'; import {DirectiveNormalizer} from './directive_normalizer'; import {CompileMetadataResolver} from './metadata_resolver'; import {CompilerConfig} from './config'; import * as ir from './output/output_ast'; import {jitStatements} from './output/output_jit'; import {interpretStatements} from './output/output_interpreter'; import {InterpretiveAppViewInstanceFactory} from './output/interpretive_view'; import {XHR} from './xhr'; /** * 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 RuntimeCompiler implements ComponentResolver { private _styleCache: Map> = new Map>(); private _hostCacheKeys = new Map(); private _compiledTemplateCache = new Map(); private _compiledTemplateDone = new Map>(); constructor( private _metadataResolver: CompileMetadataResolver, private _templateNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _xhr: XHR, private _genConfig: CompilerConfig) {} resolveComponent(component: Type|string): Promise> { if (isString(component)) { return PromiseWrapper.reject( new BaseException(`Cannot resolve component using '${component}'.`), null); } let componentType = component; var compMeta: CompileDirectiveMetadata = this._metadataResolver.getDirectiveMetadata(componentType); var hostCacheKey = this._hostCacheKeys.get(componentType); if (isBlank(hostCacheKey)) { hostCacheKey = new Object(); this._hostCacheKeys.set(componentType, hostCacheKey); assertComponent(compMeta); var hostMeta: CompileDirectiveMetadata = createHostComponentMeta(compMeta.type, compMeta.selector); this._loadAndCompileComponent(hostCacheKey, hostMeta, [compMeta], [], []); } return this._compiledTemplateDone.get(hostCacheKey) .then( (compiledTemplate: CompiledTemplate) => new ComponentFactory( compMeta.selector, compiledTemplate.viewFactory, componentType)); } clearCache(): void { this._styleCache.clear(); this._compiledTemplateCache.clear(); this._compiledTemplateDone.clear(); this._hostCacheKeys.clear(); } private _loadAndCompileComponent( cacheKey: any, compMeta: CompileDirectiveMetadata, viewDirectives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[], compilingComponentsPath: any[]): CompiledTemplate { 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._compileComponentStyles(compMeta)].concat(viewDirectives.map( dirMeta => this._templateNormalizer.normalizeDirective(dirMeta)))) .then((stylesAndNormalizedViewDirMetas: any[]) => { var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1); var styles = stylesAndNormalizedViewDirMetas[0]; var parsedTemplate = this._templateParser.parse( compMeta, compMeta.template.template, normalizedViewDirMetas, pipes, compMeta.type.name); var childPromises: Promise[] = []; compiledTemplate.init(this._compileComponent( compMeta, parsedTemplate, styles, pipes, compilingComponentsPath, childPromises)); return PromiseWrapper.all(childPromises).then((_) => { return compiledTemplate; }); }); this._compiledTemplateDone.set(cacheKey, done); } return compiledTemplate; } private _compileComponent( compMeta: CompileDirectiveMetadata, parsedTemplate: TemplateAst[], styles: string[], pipes: CompilePipeMetadata[], compilingComponentsPath: any[], childPromises: Promise[]): Function { var compileResult = this._viewCompiler.compileComponent( compMeta, parsedTemplate, new ir.ExternalExpr(new CompileIdentifierMetadata({runtime: styles})), pipes); compileResult.dependencies.forEach((dep) => { var childCompilingComponentsPath = ListWrapper.clone(compilingComponentsPath); var childCacheKey = dep.comp.type.runtime; var childViewDirectives: CompileDirectiveMetadata[] = this._metadataResolver.getViewDirectivesMetadata(dep.comp.type.runtime); var childViewPipes: CompilePipeMetadata[] = this._metadataResolver.getViewPipesMetadata(dep.comp.type.runtime); var childIsRecursive = childCompilingComponentsPath.indexOf(childCacheKey) > -1 || childViewDirectives.some( dir => childCompilingComponentsPath.indexOf(dir.type.runtime) > -1); childCompilingComponentsPath.push(childCacheKey); var childComp = this._loadAndCompileComponent( dep.comp.type.runtime, dep.comp, childViewDirectives, childViewPipes, childCompilingComponentsPath); dep.factoryPlaceholder.runtime = childComp.proxyViewFactory; dep.factoryPlaceholder.name = `viewFactory_${dep.comp.type.name}`; if (!childIsRecursive) { // Only wait for a child if it is not a cycle childPromises.push(this._compiledTemplateDone.get(childCacheKey)); } }); var factory: any; if (IS_DART || !this._genConfig.useJit) { factory = interpretStatements( compileResult.statements, compileResult.viewFactoryVar, new InterpretiveAppViewInstanceFactory()); } else { factory = jitStatements( `${compMeta.type.name}.template.js`, compileResult.statements, compileResult.viewFactoryVar); } return factory; } private _compileComponentStyles(compMeta: CompileDirectiveMetadata): Promise { var compileResult = this._styleCompiler.compileComponent(compMeta); return this._resolveStylesCompileResult(compMeta.type.name, compileResult); } private _resolveStylesCompileResult(sourceUrl: string, result: StylesCompileResult): Promise { var promises = result.dependencies.map((dep) => this._loadStylesheetDep(dep)); return PromiseWrapper.all(promises) .then((cssTexts) => { var nestedCompileResultPromises: Promise[] = []; for (var i = 0; i < result.dependencies.length; i++) { var dep = result.dependencies[i]; var cssText = cssTexts[i]; var nestedCompileResult = this._styleCompiler.compileStylesheet(dep.moduleUrl, cssText, dep.isShimmed); nestedCompileResultPromises.push( this._resolveStylesCompileResult(dep.moduleUrl, nestedCompileResult)); } return PromiseWrapper.all(nestedCompileResultPromises); }) .then((nestedStylesArr) => { for (var i = 0; i < result.dependencies.length; i++) { var dep = result.dependencies[i]; dep.valuePlaceholder.runtime = nestedStylesArr[i]; dep.valuePlaceholder.name = `importedStyles${i}`; } if (IS_DART || !this._genConfig.useJit) { return interpretStatements( result.statements, result.stylesVar, new InterpretiveAppViewInstanceFactory()); } else { return jitStatements(`${sourceUrl}.css.js`, result.statements, result.stylesVar); } }); } private _loadStylesheetDep(dep: StylesCompileDependency): Promise { var cacheKey = `${dep.moduleUrl}${dep.isShimmed ? '.shim' : ''}`; var cssTextPromise = this._styleCache.get(cacheKey); if (isBlank(cssTextPromise)) { cssTextPromise = this._xhr.get(dep.moduleUrl); this._styleCache.set(cacheKey, cssTextPromise); } return cssTextPromise; } } class CompiledTemplate { viewFactory: Function = null; proxyViewFactory: Function; constructor() { this.proxyViewFactory = (viewUtils: any /** TODO #9100 */, childInjector: any /** TODO #9100 */, contextEl: any /** TODO #9100 */) => this.viewFactory(viewUtils, childInjector, contextEl); } 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.`); } }