2015-01-30 09:43:21 +01:00
|
|
|
import {Type, isBlank, isPresent, BaseException, normalizeBlank, stringify} from 'angular2/src/facade/lang';
|
2015-02-05 13:08:05 -08:00
|
|
|
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
2015-01-30 09:43:21 +01:00
|
|
|
import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection';
|
2015-02-05 13:08:05 -08:00
|
|
|
import {DOM, Element} from 'angular2/src/facade/dom';
|
2014-11-11 17:33:47 -08:00
|
|
|
|
2015-02-05 13:08:05 -08:00
|
|
|
import {ChangeDetection, Parser} from 'angular2/change_detection';
|
2014-11-11 17:33:47 -08:00
|
|
|
|
2014-11-20 12:07:48 -08:00
|
|
|
import {DirectiveMetadataReader} from './directive_metadata_reader';
|
2014-11-11 17:33:47 -08:00
|
|
|
import {ProtoView} from './view';
|
|
|
|
import {CompilePipeline} from './pipeline/compile_pipeline';
|
|
|
|
import {CompileElement} from './pipeline/compile_element';
|
|
|
|
import {createDefaultSteps} from './pipeline/default_steps';
|
2014-09-19 16:38:37 -07:00
|
|
|
import {TemplateLoader} from './template_loader';
|
2015-02-12 14:44:59 +01:00
|
|
|
import {TemplateResolver} from './template_resolver';
|
2014-12-23 10:45:20 -08:00
|
|
|
import {DirectiveMetadata} from './directive_metadata';
|
2015-02-12 14:44:59 +01:00
|
|
|
import {Template} from '../annotations/template';
|
2015-01-30 09:43:21 +01:00
|
|
|
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
2015-02-06 13:38:52 -08:00
|
|
|
import {CompileStep} from './pipeline/compile_step';
|
2015-02-24 16:05:45 +01:00
|
|
|
import {ComponentUrlMapper} from './component_url_mapper';
|
|
|
|
import {UrlResolver} from './url_resolver';
|
2014-09-19 23:03:36 +00:00
|
|
|
|
2015-02-06 15:41:02 -08:00
|
|
|
|
2014-12-02 13:21:39 -08:00
|
|
|
/**
|
|
|
|
* Cache that stores the ProtoView of the template of a component.
|
|
|
|
* Used to prevent duplicate work and resolve cyclic dependencies.
|
|
|
|
*/
|
|
|
|
export class CompilerCache {
|
|
|
|
_cache:Map;
|
|
|
|
constructor() {
|
|
|
|
this._cache = MapWrapper.create();
|
|
|
|
}
|
|
|
|
|
|
|
|
set(component:Type, protoView:ProtoView) {
|
|
|
|
MapWrapper.set(this._cache, component, protoView);
|
|
|
|
}
|
|
|
|
|
|
|
|
get(component:Type):ProtoView {
|
|
|
|
var result = MapWrapper.get(this._cache, component);
|
2015-01-30 09:43:21 +01:00
|
|
|
return normalizeBlank(result);
|
2014-12-02 13:21:39 -08:00
|
|
|
}
|
2014-12-22 17:50:10 -08:00
|
|
|
|
|
|
|
clear() {
|
2015-01-30 09:43:21 +01:00
|
|
|
MapWrapper.clear(this._cache);
|
2014-12-22 17:50:10 -08:00
|
|
|
}
|
2014-12-02 13:21:39 -08:00
|
|
|
}
|
|
|
|
|
2014-11-11 17:33:47 -08:00
|
|
|
/**
|
|
|
|
* The compiler loads and translates the html templates of components into
|
|
|
|
* nested ProtoViews. To decompose its functionality it uses
|
|
|
|
* the CompilePipeline and the CompileSteps.
|
|
|
|
*/
|
2014-09-19 23:03:36 +00:00
|
|
|
export class Compiler {
|
2014-11-20 12:07:48 -08:00
|
|
|
_reader: DirectiveMetadataReader;
|
2014-11-21 21:19:23 -08:00
|
|
|
_parser:Parser;
|
2014-12-02 13:21:39 -08:00
|
|
|
_compilerCache:CompilerCache;
|
2015-01-21 12:05:52 -08:00
|
|
|
_changeDetection:ChangeDetection;
|
2015-01-30 09:43:21 +01:00
|
|
|
_templateLoader:TemplateLoader;
|
|
|
|
_compiling:Map<Type, Promise>;
|
|
|
|
_shadowDomStrategy: ShadowDomStrategy;
|
|
|
|
_shadowDomDirectives: List<DirectiveMetadata>;
|
2015-02-12 14:44:59 +01:00
|
|
|
_templateResolver: TemplateResolver;
|
2015-02-24 16:05:45 +01:00
|
|
|
_componentUrlMapper: ComponentUrlMapper;
|
|
|
|
_urlResolver: UrlResolver;
|
|
|
|
_appUrl: string;
|
2015-01-30 09:43:21 +01:00
|
|
|
|
|
|
|
constructor(changeDetection:ChangeDetection,
|
|
|
|
templateLoader:TemplateLoader,
|
|
|
|
reader: DirectiveMetadataReader,
|
|
|
|
parser:Parser,
|
|
|
|
cache:CompilerCache,
|
2015-02-12 14:44:59 +01:00
|
|
|
shadowDomStrategy: ShadowDomStrategy,
|
2015-02-24 16:05:45 +01:00
|
|
|
templateResolver: TemplateResolver,
|
|
|
|
componentUrlMapper: ComponentUrlMapper,
|
|
|
|
urlResolver: UrlResolver) {
|
2015-01-21 12:05:52 -08:00
|
|
|
this._changeDetection = changeDetection;
|
2014-11-20 12:07:48 -08:00
|
|
|
this._reader = reader;
|
2014-11-11 17:33:47 -08:00
|
|
|
this._parser = parser;
|
2014-12-02 13:21:39 -08:00
|
|
|
this._compilerCache = cache;
|
2015-01-30 09:43:21 +01:00
|
|
|
this._templateLoader = templateLoader;
|
|
|
|
this._compiling = MapWrapper.create();
|
|
|
|
this._shadowDomStrategy = shadowDomStrategy;
|
|
|
|
this._shadowDomDirectives = [];
|
|
|
|
var types = shadowDomStrategy.polyfillDirectives();
|
|
|
|
for (var i = 0; i < types.length; i++) {
|
|
|
|
ListWrapper.push(this._shadowDomDirectives, reader.read(types[i]));
|
|
|
|
}
|
2015-02-12 14:44:59 +01:00
|
|
|
this._templateResolver = templateResolver;
|
2015-02-24 16:05:45 +01:00
|
|
|
this._componentUrlMapper = componentUrlMapper;
|
|
|
|
this._urlResolver = urlResolver;
|
|
|
|
this._appUrl = urlResolver.resolve(null, './');
|
2014-09-19 16:38:37 -07:00
|
|
|
}
|
|
|
|
|
2015-02-12 14:44:59 +01:00
|
|
|
createSteps(component:Type, template: Template):List<CompileStep> {
|
|
|
|
// Merge directive metadata (from the template and from the shadow dom strategy)
|
|
|
|
var dirMetadata = [];
|
|
|
|
var tplMetadata = ListWrapper.map(this._flattenDirectives(template),
|
|
|
|
(d) => this._reader.read(d));
|
|
|
|
dirMetadata = ListWrapper.concat(dirMetadata, tplMetadata);
|
|
|
|
dirMetadata = ListWrapper.concat(dirMetadata, this._shadowDomDirectives);
|
|
|
|
|
|
|
|
var cmpMetadata = this._reader.read(component);
|
|
|
|
|
2015-02-24 16:05:45 +01:00
|
|
|
var templateUrl = this._templateLoader.getTemplateUrl(template);
|
|
|
|
|
2015-02-12 14:44:59 +01:00
|
|
|
return createDefaultSteps(this._changeDetection, this._parser, cmpMetadata, dirMetadata,
|
2015-02-24 16:05:45 +01:00
|
|
|
this._shadowDomStrategy, templateUrl);
|
2014-09-19 23:03:36 +00:00
|
|
|
}
|
|
|
|
|
2015-02-12 14:44:59 +01:00
|
|
|
compile(component: Type):Promise<ProtoView> {
|
|
|
|
var protoView = this._compile(component);
|
2015-02-06 08:57:49 +01:00
|
|
|
return PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView);
|
2014-11-11 17:33:47 -08:00
|
|
|
}
|
2014-09-19 16:38:37 -07:00
|
|
|
|
2015-02-06 08:57:49 +01:00
|
|
|
// TODO(vicb): union type return ProtoView or Promise<ProtoView>
|
2015-02-12 14:44:59 +01:00
|
|
|
_compile(component: Type) {
|
|
|
|
var protoView = this._compilerCache.get(component);
|
2015-02-06 08:57:49 +01:00
|
|
|
if (isPresent(protoView)) {
|
2015-01-30 09:43:21 +01:00
|
|
|
// The component has already been compiled into a ProtoView,
|
|
|
|
// returns a resolved Promise.
|
2015-02-06 08:57:49 +01:00
|
|
|
return protoView;
|
2014-12-02 13:21:39 -08:00
|
|
|
}
|
|
|
|
|
2015-02-12 14:44:59 +01:00
|
|
|
var pvPromise = MapWrapper.get(this._compiling, component);
|
2015-01-30 09:43:21 +01:00
|
|
|
if (isPresent(pvPromise)) {
|
|
|
|
// The component is already being compiled, attach to the existing Promise
|
|
|
|
// instead of re-compiling the component.
|
|
|
|
// It happens when a template references a component multiple times.
|
|
|
|
return pvPromise;
|
2014-11-11 17:33:47 -08:00
|
|
|
}
|
2014-12-02 13:21:39 -08:00
|
|
|
|
2015-02-12 14:44:59 +01:00
|
|
|
var template = this._templateResolver.resolve(component);
|
2015-01-30 09:43:21 +01:00
|
|
|
|
2015-02-24 16:05:45 +01:00
|
|
|
var componentUrl = this._componentUrlMapper.getUrl(component);
|
|
|
|
var baseUrl = this._urlResolver.resolve(this._appUrl, componentUrl);
|
|
|
|
this._templateLoader.setBaseUrl(template, baseUrl);
|
|
|
|
|
2015-02-12 14:44:59 +01:00
|
|
|
var tplElement = this._templateLoader.load(template);
|
|
|
|
|
|
|
|
if (PromiseWrapper.isPromise(tplElement)) {
|
|
|
|
pvPromise = PromiseWrapper.then(tplElement,
|
|
|
|
(el) => this._compileTemplate(template, el, component),
|
|
|
|
(_) => { throw new BaseException(`Failed to load the template for ${stringify(component)}`); }
|
2015-02-06 08:57:49 +01:00
|
|
|
);
|
2015-02-12 14:44:59 +01:00
|
|
|
MapWrapper.set(this._compiling, component, pvPromise);
|
2015-02-06 08:57:49 +01:00
|
|
|
return pvPromise;
|
|
|
|
}
|
2015-01-30 09:43:21 +01:00
|
|
|
|
2015-02-12 14:44:59 +01:00
|
|
|
return this._compileTemplate(template, tplElement, component);
|
2015-01-30 09:43:21 +01:00
|
|
|
}
|
|
|
|
|
2015-02-06 08:57:49 +01:00
|
|
|
// TODO(vicb): union type return ProtoView or Promise<ProtoView>
|
2015-02-12 14:44:59 +01:00
|
|
|
_compileTemplate(template: Template, tplElement: Element, component: Type) {
|
|
|
|
var pipeline = new CompilePipeline(this.createSteps(component, template));
|
2015-02-06 15:41:02 -08:00
|
|
|
var compilationCtxtDescription = stringify(this._reader.read(component).type);
|
|
|
|
var compileElements;
|
|
|
|
|
|
|
|
try {
|
|
|
|
compileElements = pipeline.process(tplElement, compilationCtxtDescription);
|
|
|
|
} catch(ex) {
|
|
|
|
return PromiseWrapper.reject(ex);
|
|
|
|
}
|
|
|
|
|
2015-01-30 09:43:21 +01:00
|
|
|
var protoView = compileElements[0].inheritedProtoView;
|
2014-11-11 17:33:47 -08:00
|
|
|
|
2015-01-30 09:43:21 +01:00
|
|
|
// Populate the cache before compiling the nested components,
|
|
|
|
// so that components can reference themselves in their template.
|
2015-02-12 14:44:59 +01:00
|
|
|
this._compilerCache.set(component, protoView);
|
|
|
|
MapWrapper.delete(this._compiling, component);
|
2015-01-30 09:43:21 +01:00
|
|
|
|
|
|
|
// Compile all the components from the template
|
2015-02-06 08:57:49 +01:00
|
|
|
var nestedPVPromises = [];
|
2015-01-30 09:43:21 +01:00
|
|
|
for (var i = 0; i < compileElements.length; i++) {
|
2014-11-11 17:33:47 -08:00
|
|
|
var ce = compileElements[i];
|
|
|
|
if (isPresent(ce.componentDirective)) {
|
2015-02-06 08:57:49 +01:00
|
|
|
this._compileNestedProtoView(ce, nestedPVPromises);
|
2014-11-11 17:33:47 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-24 16:05:45 +01:00
|
|
|
if (protoView.stylePromises.length > 0) {
|
|
|
|
// The protoView is ready after all asynchronous styles are ready
|
|
|
|
var syncProtoView = protoView;
|
|
|
|
protoView = PromiseWrapper.all(syncProtoView.stylePromises).then((_) => syncProtoView);
|
|
|
|
}
|
|
|
|
|
2015-02-06 08:57:49 +01:00
|
|
|
if (nestedPVPromises.length > 0) {
|
|
|
|
// Returns ProtoView Promise when there are any asynchronous nested ProtoViews.
|
|
|
|
// The promise will resolved after nested ProtoViews are compiled.
|
|
|
|
return PromiseWrapper.then(PromiseWrapper.all(nestedPVPromises),
|
|
|
|
(_) => protoView,
|
2015-02-12 14:44:59 +01:00
|
|
|
(e) => { throw new BaseException(`${e.message} -> Failed to compile ${stringify(component)}`); }
|
2015-02-06 08:57:49 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return protoView;
|
2015-01-30 09:43:21 +01:00
|
|
|
}
|
|
|
|
|
2015-02-12 14:44:59 +01:00
|
|
|
_compileNestedProtoView(ce: CompileElement, promises: List<Promise>) {
|
|
|
|
var protoView = this._compile(ce.componentDirective.type);
|
2015-02-06 08:57:49 +01:00
|
|
|
|
|
|
|
if (PromiseWrapper.isPromise(protoView)) {
|
|
|
|
ListWrapper.push(promises, protoView);
|
|
|
|
protoView.then(function (protoView) {
|
|
|
|
ce.inheritedElementBinder.nestedProtoView = protoView;
|
|
|
|
});
|
|
|
|
} else {
|
2015-01-30 09:43:21 +01:00
|
|
|
ce.inheritedElementBinder.nestedProtoView = protoView;
|
2015-02-06 08:57:49 +01:00
|
|
|
}
|
2014-11-11 17:33:47 -08:00
|
|
|
}
|
2015-02-12 14:44:59 +01:00
|
|
|
|
|
|
|
_flattenDirectives(template: Template):List<Type> {
|
|
|
|
if (isBlank(template.directives)) return [];
|
|
|
|
|
|
|
|
var directives = [];
|
|
|
|
this._flattenList(template.directives, directives);
|
|
|
|
|
|
|
|
return directives;
|
|
|
|
}
|
|
|
|
|
|
|
|
_flattenList(tree:List<any>, out:List<Type>) {
|
|
|
|
for (var i = 0; i < tree.length; i++) {
|
|
|
|
var item = tree[i];
|
|
|
|
if (ListWrapper.isList(item)) {
|
|
|
|
this._flattenList(item, out);
|
|
|
|
} else {
|
|
|
|
ListWrapper.push(out, item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-28 16:29:11 -07:00
|
|
|
}
|
2015-02-12 14:44:59 +01:00
|
|
|
|
|
|
|
|