mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
Fix how the compiler checks for recursive components by also considering component descendants. Previously, it only checked if the current component was evaluated previously. This failed in certain cases of mutually recursive components, causing `createAsync` in tests to not resolve. closes [7084](https://github.com/angular/angular/issues/7084)
209 lines
9.3 KiB
TypeScript
209 lines
9.3 KiB
TypeScript
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<string, Promise<string>> = new Map<string, Promise<string>>();
|
|
private _hostCacheKeys = new Map<Type, any>();
|
|
private _compiledTemplateCache = new Map<any, CompiledTemplate>();
|
|
private _compiledTemplateDone = new Map<any, Promise<CompiledTemplate>>();
|
|
|
|
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<ComponentFactory<any>> {
|
|
if (isString(component)) {
|
|
return PromiseWrapper.reject(
|
|
new BaseException(`Cannot resolve component using '${component}'.`), null);
|
|
}
|
|
|
|
let componentType = <Type>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([<any>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<any>[] = [];
|
|
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<any>[]): 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<string[]> {
|
|
var compileResult = this._styleCompiler.compileComponent(compMeta);
|
|
return this._resolveStylesCompileResult(compMeta.type.name, compileResult);
|
|
}
|
|
|
|
private _resolveStylesCompileResult(sourceUrl: string, result: StylesCompileResult):
|
|
Promise<string[]> {
|
|
var promises = result.dependencies.map((dep) => this._loadStylesheetDep(dep));
|
|
return PromiseWrapper.all(promises)
|
|
.then((cssTexts) => {
|
|
var nestedCompileResultPromises: Promise<string[]>[] = [];
|
|
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<string> {
|
|
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.`);
|
|
}
|
|
}
|