Merge branch 'main' into run-batch-until-complete

pull/16971/head
Rich Harris 1 week ago
commit edea1018e2

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: don't preserve reactivity context across function boundaries

@ -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
);
@ -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);
@ -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);

@ -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<

@ -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);
@ -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;
@ -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;
}
@ -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) {

@ -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
});

@ -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
});
}

@ -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);

@ -0,0 +1,3 @@
import { test } from '../../test';
export default test({ compileOptions: { experimental: { async: true } } });

@ -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();
}

@ -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(`<!--]-->`);
});
});
}

@ -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…
Cancel
Save