fix: reset context after waiting on blockers of `@const` expressions (#18100)

Regression from #18039 - we need to have each await expression (and
waiting on blockers is one) in its own entry of `(renderer.)run`. Else
context is not restored correctly and if the synchronous expression
afterwards requires it stuff breaks.

Fixes #18098
pull/18111/head
Simon H 3 months ago committed by GitHub
parent 273f1a85a4
commit 0ed8c282f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: reset context after waiting on blockers of `@const` expressions

@ -38,7 +38,7 @@ export interface AnalysisState {
/** Collected info about async `{@const }` declarations */
async_consts?: {
id: Identifier;
/** How many `@const` declarations there are (already) in this scope */
/** How many `$.run(...)` entries are already allocated in this scope */
declaration_count: number;
};
}

@ -61,7 +61,8 @@ export function ConstTag(node, context) {
// keep the counter in sync with the number of thunks pushed in ConstTag in transform
// TODO 6.0 once non-async and non-runes mode is gone investigate making this more robust
// via something like the approach in https://github.com/sveltejs/svelte/pull/18032
const length = run.declaration_count++;
const length = run.declaration_count + (blockers.length > 0 ? 1 : 0);
run.declaration_count += blockers.length > 0 ? 2 : 1;
const blocker = b.member(run.id, b.literal(length), true);
for (const binding of bindings) {
binding.blocker = blocker;

@ -96,22 +96,15 @@ function add_const_declaration(state, id, expression, metadata) {
state.consts.push(b.let(id));
/** @type {Statement | undefined} */
let promise_stmt;
if (blockers.length === 1) {
promise_stmt = b.stmt(b.await(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
run.thunks.push(b.thunk(b.member(/** @type {Expression} */ (blockers[0]), 'promise')));
} else if (blockers.length > 0) {
promise_stmt = b.stmt(b.await(b.call('$.wait', b.array(blockers))));
run.thunks.push(b.thunk(b.call('$.wait', b.array(blockers))));
}
// keep the number of thunks pushed in sync with ConstTag in analysis phase
const assignment = b.assignment('=', id, expression);
if (promise_stmt) {
run.thunks.push(b.thunk(b.block([promise_stmt, b.stmt(assignment)]), true));
} else {
run.thunks.push(b.thunk(assignment, metadata.expression.has_await));
}
run.thunks.push(b.thunk(assignment, metadata.expression.has_await));
} else {
state.consts.push(b.const(id, expression));
state.consts.push(...after);

@ -28,22 +28,15 @@ export function ConstTag(node, context) {
context.state.init.push(b.let(identifier.name));
}
/** @type {Statement | undefined} */
let promise_stmt;
if (blockers.length === 1) {
promise_stmt = b.stmt(b.await(/** @type {Expression} */ (blockers[0])));
run.thunks.push(b.thunk(/** @type {Expression} */ (blockers[0])));
} else if (blockers.length > 0) {
promise_stmt = b.stmt(b.await(b.call('Promise.all', b.array(blockers))));
run.thunks.push(b.thunk(b.call('Promise.all', b.array(blockers))));
}
// keep the number of thunks pushed in sync with ConstTag in analysis phase
const assignment = b.assignment('=', id, init);
if (promise_stmt) {
run.thunks.push(b.thunk(b.block([promise_stmt, b.stmt(assignment)]), true));
} else {
run.thunks.push(b.thunk(assignment, node.metadata.expression.has_await));
}
run.thunks.push(b.thunk(assignment, node.metadata.expression.has_await));
} else {
context.state.init.push(b.const(id, init));
}

@ -0,0 +1,11 @@
import { tick } from 'svelte';
import { test } from '../../test';
// Tests that context is restored after await (const has to wait on a blocker), so that getContext etc work correctly
export default test({
mode: ['hydrate', 'async-server', 'client'],
async test({ assert, target }) {
await tick();
assert.htmlEqual(target.innerHTML, 'hi');
}
});

@ -0,0 +1,11 @@
<script>
import { getContext } from 'svelte';
const _unused = $derived(await 1);
//This must be created after any $derived(await...)
const bar = $derived(getContext('') ?? 'hi');
</script>
{#if true}
{@const foo = bar}
{foo}
{/if}

@ -2,6 +2,8 @@ import 'svelte/internal/disclose-version';
import 'svelte/internal/flags/async';
import * as $ from 'svelte/internal/client';
var root = $.from_html(`<!> <!>`, 1);
export default function Async_in_derived($$anchor, $$props) {
$.push($$props, true);
@ -21,7 +23,7 @@ export default function Async_in_derived($$anchor, $$props) {
}
]);
var fragment = $.comment();
var fragment = root();
var node = $.first_child(fragment);
{
@ -49,6 +51,23 @@ export default function Async_in_derived($$anchor, $$props) {
});
}
var node_1 = $.sibling(node, 2);
{
var consequent_1 = ($$anchor) => {
let x;
var promises_1 = $.run([
() => $$promises[2].promise,
() => x = $.derived(() => $.get(no2))
]);
};
$.if(node_1, ($$render) => {
if (true) $$render(consequent_1);
});
}
$.append($$anchor, fragment);
$.pop();
}

@ -42,6 +42,17 @@ export default function Async_in_derived($$renderer, $$props) {
$$renderer.push('<!--[-1-->');
}
$$renderer.push(`<!--]--> `);
if (true) {
$$renderer.push('<!--[0-->');
let x;
var promises_1 = $$renderer.run([() => $$promises[2], () => x = no2()]);
} else {
$$renderer.push('<!--[-1-->');
}
$$renderer.push(`<!--]-->`);
});
}

@ -19,3 +19,7 @@
return await 1;
})()}
{/if}
{#if true}
{@const x = no2}
{/if}

Loading…
Cancel
Save