diff --git a/modules/benchmarks/src/compiler/compiler_benchmark.js b/modules/benchmarks/src/compiler/compiler_benchmark.js index 6c2d2c5eac..67325a015a 100644 --- a/modules/benchmarks/src/compiler/compiler_benchmark.js +++ b/modules/benchmarks/src/compiler/compiler_benchmark.js @@ -9,7 +9,7 @@ import {Parser} from 'change_detection/parser/parser'; import {Lexer} from 'change_detection/parser/lexer'; import {ProtoRecordRange} from 'change_detection/record_range'; -import {Compiler} from 'core/compiler/compiler'; +import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {Component} from 'core/annotations/annotations'; @@ -81,7 +81,7 @@ function setup() { }); var reader = new CachingDirectiveMetadataReader(); - compiler = new Compiler(null, reader, new Parser(new Lexer())); + compiler = new Compiler(null, reader, new Parser(new Lexer()), new CompilerCache()); annotatedComponent = reader.annotatedType(BenchmarkComponent); } @@ -94,7 +94,7 @@ export function main() { benchmarkStep('run', function() { // Need to clone every time as the compiler might modify the template! var cloned = DOM.clone(template); - compiler.compileWithCache(null, annotatedComponent, cloned); + compiler.compileAllLoaded(null, annotatedComponent, cloned); }); }); @@ -104,7 +104,7 @@ export function main() { benchmarkStep('run', function() { // Need to clone every time as the compiler might modify the template! var cloned = DOM.clone(template); - compiler.compileWithCache(null, annotatedComponent, cloned); + compiler.compileAllLoaded(null, annotatedComponent, cloned); }); }); diff --git a/modules/core/src/application.js b/modules/core/src/application.js index 30ce233e7b..edef77c652 100644 --- a/modules/core/src/application.js +++ b/modules/core/src/application.js @@ -1,7 +1,7 @@ import {Injector, bind, OpaqueToken} from 'di/di'; import {Type, FIELD, isBlank, isPresent, BaseException} from 'facade/lang'; import {DOM, Element} from 'facade/dom'; -import {Compiler} from './compiler/compiler'; +import {Compiler, CompilerCache} from './compiler/compiler'; import {ProtoView} from './compiler/view'; import {Reflector, reflector} from 'reflection/reflection'; import {Parser} from 'change_detection/parser/parser'; @@ -17,7 +17,7 @@ var _rootInjector: Injector; // Contains everything that is safe to share between applications. var _rootBindings = [ - bind(Reflector).toValue(reflector), Compiler, TemplateLoader, DirectiveMetadataReader, Parser, Lexer + bind(Reflector).toValue(reflector), Compiler, CompilerCache, TemplateLoader, DirectiveMetadataReader, Parser, Lexer ]; export var appViewToken = new OpaqueToken('AppView'); diff --git a/modules/core/src/compiler/compiler.js b/modules/core/src/compiler/compiler.js index 92f8693ff8..b31e2f7b5c 100644 --- a/modules/core/src/compiler/compiler.js +++ b/modules/core/src/compiler/compiler.js @@ -1,6 +1,6 @@ -import {Type, FIELD, isBlank, isPresent} from 'facade/lang'; +import {Type, FIELD, isBlank, isPresent, BaseException, stringify} from 'facade/lang'; import {Promise, PromiseWrapper} from 'facade/async'; -import {List, ListWrapper} from 'facade/collection'; +import {List, ListWrapper, MapWrapper} from 'facade/collection'; import {DOM, Element} from 'facade/dom'; import {Parser} from 'change_detection/parser/parser'; @@ -14,6 +14,30 @@ import {TemplateLoader} from './template_loader'; import {AnnotatedType} from './annotated_type'; import {Component} from '../annotations/annotations'; +/** + * 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); + if (isBlank(result)) { + // need to normalize undefined to null so that type checking passes :-( + return null; + } + return result; + } +} + /** * The compiler loads and translates the html templates of components into * nested ProtoViews. To decompose its functionality it uses @@ -23,10 +47,12 @@ export class Compiler { _templateLoader:TemplateLoader; _reader: DirectiveMetadataReader; _parser:Parser; - constructor(templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser) { + _compilerCache:CompilerCache; + constructor(templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser, cache:CompilerCache) { this._templateLoader = templateLoader; this._reader = reader; this._parser = parser; + this._compilerCache = cache; } createSteps(component:AnnotatedType):List { @@ -40,15 +66,22 @@ export class Compiler { } compile(component:Type, templateRoot:Element = null):Promise { - // TODO load all components transitively from the cache first - var cache = null; - return PromiseWrapper.resolve(this.compileWithCache( - cache, this._reader.annotatedType(component), templateRoot) + var templateCache = null; + // TODO load all components that have urls + // transitively via the _templateLoader and store them in templateCache + + return PromiseWrapper.resolve(this.compileAllLoaded( + templateCache, this._reader.annotatedType(component), templateRoot) ); } // public so that we can compile in sync in performance tests. - compileWithCache(cache, component:AnnotatedType, templateRoot:Element = null):ProtoView { + compileAllLoaded(templateCache, component:AnnotatedType, templateRoot:Element = null):ProtoView { + var rootProtoView = this._compilerCache.get(component.type); + if (isPresent(rootProtoView)) { + return rootProtoView; + } + if (isBlank(templateRoot)) { // TODO: read out the cache if templateRoot = null. Could contain: // - templateRoot string @@ -57,15 +90,18 @@ export class Compiler { var annotation:any = component.annotation; templateRoot = DOM.createTemplate(annotation.template.inline); } + var pipeline = new CompilePipeline(this.createSteps(component)); var compileElements = pipeline.process(templateRoot); - var rootProtoView = compileElements[0].inheritedProtoView; - // TODO: put the rootProtoView into the cache to support recursive templates! + rootProtoView = compileElements[0].inheritedProtoView; + // Save the rootProtoView before we recurse so that we are able + // to compile components that use themselves in their template. + this._compilerCache.set(component.type, rootProtoView); for (var i=0; i { @@ -77,6 +77,35 @@ export function main() { }); + it('should cache components', (done) => { + var el = createElement('
'); + var compiler = createCompiler( (parent, current, control) => { + current.inheritedProtoView = new ProtoView(current.element, null); + }); + var firstProtoView; + compiler.compile(MainComponent, el).then( (protoView) => { + firstProtoView = protoView; + return compiler.compile(MainComponent, el); + }).then( (protoView) => { + expect(firstProtoView).toBe(protoView); + done(); + }); + + }); + + it('should allow recursive components', (done) => { + var compiler = createCompiler( (parent, current, control) => { + current.inheritedProtoView = new ProtoView(current.element, null); + current.inheritedElementBinder = current.inheritedProtoView.bindElement(null); + current.componentDirective = reader.annotatedType(RecursiveComponent); + }); + compiler.compile(RecursiveComponent, null).then( (protoView) => { + expect(protoView.elementBinders[0].nestedProtoView).toBe(protoView); + done(); + }); + + }); + }); } @@ -95,10 +124,18 @@ class MainComponent {} }) class NestedComponent {} +@Component({ + template: new TemplateConfig({ + inline: '
' + }), + selector: 'rec-comp' +}) +class RecursiveComponent {} + class TestableCompiler extends Compiler { steps:List; - constructor(templateLoader:TemplateLoader, reader:DirectiveMetadataReader, parser, steps:List) { - super(templateLoader, reader, parser); + constructor(reader:DirectiveMetadataReader, steps:List) { + super(null, reader, new Parser(new Lexer()), new CompilerCache()); this.steps = steps; } createSteps(component):List { diff --git a/modules/core/test/compiler/integration_spec.js b/modules/core/test/compiler/integration_spec.js index 3588aa9ad4..d248cf8b6c 100644 --- a/modules/core/test/compiler/integration_spec.js +++ b/modules/core/test/compiler/integration_spec.js @@ -7,7 +7,7 @@ import {ChangeDetector} from 'change_detection/change_detector'; import {Parser} from 'change_detection/parser/parser'; import {Lexer} from 'change_detection/parser/lexer'; -import {Compiler} from 'core/compiler/compiler'; +import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {Decorator, Component, Template} from 'core/annotations/annotations'; @@ -21,7 +21,7 @@ export function main() { var compiler; beforeEach( () => { - compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer())); + compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache()); }); describe('react to record changes', function() { diff --git a/modules/examples/src/hello_world/index_static.js b/modules/examples/src/hello_world/index_static.js index 70b6f06ffc..a173acd0df 100644 --- a/modules/examples/src/hello_world/index_static.js +++ b/modules/examples/src/hello_world/index_static.js @@ -4,7 +4,7 @@ import {Component, Decorator, TemplateConfig, NgElement} from 'core/core'; import {Parser} from 'change_detection/parser/parser'; import {Lexer} from 'change_detection/parser/lexer'; -import {Compiler} from 'core/compiler/compiler'; +import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {TemplateLoader} from 'core/compiler/template_loader'; @@ -36,8 +36,14 @@ function setup() { }); reflector.registerType(Compiler, { - "factory": (templateLoader, reader, parser) => new Compiler(templateLoader, reader, parser), - "parameters": [[TemplateLoader], [DirectiveMetadataReader], [Parser]], + "factory": (templateLoader, reader, parser, compilerCache) => new Compiler(templateLoader, reader, parser, compilerCache), + "parameters": [[TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]], + "annotations": [] + }); + + reflector.registerType(CompilerCache, { + "factory": () => new CompilerCache(), + "parameters": [], "annotations": [] });