Skip to content

Commit 68d4f9c

Browse files
authored
fix: wait on dependencies of async bindings (#17120)
* fix: wait on dependencies of async bindings Correctly takes into account blockers for bindings. Also ensures it works for functional bindings. During that I also noticed a bug in our `nodes_end` assignment logic, which the added test also regresses against. Fixes #17092 Fixes #17090 * ensure async static props/attributes are awaited * wait on blockers in binding validation * await dependencies of style directives * tidy
1 parent 3a4dc7d commit 68d4f9c

File tree

27 files changed

+257
-68
lines changed

27 files changed

+257
-68
lines changed

.changeset/eleven-cases-sing.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: ensure async static props/attributes are awaited

.changeset/five-coats-travel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: wait on dependencies of async bindings

.changeset/huge-walls-hang.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: await dependencies of style directives

packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,16 @@ export function BindDirective(node, context) {
159159

160160
mark_subtree_dynamic(context.path);
161161

162+
const [get, set] = node.expression.expressions;
163+
// We gotta jump across the getter/setter functions to avoid the expression metadata field being reset to null
164+
context.visit(get.type === 'ArrowFunctionExpression' ? get.body : get, {
165+
...context.state,
166+
expression: node.metadata.expression
167+
});
168+
context.visit(set.type === 'ArrowFunctionExpression' ? set.body : set, {
169+
...context.state,
170+
expression: node.metadata.expression
171+
});
162172
return;
163173
}
164174

@@ -247,13 +257,14 @@ export function BindDirective(node, context) {
247257

248258
node.metadata = {
249259
binding_group_name: group_name,
250-
parent_each_blocks: each_blocks
260+
parent_each_blocks: each_blocks,
261+
expression: node.metadata.expression
251262
};
252263
}
253264

254265
if (binding?.kind === 'each' && binding.metadata?.inside_rest) {
255266
w.bind_invalid_each_rest(binding.node, binding.node.name);
256267
}
257268

258-
context.next();
269+
context.next({ ...context.state, expression: node.metadata.expression });
259270
}

packages/svelte/src/compiler/phases/2-analyze/visitors/StyleDirective.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,17 @@ export function StyleDirective(node, context) {
2323
if (binding.kind !== 'normal') {
2424
node.metadata.expression.has_state = true;
2525
}
26+
if (binding.blocker) {
27+
node.metadata.expression.dependencies.add(binding);
28+
}
2629
}
2730
} else {
2831
context.next();
2932

3033
for (const chunk of get_attribute_chunks(node.value)) {
3134
if (chunk.type !== 'ExpressionTag') continue;
3235

33-
node.metadata.expression.has_state ||= chunk.metadata.expression.has_state;
34-
node.metadata.expression.has_call ||= chunk.metadata.expression.has_call;
35-
node.metadata.expression.has_await ||= chunk.metadata.expression.has_await;
36+
node.metadata.expression.merge(chunk.metadata.expression);
3637
}
3738
}
3839
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,13 @@ export function BindDirective(node, context) {
250250

251251
let statement = defer ? b.stmt(b.call('$.effect', b.thunk(call))) : b.stmt(call);
252252

253-
// TODO this doesn't account for function bindings
254-
if (node.metadata.binding?.blocker) {
253+
if (node.metadata.expression.is_async()) {
255254
statement = b.stmt(
256-
b.call(b.member(node.metadata.binding.blocker, b.id('then')), b.thunk(b.block([statement])))
255+
b.call(
256+
'$.run_after_blockers',
257+
node.metadata.expression.blockers(),
258+
b.thunk(b.block([statement]))
259+
)
257260
);
258261
}
259262

packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -484,21 +484,6 @@ function setup_select_synchronization(value_binding, context) {
484484
);
485485
}
486486

487-
/**
488-
* @param {ExpressionMetadata} target
489-
* @param {ExpressionMetadata} source
490-
*/
491-
function merge_metadata(target, source) {
492-
target.has_assignment ||= source.has_assignment;
493-
target.has_await ||= source.has_await;
494-
target.has_call ||= source.has_call;
495-
target.has_member_expression ||= source.has_member_expression;
496-
target.has_state ||= source.has_state;
497-
498-
for (const r of source.references) target.references.add(r);
499-
for (const b of source.dependencies) target.dependencies.add(b);
500-
}
501-
502487
/**
503488
* @param {AST.ClassDirective[]} class_directives
504489
* @param {ComponentContext} context
@@ -514,7 +499,7 @@ export function build_class_directives_object(
514499
const metadata = new ExpressionMetadata();
515500

516501
for (const d of class_directives) {
517-
merge_metadata(metadata, d.metadata.expression);
502+
metadata.merge(d.metadata.expression);
518503

519504
const expression = /** @type Expression */ (context.visit(d.expression));
520505
properties.push(b.init(d.name, expression));
@@ -541,7 +526,7 @@ export function build_style_directives_object(
541526
const metadata = new ExpressionMetadata();
542527

543528
for (const d of style_directives) {
544-
merge_metadata(metadata, d.metadata.expression);
529+
metadata.merge(d.metadata.expression);
545530

546531
const expression =
547532
d.value === true

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,6 @@ export function build_component(node, component_name, context) {
171171
attribute.value,
172172
context,
173173
(value, metadata) => {
174-
if (!metadata.has_state && !metadata.has_await) return value;
175-
176174
// When we have a non-simple computation, anything other than an Identifier or Member expression,
177175
// then there's a good chance it needs to be memoized to avoid over-firing when read within the
178176
// child component (e.g. `active={i === index}`)
@@ -198,7 +196,12 @@ export function build_component(node, component_name, context) {
198196
push_prop(b.init(attribute.name, value));
199197
}
200198
} else if (attribute.type === 'BindDirective') {
201-
const expression = /** @type {Expression} */ (context.visit(attribute.expression));
199+
const expression = /** @type {Expression} */ (
200+
context.visit(attribute.expression, { ...context.state, memoizer })
201+
);
202+
203+
// Bindings are a bit special: we don't want to add them to (async) deriveds but we need to check if they have blockers
204+
memoizer.check_blockers(attribute.metadata.expression);
202205

203206
if (
204207
dev &&

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export function build_attribute_value(value, context, memoize = (value) => value
122122

123123
return {
124124
value: memoize(expression, chunk.metadata.expression),
125-
has_state: chunk.metadata.expression.has_state || chunk.metadata.expression.has_await
125+
has_state: chunk.metadata.expression.has_state || chunk.metadata.expression.is_async()
126126
};
127127
}
128128

@@ -170,7 +170,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c
170170
if (class_directives.length) {
171171
next = build_class_directives_object(class_directives, context);
172172
has_state ||= class_directives.some(
173-
(d) => d.metadata.expression.has_state || d.metadata.expression.has_await
173+
(d) => d.metadata.expression.has_state || d.metadata.expression.is_async()
174174
);
175175

176176
if (has_state) {
@@ -240,7 +240,7 @@ export function build_set_style(node_id, attribute, style_directives, context) {
240240
if (style_directives.length) {
241241
next = build_style_directives_object(style_directives, context);
242242
has_state ||= style_directives.some(
243-
(d) => d.metadata.expression.has_state || d.metadata.expression.has_await
243+
(d) => d.metadata.expression.has_state || d.metadata.expression.is_async()
244244
);
245245

246246
if (has_state) {

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,7 @@ export class Memoizer {
3131
* @param {boolean} memoize_if_state
3232
*/
3333
add(expression, metadata, memoize_if_state = false) {
34-
for (const binding of metadata.dependencies) {
35-
if (binding.blocker) {
36-
this.#blockers.add(binding.blocker);
37-
}
38-
}
34+
this.check_blockers(metadata);
3935

4036
const should_memoize =
4137
metadata.has_call || metadata.has_await || (memoize_if_state && metadata.has_state);
@@ -52,6 +48,17 @@ export class Memoizer {
5248
return id;
5349
}
5450

51+
/**
52+
* @param {ExpressionMetadata} metadata
53+
*/
54+
check_blockers(metadata) {
55+
for (const binding of metadata.dependencies) {
56+
if (binding.blocker) {
57+
this.#blockers.add(binding.blocker);
58+
}
59+
}
60+
}
61+
5562
apply() {
5663
return [...this.#sync, ...this.#async].map((memo, i) => {
5764
memo.id.name = `$${i}`;
@@ -348,6 +355,7 @@ export function validate_binding(state, binding, expression) {
348355
b.call(
349356
'$.validate_binding',
350357
b.literal(state.analysis.source.slice(binding.start, binding.end)),
358+
binding.metadata.expression.blockers(),
351359
b.thunk(
352360
state.store_to_invalidate ? b.sequence([b.call('$.mark_store_binding'), obj]) : obj
353361
),

0 commit comments

Comments
 (0)