This reverts commit 86c40f84741c184123f6e96c87d932e4804e7add. Reason: new issues were discovered during the g3sync. @vsavkin is working on fixing them.
453 lines
19 KiB
TypeScript
453 lines
19 KiB
TypeScript
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<Type, any>();
|
|
private _compiledTemplateCache = new Map<any, CompiledTemplate>();
|
|
private _compiledTemplateDone = new Map<any, Promise<CompiledTemplate>>();
|
|
|
|
constructor(private _runtimeMetadataResolver: RuntimeMetadataResolver,
|
|
private _templateNormalizer: TemplateNormalizer,
|
|
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
|
private _cdCompiler: ChangeDetectionCompiler,
|
|
private _protoViewCompiler: ProtoViewCompiler, private _viewCompiler: ViewCompiler,
|
|
private _resolvedMetadataCache: ResolvedMetadataCache,
|
|
private _genConfig: ChangeDetectorGenConfig) {}
|
|
|
|
normalizeDirectiveMetadata(directive: CompileDirectiveMetadata):
|
|
Promise<CompileDirectiveMetadata> {
|
|
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<HostViewFactory> {
|
|
var compMeta: CompileDirectiveMetadata =
|
|
this._runtimeMetadataResolver.getDirectiveMetadata(type);
|
|
var hostCacheKey = this._hostCacheKeys.get(type);
|
|
if (isBlank(hostCacheKey)) {
|
|
hostCacheKey = new Object();
|
|
this._hostCacheKeys.set(type, hostCacheKey);
|
|
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 = <CompileDirectiveMetadata>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 = <CompileDirectiveMetadata[]>removeDuplicates(viewDirectives);
|
|
let uniqViewPipes = <CompilePipeMetadata[]>removeDuplicates(pipes);
|
|
var compiledTemplate = this._compiledTemplateCache.get(cacheKey);
|
|
var done = this._compiledTemplateDone.get(cacheKey);
|
|
if (isBlank(compiledTemplate)) {
|
|
compiledTemplate = new CompiledTemplate();
|
|
this._compiledTemplateCache.set(cacheKey, compiledTemplate);
|
|
done = PromiseWrapper
|
|
.all([<any>this._styleCompiler.compileComponentRuntime(compMeta.template)].concat(
|
|
uniqViewDirectives.map(dirMeta => this.normalizeDirectiveMetadata(dirMeta))))
|
|
.then((stylesAndNormalizedViewDirMetas: any[]) => {
|
|
var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1);
|
|
var styles = stylesAndNormalizedViewDirMetas[0];
|
|
var parsedTemplate = this._templateParser.parse(
|
|
compMeta.template.template, normalizedViewDirMetas, uniqViewPipes,
|
|
compMeta.type.name);
|
|
|
|
var childPromises = [];
|
|
var usedDirectives = DirectiveCollector.findUsedDirectives(parsedTemplate);
|
|
usedDirectives.components.forEach(
|
|
component => this._compileNestedComponentRuntime(
|
|
component, 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<any>[]) {
|
|
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 = <CompileDirectiveMetadata[]>removeDuplicates(directives);
|
|
let uniqPipes = <CompilePipeMetadata[]>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<string, BoundEventAst>): any {
|
|
return null;
|
|
}
|
|
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
|
|
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
|
|
}
|
|
|
|
|
|
function filterPipes(template: TemplateAst[],
|
|
allPipes: CompilePipeMetadata[]): CompilePipeMetadata[] {
|
|
var visitor = new PipeVisitor();
|
|
templateVisitAll(visitor, template);
|
|
return allPipes.filter((pipeMeta) => SetWrapper.has(visitor.collector.pipes, pipeMeta.name));
|
|
}
|
|
|
|
class PipeVisitor implements TemplateAstVisitor {
|
|
collector: PipeCollector = new PipeCollector();
|
|
|
|
visitBoundText(ast: BoundTextAst, context: any): any {
|
|
ast.value.visit(this.collector);
|
|
return null;
|
|
}
|
|
visitText(ast: TextAst, context: any): any { return null; }
|
|
|
|
visitNgContent(ast: NgContentAst, context: any): any { return null; }
|
|
|
|
visitElement(ast: ElementAst, context: any): any {
|
|
templateVisitAll(this, ast.inputs);
|
|
templateVisitAll(this, ast.outputs);
|
|
templateVisitAll(this, ast.directives);
|
|
templateVisitAll(this, ast.children);
|
|
return null;
|
|
}
|
|
|
|
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
|
templateVisitAll(this, ast.outputs);
|
|
templateVisitAll(this, ast.directives);
|
|
templateVisitAll(this, ast.children);
|
|
return null;
|
|
}
|
|
visitVariable(ast: VariableAst, ctx: any): any { return null; }
|
|
visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any { return null; }
|
|
visitDirective(ast: DirectiveAst, ctx: any): any {
|
|
templateVisitAll(this, ast.inputs);
|
|
templateVisitAll(this, ast.hostEvents);
|
|
templateVisitAll(this, ast.hostProperties);
|
|
return null;
|
|
}
|
|
visitEvent(ast: BoundEventAst, eventTargetAndNames: Map<string, BoundEventAst>): any {
|
|
ast.handler.visit(this.collector);
|
|
return null;
|
|
}
|
|
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {
|
|
ast.value.visit(this.collector);
|
|
return null;
|
|
}
|
|
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {
|
|
ast.value.visit(this.collector);
|
|
return null;
|
|
}
|
|
}
|