Skip to content

Commit 0f2d9e6

Browse files
chadhietalakrisselden
authored andcommitted
[Glimmer] Late bound layouts (#13588)
* Updating glimmer-engine WIP * [Glimmer2] Late bound layouts
1 parent dfd80f3 commit 0f2d9e6

File tree

17 files changed

+252
-380
lines changed

17 files changed

+252
-380
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"express": "^4.5.0",
3838
"finalhandler": "^0.4.0",
3939
"github": "^0.2.3",
40-
"glimmer-engine": "tildeio/glimmer#0beb69d",
40+
"glimmer-engine": "tildeio/glimmer#525e75e",
4141
"glob": "^5.0.13",
4242
"htmlbars": "0.14.23",
4343
"mocha": "^2.4.5",

packages/ember-glimmer-template-compiler/lib/system/template.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ class Wrapper {
55
return new this(options);
66
}
77

8-
constructor({ env }) {
8+
constructor({ env }, id) {
9+
this.id = id;
910
this._entryPoint = null;
1011
this._layout = null;
1112
this.env = env;
@@ -30,13 +31,17 @@ class Wrapper {
3031
}
3132
}
3233

34+
let templateId = 0;
3335
const template = function(json) {
34-
return class extends Wrapper {
36+
let id = templateId++;
37+
let Factory = class extends Wrapper {
3538
constructor(options) {
36-
super(options);
39+
super(options, id);
3740
this.spec = JSON.parse(json);
3841
}
3942
};
43+
Factory.id = id;
44+
return Factory;
4045
};
4146

4247
export default template;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { precompile, compile, template } from 'ember-glimmer-template-compiler';
2+
3+
QUnit.module(`Glimmer Precompile:`);
4+
5+
QUnit.test('returns a string', (assert) => {
6+
let str = precompile('Hello');
7+
assert.equal(typeof str, 'string');
8+
});
9+
10+
QUnit.test('when wrapped in a template, precompile is the same as compile', (assert) => {
11+
// Simulating what happens in a broccoli plugin
12+
// when it is creating an AMD module. e.g.
13+
// ...
14+
// processString(content) {
15+
// return `template(${precompile(content)})`;
16+
// }
17+
let Precompiled = template(JSON.parse(precompile('Hello')));
18+
let Compiled = compile('Hello');
19+
20+
assert.equal(Precompiled.toString(), Compiled.toString(), 'Both return factories');
21+
22+
let precompiled = new Precompiled({ env: {} });
23+
let compiled = new Compiled({ env: {} });
24+
25+
assert.ok(typeof precompiled.spec !== 'string', 'Spec has been parsed');
26+
assert.ok(typeof compiled.spec !== 'string', 'Spec has been parsed');
27+
});

packages/ember-glimmer/lib/environment.js

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export default class Environment extends GlimmerEnvironment {
112112
super(dom);
113113
this.owner = owner;
114114
this._components = new Dict();
115+
this._templateCache = new Dict();
115116
this.builtInModifiers = {
116117
action: new ActionModifierManager()
117118
};
@@ -131,25 +132,24 @@ export default class Environment extends GlimmerEnvironment {
131132

132133
if (key !== 'partial' && isSimple && (isInline || isBlock)) {
133134
if (key === 'component') {
134-
return new DynamicComponentSyntax({ args, templates, isBlock });
135+
return new DynamicComponentSyntax({ args, templates });
135136
} else if (key === 'outlet') {
136137
return new OutletSyntax({ args });
137138
} else if (key.indexOf('-') >= 0) {
138-
let definition = this.createComponentDefinition(path, isBlock);
139+
let definition = this.getComponentDefinition(path);
139140

140141
if (definition) {
141142
wrapClassBindingAttribute(args);
142143
wrapClassAttribute(args);
143144
return new CurlyComponentSyntax({ args, definition, templates });
145+
} else if (isBlock && !this.hasHelper(key)) {
146+
assert(`A helper named '${path[0]}' could not be found`, false);
144147
}
145148
} else {
146149
// Check if it's a keyword
147150
let mappedKey = builtInComponents[key];
148151
if (mappedKey) {
149-
if (mappedKey !== key) {
150-
path = path.map((segment) => segment === key ? mappedKey : segment);
151-
}
152-
let definition = this.createComponentDefinition(path, isBlock);
152+
let definition = this.getComponentDefinition([mappedKey]);
153153
wrapClassBindingAttribute(args);
154154
wrapClassAttribute(args);
155155
return new CurlyComponentSyntax({ args, definition, templates });
@@ -167,14 +167,17 @@ export default class Environment extends GlimmerEnvironment {
167167
return false;
168168
}
169169

170-
createComponentDefinition(name, isBlock) {
170+
getComponentDefinition(path) {
171+
let name = path[0];
171172
let definition = this._components[name];
172173

173174
if (!definition) {
174-
let { component: ComponentClass, layout } = lookupComponent(this.owner, name[0]);
175+
let { component: ComponentClass, layout } = lookupComponent(this.owner, name);
175176

176177
if (ComponentClass || layout) {
177-
definition = this._components[name] = new CurlyComponentDefinition(name, ComponentClass, layout, isBlock);
178+
definition = this._components[name] = new CurlyComponentDefinition(name, ComponentClass, layout);
179+
} else if (!this.hasHelper(name)) {
180+
assert(`Glimmer error: Could not find component named "${name}" (no component or template with that name was found)`, !!(ComponentClass || layout));
178181
}
179182
}
180183

packages/ember-glimmer/lib/syntax/curly-component.js

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { StatementSyntax, ValueReference } from 'glimmer-runtime';
22
import { AttributeBindingReference, RootReference, applyClassNameBinding } from '../utils/references';
33
import { DIRTY_TAG, IS_DISPATCHING_ATTRS, HAS_BLOCK } from '../component';
44
import { assert } from 'ember-metal/debug';
5-
import isEnabled from 'ember-metal/features';
6-
import { meta as metaFor } from 'ember-metal/meta';
7-
import { watchKey } from 'ember-metal/watch_key';
85
import processArgs from '../utils/process-args';
6+
import { getOwner } from 'container/owner';
7+
import { privatize as P } from 'container/registry';
8+
import get from 'ember-metal/property_get';
9+
10+
const DEFAULT_LAYOUT = P`template:components/-default`;
911

1012
function aliasIdToElementId(args, props) {
1113
if (args.named.has('id')) {
@@ -38,7 +40,7 @@ class ComponentStateBucket {
3840
}
3941

4042
class CurlyComponentManager {
41-
create(definition, args, dynamicScope) {
43+
create(definition, args, dynamicScope, hasBlock) {
4244
let parentView = dynamicScope.view;
4345

4446
let klass = definition.ComponentClass;
@@ -48,20 +50,10 @@ class CurlyComponentManager {
4850
aliasIdToElementId(args, props);
4951

5052
props.renderer = parentView.renderer;
51-
props[HAS_BLOCK] = definition.isBlock;
53+
props[HAS_BLOCK] = hasBlock;
5254

5355
let component = klass.create(props);
5456

55-
if (isEnabled('mandatory-setter')) {
56-
let meta = metaFor(component);
57-
let keys = Object.keys(props);
58-
59-
for (let i = 0; i < keys.length; i++) {
60-
// Watching a key triggers Ember to install the mandatory setter
61-
watchKey(component, keys[i], meta);
62-
}
63-
}
64-
6557
dynamicScope.view = component;
6658
parentView.appendChild(component);
6759

@@ -105,6 +97,37 @@ class CurlyComponentManager {
10597
return bucket;
10698
}
10799

100+
ensureCompilable(definition, bucket, env) {
101+
if (definition.template) {
102+
return definition;
103+
}
104+
105+
let { component } = bucket;
106+
let template;
107+
let TemplateFactory = component.layout;
108+
// seen the definition but not the template
109+
if (TemplateFactory) {
110+
if (env._templateCache[TemplateFactory.id]) {
111+
template = env._templateCache[TemplateFactory.id];
112+
} else {
113+
template = new TemplateFactory(env);
114+
env._templateCache[TemplateFactory.id] = template;
115+
}
116+
} else {
117+
let layoutName = component.layoutName && get(component, 'layoutName');
118+
let owner = getOwner(component);
119+
120+
if (layoutName) {
121+
template = owner.lookup('template:' + layoutName);
122+
}
123+
if (!template) {
124+
template = owner.lookup(DEFAULT_LAYOUT);
125+
}
126+
}
127+
128+
return definition.lateBound(template);
129+
}
130+
108131
getSelf({ component }) {
109132
return new RootReference(component);
110133
}
@@ -199,10 +222,10 @@ function elementId(vm) {
199222
}
200223

201224
export class CurlyComponentDefinition extends ComponentDefinition {
202-
constructor(name, ComponentClass, template, isBlock) {
225+
constructor(name, ComponentClass, template) {
203226
super(name, MANAGER, ComponentClass || Component);
204227
this.template = template;
205-
this.isBlock = isBlock;
228+
this._cache = undefined;
206229
}
207230

208231
compile(builder) {
@@ -211,4 +234,18 @@ export class CurlyComponentDefinition extends ComponentDefinition {
211234
builder.attrs.dynamic('id', elementId);
212235
builder.attrs.static('class', 'ember-view');
213236
}
237+
238+
lateBound(template) {
239+
let definition;
240+
if (this._cache) {
241+
definition = this._cache[template.id];
242+
} else {
243+
this._cache = {};
244+
}
245+
if (!definition) {
246+
definition = new CurlyComponentDefinition(this.name, this.ComponentClass, template);
247+
this._cache[template.id] = definition;
248+
}
249+
return definition;
250+
}
214251
}
Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
11
import { ArgsSyntax, StatementSyntax } from 'glimmer-runtime';
2-
import { ConstReference, isConst } from 'glimmer-reference';
3-
import { assert } from 'ember-metal/debug';
2+
import { ConstReference, isConst, UNDEFINED_REFERENCE } from 'glimmer-reference';
43

5-
class DynamicComponentLookup {
6-
constructor(args, isBlock) {
7-
this.args = ArgsSyntax.fromPositionalArgs(args.positional.slice(0, 1));
8-
this.factory = (args, options) => dynamicComponentFor(args, options, isBlock);
9-
}
10-
}
11-
12-
function dynamicComponentFor(args, { env }, isBlock) {
4+
function dynamicComponentFor(vm) {
5+
let env = vm.env;
6+
let args = vm.getArgs();
137
let nameRef = args.positional.at(0);
148

159
if (isConst(nameRef)) {
16-
return new ConstReference(lookup(env, nameRef.value(), isBlock));
10+
let name = nameRef.value();
11+
let definition = env.getComponentDefinition([name]);
12+
13+
return new ConstReference(definition);
1714
} else {
18-
return new DynamicComponentReference({ nameRef, env, isBlock });
15+
return new DynamicComponentReference({ nameRef, env });
1916
}
2017
}
2118

2219
export class DynamicComponentSyntax extends StatementSyntax {
23-
constructor({ args, templates, isBlock }) {
20+
constructor({ args, templates }) {
2421
super();
25-
this.definition = new DynamicComponentLookup(args, isBlock);
22+
this.definitionArgs = ArgsSyntax.fromPositionalArgs(args.positional.slice(0, 1));
23+
this.definition = dynamicComponentFor;
2624
this.args = ArgsSyntax.build(args.positional.slice(1), args.named);
2725
this.templates = templates;
2826
this.shadow = null;
@@ -34,27 +32,20 @@ export class DynamicComponentSyntax extends StatementSyntax {
3432
}
3533

3634
class DynamicComponentReference {
37-
constructor({ nameRef, env, isBlock }) {
35+
constructor({ nameRef, env }) {
3836
this.nameRef = nameRef;
3937
this.env = env;
4038
this.tag = nameRef.tag;
41-
this.isBlock = isBlock;
4239
}
4340

4441
value() {
45-
let { env, nameRef, isBlock } = this;
46-
return lookup(env, nameRef.value(), isBlock);
42+
let { env, nameRef } = this;
43+
let name = nameRef.value();
44+
let definition = env.getComponentDefinition([name]);
45+
return definition;
4746
}
48-
}
49-
50-
function lookup(env, name, isBlock) {
51-
if (typeof name === 'string') {
52-
let componentDefinition = env.createComponentDefinition([name], isBlock);
53-
assert(`Glimmer error: Could not find component named "${name}" (no component or template with that name was found)`, componentDefinition);
5447

55-
return componentDefinition;
56-
} else {
57-
throw new Error(`Cannot render ${name} as a component`);
48+
get() {
49+
return UNDEFINED_REFERENCE;
5850
}
5951
}
60-

packages/ember-glimmer/lib/syntax/outlet.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,13 @@ import { ConstReference } from 'glimmer-reference';
33
import { generateGuid, guidFor } from 'ember-metal/utils';
44
import { RootReference, NULL_REFERENCE } from '../utils/references';
55

6-
class OutletComponentLookup {
7-
constructor(args) {
8-
this.args = args;
9-
this.factory = outletComponentFor;
10-
}
11-
}
12-
13-
function outletComponentFor(args, vm) {
6+
function outletComponentFor(vm) {
147
let { outletState, isTopLevel } = vm.dynamicScope();
158

169
if (isTopLevel) {
1710
return new TopLevelOutletComponentReference(outletState);
1811
} else {
12+
let args = vm.getArgs();
1913
let outletName = args.positional.at(0).value() || 'main';
2014
return new OutletComponentReference(outletName, outletState.get(outletName));
2115
}
@@ -24,7 +18,8 @@ function outletComponentFor(args, vm) {
2418
export class OutletSyntax extends StatementSyntax {
2519
constructor({ args }) {
2620
super();
27-
this.definition = new OutletComponentLookup(args);
21+
this.definitionArgs = args;
22+
this.definition = outletComponentFor;
2823
this.args = ArgsSyntax.empty();
2924
this.templates = null;
3025
this.shadow = null;
@@ -59,8 +54,10 @@ class OutletComponentReference {
5954
let { outletName, reference, definition, lastState } = this;
6055
let newState = reference.value();
6156

57+
definition = revalidate(definition, lastState, newState);
58+
6259
if (definition) {
63-
return revalidate(definition, lastState, newState);
60+
return definition;
6461
} else if (newState) {
6562
return this.definition = new OutletComponentDefinition(outletName, newState.render.template);
6663
} else {
@@ -96,6 +93,10 @@ class AbstractOutletComponentManager {
9693
throw new Error('Not implemented: create');
9794
}
9895

96+
ensureCompilable(definition) {
97+
return definition;
98+
}
99+
99100
getSelf(state) {
100101
return new RootReference(state.render.controller);
101102
}

packages/ember-glimmer/lib/utils/lookup-component.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,6 @@
1-
import { privatize as P } from 'container/registry';
2-
3-
const DEFAULT_LAYOUT = P`template:components/-default`;
4-
51
function lookupComponentPair(componentLookup, owner, name, options) {
62
let component = componentLookup.componentFor(name, owner, options);
73
let layout = componentLookup.layoutFor(name, owner, options);
8-
9-
if (!layout && component) {
10-
let layoutProp = component.proto().layout;
11-
if (layoutProp) {
12-
let templateFullName = 'template:components/' + name;
13-
owner.register(templateFullName, layoutProp);
14-
layout = owner.lookup(templateFullName, options);
15-
} else {
16-
layout = owner.lookup(DEFAULT_LAYOUT);
17-
}
18-
}
19-
204
return {
215
component,
226
layout

0 commit comments

Comments
 (0)