+
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/_expected.html
new file mode 100644
index 0000000000..c3b0e19566
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/_expected.html
@@ -0,0 +1 @@
+yes yes yes
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/main.svelte
new file mode 100644
index 0000000000..a97f32f034
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/main.svelte
@@ -0,0 +1,5 @@
+{#if await Promise.resolve(false)}
+ {await Promise.reject('no no no')}
+{:else}
+ {await Promise.resolve('yes yes yes')}
+{/if}
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/_expected.html
new file mode 100644
index 0000000000..c3b0e19566
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/_expected.html
@@ -0,0 +1 @@
+yes yes yes
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/main.svelte
new file mode 100644
index 0000000000..4ca3462771
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/main.svelte
@@ -0,0 +1,5 @@
+{#if await Promise.resolve(true)}
+ {await Promise.resolve('yes yes yes')}
+{:else}
+ {await Promise.reject('no no no')}
+{/if}
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-complex-value/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-complex-value/_expected.html
new file mode 100644
index 0000000000..f2fd8e71b9
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-complex-value/_expected.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-complex-value/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-complex-value/main.svelte
new file mode 100644
index 0000000000..2ad9b64509
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-complex-value/main.svelte
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-simple-value/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-simple-value/_expected.html
new file mode 100644
index 0000000000..f642109218
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-simple-value/_expected.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-simple-value/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-simple-value/main.svelte
new file mode 100644
index 0000000000..a5d91424cb
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-option-implicit-simple-value/main.svelte
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-select-value-component/Option.svelte b/packages/svelte/tests/server-side-rendering/samples/async-select-value-component/Option.svelte
new file mode 100644
index 0000000000..a17413bd8d
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-select-value-component/Option.svelte
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-select-value-component/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-select-value-component/_expected.html
new file mode 100644
index 0000000000..96d1d8b233
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-select-value-component/_expected.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-select-value-component/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-select-value-component/main.svelte
new file mode 100644
index 0000000000..aaf10c2cb3
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-select-value-component/main.svelte
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value-complex/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value-complex/_expected.html
new file mode 100644
index 0000000000..de7cba8837
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value-complex/_expected.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value-complex/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value-complex/main.svelte
new file mode 100644
index 0000000000..2cc4065eb6
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value-complex/main.svelte
@@ -0,0 +1,13 @@
+
+
+{#snippet option(val)}{await val}{/snippet}
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value/_expected.html
new file mode 100644
index 0000000000..de7cba8837
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value/_expected.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value/main.svelte
new file mode 100644
index 0000000000..8d9393cf6c
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value/main.svelte
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/comment-preserve/_expected.html b/packages/svelte/tests/server-side-rendering/samples/comment-preserve/_expected.html
index 3eb11afc34..df372583b1 100644
--- a/packages/svelte/tests/server-side-rendering/samples/comment-preserve/_expected.html
+++ b/packages/svelte/tests/server-side-rendering/samples/comment-preserve/_expected.html
@@ -1 +1 @@
-before
after
+before
after
diff --git a/packages/svelte/tests/server-side-rendering/samples/context/Child.svelte b/packages/svelte/tests/server-side-rendering/samples/context/Child.svelte
new file mode 100644
index 0000000000..fab18ea195
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/context/Child.svelte
@@ -0,0 +1,8 @@
+
+
+{getContext('key')}
diff --git a/packages/svelte/tests/server-side-rendering/samples/context/_config.js b/packages/svelte/tests/server-side-rendering/samples/context/_config.js
new file mode 100644
index 0000000000..05de37a8bd
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/context/_config.js
@@ -0,0 +1,5 @@
+import { test } from '../../test';
+
+export default test({
+ mode: ['async']
+});
diff --git a/packages/svelte/tests/server-side-rendering/samples/context/_expected.html b/packages/svelte/tests/server-side-rendering/samples/context/_expected.html
new file mode 100644
index 0000000000..554133d8d9
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/context/_expected.html
@@ -0,0 +1,3 @@
+value
+value
+child value
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/context/main.svelte b/packages/svelte/tests/server-side-rendering/samples/context/main.svelte
new file mode 100644
index 0000000000..fd75a6cbae
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/context/main.svelte
@@ -0,0 +1,11 @@
+
+
+{getContext('key')}
+{(await Promise.resolve(true)) && getContext('key')}
+
diff --git a/packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html b/packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html
index 8fb1be347c..0919060246 100644
--- a/packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html
+++ b/packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html
@@ -1 +1 @@
-Just a dummy page.
+Just a dummy page.
diff --git a/packages/svelte/tests/server-side-rendering/samples/head-svelte-components-raw-content/_expected.html b/packages/svelte/tests/server-side-rendering/samples/head-svelte-components-raw-content/_expected.html
index 290179a702..8c9e75c033 100644
--- a/packages/svelte/tests/server-side-rendering/samples/head-svelte-components-raw-content/_expected.html
+++ b/packages/svelte/tests/server-side-rendering/samples/head-svelte-components-raw-content/_expected.html
@@ -1,4 +1,5 @@
+
lorem
@@ -15,3 +16,4 @@
+
diff --git a/packages/svelte/tests/server-side-rendering/samples/invalid-nested-svelte-element/_config.js b/packages/svelte/tests/server-side-rendering/samples/invalid-nested-svelte-element/_config.js
index 71edff6a68..6325ea7d0e 100644
--- a/packages/svelte/tests/server-side-rendering/samples/invalid-nested-svelte-element/_config.js
+++ b/packages/svelte/tests/server-side-rendering/samples/invalid-nested-svelte-element/_config.js
@@ -4,8 +4,6 @@ export default test({
compileOptions: {
dev: true
},
-
- errors: [
+ error:
'node_invalid_placement_ssr: `` (packages/svelte/tests/server-side-rendering/samples/invalid-nested-svelte-element/main.svelte:2:1) cannot be a child of `
` (packages/svelte/tests/server-side-rendering/samples/invalid-nested-svelte-element/main.svelte:1:0)\n\nThis can cause content to shift around as the browser repairs the HTML, and will likely result in a `hydration_mismatch` warning.'
- ]
});
diff --git a/packages/svelte/tests/server-side-rendering/test.ts b/packages/svelte/tests/server-side-rendering/test.ts
index 3e57539427..7eede332a7 100644
--- a/packages/svelte/tests/server-side-rendering/test.ts
+++ b/packages/svelte/tests/server-side-rendering/test.ts
@@ -9,79 +9,133 @@ import { assert } from 'vitest';
import { render } from 'svelte/server';
import { compile_directory, should_update_expected, try_read_file } from '../helpers.js';
import { assert_html_equal_with_options } from '../html_equal.js';
-import { suite, type BaseTest } from '../suite.js';
+import { suite_with_variants, type BaseTest } from '../suite.js';
import type { CompileOptions } from '#compiler';
+import { seen } from '../../src/internal/server/dev.js';
interface SSRTest extends BaseTest {
+ mode?: ('sync' | 'async')[];
compileOptions?: Partial;
+ load_compiled?: boolean;
props?: Record;
id_prefix?: string;
withoutNormalizeHtml?: boolean;
- errors?: string[];
+ error?: string;
}
-// eslint-disable-next-line no-console
-let console_error = console.error;
+// TODO remove this shim when we can
+// @ts-expect-error
+Promise.withResolvers = () => {
+ let resolve;
+ let reject;
+
+ const promise = new Promise((f, r) => {
+ resolve = f;
+ reject = r;
+ });
+
+ return { promise, resolve, reject };
+};
+
+const { test, run } = suite_with_variants(
+ ['sync', 'async'],
+ (variant, config, test_name) => {
+ if (config.mode && !config.mode.includes(variant)) {
+ return 'no-test';
+ }
-const { test, run } = suite(async (config, test_dir) => {
- await compile_directory(test_dir, 'server', config.compileOptions);
+ if (test_name.startsWith('async') && variant === 'sync') {
+ return 'no-test';
+ }
- const errors: string[] = [];
+ return false;
+ },
+ async (config, test_dir) => {
+ const compile_options = {
+ experimental: {
+ async: true,
+ ...config.compileOptions?.experimental
+ },
+ ...config.compileOptions
+ };
+
+ if (!config.load_compiled) {
+ await compile_directory(test_dir, 'server', compile_options);
+ }
- console.error = (...args) => {
- errors.push(...args);
- };
+ return compile_options;
+ },
+ async (config, test_dir, variant, compile_options) => {
+ const Component = (await import(`${test_dir}/_output/server/main.svelte.js`)).default;
+ const expected_html = try_read_file(`${test_dir}/_expected.html`);
+ const is_async = variant === 'async';
- const Component = (await import(`${test_dir}/_output/server/main.svelte.js`)).default;
- const expected_html = try_read_file(`${test_dir}/_expected.html`);
- const rendered = render(Component, { props: config.props || {}, idPrefix: config.id_prefix });
- const { body, head } = rendered;
+ seen?.clear();
+
+ let rendered;
+ try {
+ const render_result = render(Component, {
+ props: config.props || {},
+ idPrefix: config.id_prefix
+ });
+ rendered = is_async ? await render_result : render_result;
+ } catch (error) {
+ if (config.error) {
+ assert.deepEqual((error as Error).message, config.error);
+ return;
+ } else {
+ throw error;
+ }
+ }
- fs.writeFileSync(`${test_dir}/_output/rendered.html`, body);
+ const { body, head } = rendered;
- if (head) {
- fs.writeFileSync(`${test_dir}/_output/rendered_head.html`, head);
- }
+ fs.writeFileSync(
+ `${test_dir}/_output/${is_async ? 'async_rendered.html' : 'rendered.html'}`,
+ body
+ );
- try {
- assert_html_equal_with_options(body, expected_html || '', {
- preserveComments: config.compileOptions?.preserveComments,
- withoutNormalizeHtml: config.withoutNormalizeHtml
- });
- } catch (error: any) {
- if (should_update_expected()) {
- fs.writeFileSync(`${test_dir}/_expected.html`, body);
- console.log(`Updated ${test_dir}/_expected.html.`);
- } else {
- error.message += '\n' + `${test_dir}/main.svelte`;
- throw error;
+ if (head) {
+ fs.writeFileSync(
+ `${test_dir}/_output/${is_async ? 'async_rendered_head.html' : 'rendered_head.html'}`,
+ head
+ );
}
- }
- if (fs.existsSync(`${test_dir}/_expected_head.html`)) {
try {
- assert_html_equal_with_options(
- head,
- fs.readFileSync(`${test_dir}/_expected_head.html`, 'utf-8'),
- {}
- );
+ assert_html_equal_with_options(body, expected_html || '', {
+ preserveComments: compile_options.preserveComments,
+ withoutNormalizeHtml: config.withoutNormalizeHtml
+ });
} catch (error: any) {
if (should_update_expected()) {
- fs.writeFileSync(`${test_dir}/_expected_head.html`, head);
- console.log(`Updated ${test_dir}/_expected_head.html.`);
- error.message += '\n' + `${test_dir}/main.svelte`;
+ fs.writeFileSync(`${test_dir}/_expected.html`, body);
+ console.log(`Updated ${test_dir}/_expected.html.`);
} else {
+ error.message += '\n' + `${test_dir}/main.svelte`;
throw error;
}
}
- }
- if (errors.length > 0) {
- assert.deepEqual(config.errors, errors);
+ if (fs.existsSync(`${test_dir}/_expected_head.html`)) {
+ try {
+ assert_html_equal_with_options(
+ head,
+ fs.readFileSync(`${test_dir}/_expected_head.html`, 'utf-8'),
+ {}
+ );
+ } catch (error: any) {
+ if (should_update_expected()) {
+ fs.writeFileSync(`${test_dir}/_expected_head.html`, head);
+ console.log(`Updated ${test_dir}/_expected_head.html.`);
+ error.message += '\n' + `${test_dir}/main.svelte`;
+ } else {
+ throw error;
+ }
+ }
+ }
}
-
- console.error = console_error;
-});
+);
export { test };
diff --git a/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_config.js b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_config.js
new file mode 100644
index 0000000000..2e30bbeb16
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_config.js
@@ -0,0 +1,3 @@
+import { test } from '../../test';
+
+export default test({ compileOptions: { experimental: { async: true } } });
diff --git a/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/client/index.svelte.js
new file mode 100644
index 0000000000..cf667e1624
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/client/index.svelte.js
@@ -0,0 +1,35 @@
+import 'svelte/internal/disclose-version';
+import 'svelte/internal/flags/async';
+import * as $ from 'svelte/internal/client';
+
+export default function Async_each_fallback_hoisting($$anchor) {
+ var fragment = $.comment();
+ var node = $.first_child(fragment);
+
+ $.async(node, [() => Promise.resolve([])], (node, $$collection) => {
+ $.each(
+ node,
+ 16,
+ () => $.get($$collection),
+ $.index,
+ ($$anchor, item) => {
+ $.next();
+
+ var text = $.text();
+
+ $.template_effect(($0) => $.set_text(text, $0), void 0, [() => Promise.reject('This should never be reached')]);
+ $.append($$anchor, text);
+ },
+ ($$anchor) => {
+ $.next();
+
+ var text_1 = $.text();
+
+ $.template_effect(($0) => $.set_text(text_1, $0), void 0, [() => Promise.resolve(4)]);
+ $.append($$anchor, text_1);
+ }
+ );
+ });
+
+ $.append($$anchor, fragment);
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js
new file mode 100644
index 0000000000..808818cc9e
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js
@@ -0,0 +1,25 @@
+import 'svelte/internal/flags/async';
+import * as $ from 'svelte/internal/server';
+
+export default function Async_each_fallback_hoisting($$payload) {
+ $$payload.child(async ($$payload) => {
+ const each_array = $.ensure_array_like(await Promise.resolve([]));
+
+ if (each_array.length !== 0) {
+ $$payload.push('');
+
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
+ let item = each_array[$$index];
+
+ $$payload.push(``);
+ $$payload.push(async () => $.escape(await Promise.reject('This should never be reached')));
+ }
+ } else {
+ $$payload.push('');
+ $$payload.push(``);
+ $$payload.push(async () => $.escape(await Promise.resolve(4)));
+ }
+ });
+
+ $$payload.push(``);
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/index.svelte b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/index.svelte
new file mode 100644
index 0000000000..e580345a2e
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/index.svelte
@@ -0,0 +1,5 @@
+{#each await Promise.resolve([]) as item}
+ {await Promise.reject('This should never be reached')}
+{:else}
+ {await Promise.resolve(4)}
+{/each}
diff --git a/packages/svelte/tests/snapshot/samples/async-each-hoisting/_config.js b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_config.js
new file mode 100644
index 0000000000..2e30bbeb16
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_config.js
@@ -0,0 +1,3 @@
+import { test } from '../../test';
+
+export default test({ compileOptions: { experimental: { async: true } } });
diff --git a/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/client/index.svelte.js
new file mode 100644
index 0000000000..a1535d6886
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/client/index.svelte.js
@@ -0,0 +1,24 @@
+import 'svelte/internal/disclose-version';
+import 'svelte/internal/flags/async';
+import * as $ from 'svelte/internal/client';
+
+export default function Async_each_hoisting($$anchor) {
+ const first = Promise.resolve(1);
+ const second = Promise.resolve(2);
+ const third = Promise.resolve(3);
+ var fragment = $.comment();
+ var node = $.first_child(fragment);
+
+ $.async(node, [() => Promise.resolve([first, second, third])], (node, $$collection) => {
+ $.each(node, 17, () => $.get($$collection), $.index, ($$anchor, item) => {
+ $.next();
+
+ var text = $.text();
+
+ $.template_effect(($0) => $.set_text(text, $0), void 0, [() => $.get(item)]);
+ $.append($$anchor, text);
+ });
+ });
+
+ $.append($$anchor, fragment);
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js
new file mode 100644
index 0000000000..6c2f837954
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js
@@ -0,0 +1,23 @@
+import 'svelte/internal/flags/async';
+import * as $ from 'svelte/internal/server';
+
+export default function Async_each_hoisting($$payload) {
+ const first = Promise.resolve(1);
+ const second = Promise.resolve(2);
+ const third = Promise.resolve(3);
+
+ $$payload.push(``);
+
+ $$payload.child(async ($$payload) => {
+ const each_array = $.ensure_array_like(await Promise.resolve([first, second, third]));
+
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
+ let item = each_array[$$index];
+
+ $$payload.push(``);
+ $$payload.push(async () => $.escape(await item));
+ }
+ });
+
+ $$payload.push(``);
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/async-each-hoisting/index.svelte b/packages/svelte/tests/snapshot/samples/async-each-hoisting/index.svelte
new file mode 100644
index 0000000000..4b6bf7eadc
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-each-hoisting/index.svelte
@@ -0,0 +1,9 @@
+
+
+{#each await Promise.resolve([first, second, third]) as item}
+ {await item}
+{/each}
diff --git a/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_config.js b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_config.js
new file mode 100644
index 0000000000..2e30bbeb16
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_config.js
@@ -0,0 +1,3 @@
+import { test } from '../../test';
+
+export default test({ compileOptions: { experimental: { async: true } } });
diff --git a/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/client/index.svelte.js
new file mode 100644
index 0000000000..e385f5d234
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/client/index.svelte.js
@@ -0,0 +1,30 @@
+import 'svelte/internal/disclose-version';
+import 'svelte/internal/flags/async';
+import * as $ from 'svelte/internal/client';
+
+export default function Async_if_alternate_hoisting($$anchor) {
+ var fragment = $.comment();
+ var node = $.first_child(fragment);
+
+ $.async(node, [() => Promise.resolve(false)], (node, $$condition) => {
+ var consequent = ($$anchor) => {
+ var text = $.text();
+
+ $.template_effect(($0) => $.set_text(text, $0), void 0, [() => Promise.reject('no no no')]);
+ $.append($$anchor, text);
+ };
+
+ var alternate = ($$anchor) => {
+ var text_1 = $.text();
+
+ $.template_effect(($0) => $.set_text(text_1, $0), void 0, [() => Promise.resolve('yes yes yes')]);
+ $.append($$anchor, text_1);
+ };
+
+ $.if(node, ($$render) => {
+ if ($.get($$condition)) $$render(consequent); else $$render(alternate, false);
+ });
+ });
+
+ $.append($$anchor, fragment);
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js
new file mode 100644
index 0000000000..a645882a54
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js
@@ -0,0 +1,16 @@
+import 'svelte/internal/flags/async';
+import * as $ from 'svelte/internal/server';
+
+export default function Async_if_alternate_hoisting($$payload) {
+ $$payload.child(async ($$payload) => {
+ if (await Promise.resolve(false)) {
+ $$payload.push('');
+ $$payload.push(async () => $.escape(await Promise.reject('no no no')));
+ } else {
+ $$payload.push('');
+ $$payload.push(async () => $.escape(await Promise.resolve('yes yes yes')));
+ }
+ });
+
+ $$payload.push(``);
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/index.svelte b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/index.svelte
new file mode 100644
index 0000000000..a97f32f034
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/index.svelte
@@ -0,0 +1,5 @@
+{#if await Promise.resolve(false)}
+ {await Promise.reject('no no no')}
+{:else}
+ {await Promise.resolve('yes yes yes')}
+{/if}
diff --git a/packages/svelte/tests/snapshot/samples/async-if-hoisting/_config.js b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_config.js
new file mode 100644
index 0000000000..2e30bbeb16
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_config.js
@@ -0,0 +1,3 @@
+import { test } from '../../test';
+
+export default test({ compileOptions: { experimental: { async: true } } });
diff --git a/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/client/index.svelte.js
new file mode 100644
index 0000000000..356e8e9607
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/client/index.svelte.js
@@ -0,0 +1,30 @@
+import 'svelte/internal/disclose-version';
+import 'svelte/internal/flags/async';
+import * as $ from 'svelte/internal/client';
+
+export default function Async_if_hoisting($$anchor) {
+ var fragment = $.comment();
+ var node = $.first_child(fragment);
+
+ $.async(node, [() => Promise.resolve(true)], (node, $$condition) => {
+ var consequent = ($$anchor) => {
+ var text = $.text();
+
+ $.template_effect(($0) => $.set_text(text, $0), void 0, [() => Promise.resolve('yes yes yes')]);
+ $.append($$anchor, text);
+ };
+
+ var alternate = ($$anchor) => {
+ var text_1 = $.text();
+
+ $.template_effect(($0) => $.set_text(text_1, $0), void 0, [() => Promise.reject('no no no')]);
+ $.append($$anchor, text_1);
+ };
+
+ $.if(node, ($$render) => {
+ if ($.get($$condition)) $$render(consequent); else $$render(alternate, false);
+ });
+ });
+
+ $.append($$anchor, fragment);
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js
new file mode 100644
index 0000000000..1d75b5ad96
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js
@@ -0,0 +1,16 @@
+import 'svelte/internal/flags/async';
+import * as $ from 'svelte/internal/server';
+
+export default function Async_if_hoisting($$payload) {
+ $$payload.child(async ($$payload) => {
+ if (await Promise.resolve(true)) {
+ $$payload.push('');
+ $$payload.push(async () => $.escape(await Promise.resolve('yes yes yes')));
+ } else {
+ $$payload.push('');
+ $$payload.push(async () => $.escape(await Promise.reject('no no no')));
+ }
+ });
+
+ $$payload.push(``);
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/async-if-hoisting/index.svelte b/packages/svelte/tests/snapshot/samples/async-if-hoisting/index.svelte
new file mode 100644
index 0000000000..4ca3462771
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-if-hoisting/index.svelte
@@ -0,0 +1,5 @@
+{#if await Promise.resolve(true)}
+ {await Promise.resolve('yes yes yes')}
+{:else}
+ {await Promise.reject('no no no')}
+{/if}
diff --git a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js
index cc2628c852..c0e3512178 100644
--- a/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/await-block-scope/_expected/server/index.svelte.js
@@ -8,7 +8,7 @@ export default function Await_block_scope($$payload) {
counter.count += 1;
}
- $$payload.out.push(` `);
+ $$payload.push(` `);
$.await($$payload, promise, () => {}, (counter) => {});
- $$payload.out.push(` ${$.escape(counter.count)}`);
+ $$payload.push(` ${$.escape(counter.count)}`);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js
index c0db7d2fd5..f2b687489b 100644
--- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js
@@ -2,7 +2,7 @@ import * as $ from 'svelte/internal/server';
import TextInput from './Child.svelte';
function snippet($$payload) {
- $$payload.out.push(`Something`);
+ $$payload.push(`Something`);
}
export default function Bind_component_snippet($$payload) {
@@ -23,14 +23,14 @@ export default function Bind_component_snippet($$payload) {
}
});
- $$payload.out.push(` value: ${$.escape(value)}`);
+ $$payload.push(` value: ${$.escape(value)}`);
}
do {
$$settled = true;
- $$inner_payload = $.copy_payload($$payload);
+ $$inner_payload = $$payload.copy();
$$render_inner($$inner_payload);
} while (!$$settled);
- $.assign_payload($$payload, $$inner_payload);
+ $$payload.subsume($$inner_payload);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/server/index.svelte.js
index abfc264fea..e426a08989 100644
--- a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/server/index.svelte.js
@@ -1,38 +1,36 @@
import * as $ from 'svelte/internal/server';
export default function Class_state_field_constructor_assignment($$payload, $$props) {
- $.push();
-
- class Foo {
- a = 0;
- #b;
- #foo = $.derived(() => ({ bar: this.a * 2 }));
-
- get foo() {
- return this.#foo();
- }
-
- set foo($$value) {
- return this.#foo($$value);
- }
-
- #bar = $.derived(() => ({ baz: this.foo }));
-
- get bar() {
- return this.#bar();
- }
-
- set bar($$value) {
- return this.#bar($$value);
+ $$payload.component(($$payload) => {
+ class Foo {
+ a = 0;
+ #b;
+ #foo = $.derived(() => ({ bar: this.a * 2 }));
+
+ get foo() {
+ return this.#foo();
+ }
+
+ set foo($$value) {
+ return this.#foo($$value);
+ }
+
+ #bar = $.derived(() => ({ baz: this.foo }));
+
+ get bar() {
+ return this.#bar();
+ }
+
+ set bar($$value) {
+ return this.#bar($$value);
+ }
+
+ constructor() {
+ this.a = 1;
+ this.#b = 2;
+ this.foo.bar = 3;
+ this.bar = 4;
+ }
}
-
- constructor() {
- this.a = 1;
- this.#b = 2;
- this.foo.bar = 3;
- this.bar = 4;
- }
- }
-
- $.pop();
+ });
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/_expected/server/index.svelte.js
index ac3dfcd2cb..a17c437cd9 100644
--- a/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/delegated-locally-declared-shadowed/_expected/server/index.svelte.js
@@ -1,13 +1,13 @@
import * as $ from 'svelte/internal/server';
export default function Delegated_locally_declared_shadowed($$payload) {
- const each_array = $.ensure_array_like({ length: 1 });
+ $$payload.push(``);
- $$payload.out.push(``);
+ const each_array = $.ensure_array_like({ length: 1 });
for (let index = 0, $$length = each_array.length; index < $$length; index++) {
- $$payload.out.push(``);
+ $$payload.push(``);
}
- $$payload.out.push(``);
+ $$payload.push(``);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js
index 9c837d4e1d..deaeaf6bfa 100644
--- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js
@@ -6,5 +6,5 @@ export default function Main($$payload) {
let y = () => 'test';
- $$payload.out.push(` `);
+ $$payload.push(` `);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/server/index.svelte.js
index 8fa0c5f28c..abec4f42fa 100644
--- a/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/each-index-non-null/_expected/server/index.svelte.js
@@ -1,13 +1,13 @@
import * as $ from 'svelte/internal/server';
export default function Each_index_non_null($$payload) {
- const each_array = $.ensure_array_like(Array(10));
+ $$payload.push(``);
- $$payload.out.push(``);
+ const each_array = $.ensure_array_like(Array(10));
for (let i = 0, $$length = each_array.length; i < $$length; i++) {
- $$payload.out.push(`index: ${$.escape(i)}
`);
+ $$payload.push(`index: ${$.escape(i)}
`);
}
- $$payload.out.push(``);
+ $$payload.push(``);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js
index 6dbe8130da..fda6e5fbb3 100644
--- a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/server/index.svelte.js
@@ -1,15 +1,15 @@
import * as $ from 'svelte/internal/server';
export default function Each_string_template($$payload) {
- const each_array = $.ensure_array_like(['foo', 'bar', 'baz']);
+ $$payload.push(``);
- $$payload.out.push(``);
+ const each_array = $.ensure_array_like(['foo', 'bar', 'baz']);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let thing = each_array[$$index];
- $$payload.out.push(`${$.escape(thing)}, `);
+ $$payload.push(`${$.escape(thing)}, `);
}
- $$payload.out.push(``);
+ $$payload.push(``);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js
index ce4f09ed1d..a9fa4efd50 100644
--- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js
@@ -15,7 +15,7 @@ export default function Function_prop_no_getter($$payload) {
onmouseenter: () => count = plusOne(count),
children: ($$payload) => {
- $$payload.out.push(`clicks: ${$.escape(count)}`);
+ $$payload.push(`clicks: ${$.escape(count)}`);
},
$$slots: { default: true }
diff --git a/packages/svelte/tests/snapshot/samples/functional-templating/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/functional-templating/_expected/server/index.svelte.js
index b1a0a5f9e6..e23a50cff5 100644
--- a/packages/svelte/tests/snapshot/samples/functional-templating/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/functional-templating/_expected/server/index.svelte.js
@@ -1,5 +1,5 @@
import * as $ from 'svelte/internal/server';
export default function Functional_templating($$payload) {
- $$payload.out.push(`hello
child element
another child element
`);
+ $$payload.push(`hello
child element
another child element
`);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/hello-world/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/hello-world/_expected/server/index.svelte.js
index 30f6d6b74a..6bdf5b4495 100644
--- a/packages/svelte/tests/snapshot/samples/hello-world/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/hello-world/_expected/server/index.svelte.js
@@ -1,5 +1,5 @@
import * as $ from 'svelte/internal/server';
export default function Hello_world($$payload) {
- $$payload.out.push(`hello world
`);
+ $$payload.push(`hello world
`);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js
index ea1d12c83b..99813a32b1 100644
--- a/packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/hmr/_expected/server/index.svelte.js
@@ -1,5 +1,5 @@
import * as $ from 'svelte/internal/server';
export default function Hmr($$payload) {
- $$payload.out.push(`hello world
`);
+ $$payload.push(`hello world
`);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/server/index.svelte.js
index 18e01b4f72..f096ddd3ba 100644
--- a/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/nullish-coallescence-omittance/_expected/server/index.svelte.js
@@ -4,5 +4,5 @@ export default function Nullish_coallescence_omittance($$payload) {
let name = 'world';
let count = 0;
- $$payload.out.push(`Hello, world!
123 Hello, world
`);
+ $$payload.push(`Hello, world!
123 Hello, world
`);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.js
index 33a3633939..7871172433 100644
--- a/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.js
@@ -1,16 +1,15 @@
import * as $ from 'svelte/internal/server';
export default function Props_identifier($$payload, $$props) {
- $.push();
+ $$payload.component(($$payload) => {
+ let { $$slots, $$events, ...props } = $$props;
- let { $$slots, $$events, ...props } = $$props;
-
- props.a;
- props[a];
- props.a.b;
- props.a.b = true;
- props.a = true;
- props[a] = true;
- props;
- $.pop();
+ props.a;
+ props[a];
+ props.a.b;
+ props.a.b = true;
+ props.a = true;
+ props[a] = true;
+ props;
+ });
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/purity/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/purity/_expected/server/index.svelte.js
index 29b0d0d594..b19e3aac52 100644
--- a/packages/svelte/tests/snapshot/samples/purity/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/purity/_expected/server/index.svelte.js
@@ -1,7 +1,7 @@
import * as $ from 'svelte/internal/server';
export default function Purity($$payload) {
- $$payload.out.push(`0
${$.escape(location.href)}
`);
+ $$payload.push(`0
${$.escape(location.href)}
`);
Child($$payload, { prop: encodeURIComponent('hello') });
- $$payload.out.push(``);
+ $$payload.push(``);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js
index bad475ec86..e49480f682 100644
--- a/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/skip-static-subtree/_expected/server/index.svelte.js
@@ -3,5 +3,5 @@ import * as $ from 'svelte/internal/server';
export default function Skip_static_subtree($$payload, $$props) {
let { title, content } = $$props;
- $$payload.out.push(` ${$.escape(title)}
we don't need to traverse these nodes
or
these
ones
${$.html(content)} these
trailing
nodes
can
be
completely
ignored
`);
+ $$payload.push(` ${$.escape(title)}
we don't need to traverse these nodes
or
these
ones
${$.html(content)} these
trailing
nodes
can
be
completely
ignored
`);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js
index c2736b0f43..2c66dc5759 100644
--- a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/server/index.svelte.js
@@ -11,5 +11,5 @@ export default function State_proxy_literal($$payload) {
tpl = ``;
}
- $$payload.out.push(` `);
+ $$payload.push(` `);
}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/server/index.svelte.js
index f7dc586026..1ed2b812db 100644
--- a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/server/index.svelte.js
@@ -12,5 +12,5 @@ export default function Text_nodes_deriveds($$payload) {
return count2;
}
- $$payload.out.push(`${$.escape(text1())}${$.escape(text2())}
`);
+ $$payload.push(`${$.escape(text1())}${$.escape(text2())}
`);
}
\ No newline at end of file
diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts
index 9888de59b2..2c015b5a10 100644
--- a/packages/svelte/types/index.d.ts
+++ b/packages/svelte/types/index.d.ts
@@ -2493,7 +2493,7 @@ declare module 'svelte/server' {
}
]
): RenderOutput;
- interface RenderOutput {
+ interface SyncRenderOutput {
/** HTML that goes into the `` */
head: string;
/** @deprecated use `body` instead */
@@ -2502,6 +2502,8 @@ declare module 'svelte/server' {
body: string;
}
+ type RenderOutput = SyncRenderOutput & PromiseLike;
+
export {};
}
diff --git a/playgrounds/sandbox/package.json b/playgrounds/sandbox/package.json
index 8100084832..a5a28879e0 100644
--- a/playgrounds/sandbox/package.json
+++ b/playgrounds/sandbox/package.json
@@ -12,7 +12,8 @@
"preview": "vite preview",
"download": "node scripts/download.js",
"hash": "node scripts/hash.js",
- "create-test": "node scripts/create-test.js"
+ "create-test": "node scripts/create-test.js",
+ "start": "node run.js"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.6",
From eaf4d40f5135e7c05068c4d4ea4a98b4552840a1 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 17 Sep 2025 17:54:19 -0400
Subject: [PATCH 22/68] Version Packages (#16774)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
.changeset/forty-insects-cheat.md | 5 -----
.changeset/pink-gifts-sell.md | 5 -----
packages/svelte/CHANGELOG.md | 10 ++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
5 files changed, 12 insertions(+), 12 deletions(-)
delete mode 100644 .changeset/forty-insects-cheat.md
delete mode 100644 .changeset/pink-gifts-sell.md
diff --git a/.changeset/forty-insects-cheat.md b/.changeset/forty-insects-cheat.md
deleted file mode 100644
index 993a8fdb24..0000000000
--- a/.changeset/forty-insects-cheat.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': minor
----
-
-feat: experimental async SSR
diff --git a/.changeset/pink-gifts-sell.md b/.changeset/pink-gifts-sell.md
deleted file mode 100644
index f3f91ff7d9..0000000000
--- a/.changeset/pink-gifts-sell.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: correctly SSR hidden="until-found"
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 62f109c82f..57ee2565c4 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,15 @@
# svelte
+## 5.39.0
+
+### Minor Changes
+
+- feat: experimental async SSR ([#16748](https://github.com/sveltejs/svelte/pull/16748))
+
+### Patch Changes
+
+- fix: correctly SSR hidden="until-found" ([#16773](https://github.com/sveltejs/svelte/pull/16773))
+
## 5.38.10
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 6c91ec6bbb..24e9aa27e8 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.38.10",
+ "version": "5.39.0",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 9bfa7a5421..5b682ef1d3 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.38.10';
+export const VERSION = '5.39.0';
export const PUBLIC_VERSION = '5';
From 037314a4c0017dd2ed4262c1a8218fa0935046e0 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 17 Sep 2025 18:05:31 -0400
Subject: [PATCH 23/68] chore: remove actions-permissions monitor (#16745)
---
.github/workflows/ecosystem-ci-trigger.yml | 1 -
.github/workflows/pkg.pr.new-comment.yml | 1 -
.github/workflows/release.yml | 1 -
3 files changed, 3 deletions(-)
diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml
index 9be1f00104..7753b606e1 100644
--- a/.github/workflows/ecosystem-ci-trigger.yml
+++ b/.github/workflows/ecosystem-ci-trigger.yml
@@ -15,7 +15,6 @@ jobs:
contents: read # to clone the repo
steps:
- name: monitor action permissions
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: check user authorization # user needs triage permission
uses: actions/github-script@v7
id: check-permissions
diff --git a/.github/workflows/pkg.pr.new-comment.yml b/.github/workflows/pkg.pr.new-comment.yml
index 3f1fca5a0b..8bf13bb8f1 100644
--- a/.github/workflows/pkg.pr.new-comment.yml
+++ b/.github/workflows/pkg.pr.new-comment.yml
@@ -14,7 +14,6 @@ jobs:
name: 'Update comment'
runs-on: ubuntu-latest
steps:
- - uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Download artifact
uses: actions/download-artifact@v4
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f9d683a2ae..b78346d883 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -17,7 +17,6 @@ jobs:
name: Release
runs-on: ubuntu-latest
steps:
- - uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Checkout Repo
uses: actions/checkout@v4
with:
From fc35d9fab8440a90c57368103f4a5e0bbd215381 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 17 Sep 2025 19:21:23 -0400
Subject: [PATCH 24/68] fix: add `then` to class component `render` output
(#16783)
* fix: add `then` to class component `render` output
* drive-by tidy up
* fix types
---
.changeset/sixty-crabs-laugh.md | 5 ++
packages/svelte/src/internal/server/index.js | 8 +--
packages/svelte/src/legacy/legacy-server.js | 57 +++++++++++++++++---
3 files changed, 58 insertions(+), 12 deletions(-)
create mode 100644 .changeset/sixty-crabs-laugh.md
diff --git a/.changeset/sixty-crabs-laugh.md b/.changeset/sixty-crabs-laugh.md
new file mode 100644
index 0000000000..1780ddc166
--- /dev/null
+++ b/.changeset/sixty-crabs-laugh.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: add `then` to class component `render` output
diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js
index a2cf222da6..13a0706549 100644
--- a/packages/svelte/src/internal/server/index.js
+++ b/packages/svelte/src/internal/server/index.js
@@ -1,5 +1,5 @@
/** @import { ComponentType, SvelteComponent, Component } from 'svelte' */
-/** @import { RenderOutput, SSRContext } from '#server' */
+/** @import { RenderOutput } from '#server' */
/** @import { Store } from '#shared' */
/** @import { AccumulatedContent } from './payload.js' */
export { FILENAME, HMR } from '../../constants.js';
@@ -14,14 +14,10 @@ import {
} from '../../constants.js';
import { escape_html } from '../../escaping.js';
import { DEV } from 'esm-env';
-import { ssr_context, pop, push, set_ssr_context } from './context.js';
import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN, BLOCK_OPEN_ELSE } from './hydration.js';
import { validate_store } from '../shared/validate.js';
import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js';
-import { Payload, SSRState } from './payload.js';
-import { abort } from './abort-signal.js';
-import { async_mode_flag } from '../flags/index.js';
-import * as e from './errors.js';
+import { Payload } from './payload.js';
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
// https://infra.spec.whatwg.org/#noncharacter
diff --git a/packages/svelte/src/legacy/legacy-server.js b/packages/svelte/src/legacy/legacy-server.js
index 92f8757d7a..b7d3e673bc 100644
--- a/packages/svelte/src/legacy/legacy-server.js
+++ b/packages/svelte/src/legacy/legacy-server.js
@@ -1,11 +1,15 @@
/** @import { SvelteComponent } from '../index.js' */
import { asClassComponent as as_class_component, createClassComponent } from './legacy-client.js';
import { render } from '../internal/server/index.js';
+import { async_mode_flag } from '../internal/flags/index.js';
+import * as w from '../internal/server/warnings.js';
// By having this as a separate entry point for server environments, we save the client bundle from having to include the server runtime
export { createClassComponent };
+/** @typedef {{ head: string, html: string, css: { code: string, map: null }}} LegacyRenderResult */
+
/**
* Takes a Svelte 5 component and returns a Svelte 4 compatible component constructor.
*
@@ -21,15 +25,56 @@ export { createClassComponent };
*/
export function asClassComponent(component) {
const component_constructor = as_class_component(component);
- /** @type {(props?: {}, opts?: { $$slots?: {}; context?: Map; }) => { html: any; css: { code: string; map: any; }; head: string; } } */
+ /** @type {(props?: {}, opts?: { $$slots?: {}; context?: Map; }) => LegacyRenderResult & PromiseLike } */
const _render = (props, { context } = {}) => {
// @ts-expect-error the typings are off, but this will work if the component is compiled in SSR mode
const result = render(component, { props, context });
- return {
- css: { code: '', map: null },
- head: result.head,
- html: result.body
- };
+
+ const munged = Object.defineProperties(
+ /** @type {LegacyRenderResult & PromiseLike} */ ({}),
+ {
+ css: {
+ value: { code: '', map: null }
+ },
+ head: {
+ get: () => result.head
+ },
+ html: {
+ get: () => result.body
+ },
+ then: {
+ /**
+ * this is not type-safe, but honestly it's the best I can do right now, and it's a straightforward function.
+ *
+ * @template TResult1
+ * @template [TResult2=never]
+ * @param { (value: LegacyRenderResult) => TResult1 } onfulfilled
+ * @param { (reason: unknown) => TResult2 } onrejected
+ */
+ value: (onfulfilled, onrejected) => {
+ if (!async_mode_flag) {
+ w.experimental_async_ssr();
+ const user_result = onfulfilled({
+ css: munged.css,
+ head: munged.head,
+ html: munged.html
+ });
+ return Promise.resolve(user_result);
+ }
+
+ return result.then((result) => {
+ return onfulfilled({
+ css: munged.css,
+ head: result.head,
+ html: result.body
+ });
+ }, onrejected);
+ }
+ }
+ }
+ );
+
+ return munged;
};
// @ts-expect-error this is present for SSR
From 6798efacb2045356e5630b1a6dffcf6ab5a1161d Mon Sep 17 00:00:00 2001
From: Elliott Johnson
Date: Wed, 17 Sep 2025 17:30:23 -0600
Subject: [PATCH 25/68] chore: Rename Payload to Renderer (#16786)
---
.../3-transform/server/transform-server.js | 22 +-
.../3-transform/server/visitors/AwaitBlock.js | 6 +-
.../3-transform/server/visitors/EachBlock.js | 8 +-
.../3-transform/server/visitors/IfBlock.js | 8 +-
.../server/visitors/RegularElement.js | 16 +-
.../3-transform/server/visitors/RenderTag.js | 2 +-
.../server/visitors/SlotElement.js | 6 +-
.../server/visitors/SnippetBlock.js | 4 +-
.../server/visitors/SvelteBoundary.js | 2 +-
.../server/visitors/SvelteElement.js | 4 +-
.../3-transform/server/visitors/SvelteHead.js | 2 +-
.../server/visitors/TitleElement.js | 2 +-
.../server/visitors/shared/component.js | 10 +-
.../server/visitors/shared/element.js | 6 +-
.../server/visitors/shared/utils.js | 12 +-
packages/svelte/src/index-server.js | 4 +-
.../src/internal/server/blocks/snippet.js | 6 +-
packages/svelte/src/internal/server/dev.js | 28 +--
packages/svelte/src/internal/server/index.js | 134 +++++-----
.../server/{payload.js => renderer.js} | 172 ++++++-------
.../{payload.test.ts => renderer.test.ts} | 238 +++++++++---------
.../svelte/src/internal/server/types.d.ts | 6 +-
.../_expected/server/index.svelte.js | 18 +-
.../_expected/server/index.svelte.js | 12 +-
.../_expected/server/index.svelte.js | 14 +-
.../_expected/server/index.svelte.js | 14 +-
.../_expected/server/index.svelte.js | 8 +-
.../_expected/server/index.svelte.js | 20 +-
.../_expected/server/index.svelte.js | 4 +-
.../_expected/server/index.svelte.js | 4 +-
.../_expected/server/index.svelte.js | 8 +-
.../_expected/server/main.svelte.js | 4 +-
.../_expected/server/index.svelte.js | 8 +-
.../_expected/server/index.svelte.js | 8 +-
.../_expected/server/index.svelte.js | 8 +-
.../_expected/server/index.svelte.js | 4 +-
.../_expected/server/index.svelte.js | 4 +-
.../hmr/_expected/server/index.svelte.js | 4 +-
.../_expected/server/index.svelte.js | 2 +-
.../_expected/server/index.svelte.js | 4 +-
.../_expected/server/index.svelte.js | 4 +-
.../purity/_expected/server/index.svelte.js | 8 +-
.../_expected/server/index.svelte.js | 4 +-
.../_expected/server/index.svelte.js | 4 +-
.../_expected/server/index.svelte.js | 4 +-
.../_expected/server/index.svelte.js | 4 +-
46 files changed, 438 insertions(+), 436 deletions(-)
rename packages/svelte/src/internal/server/{payload.js => renderer.js} (70%)
rename packages/svelte/src/internal/server/{payload.test.ts => renderer.test.ts} (52%)
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
index 4486c6e7a3..4a1ba17238 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
@@ -40,7 +40,7 @@ import { TitleElement } from './visitors/TitleElement.js';
import { UpdateExpression } from './visitors/UpdateExpression.js';
import { VariableDeclaration } from './visitors/VariableDeclaration.js';
import { SvelteBoundary } from './visitors/SvelteBoundary.js';
-import { call_child_payload, call_component_payload } from './visitors/shared/utils.js';
+import { call_child_renderer, call_component_renderer } from './visitors/shared/utils.js';
/** @type {Visitors} */
const global_visitors = {
@@ -188,21 +188,21 @@ export function server_component(analysis, options) {
template.body = [
...snippets,
b.let('$$settled', b.true),
- b.let('$$inner_payload'),
+ b.let('$$inner_renderer'),
b.function_declaration(
b.id('$$render_inner'),
- [b.id('$$payload')],
+ [b.id(' $$renderer')],
b.block(/** @type {Statement[]} */ (rest))
),
b.do_while(
b.unary('!', b.id('$$settled')),
b.block([
b.stmt(b.assignment('=', b.id('$$settled'), b.true)),
- b.stmt(b.assignment('=', b.id('$$inner_payload'), b.call('$$payload.copy'))),
- b.stmt(b.call('$$render_inner', b.id('$$inner_payload')))
+ b.stmt(b.assignment('=', b.id('$$inner_renderer'), b.call(' $$renderer.copy'))),
+ b.stmt(b.call('$$render_inner', b.id('$$inner_renderer')))
])
),
- b.stmt(b.call('$$payload.subsume', b.id('$$inner_payload')))
+ b.stmt(b.call(' $$renderer.subsume', b.id('$$inner_renderer')))
];
}
@@ -244,7 +244,7 @@ export function server_component(analysis, options) {
]);
if (analysis.instance.has_await) {
- component_block = b.block([call_child_payload(component_block, true)]);
+ component_block = b.block([call_child_renderer(component_block, true)]);
}
// trick esrap into including comments
@@ -253,7 +253,7 @@ export function server_component(analysis, options) {
if (analysis.props_id) {
// need to be placed on first line of the component for hydration
component_block.body.unshift(
- b.const(analysis.props_id, b.call('$.props_id', b.id('$$payload')))
+ b.const(analysis.props_id, b.call('$.props_id', b.id(' $$renderer')))
);
}
@@ -261,7 +261,7 @@ export function server_component(analysis, options) {
if (should_inject_context) {
component_block = b.block([
- call_component_payload(component_block, dev && b.id(component_name))
+ call_component_renderer(component_block, dev && b.id(component_name))
]);
}
@@ -301,7 +301,7 @@ export function server_component(analysis, options) {
const code = b.literal(render_stylesheet(analysis.source, analysis, options).code);
body.push(b.const('$$css', b.object([b.init('hash', hash), b.init('code', code)])));
- component_block.body.unshift(b.stmt(b.call('$$payload.global.css.add', b.id('$$css'))));
+ component_block.body.unshift(b.stmt(b.call(' $$renderer.global.css.add', b.id('$$css'))));
}
let should_inject_props =
@@ -315,7 +315,7 @@ export function server_component(analysis, options) {
const component_function = b.function_declaration(
b.id(analysis.name),
- should_inject_props ? [b.id('$$payload'), b.id('$$props')] : [b.id('$$payload')],
+ should_inject_props ? [b.id(' $$renderer'), b.id('$$props')] : [b.id(' $$renderer')],
component_block
);
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js
index ea60c95f68..1de64c62f5 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js
@@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders';
-import { block_close, call_child_payload } from './shared/utils.js';
+import { block_close, call_child_renderer } from './shared/utils.js';
/**
* @param {AST.AwaitBlock} node
@@ -13,7 +13,7 @@ export function AwaitBlock(node, context) {
let statement = b.stmt(
b.call(
'$.await',
- b.id('$$payload'),
+ b.id(' $$renderer'),
/** @type {Expression} */ (context.visit(node.expression)),
b.thunk(
node.pending ? /** @type {BlockStatement} */ (context.visit(node.pending)) : b.block([])
@@ -26,7 +26,7 @@ export function AwaitBlock(node, context) {
);
if (node.metadata.expression.has_await) {
- statement = call_child_payload(b.block([statement]), true);
+ statement = call_child_renderer(b.block([statement]), true);
}
context.state.template.push(statement, block_close);
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js
index eb0da55657..dc7cf3e73a 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js
@@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders';
-import { block_close, block_open, block_open_else, call_child_payload } from './shared/utils.js';
+import { block_close, block_open, block_open_else, call_child_renderer } from './shared/utils.js';
/**
* @param {AST.EachBlock} node
@@ -45,11 +45,11 @@ export function EachBlock(node, context) {
);
if (node.fallback) {
- const open = b.stmt(b.call(b.id('$$payload.push'), block_open));
+ const open = b.stmt(b.call(b.id(' $$renderer.push'), block_open));
const fallback = /** @type {BlockStatement} */ (context.visit(node.fallback));
- fallback.body.unshift(b.stmt(b.call(b.id('$$payload.push'), block_open_else)));
+ fallback.body.unshift(b.stmt(b.call(b.id(' $$renderer.push'), block_open_else)));
block.body.push(
b.if(
@@ -64,7 +64,7 @@ export function EachBlock(node, context) {
}
if (node.metadata.expression.has_await) {
- state.template.push(call_child_payload(block, true), block_close);
+ state.template.push(call_child_renderer(block, true), block_close);
} else {
state.template.push(...block.body, block_close);
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js
index 37d4585900..d16f13092b 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/IfBlock.js
@@ -2,7 +2,7 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types.js' */
import * as b from '#compiler/builders';
-import { block_close, block_open, block_open_else, call_child_payload } from './shared/utils.js';
+import { block_close, block_open, block_open_else, call_child_renderer } from './shared/utils.js';
/**
* @param {AST.IfBlock} node
@@ -16,15 +16,15 @@ export function IfBlock(node, context) {
? /** @type {BlockStatement} */ (context.visit(node.alternate))
: b.block([]);
- consequent.body.unshift(b.stmt(b.call(b.id('$$payload.push'), block_open)));
+ consequent.body.unshift(b.stmt(b.call(b.id(' $$renderer.push'), block_open)));
- alternate.body.unshift(b.stmt(b.call(b.id('$$payload.push'), block_open_else)));
+ alternate.body.unshift(b.stmt(b.call(b.id(' $$renderer.push'), block_open_else)));
/** @type {Statement} */
let statement = b.if(test, consequent, alternate);
if (node.metadata.expression.has_await) {
- statement = call_child_payload(b.block([statement]), true);
+ statement = call_child_renderer(b.block([statement]), true);
}
context.state.template.push(statement, block_close);
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/RegularElement.js
index 29aad94a31..52e06612c7 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/RegularElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/RegularElement.js
@@ -12,7 +12,7 @@ import {
process_children,
build_template,
build_attribute_value,
- call_child_payload
+ call_child_renderer
} from './shared/utils.js';
/**
@@ -68,7 +68,7 @@ export function RegularElement(node, context) {
b.stmt(
b.call(
'$.push_element',
- b.id('$$payload'),
+ b.id(' $$renderer'),
b.literal(node.name),
b.literal(location.line),
b.literal(location.column)
@@ -97,7 +97,7 @@ export function RegularElement(node, context) {
b.stmt(
b.assignment(
'=',
- b.id('$$payload.local.select_value'),
+ b.id(' $$renderer.local.select_value'),
b.member(
build_spread_object(
node,
@@ -125,7 +125,7 @@ export function RegularElement(node, context) {
);
}
- const left = b.id('$$payload.local.select_value');
+ const left = b.id(' $$renderer.local.select_value');
if (value.type === 'Attribute') {
state.template.push(
b.stmt(b.assignment('=', left, build_attribute_value(value.value, context)))
@@ -160,7 +160,7 @@ export function RegularElement(node, context) {
b.stmt(
b.call(
'$.simple_valueless_option',
- b.id('$$payload'),
+ b.id(' $$renderer'),
b.thunk(
node.metadata.synthetic_value_node.expression,
node.metadata.synthetic_value_node.metadata.expression.has_await
@@ -175,9 +175,9 @@ export function RegularElement(node, context) {
b.stmt(
b.call(
'$.valueless_option',
- b.id('$$payload'),
+ b.id(' $$renderer'),
b.arrow(
- [b.id('$$payload')],
+ [b.id(' $$renderer')],
b.block([...inner_state.init, ...build_template(inner_state.template)])
)
)
@@ -216,7 +216,7 @@ export function RegularElement(node, context) {
// element hadn't resolved prior to hitting the second element.
const elements = state.template.splice(template_start, Infinity);
state.template.push(
- call_child_payload(b.block(build_template(elements)), select_with_value_async)
+ call_child_renderer(b.block(build_template(elements)), select_with_value_async)
);
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/RenderTag.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/RenderTag.js
index dd2ede3ba3..591f95cc6f 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/RenderTag.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/RenderTag.js
@@ -23,7 +23,7 @@ export function RenderTag(node, context) {
b.stmt(
(node.expression.type === 'CallExpression' ? b.call : b.maybe_call)(
snippet_function,
- b.id('$$payload'),
+ b.id(' $$renderer'),
...snippet_args
)
)
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js
index 2d16a07b4e..9865317daf 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SlotElement.js
@@ -6,7 +6,7 @@ import {
empty_comment,
build_attribute_value,
PromiseOptimiser,
- call_child_payload
+ call_child_renderer
} from './shared/utils.js';
/**
@@ -57,7 +57,7 @@ export function SlotElement(node, context) {
const slot = b.call(
'$.slot',
- b.id('$$payload'),
+ b.id(' $$renderer'),
b.id('$$props'),
name,
props_expression,
@@ -66,7 +66,7 @@ export function SlotElement(node, context) {
const statement =
optimiser.expressions.length > 0
- ? call_child_payload(b.block([optimiser.apply(), b.stmt(slot)]), true)
+ ? call_child_renderer(b.block([optimiser.apply(), b.stmt(slot)]), true)
: b.stmt(slot);
context.state.template.push(empty_comment, statement, empty_comment);
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js
index 238485e665..38cf38deaf 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js
@@ -11,7 +11,7 @@ import * as b from '#compiler/builders';
export function SnippetBlock(node, context) {
let fn = b.function_declaration(
node.expression,
- [b.id('$$payload'), ...node.parameters],
+ [b.id(' $$renderer'), ...node.parameters],
/** @type {BlockStatement} */ (context.visit(node.body))
);
@@ -21,7 +21,7 @@ export function SnippetBlock(node, context) {
const statements = node.metadata.can_hoist ? context.state.hoisted : context.state.init;
if (dev) {
- fn.body.body.unshift(b.stmt(b.call('$.validate_snippet_args', b.id('$$payload'))));
+ fn.body.body.unshift(b.stmt(b.call('$.validate_snippet_args', b.id(' $$renderer'))));
statements.push(b.stmt(b.call('$.prevent_snippet_stringification', fn.id)));
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js
index 355b33aea7..44114a5b2c 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js
@@ -24,7 +24,7 @@ export function SvelteBoundary(node, context) {
const pending = pending_attribute
? b.call(
build_attribute_value(pending_attribute.value, context, false, true),
- b.id('$$payload')
+ b.id(' $$renderer')
)
: /** @type {BlockStatement} */ (context.visit(pending_snippet.body));
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteElement.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteElement.js
index fd16219860..5fa842a9af 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteElement.js
@@ -45,7 +45,7 @@ export function SvelteElement(node, context) {
b.stmt(
b.call(
'$.push_element',
- b.id('$$payload'),
+ b.id(' $$renderer'),
tag,
b.literal(location.line),
b.literal(location.column)
@@ -61,7 +61,7 @@ export function SvelteElement(node, context) {
b.stmt(
b.call(
'$.element',
- b.id('$$payload'),
+ b.id(' $$renderer'),
tag,
attributes.body.length > 0 && b.thunk(attributes),
children.body.length > 0 && b.thunk(children)
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteHead.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteHead.js
index 7d064ffbf5..6ca797669c 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteHead.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteHead.js
@@ -11,6 +11,6 @@ export function SvelteHead(node, context) {
const block = /** @type {BlockStatement} */ (context.visit(node.fragment));
context.state.template.push(
- b.stmt(b.call('$.head', b.id('$$payload'), b.arrow([b.id('$$payload')], block)))
+ b.stmt(b.call('$.head', b.id(' $$renderer'), b.arrow([b.id(' $$renderer')], block)))
);
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/TitleElement.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/TitleElement.js
index a3ffb2df3d..06e0a3b4c0 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/TitleElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/TitleElement.js
@@ -14,6 +14,6 @@ export function TitleElement(node, context) {
template.push(b.literal(''));
context.state.init.push(
- b.stmt(b.call('$.build_title', b.id('$$payload'), b.thunk(b.block(build_template(template)))))
+ b.stmt(b.call('$.build_title', b.id(' $$renderer'), b.thunk(b.block(build_template(template)))))
);
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js
index 614a2ed258..5eeb5e3a0f 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js
@@ -4,7 +4,7 @@
import {
empty_comment,
build_attribute_value,
- call_child_payload,
+ call_child_renderer,
PromiseOptimiser
} from './utils.js';
import * as b from '#compiler/builders';
@@ -215,7 +215,7 @@ export function build_inline_component(node, expression, context) {
if (block.body.length === 0) continue;
/** @type {Pattern[]} */
- const params = [b.id('$$payload')];
+ const params = [b.id(' $$renderer')];
if (lets[slot_name].length > 0) {
const pattern = b.object_pattern(
@@ -292,7 +292,7 @@ export function build_inline_component(node, expression, context) {
let statement = b.stmt(
(node.type === 'SvelteComponent' ? b.maybe_call : b.call)(
expression,
- b.id('$$payload'),
+ b.id(' $$renderer'),
props_expression
)
);
@@ -308,7 +308,7 @@ export function build_inline_component(node, expression, context) {
statement = b.stmt(
b.call(
'$.css_props',
- b.id('$$payload'),
+ b.id(' $$renderer'),
b.literal(context.state.namespace === 'svg' ? false : true),
b.object(custom_css_props),
b.thunk(b.block([statement])),
@@ -318,7 +318,7 @@ export function build_inline_component(node, expression, context) {
}
if (optimiser.expressions.length > 0) {
- statement = call_child_payload(b.block([optimiser.apply(), statement]), true);
+ statement = call_child_renderer(b.block([optimiser.apply(), statement]), true);
}
if (dynamic && custom_css_props.length === 0) {
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
index 84692fca9c..ddcc5f7901 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
@@ -207,7 +207,7 @@ export function build_element_attributes(node, context) {
context.state.template.push(
b.call(
'$.maybe_selected',
- b.id('$$payload'),
+ b.id(' $$renderer'),
b.member(
build_spread_object(
node,
@@ -264,7 +264,7 @@ export function build_element_attributes(node, context) {
context.state.template.push(
b.call(
'$.maybe_selected',
- b.id('$$payload'),
+ b.id(' $$renderer'),
literal_value != null ? b.literal(/** @type {any} */ (literal_value)) : b.void0
)
);
@@ -296,7 +296,7 @@ export function build_element_attributes(node, context) {
}
if (name === 'value' && node.name === 'option') {
- context.state.template.push(b.call('$.maybe_selected', b.id('$$payload'), value));
+ context.state.template.push(b.call('$.maybe_selected', b.id(' $$renderer'), value));
}
}
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js
index a48c19ae7c..971df1de93 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js
@@ -80,7 +80,7 @@ export function process_children(nodes, { visit, state }) {
flush();
const visited = /** @type {Expression} */ (visit(node.expression));
state.template.push(
- b.stmt(b.call('$$payload.push', b.thunk(b.call('$.escape', visited), true)))
+ b.stmt(b.call(' $$renderer.push', b.thunk(b.call('$.escape', visited), true)))
);
} else if (node.type === 'Text' || node.type === 'Comment' || node.type === 'ExpressionTag') {
sequence.push(node);
@@ -119,7 +119,7 @@ export function build_template(template) {
statements.push(
b.stmt(
b.call(
- b.id('$$payload.push'),
+ b.id(' $$renderer.push'),
b.template(
strings.map((cooked, i) => b.quasi(cooked, i === strings.length - 1)),
expressions
@@ -266,8 +266,8 @@ export function build_getter(node, state) {
* @param {boolean} async
* @returns {Statement}
*/
-export function call_child_payload(body, async) {
- return b.stmt(b.call('$$payload.child', b.arrow([b.id('$$payload')], body, async)));
+export function call_child_renderer(body, async) {
+ return b.stmt(b.call(' $$renderer.child', b.arrow([b.id(' $$renderer')], body, async)));
}
/**
@@ -275,9 +275,9 @@ export function call_child_payload(body, async) {
* @param {Identifier | false} component_fn_id
* @returns {Statement}
*/
-export function call_component_payload(body, component_fn_id) {
+export function call_component_renderer(body, component_fn_id) {
return b.stmt(
- b.call('$$payload.component', b.arrow([b.id('$$payload')], body, false), component_fn_id)
+ b.call(' $$renderer.component', b.arrow([b.id(' $$renderer')], body, false), component_fn_id)
);
}
diff --git a/packages/svelte/src/index-server.js b/packages/svelte/src/index-server.js
index 0b05fb6d12..f193c46894 100644
--- a/packages/svelte/src/index-server.js
+++ b/packages/svelte/src/index-server.js
@@ -1,12 +1,12 @@
/** @import { SSRContext } from '#server' */
-/** @import { Payload } from './internal/server/payload.js' */
+/** @import { Renderer } from './internal/server/renderer.js' */
import { ssr_context } from './internal/server/context.js';
import { noop } from './internal/shared/utils.js';
import * as e from './internal/server/errors.js';
/** @param {() => void} fn */
export function onDestroy(fn) {
- /** @type {Payload} */ (/** @type {SSRContext} */ (ssr_context).r).on_destroy(fn);
+ /** @type {Renderer} */ (/** @type {SSRContext} */ (ssr_context).r).on_destroy(fn);
}
export {
diff --git a/packages/svelte/src/internal/server/blocks/snippet.js b/packages/svelte/src/internal/server/blocks/snippet.js
index 109dbf683a..80a5f7f467 100644
--- a/packages/svelte/src/internal/server/blocks/snippet.js
+++ b/packages/svelte/src/internal/server/blocks/snippet.js
@@ -1,5 +1,5 @@
/** @import { Snippet } from 'svelte' */
-/** @import { Payload } from '../payload' */
+/** @import { Renderer } from '../renderer' */
/** @import { Getters } from '#shared' */
/**
@@ -13,9 +13,9 @@
*/
export function createRawSnippet(fn) {
// @ts-expect-error the types are a lie
- return (/** @type {Payload} */ payload, /** @type {Params} */ ...args) => {
+ return (/** @type {Renderer} */ renderer, /** @type {Params} */ ...args) => {
var getters = /** @type {Getters} */ (args.map((value) => () => value));
- payload.push(
+ renderer.push(
fn(...getters)
.render()
.trim()
diff --git a/packages/svelte/src/internal/server/dev.js b/packages/svelte/src/internal/server/dev.js
index cc9f6496c0..66dff0fde2 100644
--- a/packages/svelte/src/internal/server/dev.js
+++ b/packages/svelte/src/internal/server/dev.js
@@ -6,7 +6,7 @@ import {
} from '../../html-tree-validation.js';
import { set_ssr_context, ssr_context } from './context.js';
import * as e from './errors.js';
-import { Payload } from './payload.js';
+import { Renderer } from './renderer.js';
// TODO move this
/**
@@ -26,10 +26,10 @@ import { Payload } from './payload.js';
export let seen;
/**
- * @param {Payload} payload
+ * @param {Renderer} renderer
* @param {string} message
*/
-function print_error(payload, message) {
+function print_error(renderer, message) {
message =
`node_invalid_placement_ssr: ${message}\n\n` +
'This can cause content to shift around as the browser repairs the HTML, and will likely result in a `hydration_mismatch` warning.';
@@ -39,19 +39,19 @@ function print_error(payload, message) {
// eslint-disable-next-line no-console
console.error(message);
- payload.child(
- (payload) => payload.push(``),
+ renderer.child(
+ (renderer) => renderer.push(``),
'head'
);
}
/**
- * @param {Payload} payload
+ * @param {Renderer} renderer
* @param {string} tag
* @param {number} line
* @param {number} column
*/
-export function push_element(payload, tag, line, column) {
+export function push_element(renderer, tag, line, column) {
var context = /** @type {SSRContext} */ (ssr_context);
var filename = context.function[FILENAME];
var parent = context.element;
@@ -67,7 +67,7 @@ export function push_element(payload, tag, line, column) {
: undefined;
const message = is_tag_valid_with_parent(tag, parent.tag, child_loc, parent_loc);
- if (message) print_error(payload, message);
+ if (message) print_error(renderer, message);
while (ancestor != null) {
ancestors.push(ancestor.tag);
@@ -76,7 +76,7 @@ export function push_element(payload, tag, line, column) {
: undefined;
const message = is_tag_valid_with_ancestor(tag, ancestors, child_loc, ancestor_loc);
- if (message) print_error(payload, message);
+ if (message) print_error(renderer, message);
ancestor = ancestor.parent;
}
@@ -90,13 +90,13 @@ export function pop_element() {
}
/**
- * @param {Payload} payload
+ * @param {Renderer} renderer
*/
-export function validate_snippet_args(payload) {
+export function validate_snippet_args(renderer) {
if (
- typeof payload !== 'object' ||
- // for some reason typescript consider the type of payload as never after the first instanceof
- !(payload instanceof Payload)
+ typeof renderer !== 'object' ||
+ // for some reason typescript consider the type of renderer as never after the first instanceof
+ !(renderer instanceof Renderer)
) {
e.invalid_snippet_arguments();
}
diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js
index 13a0706549..618e75f932 100644
--- a/packages/svelte/src/internal/server/index.js
+++ b/packages/svelte/src/internal/server/index.js
@@ -1,7 +1,7 @@
/** @import { ComponentType, SvelteComponent, Component } from 'svelte' */
/** @import { RenderOutput } from '#server' */
/** @import { Store } from '#shared' */
-/** @import { AccumulatedContent } from './payload.js' */
+/** @import { AccumulatedContent } from './renderer.js' */
export { FILENAME, HMR } from '../../constants.js';
import { attr, clsx, to_class, to_style } from '../shared/attributes.js';
import { is_promise, noop } from '../shared/utils.js';
@@ -17,7 +17,7 @@ import { DEV } from 'esm-env';
import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN, BLOCK_OPEN_ELSE } from './hydration.js';
import { validate_store } from '../shared/validate.js';
import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js';
-import { Payload } from './payload.js';
+import { Renderer } from './renderer.js';
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
// https://infra.spec.whatwg.org/#noncharacter
@@ -25,30 +25,30 @@ const INVALID_ATTR_NAME_CHAR_REGEX =
/[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
/**
- * @param {Payload} payload
+ * @param {Renderer} renderer
* @param {string} tag
* @param {() => void} attributes_fn
* @param {() => void} children_fn
* @returns {void}
*/
-export function element(payload, tag, attributes_fn = noop, children_fn = noop) {
- payload.push('');
+export function element(renderer, tag, attributes_fn = noop, children_fn = noop) {
+ renderer.push('');
if (tag) {
- payload.push(`<${tag}`);
+ renderer.push(`<${tag}`);
attributes_fn();
- payload.push(`>`);
+ renderer.push(`>`);
if (!is_void(tag)) {
children_fn();
if (!is_raw_text_element(tag)) {
- payload.push(EMPTY_COMMENT);
+ renderer.push(EMPTY_COMMENT);
}
- payload.push(`${tag}>`);
+ renderer.push(`${tag}>`);
}
}
- payload.push('');
+ renderer.push('');
}
/**
@@ -60,49 +60,49 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop)
* @returns {RenderOutput}
*/
export function render(component, options = {}) {
- return Payload.render(/** @type {Component} */ (component), options);
+ return Renderer.render(/** @type {Component} */ (component), options);
}
/**
- * @param {Payload} payload
- * @param {(payload: Payload) => Promise | void} fn
+ * @param {Renderer} renderer
+ * @param {(renderer: Renderer) => Promise | void} fn
* @returns {void}
*/
-export function head(payload, fn) {
- payload.child((payload) => {
- payload.push(BLOCK_OPEN);
- payload.child(fn);
- payload.push(BLOCK_CLOSE);
+export function head(renderer, fn) {
+ renderer.child((renderer) => {
+ renderer.push(BLOCK_OPEN);
+ renderer.child(fn);
+ renderer.push(BLOCK_CLOSE);
}, 'head');
}
/**
- * @param {Payload} payload
+ * @param {Renderer} renderer
* @param {boolean} is_html
* @param {Record} props
* @param {() => void} component
* @param {boolean} dynamic
* @returns {void}
*/
-export function css_props(payload, is_html, props, component, dynamic = false) {
+export function css_props(renderer, is_html, props, component, dynamic = false) {
const styles = style_object_to_string(props);
if (is_html) {
- payload.push(``);
+ renderer.push(``);
} else {
- payload.push(``);
+ renderer.push(``);
}
if (dynamic) {
- payload.push('');
+ renderer.push('');
}
component();
if (is_html) {
- payload.push(``);
+ renderer.push(``);
} else {
- payload.push(``);
+ renderer.push(``);
}
}
@@ -304,14 +304,14 @@ export function unsubscribe_stores(store_values) {
}
/**
- * @param {Payload} payload
+ * @param {Renderer} renderer
* @param {Record} $$props
* @param {string} name
* @param {Record} slot_props
* @param {null | (() => void)} fallback_fn
* @returns {void}
*/
-export function slot(payload, $$props, name, slot_props, fallback_fn) {
+export function slot(renderer, $$props, name, slot_props, fallback_fn) {
var slot_fn = $$props.$$slots?.[name];
// Interop: Can use snippets to fill slots
if (slot_fn === true) {
@@ -319,7 +319,7 @@ export function slot(payload, $$props, name, slot_props, fallback_fn) {
}
if (slot_fn !== undefined) {
- slot_fn(payload, slot_props);
+ slot_fn(renderer, slot_props);
} else {
fallback_fn?.();
}
@@ -387,21 +387,21 @@ export function bind_props(props_parent, props_now) {
/**
* @template V
- * @param {Payload} payload
+ * @param {Renderer} renderer
* @param {Promise} promise
* @param {null | (() => void)} pending_fn
* @param {(value: V) => void} then_fn
* @returns {void}
*/
-function await_block(payload, promise, pending_fn, then_fn) {
+function await_block(renderer, promise, pending_fn, then_fn) {
if (is_promise(promise)) {
- payload.push(BLOCK_OPEN);
+ renderer.push(BLOCK_OPEN);
promise.then(null, noop);
if (pending_fn !== null) {
pending_fn();
}
} else if (then_fn !== null) {
- payload.push(BLOCK_OPEN_ELSE);
+ renderer.push(BLOCK_OPEN_ELSE);
then_fn(promise);
}
}
@@ -443,12 +443,12 @@ export function once(get_value) {
/**
* Create an unique ID
- * @param {Payload} payload
+ * @param {Renderer} renderer
* @returns {string}
*/
-export function props_id(payload) {
- const uid = payload.global.uid();
- payload.push('');
+export function props_id(renderer) {
+ const uid = renderer.global.uid();
+ renderer.push('');
return uid;
}
@@ -496,11 +496,11 @@ export function derived(fn) {
/**
*
- * @param {Payload} payload
+ * @param {Renderer} renderer
* @param {unknown} value
*/
-export function maybe_selected(payload, value) {
- return value === payload.local.select_value ? ' selected' : '';
+export function maybe_selected(renderer, value) {
+ return value === renderer.local.select_value ? ' selected' : '';
}
/**
@@ -508,25 +508,25 @@ export function maybe_selected(payload, value) {
* content as its `value` to determine whether we should apply the `selected` attribute.
* This has to be done at runtime, for hopefully obvious reasons. It is also complicated,
* for sad reasons.
- * @param {Payload} payload
- * @param {((payload: Payload) => void | Promise)} children
+ * @param {Renderer} renderer
+ * @param {((renderer: Renderer) => void | Promise)} children
* @returns {void}
*/
-export function valueless_option(payload, children) {
- const i = payload.length;
+export function valueless_option(renderer, children) {
+ const i = renderer.length;
- // prior to children, `payload` has some combination of string/unresolved payload that ends in `
+ 1/2
`
);
- assert.deepEqual(logs, [0, 0]);
+ assert.deepEqual(logs, [0, 0, 0, 0]);
}
});
diff --git a/packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/main.svelte b/packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/main.svelte
index bbad2cdf4a..c0dd86993a 100644
--- a/packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/untrack-own-deriveds/main.svelte
@@ -17,10 +17,14 @@
$effect(() => {
foo = new Foo();
});
+
+ let bar = $derived(new Foo());
-
+
{#if foo}
{foo.value}/{foo.double}
{/if}
+
+{bar.value}/{bar.double}
From 1ef297f25de64f220bce8ff020f38d399b69a647 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Tue, 23 Sep 2025 23:47:50 +0200
Subject: [PATCH 44/68] fix: ensure tick resolves within a macrotask (#16825)
Race them against each other - in almost all cases requestAnimationFrame will fire first, but e.g. in case the window is not focused or a view transition happens, requestAnimationFrame will be delayed and setTimeout helps us resolve fast enough in that case
Fixes #16429
Fixes https://github.com/sveltejs/kit/issues/14220
---
.changeset/wise-bottles-explode.md | 5 +++++
packages/svelte/src/internal/client/runtime.js | 8 +++++++-
2 files changed, 12 insertions(+), 1 deletion(-)
create mode 100644 .changeset/wise-bottles-explode.md
diff --git a/.changeset/wise-bottles-explode.md b/.changeset/wise-bottles-explode.md
new file mode 100644
index 0000000000..d3b2931272
--- /dev/null
+++ b/.changeset/wise-bottles-explode.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ensure tick resolves within a macrotask
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index 22a1890e0f..b8f5f5ffc9 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -500,7 +500,13 @@ export function update_effect(effect) {
*/
export async function tick() {
if (async_mode_flag) {
- return new Promise((f) => requestAnimationFrame(() => f()));
+ return new Promise((f) => {
+ // Race them against each other - in almost all cases requestAnimationFrame will fire first,
+ // but e.g. in case the window is not focused or a view transition happens, requestAnimationFrame
+ // will be delayed and setTimeout helps us resolve fast enough in that case
+ requestAnimationFrame(() => f());
+ setTimeout(() => f());
+ });
}
await Promise.resolve();
From 0ed0f1ef69a67324ef411b61c6cf088e4abcd57b Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 24 Sep 2025 11:08:06 -0400
Subject: [PATCH 45/68] chore: disable inspector in playground (#16836)
---
playgrounds/sandbox/package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/playgrounds/sandbox/package.json b/playgrounds/sandbox/package.json
index 641f91ddfe..84aeab586b 100644
--- a/playgrounds/sandbox/package.json
+++ b/playgrounds/sandbox/package.json
@@ -5,8 +5,8 @@
"type": "module",
"scripts": {
"prepare": "node scripts/create-app-svelte.js",
- "dev": "vite --host",
- "ssr": "node --conditions=development ./ssr-dev.js",
+ "dev": "SVELTE_INSPECTOR_OPTIONS=false vite --host",
+ "ssr": "SVELTE_INSPECTOR_OPTIONS=false node --conditions=development ./ssr-dev.js",
"build": "vite build --outDir dist/client && vite build --outDir dist/server --ssr ssr-prod.js",
"prod": "npm run build && node dist/server/ssr-prod",
"preview": "vite preview",
From 7b2113e1bfb6da2078abe532b0530e25f8f85285 Mon Sep 17 00:00:00 2001
From: Aaron Ajose
Date: Wed, 24 Sep 2025 18:32:59 +0300
Subject: [PATCH 46/68] chore: fix typos (#16830)
* chore: fix typos
* revert bad change
---------
Co-authored-by: Rich Harris
---
packages/svelte/src/internal/client/render.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js
index f5e32a2196..b1165a6e7a 100644
--- a/packages/svelte/src/internal/client/render.js
+++ b/packages/svelte/src/internal/client/render.js
@@ -143,7 +143,7 @@ export function hydrate(component, options) {
e.hydration_failed();
}
- // If an error occured above, the operations might not yet have been initialised.
+ // If an error occurred above, the operations might not yet have been initialised.
init_operations();
clear_text_content(target);
From c75e86267758a534c76c3d02eae6026171792704 Mon Sep 17 00:00:00 2001
From: Tee Ming
Date: Wed, 24 Sep 2025 23:34:05 +0800
Subject: [PATCH 47/68] Update 19-await-expressions.md (#16827)
---
documentation/docs/03-template-syntax/19-await-expressions.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/documentation/docs/03-template-syntax/19-await-expressions.md b/documentation/docs/03-template-syntax/19-await-expressions.md
index e43d6c81d1..d3896a5c4e 100644
--- a/documentation/docs/03-template-syntax/19-await-expressions.md
+++ b/documentation/docs/03-template-syntax/19-await-expressions.md
@@ -83,7 +83,7 @@ let b = $derived(await two());
To render placeholder UI, you can wrap content in a `` with a [`pending`](svelte-boundary#Properties-pending) snippet. This will be shown when the boundary is first created, but not for subsequent updates, which are globally coordinated.
-After the contents of a boundary have resolved for the first time and replaced the `pending` snippet, you can detect subsequent async work with [`$effect.pending()`]($effect#$effect.pending). This is what you would use display a "we're asynchronously validating your input" spinner next to a form field, for example.
+After the contents of a boundary have resolved for the first time and have replaced the `pending` snippet, you can detect subsequent async work with [`$effect.pending()`]($effect#$effect.pending). This is what you would use to display a "we're asynchronously validating your input" spinner next to a form field, for example.
You can also use [`settled()`](svelte#settled) to get a promise that resolves when the current update is complete:
From 24944e61f5ef5aebf00aa6f86f86edaf3ad70986 Mon Sep 17 00:00:00 2001
From: 7nik
Date: Wed, 24 Sep 2025 18:35:24 +0300
Subject: [PATCH 48/68] fix: async `class:` + spread attributes were compiled
into sync server-side code (#16834)
---
.changeset/tasty-snails-dress.md | 5 +++++
.../phases/3-transform/server/visitors/shared/element.js | 5 ++++-
.../samples/async-directive-with-spreading/_expected.html | 1 +
.../samples/async-directive-with-spreading/main.svelte | 2 ++
4 files changed, 12 insertions(+), 1 deletion(-)
create mode 100644 .changeset/tasty-snails-dress.md
create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-directive-with-spreading/_expected.html
create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-directive-with-spreading/main.svelte
diff --git a/.changeset/tasty-snails-dress.md b/.changeset/tasty-snails-dress.md
new file mode 100644
index 0000000000..0fb847c1ff
--- /dev/null
+++ b/.changeset/tasty-snails-dress.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: async `class:` + spread attributes were compiled into sync server-side code
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
index da90011259..3b5f2423a3 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
@@ -372,7 +372,10 @@ function build_element_spread_attributes(
directive.name,
directive.expression.type === 'Identifier' && directive.expression.name === directive.name
? b.id(directive.name)
- : /** @type {Expression} */ (context.visit(directive.expression))
+ : transform(
+ /** @type {Expression} */ (context.visit(directive.expression)),
+ directive.metadata.expression
+ )
);
});
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-directive-with-spreading/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-directive-with-spreading/_expected.html
new file mode 100644
index 0000000000..d46a957bdf
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-directive-with-spreading/_expected.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/async-directive-with-spreading/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-directive-with-spreading/main.svelte
new file mode 100644
index 0000000000..ed4270b754
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/async-directive-with-spreading/main.svelte
@@ -0,0 +1,2 @@
+
+
From ac7e16002939f627bd961a9194e497f53ff466f9 Mon Sep 17 00:00:00 2001
From: LeeWxx <99359120+LeeWxx@users.noreply.github.com>
Date: Thu, 25 Sep 2025 01:35:51 +0900
Subject: [PATCH 49/68] fix: SSR scoped classes for