Skip to content

Commit 9c2d411

Browse files
committed
feat(compiler): allow recursive components
1 parent bc6f0db commit 9c2d411

File tree

6 files changed

+105
-26
lines changed

6 files changed

+105
-26
lines changed

modules/benchmarks/src/compiler/compiler_benchmark.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {Parser} from 'change_detection/parser/parser';
99
import {Lexer} from 'change_detection/parser/lexer';
1010
import {ProtoRecordRange} from 'change_detection/record_range';
1111

12-
import {Compiler} from 'core/compiler/compiler';
12+
import {Compiler, CompilerCache} from 'core/compiler/compiler';
1313
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
1414

1515
import {Component} from 'core/annotations/annotations';
@@ -81,7 +81,7 @@ function setup() {
8181
});
8282

8383
var reader = new CachingDirectiveMetadataReader();
84-
compiler = new Compiler(null, reader, new Parser(new Lexer()));
84+
compiler = new Compiler(null, reader, new Parser(new Lexer()), new CompilerCache());
8585
annotatedComponent = reader.annotatedType(BenchmarkComponent);
8686
}
8787

@@ -94,7 +94,7 @@ export function main() {
9494
benchmarkStep('run', function() {
9595
// Need to clone every time as the compiler might modify the template!
9696
var cloned = DOM.clone(template);
97-
compiler.compileWithCache(null, annotatedComponent, cloned);
97+
compiler.compileAllLoaded(null, annotatedComponent, cloned);
9898
});
9999
});
100100

@@ -104,7 +104,7 @@ export function main() {
104104
benchmarkStep('run', function() {
105105
// Need to clone every time as the compiler might modify the template!
106106
var cloned = DOM.clone(template);
107-
compiler.compileWithCache(null, annotatedComponent, cloned);
107+
compiler.compileAllLoaded(null, annotatedComponent, cloned);
108108
});
109109
});
110110

modules/core/src/application.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {Injector, bind, OpaqueToken} from 'di/di';
22
import {Type, FIELD, isBlank, isPresent, BaseException} from 'facade/lang';
33
import {DOM, Element} from 'facade/dom';
4-
import {Compiler} from './compiler/compiler';
4+
import {Compiler, CompilerCache} from './compiler/compiler';
55
import {ProtoView} from './compiler/view';
66
import {Reflector, reflector} from 'reflection/reflection';
77
import {Parser} from 'change_detection/parser/parser';
@@ -17,7 +17,7 @@ var _rootInjector: Injector;
1717

1818
// Contains everything that is safe to share between applications.
1919
var _rootBindings = [
20-
bind(Reflector).toValue(reflector), Compiler, TemplateLoader, DirectiveMetadataReader, Parser, Lexer
20+
bind(Reflector).toValue(reflector), Compiler, CompilerCache, TemplateLoader, DirectiveMetadataReader, Parser, Lexer
2121
];
2222

2323
export var appViewToken = new OpaqueToken('AppView');

modules/core/src/compiler/compiler.js

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {Type, FIELD, isBlank, isPresent} from 'facade/lang';
1+
import {Type, FIELD, isBlank, isPresent, BaseException, stringify} from 'facade/lang';
22
import {Promise, PromiseWrapper} from 'facade/async';
3-
import {List, ListWrapper} from 'facade/collection';
3+
import {List, ListWrapper, MapWrapper} from 'facade/collection';
44
import {DOM, Element} from 'facade/dom';
55

66
import {Parser} from 'change_detection/parser/parser';
@@ -14,6 +14,30 @@ import {TemplateLoader} from './template_loader';
1414
import {AnnotatedType} from './annotated_type';
1515
import {Component} from '../annotations/annotations';
1616

17+
/**
18+
* Cache that stores the ProtoView of the template of a component.
19+
* Used to prevent duplicate work and resolve cyclic dependencies.
20+
*/
21+
export class CompilerCache {
22+
_cache:Map;
23+
constructor() {
24+
this._cache = MapWrapper.create();
25+
}
26+
27+
set(component:Type, protoView:ProtoView) {
28+
MapWrapper.set(this._cache, component, protoView);
29+
}
30+
31+
get(component:Type):ProtoView {
32+
var result = MapWrapper.get(this._cache, component);
33+
if (isBlank(result)) {
34+
// need to normalize undefined to null so that type checking passes :-(
35+
return null;
36+
}
37+
return result;
38+
}
39+
}
40+
1741
/**
1842
* The compiler loads and translates the html templates of components into
1943
* nested ProtoViews. To decompose its functionality it uses
@@ -23,10 +47,12 @@ export class Compiler {
2347
_templateLoader:TemplateLoader;
2448
_reader: DirectiveMetadataReader;
2549
_parser:Parser;
26-
constructor(templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser) {
50+
_compilerCache:CompilerCache;
51+
constructor(templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser, cache:CompilerCache) {
2752
this._templateLoader = templateLoader;
2853
this._reader = reader;
2954
this._parser = parser;
55+
this._compilerCache = cache;
3056
}
3157

3258
createSteps(component:AnnotatedType):List<CompileStep> {
@@ -40,15 +66,22 @@ export class Compiler {
4066
}
4167

4268
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {
43-
// TODO load all components transitively from the cache first
44-
var cache = null;
45-
return PromiseWrapper.resolve(this.compileWithCache(
46-
cache, this._reader.annotatedType(component), templateRoot)
69+
var templateCache = null;
70+
// TODO load all components that have urls
71+
// transitively via the _templateLoader and store them in templateCache
72+
73+
return PromiseWrapper.resolve(this.compileAllLoaded(
74+
templateCache, this._reader.annotatedType(component), templateRoot)
4775
);
4876
}
4977

5078
// public so that we can compile in sync in performance tests.
51-
compileWithCache(cache, component:AnnotatedType, templateRoot:Element = null):ProtoView {
79+
compileAllLoaded(templateCache, component:AnnotatedType, templateRoot:Element = null):ProtoView {
80+
var rootProtoView = this._compilerCache.get(component.type);
81+
if (isPresent(rootProtoView)) {
82+
return rootProtoView;
83+
}
84+
5285
if (isBlank(templateRoot)) {
5386
// TODO: read out the cache if templateRoot = null. Could contain:
5487
// - templateRoot string
@@ -57,15 +90,18 @@ export class Compiler {
5790
var annotation:any = component.annotation;
5891
templateRoot = DOM.createTemplate(annotation.template.inline);
5992
}
93+
6094
var pipeline = new CompilePipeline(this.createSteps(component));
6195
var compileElements = pipeline.process(templateRoot);
62-
var rootProtoView = compileElements[0].inheritedProtoView;
63-
// TODO: put the rootProtoView into the cache to support recursive templates!
96+
rootProtoView = compileElements[0].inheritedProtoView;
97+
// Save the rootProtoView before we recurse so that we are able
98+
// to compile components that use themselves in their template.
99+
this._compilerCache.set(component.type, rootProtoView);
64100

65101
for (var i=0; i<compileElements.length; i++) {
66102
var ce = compileElements[i];
67103
if (isPresent(ce.componentDirective)) {
68-
ce.inheritedElementBinder.nestedProtoView = this.compileWithCache(cache, ce.componentDirective, null);
104+
ce.inheritedElementBinder.nestedProtoView = this.compileAllLoaded(templateCache, ce.componentDirective, null);
69105
}
70106
}
71107

modules/core/test/compiler/compiler_spec.js

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {describe, beforeEach, it, expect, ddescribe, iit} from 'test_lib/test_li
22
import {DOM} from 'facade/dom';
33
import {List} from 'facade/collection';
44

5-
import {Compiler} from 'core/compiler/compiler';
5+
import {Compiler, CompilerCache} from 'core/compiler/compiler';
66
import {ProtoView} from 'core/compiler/view';
77
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
88
import {TemplateLoader} from 'core/compiler/template_loader';
@@ -25,7 +25,7 @@ export function main() {
2525

2626
function createCompiler(processClosure) {
2727
var steps = [new MockStep(processClosure)];
28-
return new TestableCompiler(null, reader, new Parser(new Lexer()), steps);
28+
return new TestableCompiler(reader, steps);
2929
}
3030

3131
it('should run the steps and return the ProtoView of the root element', (done) => {
@@ -77,6 +77,35 @@ export function main() {
7777

7878
});
7979

80+
it('should cache components', (done) => {
81+
var el = createElement('<div></div>');
82+
var compiler = createCompiler( (parent, current, control) => {
83+
current.inheritedProtoView = new ProtoView(current.element, null);
84+
});
85+
var firstProtoView;
86+
compiler.compile(MainComponent, el).then( (protoView) => {
87+
firstProtoView = protoView;
88+
return compiler.compile(MainComponent, el);
89+
}).then( (protoView) => {
90+
expect(firstProtoView).toBe(protoView);
91+
done();
92+
});
93+
94+
});
95+
96+
it('should allow recursive components', (done) => {
97+
var compiler = createCompiler( (parent, current, control) => {
98+
current.inheritedProtoView = new ProtoView(current.element, null);
99+
current.inheritedElementBinder = current.inheritedProtoView.bindElement(null);
100+
current.componentDirective = reader.annotatedType(RecursiveComponent);
101+
});
102+
compiler.compile(RecursiveComponent, null).then( (protoView) => {
103+
expect(protoView.elementBinders[0].nestedProtoView).toBe(protoView);
104+
done();
105+
});
106+
107+
});
108+
80109
});
81110

82111
}
@@ -95,10 +124,18 @@ class MainComponent {}
95124
})
96125
class NestedComponent {}
97126

127+
@Component({
128+
template: new TemplateConfig({
129+
inline: '<div rec-comp></div>'
130+
}),
131+
selector: 'rec-comp'
132+
})
133+
class RecursiveComponent {}
134+
98135
class TestableCompiler extends Compiler {
99136
steps:List;
100-
constructor(templateLoader:TemplateLoader, reader:DirectiveMetadataReader, parser, steps:List<CompileStep>) {
101-
super(templateLoader, reader, parser);
137+
constructor(reader:DirectiveMetadataReader, steps:List<CompileStep>) {
138+
super(null, reader, new Parser(new Lexer()), new CompilerCache());
102139
this.steps = steps;
103140
}
104141
createSteps(component):List<CompileStep> {

modules/core/test/compiler/integration_spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {ChangeDetector} from 'change_detection/change_detector';
77
import {Parser} from 'change_detection/parser/parser';
88
import {Lexer} from 'change_detection/parser/lexer';
99

10-
import {Compiler} from 'core/compiler/compiler';
10+
import {Compiler, CompilerCache} from 'core/compiler/compiler';
1111
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
1212

1313
import {Decorator, Component, Template} from 'core/annotations/annotations';
@@ -21,7 +21,7 @@ export function main() {
2121
var compiler;
2222

2323
beforeEach( () => {
24-
compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()));
24+
compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache());
2525
});
2626

2727
describe('react to record changes', function() {

modules/examples/src/hello_world/index_static.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {Component, Decorator, TemplateConfig, NgElement} from 'core/core';
44
import {Parser} from 'change_detection/parser/parser';
55
import {Lexer} from 'change_detection/parser/lexer';
66

7-
import {Compiler} from 'core/compiler/compiler';
7+
import {Compiler, CompilerCache} from 'core/compiler/compiler';
88
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
99
import {TemplateLoader} from 'core/compiler/template_loader';
1010

@@ -36,8 +36,14 @@ function setup() {
3636
});
3737

3838
reflector.registerType(Compiler, {
39-
"factory": (templateLoader, reader, parser) => new Compiler(templateLoader, reader, parser),
40-
"parameters": [[TemplateLoader], [DirectiveMetadataReader], [Parser]],
39+
"factory": (templateLoader, reader, parser, compilerCache) => new Compiler(templateLoader, reader, parser, compilerCache),
40+
"parameters": [[TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]],
41+
"annotations": []
42+
});
43+
44+
reflector.registerType(CompilerCache, {
45+
"factory": () => new CompilerCache(),
46+
"parameters": [],
4147
"annotations": []
4248
});
4349

0 commit comments

Comments
 (0)