fix: throw when `setContext` is called after an `await` during SSR (#18739)

Fixes #17233.
main
Bao Nguyen 1 day ago committed by GitHub
parent 9d91a40faf
commit 4bf15ae6f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: throw `set_context_after_init` when `setContext` is called after an `await` during SSR

@ -225,14 +225,6 @@ Rest element properties of `$props()` such as `%property%` are readonly
The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files
```
### set_context_after_init
```
`setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
```
This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6.
### state_descriptors_fixed
```

@ -80,6 +80,14 @@ Context was not set in the current component or any of its ancestors
The [`createContext()`](svelte#createContext) utility returns a `[get, set, has]` triplet of functions. `get` will throw an error if `set` was not used to set the context in the current component or any of its ancestors.
### set_context_after_init
```
`setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
```
This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6.
### snippet_without_render_tag
```

@ -171,12 +171,6 @@ This can happen if you render a hydratable on the client that was not rendered o
> The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files
## set_context_after_init
> `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6.
## state_descriptors_fixed
> Property descriptors defined on `$state` objects must contain `value` and always be `enumerable`, `configurable` and `writable`.

@ -66,6 +66,12 @@ Certain lifecycle methods can only be used during component initialisation. To f
The [`createContext()`](svelte#createContext) utility returns a `[get, set, has]` triplet of functions. `get` will throw an error if `set` was not used to set the context in the current component or any of its ancestors.
## set_context_after_init
> `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6.
## snippet_without_render_tag
> Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`.

@ -429,22 +429,6 @@ export function rune_outside_svelte(rune) {
}
}
/**
* `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
* @returns {never}
*/
export function set_context_after_init() {
if (DEV) {
const error = new Error(`set_context_after_init\n\`setContext\` must be called when a component first initializes, not in a subsequent effect or after an \`await\` expression\nhttps://svelte.dev/e/set_context_after_init`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/set_context_after_init`);
}
}
/**
* Property descriptors defined on `$state` objects must contain `value` and always be `enumerable`, `configurable` and `writable`.
* @returns {never}

@ -1,6 +1,7 @@
/** @import { SSRContext } from '#server' */
import { DEV } from 'esm-env';
import { create_context, get_or_init_context_map } from '../shared/context.js';
import * as e from './errors.js';
/** @type {SSRContext | null} */
export var ssr_context = null;
@ -40,7 +41,13 @@ export function getContext(key) {
* @returns {T}
*/
export function setContext(key, context) {
get_or_init_context_map(ssr_context, 'setContext').set(key, context);
const context_map = get_or_init_context_map(ssr_context, 'setContext');
if (/** @type {SSRContext} */ (ssr_context).i) {
e.set_context_after_init();
}
context_map.set(key, context);
return context;
}
@ -61,7 +68,7 @@ export function getAllContexts() {
* @param {Function} [fn]
*/
export function push(fn) {
ssr_context = { p: ssr_context, c: null, r: null };
ssr_context = { p: ssr_context, c: null, r: null, i: false };
if (DEV) {
ssr_context.function = fn;

@ -162,6 +162,11 @@ export class Renderer {
let promise = Promise.resolve(thunks[0]());
const promises = [promise];
if (context !== null && thunks.length > 1) {
// the remaining thunks run after an `await`, by which point it is too late to set context
context.i = true;
}
for (const fn of thunks.slice(1)) {
promise = promise.then(() => {
const previous_context = ssr_context;
@ -209,7 +214,8 @@ export class Renderer {
...ssr_context,
p: parent,
c: null,
r: child
r: child,
i: ssr_context?.i ?? false
});
const result = fn(child);
@ -260,7 +266,8 @@ export class Renderer {
...ssr_context,
p: parent_context,
c: null,
r: child
r: child,
i: ssr_context?.i ?? false
});
try {
@ -859,7 +866,7 @@ export class Renderer {
try {
/** @type {SSRContext} */
const context = { p: null, c: options.context ?? null, r: renderer };
const context = { p: null, c: options.context ?? null, r: renderer, i: false };
set_ssr_context(context);
renderer.push(BLOCK_OPEN);

@ -9,6 +9,8 @@ export interface SSRContext {
c: null | Map<unknown, unknown>;
/** renderer */
r: null | Renderer;
/** True if initialized, i.e. an `await` was reached */
i: boolean;
/** dev mode only: the current component function */
function?: any;
/** dev mode only: the current element */

@ -101,6 +101,22 @@ export function missing_context() {
}
}
/**
* `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
* @returns {never}
*/
export function set_context_after_init() {
if (DEV) {
const error = new Error(`set_context_after_init\n\`setContext\` must be called when a component first initializes, not in a subsequent effect or after an \`await\` expression\nhttps://svelte.dev/e/set_context_after_init`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/set_context_after_init`);
}
}
/**
* Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`.
* @returns {never}

@ -1,7 +1,6 @@
import { test } from '../../test';
export default test({
skip: true, // TODO it appears there might be an actual bug here; the promise isn't ever actually awaited in spite of being awaited in the component
mode: ['async'],
error: 'lifecycle_outside_component'
error: 'set_context_after_init'
});

Loading…
Cancel
Save