Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/short-banks-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: don't preserve reactivity context across function boundaries
6 changes: 3 additions & 3 deletions packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export function analyze_module(source, options) {
fragment: null,
parent_element: null,
reactive_statement: null,
in_derived: false
derived_function_depth: -1
},
visitors
);
Expand Down Expand Up @@ -703,7 +703,7 @@ export function analyze_component(root, source, options) {
state_fields: new Map(),
function_depth: scope.function_depth,
reactive_statement: null,
in_derived: false
derived_function_depth: -1
};

walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);
Expand Down Expand Up @@ -771,7 +771,7 @@ export function analyze_component(root, source, options) {
expression: null,
state_fields: new Map(),
function_depth: scope.function_depth,
in_derived: false
derived_function_depth: -1
};

walk(/** @type {AST.SvelteNode} */ (ast), state, visitors);
Expand Down
4 changes: 2 additions & 2 deletions packages/svelte/src/compiler/phases/2-analyze/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ export interface AnalysisState {
reactive_statement: null | ReactiveStatement;

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

export type Context<State extends AnalysisState = AnalysisState> = import('zimmerframe').Context<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ export function AwaitExpression(node, context) {
// b) awaits that precede other expressions in template or `$derived(...)`
if (
tla ||
(is_reactive_expression(context.path, context.state.in_derived) &&
(is_reactive_expression(
context.path,
context.state.derived_function_depth === context.state.function_depth
) &&
!is_last_evaluated_expression(context.path, node))
) {
context.state.analysis.pickled_awaits.add(node);
Expand Down Expand Up @@ -53,9 +56,7 @@ export function AwaitExpression(node, context) {
* @param {boolean} in_derived
*/
export function is_reactive_expression(path, in_derived) {
if (in_derived) {
return true;
}
if (in_derived) return true;

let i = path.length;

Expand All @@ -67,6 +68,7 @@ export function is_reactive_expression(path, in_derived) {
parent.type === 'FunctionExpression' ||
parent.type === 'FunctionDeclaration'
) {
// No reactive expression found between function and await
return false;
}

Expand All @@ -83,11 +85,16 @@ export function is_reactive_expression(path, in_derived) {
* @param {AST.SvelteNode[]} path
* @param {Expression | SpreadElement | Property} node
*/
export function is_last_evaluated_expression(path, node) {
function is_last_evaluated_expression(path, node) {
let i = path.length;

while (i--) {
const parent = /** @type {Expression | Property | SpreadElement} */ (path[i]);
const parent = path[i];

if (parent.type === 'ConstTag') {
// {@const ...} tags are treated as deriveds and its contents should all get the preserve-reactivity treatment
return false;
}

// @ts-expect-error we could probably use a neater/more robust mechanism
if (parent.metadata) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export function CallExpression(node, context) {
context.next({
...context.state,
function_depth: context.state.function_depth + 1,
in_derived: true,
derived_function_depth: context.state.function_depth + 1,
expression
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export function ConstTag(node, context) {
context.visit(declaration.init, {
...context.state,
expression: node.metadata.expression,
in_derived: true
// We're treating this like a $derived under the hood
function_depth: context.state.function_depth + 1,
derived_function_depth: context.state.function_depth + 1
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,6 @@ export function VariableDeclarator(node, context) {
}
}

if (rune === '$derived') {
context.visit(node.id);
context.visit(/** @type {Expression} */ (node.init), { ...context.state, in_derived: true });
return;
}

if (rune === '$props') {
if (node.id.type !== 'ObjectPattern' && node.id.type !== 'Identifier') {
e.props_invalid_identifier(node);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { test } from '../../test';

export default test({ compileOptions: { experimental: { async: true } } });
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'svelte/internal/disclose-version';
import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/client';

export default function Async_in_derived($$anchor, $$props) {
$.push($$props, true);

$.async_body($$anchor, async ($$anchor) => {
let yes1 = (await $.save($.async_derived(async () => (await $.save(1))())))();
let yes2 = (await $.save($.async_derived(async () => foo((await $.save(1))()))))();

let no1 = $.derived(async () => {
return await 1;
});

let no2 = $.derived(() => async () => {
return await 1;
});

if ($.aborted()) return;

var fragment = $.comment();
var node = $.first_child(fragment);

{
var consequent = ($$anchor) => {
$.async_body($$anchor, async ($$anchor) => {
const yes1 = (await $.save($.async_derived(async () => (await $.save(1))())))();
const yes2 = (await $.save($.async_derived(async () => foo((await $.save(1))()))))();

const no1 = $.derived(() => (async () => {
return await 1;
})());

const no2 = $.derived(() => (async () => {
return await 1;
})());

if ($.aborted()) return;
});
};

$.if(node, ($$render) => {
if (true) $$render(consequent);
});
}

$.append($$anchor, fragment);
});

$.pop();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/server';

export default function Async_in_derived($$renderer, $$props) {
$$renderer.component(($$renderer) => {
$$renderer.async(async ($$renderer) => {
let yes1 = (await $.save(1))();
let yes2 = foo((await $.save(1))());

let no1 = (async () => {
return await 1;
})();

let no2 = async () => {
return await 1;
};

$$renderer.async(async ($$renderer) => {
if (true) {
$$renderer.push('<!--[-->');

const yes1 = (await $.save(1))();
const yes2 = foo((await $.save(1))());

const no1 = (async () => {
return await 1;
})();

const no2 = (async () => {
return await 1;
})();
} else {
$$renderer.push('<!--[!-->');
}
});

$$renderer.push(`<!--]-->`);
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script>
let yes1 = $derived(await 1);
let yes2 = $derived(foo(await 1));
let no1 = $derived.by(async () => {
return await 1;
});
let no2 = $derived(async () => {
return await 1;
});
</script>

{#if true}
{@const yes1 = await 1}
{@const yes2 = foo(await 1)}
{@const no1 = (async () => {
return await 1;
})()}
{@const no2 = (async () => {
return await 1;
})()}
{/if}
Loading