feat(compiler): allow recursive components
This commit is contained in:
parent
bc6f0dba46
commit
9c2d411450
|
@ -9,7 +9,7 @@ import {Parser} from 'change_detection/parser/parser';
|
||||||
import {Lexer} from 'change_detection/parser/lexer';
|
import {Lexer} from 'change_detection/parser/lexer';
|
||||||
import {ProtoRecordRange} from 'change_detection/record_range';
|
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 {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
|
||||||
|
|
||||||
import {Component} from 'core/annotations/annotations';
|
import {Component} from 'core/annotations/annotations';
|
||||||
|
@ -81,7 +81,7 @@ function setup() {
|
||||||
});
|
});
|
||||||
|
|
||||||
var reader = new CachingDirectiveMetadataReader();
|
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);
|
annotatedComponent = reader.annotatedType(BenchmarkComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ export function main() {
|
||||||
benchmarkStep('run', function() {
|
benchmarkStep('run', function() {
|
||||||
// Need to clone every time as the compiler might modify the template!
|
// Need to clone every time as the compiler might modify the template!
|
||||||
var cloned = DOM.clone(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() {
|
benchmarkStep('run', function() {
|
||||||
// Need to clone every time as the compiler might modify the template!
|
// Need to clone every time as the compiler might modify the template!
|
||||||
var cloned = DOM.clone(template);
|
var cloned = DOM.clone(template);
|
||||||
compiler.compileWithCache(null, annotatedComponent, cloned);
|
compiler.compileAllLoaded(null, annotatedComponent, cloned);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {Injector, bind, OpaqueToken} from 'di/di';
|
import {Injector, bind, OpaqueToken} from 'di/di';
|
||||||
import {Type, FIELD, isBlank, isPresent, BaseException} from 'facade/lang';
|
import {Type, FIELD, isBlank, isPresent, BaseException} from 'facade/lang';
|
||||||
import {DOM, Element} from 'facade/dom';
|
import {DOM, Element} from 'facade/dom';
|
||||||
import {Compiler} from './compiler/compiler';
|
import {Compiler, CompilerCache} from './compiler/compiler';
|
||||||
import {ProtoView} from './compiler/view';
|
import {ProtoView} from './compiler/view';
|
||||||
import {Reflector, reflector} from 'reflection/reflection';
|
import {Reflector, reflector} from 'reflection/reflection';
|
||||||
import {Parser} from 'change_detection/parser/parser';
|
import {Parser} from 'change_detection/parser/parser';
|
||||||
|
@ -17,7 +17,7 @@ var _rootInjector: Injector;
|
||||||
|
|
||||||
// Contains everything that is safe to share between applications.
|
// Contains everything that is safe to share between applications.
|
||||||
var _rootBindings = [
|
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');
|
export var appViewToken = new OpaqueToken('AppView');
|
||||||
|
|
|
@ -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 {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 {DOM, Element} from 'facade/dom';
|
||||||
|
|
||||||
import {Parser} from 'change_detection/parser/parser';
|
import {Parser} from 'change_detection/parser/parser';
|
||||||
|
@ -14,6 +14,30 @@ import {TemplateLoader} from './template_loader';
|
||||||
import {AnnotatedType} from './annotated_type';
|
import {AnnotatedType} from './annotated_type';
|
||||||
import {Component} from '../annotations/annotations';
|
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
|
* The compiler loads and translates the html templates of components into
|
||||||
* nested ProtoViews. To decompose its functionality it uses
|
* nested ProtoViews. To decompose its functionality it uses
|
||||||
|
@ -23,10 +47,12 @@ export class Compiler {
|
||||||
_templateLoader:TemplateLoader;
|
_templateLoader:TemplateLoader;
|
||||||
_reader: DirectiveMetadataReader;
|
_reader: DirectiveMetadataReader;
|
||||||
_parser:Parser;
|
_parser:Parser;
|
||||||
constructor(templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser) {
|
_compilerCache:CompilerCache;
|
||||||
|
constructor(templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser, cache:CompilerCache) {
|
||||||
this._templateLoader = templateLoader;
|
this._templateLoader = templateLoader;
|
||||||
this._reader = reader;
|
this._reader = reader;
|
||||||
this._parser = parser;
|
this._parser = parser;
|
||||||
|
this._compilerCache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
createSteps(component:AnnotatedType):List<CompileStep> {
|
createSteps(component:AnnotatedType):List<CompileStep> {
|
||||||
|
@ -40,15 +66,22 @@ export class Compiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {
|
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {
|
||||||
// TODO load all components transitively from the cache first
|
var templateCache = null;
|
||||||
var cache = null;
|
// TODO load all components that have urls
|
||||||
return PromiseWrapper.resolve(this.compileWithCache(
|
// transitively via the _templateLoader and store them in templateCache
|
||||||
cache, this._reader.annotatedType(component), templateRoot)
|
|
||||||
|
return PromiseWrapper.resolve(this.compileAllLoaded(
|
||||||
|
templateCache, this._reader.annotatedType(component), templateRoot)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public so that we can compile in sync in performance tests.
|
// 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)) {
|
if (isBlank(templateRoot)) {
|
||||||
// TODO: read out the cache if templateRoot = null. Could contain:
|
// TODO: read out the cache if templateRoot = null. Could contain:
|
||||||
// - templateRoot string
|
// - templateRoot string
|
||||||
|
@ -57,15 +90,18 @@ export class Compiler {
|
||||||
var annotation:any = component.annotation;
|
var annotation:any = component.annotation;
|
||||||
templateRoot = DOM.createTemplate(annotation.template.inline);
|
templateRoot = DOM.createTemplate(annotation.template.inline);
|
||||||
}
|
}
|
||||||
|
|
||||||
var pipeline = new CompilePipeline(this.createSteps(component));
|
var pipeline = new CompilePipeline(this.createSteps(component));
|
||||||
var compileElements = pipeline.process(templateRoot);
|
var compileElements = pipeline.process(templateRoot);
|
||||||
var rootProtoView = compileElements[0].inheritedProtoView;
|
rootProtoView = compileElements[0].inheritedProtoView;
|
||||||
// TODO: put the rootProtoView into the cache to support recursive templates!
|
// 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<compileElements.length; i++) {
|
for (var i=0; i<compileElements.length; i++) {
|
||||||
var ce = compileElements[i];
|
var ce = compileElements[i];
|
||||||
if (isPresent(ce.componentDirective)) {
|
if (isPresent(ce.componentDirective)) {
|
||||||
ce.inheritedElementBinder.nestedProtoView = this.compileWithCache(cache, ce.componentDirective, null);
|
ce.inheritedElementBinder.nestedProtoView = this.compileAllLoaded(templateCache, ce.componentDirective, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {describe, beforeEach, it, expect, ddescribe, iit} from 'test_lib/test_li
|
||||||
import {DOM} from 'facade/dom';
|
import {DOM} from 'facade/dom';
|
||||||
import {List} from 'facade/collection';
|
import {List} from 'facade/collection';
|
||||||
|
|
||||||
import {Compiler} from 'core/compiler/compiler';
|
import {Compiler, CompilerCache} from 'core/compiler/compiler';
|
||||||
import {ProtoView} from 'core/compiler/view';
|
import {ProtoView} from 'core/compiler/view';
|
||||||
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
|
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
|
||||||
import {TemplateLoader} from 'core/compiler/template_loader';
|
import {TemplateLoader} from 'core/compiler/template_loader';
|
||||||
|
@ -25,7 +25,7 @@ export function main() {
|
||||||
|
|
||||||
function createCompiler(processClosure) {
|
function createCompiler(processClosure) {
|
||||||
var steps = [new MockStep(processClosure)];
|
var steps = [new MockStep(processClosure)];
|
||||||
return new TestableCompiler(null, reader, new Parser(new Lexer()), steps);
|
return new TestableCompiler(reader, steps);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should run the steps and return the ProtoView of the root element', (done) => {
|
it('should run the steps and return the ProtoView of the root element', (done) => {
|
||||||
|
@ -77,6 +77,35 @@ export function main() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should cache components', (done) => {
|
||||||
|
var el = createElement('<div></div>');
|
||||||
|
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 {}
|
class NestedComponent {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: new TemplateConfig({
|
||||||
|
inline: '<div rec-comp></div>'
|
||||||
|
}),
|
||||||
|
selector: 'rec-comp'
|
||||||
|
})
|
||||||
|
class RecursiveComponent {}
|
||||||
|
|
||||||
class TestableCompiler extends Compiler {
|
class TestableCompiler extends Compiler {
|
||||||
steps:List;
|
steps:List;
|
||||||
constructor(templateLoader:TemplateLoader, reader:DirectiveMetadataReader, parser, steps:List<CompileStep>) {
|
constructor(reader:DirectiveMetadataReader, steps:List<CompileStep>) {
|
||||||
super(templateLoader, reader, parser);
|
super(null, reader, new Parser(new Lexer()), new CompilerCache());
|
||||||
this.steps = steps;
|
this.steps = steps;
|
||||||
}
|
}
|
||||||
createSteps(component):List<CompileStep> {
|
createSteps(component):List<CompileStep> {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {ChangeDetector} from 'change_detection/change_detector';
|
||||||
import {Parser} from 'change_detection/parser/parser';
|
import {Parser} from 'change_detection/parser/parser';
|
||||||
import {Lexer} from 'change_detection/parser/lexer';
|
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 {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
|
||||||
|
|
||||||
import {Decorator, Component, Template} from 'core/annotations/annotations';
|
import {Decorator, Component, Template} from 'core/annotations/annotations';
|
||||||
|
@ -21,7 +21,7 @@ export function main() {
|
||||||
var compiler;
|
var compiler;
|
||||||
|
|
||||||
beforeEach( () => {
|
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() {
|
describe('react to record changes', function() {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {Component, Decorator, TemplateConfig, NgElement} from 'core/core';
|
||||||
import {Parser} from 'change_detection/parser/parser';
|
import {Parser} from 'change_detection/parser/parser';
|
||||||
import {Lexer} from 'change_detection/parser/lexer';
|
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 {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
|
||||||
import {TemplateLoader} from 'core/compiler/template_loader';
|
import {TemplateLoader} from 'core/compiler/template_loader';
|
||||||
|
|
||||||
|
@ -36,8 +36,14 @@ function setup() {
|
||||||
});
|
});
|
||||||
|
|
||||||
reflector.registerType(Compiler, {
|
reflector.registerType(Compiler, {
|
||||||
"factory": (templateLoader, reader, parser) => new Compiler(templateLoader, reader, parser),
|
"factory": (templateLoader, reader, parser, compilerCache) => new Compiler(templateLoader, reader, parser, compilerCache),
|
||||||
"parameters": [[TemplateLoader], [DirectiveMetadataReader], [Parser]],
|
"parameters": [[TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]],
|
||||||
|
"annotations": []
|
||||||
|
});
|
||||||
|
|
||||||
|
reflector.registerType(CompilerCache, {
|
||||||
|
"factory": () => new CompilerCache(),
|
||||||
|
"parameters": [],
|
||||||
"annotations": []
|
"annotations": []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue