From b7f39b464a00eda8eeb233dbf52d03a1fe0a740b Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Sat, 25 Oct 2025 17:16:29 +0200 Subject: [PATCH] fix: always allow `setContext` before first await in component (#17031) The previous check was flawed because EFFECT_RAN would be set by the time it is checked, since a promise in a parent component will cause a delay of the inner component being instantiated. Instead we have a new field on the component context checking if the component was already popped (if se we are indeed too late). Don't love it to have a field just for this but I don't see another way to reliably check it. Fixes #16629 --- .changeset/itchy-hats-study.md | 5 +++++ packages/svelte/src/internal/client/constants.js | 1 + packages/svelte/src/internal/client/context.js | 9 ++++++++- packages/svelte/src/internal/client/error-handling.js | 4 ++-- packages/svelte/src/internal/client/types.d.ts | 4 +++- .../async-context-throws-after-await/_config.js | 11 +++++++++++ .../async-context-throws-after-await/main.svelte | 7 +++++++ .../samples/async-set-context/Inner.svelte | 7 +++++++ .../samples/async-set-context/Outer.svelte | 9 +++++++++ .../samples/async-set-context/_config.js | 11 +++++++++++ .../samples/async-set-context/main.svelte | 7 +++++++ 11 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 .changeset/itchy-hats-study.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-context-throws-after-await/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-context-throws-after-await/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-set-context/Inner.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-set-context/Outer.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-set-context/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-set-context/main.svelte diff --git a/.changeset/itchy-hats-study.md b/.changeset/itchy-hats-study.md new file mode 100644 index 0000000000..e92ec5affd --- /dev/null +++ b/.changeset/itchy-hats-study.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: always allow `setContext` before first await in component diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 24dc9e4fb8..c2f7861b78 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -13,6 +13,7 @@ export const INERT = 1 << 13; export const DESTROYED = 1 << 14; // Flags exclusive to effects +/** Set once an effect that should run synchronously has run */ export const EFFECT_RAN = 1 << 15; /** * 'Transparent' effects do not create a transition boundary. diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index 751a35321a..ffdb342adb 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -128,7 +128,11 @@ export function setContext(key, context) { if (async_mode_flag) { var flags = /** @type {Effect} */ (active_effect).f; - var valid = !active_reaction && (flags & BRANCH_EFFECT) !== 0 && (flags & EFFECT_RAN) === 0; + var valid = + !active_reaction && + (flags & BRANCH_EFFECT) !== 0 && + // pop() runs synchronously, so this indicates we're setting context after an await + !(/** @type {ComponentContext} */ (component_context).i); if (!valid) { e.set_context_after_init(); @@ -173,6 +177,7 @@ export function getAllContexts() { export function push(props, runes = false, fn) { component_context = { p: component_context, + i: false, c: null, e: null, s: props, @@ -208,6 +213,8 @@ export function pop(component) { context.x = component; } + context.i = true; + component_context = context.p; if (DEV) { diff --git a/packages/svelte/src/internal/client/error-handling.js b/packages/svelte/src/internal/client/error-handling.js index 6c83a453d5..dcbbf14e20 100644 --- a/packages/svelte/src/internal/client/error-handling.js +++ b/packages/svelte/src/internal/client/error-handling.js @@ -29,7 +29,7 @@ export function handle_error(error) { // if the error occurred while creating this subtree, we let it // bubble up until it hits a boundary that can handle it if ((effect.f & BOUNDARY_EFFECT) === 0) { - if (!effect.parent && error instanceof Error) { + if (DEV && !effect.parent && error instanceof Error) { apply_adjustments(error); } @@ -61,7 +61,7 @@ export function invoke_error_boundary(error, effect) { effect = effect.parent; } - if (error instanceof Error) { + if (DEV && error instanceof Error) { apply_adjustments(error); } diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index d24218c4d3..deb3e82986 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -1,6 +1,6 @@ import type { Store } from '#shared'; import { STATE_SYMBOL } from './constants.js'; -import type { Effect, Source, Value, Reaction } from './reactivity/types.js'; +import type { Effect, Source, Value } from './reactivity/types.js'; type EventCallback = (event: Event) => boolean; export type EventCallbackMap = Record; @@ -16,6 +16,8 @@ export type ComponentContext = { c: null | Map; /** deferred effects */ e: null | Array<() => void | (() => void)>; + /** True if initialized, i.e. pop() ran */ + i: boolean; /** * props — needed for legacy mode lifecycle functions, and for `createEventDispatcher` * @deprecated remove in 6.0 diff --git a/packages/svelte/tests/runtime-runes/samples/async-context-throws-after-await/_config.js b/packages/svelte/tests/runtime-runes/samples/async-context-throws-after-await/_config.js new file mode 100644 index 0000000000..be73968a88 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-context-throws-after-await/_config.js @@ -0,0 +1,11 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + async test() { + // else runtime_error is checked too soon + await tick(); + }, + runtime_error: 'set_context_after_init' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-context-throws-after-await/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-context-throws-after-await/main.svelte new file mode 100644 index 0000000000..8e770c214b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-context-throws-after-await/main.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/async-set-context/Inner.svelte b/packages/svelte/tests/runtime-runes/samples/async-set-context/Inner.svelte new file mode 100644 index 0000000000..2c7fd5d43d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-set-context/Inner.svelte @@ -0,0 +1,7 @@ + + +

{greeting}

\ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/async-set-context/Outer.svelte b/packages/svelte/tests/runtime-runes/samples/async-set-context/Outer.svelte new file mode 100644 index 0000000000..9a493c5b75 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-set-context/Outer.svelte @@ -0,0 +1,9 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/async-set-context/_config.js b/packages/svelte/tests/runtime-runes/samples/async-set-context/_config.js new file mode 100644 index 0000000000..041f67a39e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-set-context/_config.js @@ -0,0 +1,11 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client', 'async-server'], + ssrHtml: `

hi

`, + async test({ assert, target }) { + await tick(); + assert.htmlEqual(target.innerHTML, '

hi

'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-set-context/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-set-context/main.svelte new file mode 100644 index 0000000000..01b46bda93 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-set-context/main.svelte @@ -0,0 +1,7 @@ + + +