Skip to content

Commit 8903cba

Browse files
committed
fix: don't preserve reactivity context across function boundaries
Fixes sveltejs#15133
1 parent 890399b commit 8903cba

File tree

13 files changed

+144
-105
lines changed

13 files changed

+144
-105
lines changed

.changeset/short-banks-yell.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: don't preserve reactivity context across function boundaries

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ export function analyze_module(source, options) {
306306
fragment: null,
307307
parent_element: null,
308308
reactive_statement: null,
309-
in_derived: false
309+
derived_function_depth: -1
310310
},
311311
visitors
312312
);
@@ -703,7 +703,7 @@ export function analyze_component(root, source, options) {
703703
state_fields: new Map(),
704704
function_depth: scope.function_depth,
705705
reactive_statement: null,
706-
in_derived: false
706+
derived_function_depth: -1
707707
};
708708

709709
walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);
@@ -771,7 +771,7 @@ export function analyze_component(root, source, options) {
771771
expression: null,
772772
state_fields: new Map(),
773773
function_depth: scope.function_depth,
774-
in_derived: false
774+
derived_function_depth: -1
775775
};
776776

777777
walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);

packages/svelte/src/compiler/phases/2-analyze/types.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ export interface AnalysisState {
2929
reactive_statement: null | ReactiveStatement;
3030

3131
/**
32-
* True if we're directly inside a `$derived(...)` expression (but not `$derived.by(...)`)
32+
* Set when we're inside a `$derived(...)` expression (but not `$derived.by(...)`) or `@const`
3333
*/
34-
in_derived: boolean;
34+
derived_function_depth: number;
3535
}
3636

3737
export type Context<State extends AnalysisState = AnalysisState> = import('zimmerframe').Context<

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

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ export function AwaitExpression(node, context) {
1515
// b) awaits that precede other expressions in template or `$derived(...)`
1616
if (
1717
tla ||
18-
(is_reactive_expression(context.path, context.state.in_derived) &&
18+
(is_reactive_expression(
19+
context.path,
20+
context.state.derived_function_depth === context.state.function_depth
21+
) &&
1922
!is_last_evaluated_expression(context.path, node))
2023
) {
2124
context.state.analysis.pickled_awaits.add(node);
@@ -53,6 +56,8 @@ export function AwaitExpression(node, context) {
5356
* @param {boolean} in_derived
5457
*/
5558
export function is_reactive_expression(path, in_derived) {
59+
if (in_derived) return true;
60+
5661
let i = path.length;
5762

5863
while (i--) {
@@ -63,26 +68,6 @@ export function is_reactive_expression(path, in_derived) {
6368
parent.type === 'FunctionExpression' ||
6469
parent.type === 'FunctionDeclaration'
6570
) {
66-
// Check if there's a reactive rune call (like $derived) between this function and the await
67-
for (let j = i + 1; j < path.length; j++) {
68-
const node = path[j];
69-
// @ts-expect-error
70-
if (node.metadata) {
71-
// There's a reactive expression between the function and the await
72-
return true;
73-
}
74-
// Also check for $derived, $effect, etc. calls
75-
if (
76-
node.type === 'CallExpression' &&
77-
node.callee?.type === 'Identifier' &&
78-
(node.callee.name === '$derived' ||
79-
node.callee.name === '$effect' ||
80-
node.callee.name === '$inspect')
81-
) {
82-
// This is a reactive rune call
83-
return true;
84-
}
85-
}
8671
// No reactive expression found between function and await
8772
return false;
8873
}
@@ -93,18 +78,23 @@ export function is_reactive_expression(path, in_derived) {
9378
}
9479
}
9580

96-
return in_derived;
81+
return false;
9782
}
9883

9984
/**
10085
* @param {AST.SvelteNode[]} path
10186
* @param {Expression | SpreadElement | Property} node
10287
*/
103-
export function is_last_evaluated_expression(path, node) {
88+
function is_last_evaluated_expression(path, node) {
10489
let i = path.length;
10590

10691
while (i--) {
107-
const parent = /** @type {Expression | Property | SpreadElement} */ (path[i]);
92+
const parent = path[i];
93+
94+
if (parent.type === 'ConstTag') {
95+
// {@const ...} tags are treated as deriveds and its contents should all get the preserve-reactivity treatment
96+
return false;
97+
}
10898

10999
// @ts-expect-error we could probably use a neater/more robust mechanism
110100
if (parent.metadata) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ export function CallExpression(node, context) {
248248
context.next({
249249
...context.state,
250250
function_depth: context.state.function_depth + 1,
251-
in_derived: true,
251+
derived_function_depth: context.state.function_depth + 1,
252252
expression
253253
});
254254

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export function ConstTag(node, context) {
3838
context.visit(declaration.init, {
3939
...context.state,
4040
expression: node.metadata.expression,
41-
in_derived: true
41+
// We're treating this like a $derived under the hood
42+
function_depth: context.state.function_depth + 1,
43+
derived_function_depth: context.state.function_depth + 1
4244
});
4345
}

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,6 @@ export function VariableDeclarator(node, context) {
6464
}
6565
}
6666

67-
if (rune === '$derived') {
68-
context.visit(node.id);
69-
context.visit(/** @type {Expression} */ (node.init), { ...context.state, in_derived: true });
70-
return;
71-
}
72-
7367
if (rune === '$props') {
7468
if (node.id.type !== 'ObjectPattern' && node.id.type !== 'Identifier') {
7569
e.props_invalid_identifier(node);

packages/svelte/tests/runtime-runes/samples/const-async-function-await/_config.js

Lines changed: 0 additions & 31 deletions
This file was deleted.

packages/svelte/tests/runtime-runes/samples/const-async-function-await/main.svelte

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { test } from '../../test';
2+
3+
export default test({ compileOptions: { experimental: { async: true } } });

0 commit comments

Comments
 (0)