Skip to content

Commit e9d569e

Browse files
committed
defer component initialization until after processing so all vars are available
1 parent adeeb31 commit e9d569e

4 files changed

Lines changed: 45 additions & 5 deletions

File tree

src/ext/component.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,20 +250,26 @@ export default function componentPlugin(_hyperscript) {
250250
customElements.define(tagName, ComponentClass);
251251
}
252252

253-
// Register a before-process hook to scan for component definitions
254253
var registered = new Set();
255254

255+
// Always strip _ to prevent normal processNode from running it on the template
256256
_hyperscript.addBeforeProcessHook(function(elt) {
257257
if (!elt || !elt.querySelectorAll) return;
258258
elt.querySelectorAll('script[type="text/hyperscript-template"][component]').forEach(function(tmpl) {
259-
// Always strip _ to prevent normal processNode from running it on the template
260-
var script = tmpl.getAttribute('_') || '';
259+
if (tmpl._componentScript != null) return;
260+
tmpl._componentScript = tmpl.getAttribute('_') || '';
261261
tmpl.removeAttribute('_');
262+
});
263+
});
262264

265+
// register components after processing so symbols are available
266+
_hyperscript.addAfterProcessHook(function(elt) {
267+
if (!elt || !elt.querySelectorAll) return;
268+
elt.querySelectorAll('script[type="text/hyperscript-template"][component]').forEach(function(tmpl) {
263269
var tagName = tmpl.getAttribute('component');
264270
if (!registered.has(tagName) && !customElements.get(tagName)) {
265271
registered.add(tagName);
266-
registerComponent(tmpl, script);
272+
registerComponent(tmpl, tmpl._componentScript || '');
267273
}
268274
});
269275
});

src/parsetree/features/set.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ export class SetFeature extends Feature {
3030
}
3131

3232
install(target, source, args, runtime) {
33-
this.start && this.start.execute(runtime.makeContext(target, this, target, null));
33+
queueMicrotask(() => {
34+
this.start && this.start.execute(runtime.makeContext(target, this, target, null));
35+
});
3436
}
3537

3638
static parse(parser) {

test/ext/component.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,32 @@ test.describe('the component extension', () => {
290290
}).toBe('rgb(11, 22, 33)')
291291
})
292292

293+
test('component reads enclosing scope set by a sibling init on first load', async ({html, find, evaluate}) => {
294+
await html(`
295+
<script type="text/hyperscript-template" component="test-user-card" _="init set ^user to attrs.data">
296+
<h3>${"\x24"}{^user.name}</h3>
297+
<p>${"\x24"}{^user.email}</p>
298+
</script>
299+
<div _="init set $testCurrentUser to { name: 'Carson', email: 'carson@example.com' }">
300+
<test-user-card data="$testCurrentUser"></test-user-card>
301+
</div>
302+
`)
303+
await expect.poll(() => find('test-user-card h3').textContent()).toBe('Carson')
304+
await expect.poll(() => find('test-user-card p').textContent()).toBe('carson@example.com')
305+
await evaluate(() => { delete window.$testCurrentUser })
306+
})
307+
308+
test('component reads a feature-level set from an enclosing div on first load', async ({html, find, evaluate}) => {
309+
await html(`
310+
<script type="text/hyperscript-template" component="test-plain-card" _="init set ^label to attrs.label">
311+
<span>${"\x24"}{^label}</span>
312+
</script>
313+
<div _="set $testLabel to 'hello'">
314+
<test-plain-card label="$testLabel"></test-plain-card>
315+
</div>
316+
`)
317+
await expect.poll(() => find('test-plain-card span').textContent()).toBe('hello')
318+
await evaluate(() => { delete window.$testLabel })
319+
})
320+
293321
})

www/docs/components.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ gives you `"$currentUser"` as a string, while `attrs.data` gives you the resolve
126126
Note that reactive bindings work through the `attrs` object, so you can bind external values to
127127
internal component state.
128128

129+
{% note "A note on `init immediately`" %}
130+
Components should avoid using `init immediately` or risk not having reactive effects of initialization code captured properly.
131+
{% endnote %}
132+
129133
### Slots {#slots}
130134

131135
`<slot/>` elements let callers pass content into a component. The parent's markup

0 commit comments

Comments
 (0)