From f51c04afcecd8496bc9ecd5d4d5bb4715a7fd0c0 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 4 Sep 2025 04:06:17 +0200 Subject: [PATCH 01/68] fix: ensure batch exists when resetting a failed boundary (#16698) fixes #16681 --- .changeset/ninety-ravens-join.md | 5 +++++ .../internal/client/dom/blocks/boundary.js | 3 +++ .../samples/async-boundary-reset/Test.svelte | 15 +++++++++++++++ .../samples/async-boundary-reset/_config.js | 19 +++++++++++++++++++ .../samples/async-boundary-reset/main.svelte | 12 ++++++++++++ 5 files changed, 54 insertions(+) create mode 100644 .changeset/ninety-ravens-join.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-boundary-reset/Test.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-boundary-reset/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-boundary-reset/main.svelte diff --git a/.changeset/ninety-ravens-join.md b/.changeset/ninety-ravens-join.md new file mode 100644 index 0000000000..f7bc6f8def --- /dev/null +++ b/.changeset/ninety-ravens-join.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure batch exists when resetting a failed boundary diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 60fe2b7d3c..12ca547608 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -297,6 +297,9 @@ export class Boundary { e.svelte_boundary_reset_onerror(); } + // If the failure happened while flushing effects, current_batch can be null + Batch.ensure(); + this.#pending_count = 0; if (this.#failed_effect !== null) { diff --git a/packages/svelte/tests/runtime-runes/samples/async-boundary-reset/Test.svelte b/packages/svelte/tests/runtime-runes/samples/async-boundary-reset/Test.svelte new file mode 100644 index 0000000000..2232a094cb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-boundary-reset/Test.svelte @@ -0,0 +1,15 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/async-boundary-reset/_config.js b/packages/svelte/tests/runtime-runes/samples/async-boundary-reset/_config.js new file mode 100644 index 0000000000..19b273175b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-boundary-reset/_config.js @@ -0,0 +1,19 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await tick(); + let [btn] = target.querySelectorAll('button'); + btn.click(); + await tick(); + + assert.htmlEqual(target.innerHTML, ''); + + [btn] = target.querySelectorAll('button'); + btn.click(); + await tick(); + + assert.htmlEqual(target.innerHTML, ''); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-boundary-reset/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-boundary-reset/main.svelte new file mode 100644 index 0000000000..42242f26d6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-boundary-reset/main.svelte @@ -0,0 +1,12 @@ + + + + + + {#snippet pending()}pending{/snippet} + {#snippet failed(_, reset)} + + {/snippet} + From 08b3b66865db472e5e6d2262386c1dc030c10a78 Mon Sep 17 00:00:00 2001 From: Kyle Gach Date: Thu, 4 Sep 2025 13:36:34 -0600 Subject: [PATCH 02/68] docs: add testing with storybook (#16701) * docs: add testing with storybook * docs: remove video * docs: address feedback - consistent tabs vs. spaces in snippet - add more Storybook details * Revise Storybook testing documentation condense, remove referer query parameters * Update documentation/docs/07-misc/02-testing.md Co-authored-by: Jeppe Reinhold * Tweaks - Prose updates - More helpful link * make it its own section --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Jeppe Reinhold --- documentation/docs/07-misc/02-testing.md | 46 ++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/documentation/docs/07-misc/02-testing.md b/documentation/docs/07-misc/02-testing.md index bcec4db0a3..23e2e023d3 100644 --- a/documentation/docs/07-misc/02-testing.md +++ b/documentation/docs/07-misc/02-testing.md @@ -160,9 +160,9 @@ export function logger(getValue) { ### Component testing -It is possible to test your components in isolation using Vitest. +It is possible to test your components in isolation, which allows you to render them in a browser (real or simulated), simulate behavior, and make assertions, without spinning up your whole app. -> [!NOTE] Before writing component tests, think about whether you actually need to test the component, or if it's more about the logic _inside_ the component. If so, consider extracting out that logic to test it in isolation, without the overhead of a component +> [!NOTE] Before writing component tests, think about whether you actually need to test the component, or if it's more about the logic _inside_ the component. If so, consider extracting out that logic to test it in isolation, without the overhead of a component. To get started, install jsdom (a library that shims DOM APIs): @@ -246,6 +246,48 @@ test('Component', async () => { When writing component tests that involve two-way bindings, context or snippet props, it's best to create a wrapper component for your specific test and interact with that. `@testing-library/svelte` contains some [examples](https://testing-library.com/docs/svelte-testing-library/example). +### Component testing with Storybook + +[Storybook](https://storybook.js.org) is a tool for developing and documenting UI components, and it can also be used to test your components. They're run with Vitest's browser mode, which renders your components in a real browser for the most realistic testing environment. + +To get started, first install Storybook ([using Svelte's CLI](/docs/cli/storybook)) in your project via `npx sv add storybook` and choose the recommended configuration that includes testing features. If you're already using Storybook, and for more information on Storybook's testing capabilities, follow the [Storybook testing docs](https://storybook.js.org/docs/writing-tests?renderer=svelte) to get started. + +You can create stories for component variations and test interactions with the [play function](https://storybook.js.org/docs/writing-tests/interaction-testing?renderer=svelte#writing-interaction-tests), which allows you to simulate behavior and make assertions using the Testing Library and Vitest APIs. Here's an example of two stories that can be tested, one that renders an empty LoginForm component and one that simulates a user filling out the form: + +```svelte +/// file: LoginForm.stories.svelte + + + + + { + // Simulate a user filling out the form + await userEvent.type(canvas.getByTestId('email'), 'email@provider.com'); + await userEvent.type(canvas.getByTestId('password'), 'a-random-password'); + await userEvent.click(canvas.getByRole('button')); + + // Run assertions + await expect(args.onSubmit).toHaveBeenCalledTimes(1); + await expect(canvas.getByText('You’re in!')).toBeInTheDocument(); + }} +/> +``` + ## E2E tests using Playwright E2E (short for 'end to end') tests allow you to test your full application through the eyes of the user. This section uses [Playwright](https://playwright.dev/) as an example, but you can also use other solutions like [Cypress](https://www.cypress.io/) or [NightwatchJS](https://nightwatchjs.org/). From d92fa432d1032f4e1746af654eb5a7b7f536fbe9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:08:32 +0200 Subject: [PATCH 03/68] Version Packages (#16690) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/ninety-olives-report.md | 5 ----- .changeset/ninety-ravens-join.md | 5 ----- .changeset/wise-schools-report.md | 5 ----- packages/svelte/CHANGELOG.md | 10 ++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 6 files changed, 12 insertions(+), 17 deletions(-) delete mode 100644 .changeset/ninety-olives-report.md delete mode 100644 .changeset/ninety-ravens-join.md delete mode 100644 .changeset/wise-schools-report.md diff --git a/.changeset/ninety-olives-report.md b/.changeset/ninety-olives-report.md deleted file mode 100644 index 3e66a41d02..0000000000 --- a/.changeset/ninety-olives-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: replace `undefined` with `void(0)` in CallExpressions diff --git a/.changeset/ninety-ravens-join.md b/.changeset/ninety-ravens-join.md deleted file mode 100644 index f7bc6f8def..0000000000 --- a/.changeset/ninety-ravens-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure batch exists when resetting a failed boundary diff --git a/.changeset/wise-schools-report.md b/.changeset/wise-schools-report.md deleted file mode 100644 index 47ec887256..0000000000 --- a/.changeset/wise-schools-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: place store setup inside async body diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index de94eb1897..535214781c 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.38.7 + +### Patch Changes + +- fix: replace `undefined` with `void(0)` in CallExpressions ([#16693](https://github.com/sveltejs/svelte/pull/16693)) + +- fix: ensure batch exists when resetting a failed boundary ([#16698](https://github.com/sveltejs/svelte/pull/16698)) + +- fix: place store setup inside async body ([#16687](https://github.com/sveltejs/svelte/pull/16687)) + ## 5.38.6 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index fe42603184..c6bc40ae2c 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.6", + "version": "5.38.7", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 67c586790f..d499c06797 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.6'; +export const VERSION = '5.38.7'; export const PUBLIC_VERSION = '5'; From 18dd45640b14873ea3485fd1c19b0efaaaccf05f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 9 Sep 2025 10:28:14 -0400 Subject: [PATCH 04/68] fix: send `$effect.pending` count to the correct boundary (#16732) * fix: send `$effect.pending` count to the correct boundary * make boundary.pending private, use boundary.is_pending consistently * move error to correct place * we need that error * update JSDoc --- .changeset/dirty-cycles-smash.md | 5 + .../src/internal/client/dom/blocks/async.js | 4 +- .../internal/client/dom/blocks/boundary.js | 66 ++++++++----- .../src/internal/client/reactivity/async.js | 4 +- .../src/internal/client/reactivity/batch.js | 11 ++- .../internal/client/reactivity/deriveds.js | 2 +- .../async-effect-pending-nested/_config.js | 95 +++++++++++++++++++ .../async-effect-pending-nested/main.svelte | 34 +++++++ 8 files changed, 189 insertions(+), 32 deletions(-) create mode 100644 .changeset/dirty-cycles-smash.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-pending-nested/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-pending-nested/main.svelte diff --git a/.changeset/dirty-cycles-smash.md b/.changeset/dirty-cycles-smash.md new file mode 100644 index 0000000000..1b031cf0af --- /dev/null +++ b/.changeset/dirty-cycles-smash.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: send `$effect.pending` count to the correct boundary diff --git a/packages/svelte/src/internal/client/dom/blocks/async.js b/packages/svelte/src/internal/client/dom/blocks/async.js index 82f107ab29..5ec50a5988 100644 --- a/packages/svelte/src/internal/client/dom/blocks/async.js +++ b/packages/svelte/src/internal/client/dom/blocks/async.js @@ -1,7 +1,7 @@ /** @import { TemplateNode, Value } from '#client' */ import { flatten } from '../../reactivity/async.js'; import { get } from '../../runtime.js'; -import { get_pending_boundary } from './boundary.js'; +import { get_boundary } from './boundary.js'; /** * @param {TemplateNode} node @@ -9,7 +9,7 @@ import { get_pending_boundary } from './boundary.js'; * @param {(anchor: TemplateNode, ...deriveds: Value[]) => void} fn */ export function async(node, expressions, fn) { - var boundary = get_pending_boundary(); + var boundary = get_boundary(); boundary.update_pending_count(1); diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 12ca547608..b7f1803782 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -49,11 +49,11 @@ export function boundary(node, props, children) { } export class Boundary { - pending = false; - /** @type {Boundary | null} */ parent; + #pending = false; + /** @type {TemplateNode} */ #anchor; @@ -81,6 +81,7 @@ export class Boundary { /** @type {DocumentFragment | null} */ #offscreen_fragment = null; + #local_pending_count = 0; #pending_count = 0; #is_creating_fallback = false; @@ -95,12 +96,12 @@ export class Boundary { #effect_pending_update = () => { if (this.#effect_pending) { - internal_set(this.#effect_pending, this.#pending_count); + internal_set(this.#effect_pending, this.#local_pending_count); } }; #effect_pending_subscriber = createSubscriber(() => { - this.#effect_pending = source(this.#pending_count); + this.#effect_pending = source(this.#local_pending_count); if (DEV) { tag(this.#effect_pending, '$effect.pending()'); @@ -125,7 +126,7 @@ export class Boundary { this.parent = /** @type {Effect} */ (active_effect).b; - this.pending = !!this.#props.pending; + this.#pending = !!this.#props.pending; this.#effect = block(() => { /** @type {Effect} */ (active_effect).b = this; @@ -156,7 +157,7 @@ export class Boundary { this.#pending_effect = null; }); - this.pending = false; + this.#pending = false; } }); } else { @@ -169,7 +170,7 @@ export class Boundary { if (this.#pending_count > 0) { this.#show_pending_snippet(); } else { - this.pending = false; + this.#pending = false; } } }, flags); @@ -179,6 +180,14 @@ export class Boundary { } } + /** + * Returns `true` if the effect exists inside a boundary whose pending snippet is shown + * @returns {boolean} + */ + is_pending() { + return this.#pending || (!!this.parent && this.parent.is_pending()); + } + has_pending_snippet() { return !!this.#props.pending; } @@ -220,12 +229,25 @@ export class Boundary { } } - /** @param {1 | -1} d */ + /** + * Updates the pending count associated with the currently visible pending snippet, + * if any, such that we can replace the snippet with content once work is done + * @param {1 | -1} d + */ #update_pending_count(d) { + if (!this.has_pending_snippet()) { + if (this.parent) { + this.parent.#update_pending_count(d); + return; + } + + e.await_outside_boundary(); + } + this.#pending_count += d; if (this.#pending_count === 0) { - this.pending = false; + this.#pending = false; if (this.#pending_effect) { pause_effect(this.#pending_effect, () => { @@ -240,14 +262,16 @@ export class Boundary { } } - /** @param {1 | -1} d */ + /** + * Update the source that powers `$effect.pending()` inside this boundary, + * and controls when the current `pending` snippet (if any) is removed. + * Do not call from inside the class + * @param {1 | -1} d + */ update_pending_count(d) { - if (this.has_pending_snippet()) { - this.#update_pending_count(d); - } else if (this.parent) { - this.parent.#update_pending_count(d); - } + this.#update_pending_count(d); + this.#local_pending_count += d; effect_pending_updates.add(this.#effect_pending_update); } @@ -308,7 +332,7 @@ export class Boundary { }); } - this.pending = true; + this.#pending = true; this.#main_effect = this.#run(() => { this.#is_creating_fallback = false; @@ -318,7 +342,7 @@ export class Boundary { if (this.#pending_count > 0) { this.#show_pending_snippet(); } else { - this.pending = false; + this.#pending = false; } }; @@ -384,12 +408,8 @@ function move_effect(effect, fragment) { } } -export function get_pending_boundary() { - var boundary = /** @type {Effect} */ (active_effect).b; - - while (boundary !== null && !boundary.has_pending_snippet()) { - boundary = boundary.parent; - } +export function get_boundary() { + const boundary = /** @type {Effect} */ (active_effect).b; if (boundary === null) { e.await_outside_boundary(); diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 65d004137f..b7a5d5cdb7 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -3,7 +3,7 @@ import { DESTROYED } from '#client/constants'; import { DEV } from 'esm-env'; import { component_context, is_runes, set_component_context } from '../context.js'; -import { get_pending_boundary } from '../dom/blocks/boundary.js'; +import { get_boundary } from '../dom/blocks/boundary.js'; import { invoke_error_boundary } from '../error-handling.js'; import { active_effect, @@ -39,7 +39,7 @@ export function flatten(sync, async, fn) { var parent = /** @type {Effect} */ (active_effect); var restore = capture(); - var boundary = get_pending_boundary(); + var boundary = get_boundary(); Promise.all(async.map((expression) => async_derived(expression))) .then((result) => { diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 82f1de67a9..5176a4f74b 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -15,7 +15,7 @@ import { } from '#client/constants'; import { async_mode_flag } from '../../flags/index.js'; import { deferred, define_property } from '../../shared/utils.js'; -import { get_pending_boundary } from '../dom/blocks/boundary.js'; +import { get_boundary } from '../dom/blocks/boundary.js'; import { active_effect, is_dirty, @@ -298,7 +298,10 @@ export class Batch { this.#render_effects.push(effect); } else if ((flags & CLEAN) === 0) { if ((flags & ASYNC) !== 0) { - var effects = effect.b?.pending ? this.#boundary_async_effects : this.#async_effects; + var effects = effect.b?.is_pending() + ? this.#boundary_async_effects + : this.#async_effects; + effects.push(effect); } else if (is_dirty(effect)) { if ((effect.f & BLOCK_EFFECT) !== 0) this.#block_effects.push(effect); @@ -668,9 +671,9 @@ export function schedule_effect(signal) { } export function suspend() { - var boundary = get_pending_boundary(); + var boundary = get_boundary(); var batch = /** @type {Batch} */ (current_batch); - var pending = boundary.pending; + var pending = boundary.is_pending(); boundary.update_pending_count(1); if (!pending) batch.increment(); diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 31dc267960..299251a2dc 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -135,7 +135,7 @@ export function async_derived(fn, location) { prev = promise; var batch = /** @type {Batch} */ (current_batch); - var pending = boundary.pending; + var pending = boundary.is_pending(); if (should_suspend) { boundary.update_pending_count(1); diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-pending-nested/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-pending-nested/_config.js new file mode 100644 index 0000000000..9fe354bac0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-pending-nested/_config.js @@ -0,0 +1,95 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [increment, shift] = target.querySelectorAll('button'); + + assert.htmlEqual( + target.innerHTML, + ` + + +

loading...

+ ` + ); + + shift.click(); + shift.click(); + shift.click(); + + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

0

+

0

+

0

+

inner pending: 0

+

outer pending: 0

+ ` + ); + + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

0

+

0

+

0

+

inner pending: 3

+

outer pending: 0

+ ` + ); + + shift.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

0

+

0

+

0

+

inner pending: 2

+

outer pending: 0

+ ` + ); + + shift.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

0

+

0

+

0

+

inner pending: 1

+

outer pending: 0

+ ` + ); + + shift.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

1

+

1

+

1

+

inner pending: 0

+

outer pending: 0

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-pending-nested/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-pending-nested/main.svelte new file mode 100644 index 0000000000..eeafbdc3c4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-pending-nested/main.svelte @@ -0,0 +1,34 @@ + + + + + + + +

{await push(value)}

+

{await push(value)}

+

{await push(value)}

+

inner pending: {$effect.pending()}

+
+

outer pending: {$effect.pending()}

+ + {#snippet pending()} +

loading...

+ {/snippet} +
+ + From 7f9fe999270a727d1129ff11e0cf6bb3701b15e6 Mon Sep 17 00:00:00 2001 From: "Dominik G." Date: Tue, 9 Sep 2025 17:53:35 +0200 Subject: [PATCH 05/68] refactor(release): run with node24 and remove NPM_TOKEN to use oidc publish (#16733) --- .github/workflows/release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6debe5662a..f9d683a2ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 24.x cache: pnpm - name: Install @@ -45,4 +45,3 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_CONFIG_PROVENANCE: true - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} From f778975b760bed4d5ae366d95ddc8d60e94531f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:57:51 -0400 Subject: [PATCH 06/68] Version Packages (#16734) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/dirty-cycles-smash.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/dirty-cycles-smash.md diff --git a/.changeset/dirty-cycles-smash.md b/.changeset/dirty-cycles-smash.md deleted file mode 100644 index 1b031cf0af..0000000000 --- a/.changeset/dirty-cycles-smash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: send `$effect.pending` count to the correct boundary diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 535214781c..20ae98a500 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.38.8 + +### Patch Changes + +- fix: send `$effect.pending` count to the correct boundary ([#16732](https://github.com/sveltejs/svelte/pull/16732)) + ## 5.38.7 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index c6bc40ae2c..02fa445b0e 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.7", + "version": "5.38.8", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index d499c06797..8cb69aac5b 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.7'; +export const VERSION = '5.38.8'; export const PUBLIC_VERSION = '5'; From f343b00cc6b6d2c14a5b6fe941e75928dd841a83 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:32:09 +0200 Subject: [PATCH 07/68] fix: clean up scheduling system (#16741) Consolidates our scheduling system that had diverged into two - queue_micro_task and Batch.ensure-queues and resolves some edge case race conditions related to flushSync along the way. --- .changeset/old-taxis-relate.md | 5 ++++ .../svelte/src/internal/client/dom/task.js | 20 +++++++++++++-- .../src/internal/client/reactivity/batch.js | 25 +++---------------- .../main.svelte | 5 ++++ .../dynamic-component-transition/_config.js | 5 ++-- .../_config.js | 5 ++-- 6 files changed, 38 insertions(+), 27 deletions(-) create mode 100644 .changeset/old-taxis-relate.md diff --git a/.changeset/old-taxis-relate.md b/.changeset/old-taxis-relate.md new file mode 100644 index 0000000000..f38dc03f5e --- /dev/null +++ b/.changeset/old-taxis-relate.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: clean up scheduling system diff --git a/packages/svelte/src/internal/client/dom/task.js b/packages/svelte/src/internal/client/dom/task.js index 48a2fbe660..938f3ccda2 100644 --- a/packages/svelte/src/internal/client/dom/task.js +++ b/packages/svelte/src/internal/client/dom/task.js @@ -1,4 +1,5 @@ import { run_all } from '../../shared/utils.js'; +import { is_flushing_sync } from '../reactivity/batch.js'; // Fallback for when requestIdleCallback is not available const request_idle_callback = @@ -24,12 +25,27 @@ function run_idle_tasks() { run_all(tasks); } +export function has_pending_tasks() { + return micro_tasks.length > 0 || idle_tasks.length > 0; +} + /** * @param {() => void} fn */ export function queue_micro_task(fn) { - if (micro_tasks.length === 0) { - queueMicrotask(run_micro_tasks); + if (micro_tasks.length === 0 && !is_flushing_sync) { + var tasks = micro_tasks; + queueMicrotask(() => { + // If this is false, a flushSync happened in the meantime. Do _not_ run new scheduled microtasks in that case + // as the ordering of microtasks would be broken at that point - consider this case: + // - queue_micro_task schedules microtask A to flush task X + // - synchronously after, flushSync runs, processing task X + // - synchronously after, some other microtask B is scheduled, but not through queue_micro_task but for example a Promise.resolve() in user code + // - synchronously after, queue_micro_task schedules microtask C to flush task Y + // - one tick later, microtask A now resolves, flushing task Y before microtask B, which is incorrect + // This if check prevents that race condition (that realistically will only happen in tests) + if (tasks === micro_tasks) run_micro_tasks(); + }); } micro_tasks.push(fn); diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 5176a4f74b..c28617608e 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -25,7 +25,7 @@ import { update_effect } from '../runtime.js'; import * as e from '../errors.js'; -import { flush_tasks } from '../dom/task.js'; +import { flush_tasks, has_pending_tasks, queue_micro_task } from '../dom/task.js'; import { DEV } from 'esm-env'; import { invoke_error_boundary } from '../error-handling.js'; import { old_values } from './sources.js'; @@ -56,19 +56,6 @@ export let batch_deriveds = null; /** @type {Set<() => void>} */ export let effect_pending_updates = new Set(); -/** @type {Array<() => void>} */ -let tasks = []; - -function dequeue() { - const task = /** @type {() => void} */ (tasks.shift()); - - if (tasks.length > 0) { - queueMicrotask(dequeue); - } - - task(); -} - /** @type {Effect[]} */ let queued_root_effects = []; @@ -76,7 +63,7 @@ let queued_root_effects = []; let last_scheduled_effect = null; let is_flushing = false; -let is_flushing_sync = false; +export let is_flushing_sync = false; export class Batch { /** @@ -470,11 +457,7 @@ export class Batch { /** @param {() => void} task */ static enqueue(task) { - if (tasks.length === 0) { - queueMicrotask(dequeue); - } - - tasks.unshift(task); + queue_micro_task(task); } } @@ -505,7 +488,7 @@ export function flushSync(fn) { while (true) { flush_tasks(); - if (queued_root_effects.length === 0) { + if (queued_root_effects.length === 0 && !has_pending_tasks()) { current_batch?.flush(); // we need to check again, in case we just updated an `$effect.pending()` diff --git a/packages/svelte/tests/runtime-runes/samples/async-top-level-error-nested-obsolete/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-top-level-error-nested-obsolete/main.svelte index 2f461e96c8..2d8032228a 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-top-level-error-nested-obsolete/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-top-level-error-nested-obsolete/main.svelte @@ -3,6 +3,11 @@ export let route = $state({ current: 'home' }); + + diff --git a/packages/svelte/tests/runtime-runes/samples/dynamic-component-transition/_config.js b/packages/svelte/tests/runtime-runes/samples/dynamic-component-transition/_config.js index 9c741d2b8c..19f0c38227 100644 --- a/packages/svelte/tests/runtime-runes/samples/dynamic-component-transition/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/dynamic-component-transition/_config.js @@ -5,7 +5,8 @@ export default test({ async test({ assert, target, raf }) { const btn = target.querySelector('button'); - raf.tick(0); + // one tick to not be at 0. Else the flushSync would revert the in-transition which hasn't started, and directly remove the button + raf.tick(1); flushSync(() => { btn?.click(); @@ -13,7 +14,7 @@ export default test({ assert.htmlEqual(target.innerHTML, `

Outside

`); - raf.tick(100); + raf.tick(101); assert.htmlEqual(target.innerHTML, `

Outside

`); } diff --git a/packages/svelte/tests/runtime-runes/samples/dynamic-if-component-transition/_config.js b/packages/svelte/tests/runtime-runes/samples/dynamic-if-component-transition/_config.js index 9c741d2b8c..19f0c38227 100644 --- a/packages/svelte/tests/runtime-runes/samples/dynamic-if-component-transition/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/dynamic-if-component-transition/_config.js @@ -5,7 +5,8 @@ export default test({ async test({ assert, target, raf }) { const btn = target.querySelector('button'); - raf.tick(0); + // one tick to not be at 0. Else the flushSync would revert the in-transition which hasn't started, and directly remove the button + raf.tick(1); flushSync(() => { btn?.click(); @@ -13,7 +14,7 @@ export default test({ assert.htmlEqual(target.innerHTML, `

Outside

`); - raf.tick(100); + raf.tick(101); assert.htmlEqual(target.innerHTML, `

Outside

`); } From 68d27f1c4f64b070f322229e1f51460d1bf934b3 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:52:11 -0700 Subject: [PATCH 08/68] chore: generate CSS hash using the filename (#16740) * chore: generate CSS hash using the filename * fix all tests but one * slightly kludgy fix * try this * fix --------- Co-authored-by: Rich Harris --- .changeset/fast-boxes-sort.md | 5 ++++ .../src/compiler/phases/2-analyze/index.js | 20 +++++++++------- packages/svelte/src/compiler/types/index.d.ts | 2 +- .../svelte/src/compiler/validate-options.js | 4 ++-- packages/svelte/tests/html_equal.js | 8 ++++++- .../svelte/tests/runtime-browser/assert.js | 1 + .../_config.js | 24 +++++++++---------- .../_config.js | 20 ++++++++-------- .../_config.js | 24 +++++++++---------- .../_config.js | 20 ++++++++-------- .../custom-element-svelte-class/_config.js | 4 ++-- .../_config.js | 6 ++--- .../_expected.html | 2 +- .../_expected_head.html | 2 +- .../css-injected-options-nested/_expected.css | 2 +- .../_expected.html | 2 +- .../_expected_head.html | 2 +- .../css-injected-options/_expected.css | 2 +- .../css-injected-options/_expected.html | 2 +- .../css-injected-options/_expected_head.html | 2 +- .../samples/css/_expected.css | 2 +- .../samples/css/_expected.html | 2 +- .../samples/css/_expected_head.html | 2 +- .../samples/attached-sourcemap/_config.js | 2 +- .../tests/sourcemaps/samples/css/_config.js | 2 +- packages/svelte/types/index.d.ts | 4 ++-- 26 files changed, 91 insertions(+), 77 deletions(-) create mode 100644 .changeset/fast-boxes-sort.md diff --git a/.changeset/fast-boxes-sort.md b/.changeset/fast-boxes-sort.md new file mode 100644 index 0000000000..1edabf0582 --- /dev/null +++ b/.changeset/fast-boxes-sort.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: generate CSS hash using the filename diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 92b89c588e..de27c4623b 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -456,10 +456,19 @@ export function analyze_component(root, source, options) { const is_custom_element = !!options.customElementOptions || options.customElement; + const name = module.scope.generate(options.name ?? component_name); + + state.adjust({ + component_name: name, + dev: options.dev, + rootDir: options.rootDir, + runes + }); + // TODO remove all the ?? stuff, we don't need it now that we're validating the config /** @type {ComponentAnalysis} */ const analysis = { - name: module.scope.generate(options.name ?? component_name), + name, root: scope_root, module, instance, @@ -520,7 +529,7 @@ export function analyze_component(root, source, options) { hash: root.css ? options.cssHash({ css: root.css.content.styles, - filename: options.filename, + filename: state.filename, name: component_name, hash }) @@ -534,13 +543,6 @@ export function analyze_component(root, source, options) { async_deriveds: new Set() }; - state.adjust({ - component_name: analysis.name, - dev: options.dev, - rootDir: options.rootDir, - runes - }); - if (!runes) { // every exported `let` or `var` declaration becomes a prop, everything else becomes an export for (const node of instance.ast.body) { diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index 6211e69bd3..e13c9a9e22 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -105,7 +105,7 @@ export interface CompileOptions extends ModuleCompileOptions { css?: 'injected' | 'external'; /** * A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. - * It defaults to returning `svelte-${hash(css)}`. + * It defaults to returning `svelte-${hash(filename ?? css)}`. * * @default undefined */ diff --git a/packages/svelte/src/compiler/validate-options.js b/packages/svelte/src/compiler/validate-options.js index 2b727ad093..a94a553311 100644 --- a/packages/svelte/src/compiler/validate-options.js +++ b/packages/svelte/src/compiler/validate-options.js @@ -70,8 +70,8 @@ const component_options = { return input; }), - cssHash: fun(({ css, hash }) => { - return `svelte-${hash(css)}`; + cssHash: fun(({ css, filename, hash }) => { + return `svelte-${hash(filename === '(unknown)' ? css : filename ?? css)}`; }), // TODO this is a sourcemap option, would be good to put under a sourcemap namespace diff --git a/packages/svelte/tests/html_equal.js b/packages/svelte/tests/html_equal.js index b637e4d538..76a4a957a5 100644 --- a/packages/svelte/tests/html_equal.js +++ b/packages/svelte/tests/html_equal.js @@ -32,7 +32,13 @@ function clean_children(node, opts) { return; } - node.setAttribute(attr.name, attr.value); + let value = attr.value; + + if (attr.name === 'class') { + value = value.replace(/svelte-\w+/, 'svelte-xyz123'); + } + + node.setAttribute(attr.name, value); }); for (let child of [...node.childNodes]) { diff --git a/packages/svelte/tests/runtime-browser/assert.js b/packages/svelte/tests/runtime-browser/assert.js index 9a294a48c7..e331c8b677 100644 --- a/packages/svelte/tests/runtime-browser/assert.js +++ b/packages/svelte/tests/runtime-browser/assert.js @@ -74,6 +74,7 @@ function normalize_html(window, html) { node.innerHTML = html .replace(//g, '') .replace(/>[\s\r\n]+<') + .replace(/svelte-\w+/g, 'svelte-xyz123') .trim(); normalize_children(node); diff --git a/packages/svelte/tests/runtime-legacy/samples/attribute-null-classname-with-style/_config.js b/packages/svelte/tests/runtime-legacy/samples/attribute-null-classname-with-style/_config.js index cbd0456e13..1fc15c7eda 100644 --- a/packages/svelte/tests/runtime-legacy/samples/attribute-null-classname-with-style/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/attribute-null-classname-with-style/_config.js @@ -1,43 +1,43 @@ import { ok, test } from '../../test'; export default test({ - html: '
', + html: '
', test({ assert, component, target }) { const div = target.querySelector('div'); ok(div); component.testName = null; - assert.equal(div.className, 'svelte-x1o6ra'); + assert.equal(div.className, 'svelte-70s021'); component.testName = undefined; - assert.equal(div.className, 'svelte-x1o6ra'); + assert.equal(div.className, 'svelte-70s021'); component.testName = undefined + ''; - assert.equal(div.className, 'undefined svelte-x1o6ra'); + assert.equal(div.className, 'undefined svelte-70s021'); component.testName = null + ''; - assert.equal(div.className, 'null svelte-x1o6ra'); + assert.equal(div.className, 'null svelte-70s021'); component.testName = 1; - assert.equal(div.className, '1 svelte-x1o6ra'); + assert.equal(div.className, '1 svelte-70s021'); component.testName = 0; - assert.equal(div.className, '0 svelte-x1o6ra'); + assert.equal(div.className, '0 svelte-70s021'); component.testName = false; - assert.equal(div.className, 'false svelte-x1o6ra'); + assert.equal(div.className, 'false svelte-70s021'); component.testName = true; - assert.equal(div.className, 'true svelte-x1o6ra'); + assert.equal(div.className, 'true svelte-70s021'); component.testName = {}; - assert.equal(div.className, 'svelte-x1o6ra'); + assert.equal(div.className, 'svelte-70s021'); component.testName = ''; - assert.equal(div.className, 'svelte-x1o6ra'); + assert.equal(div.className, 'svelte-70s021'); component.testName = 'testClassName'; - assert.equal(div.className, 'testClassName svelte-x1o6ra'); + assert.equal(div.className, 'testClassName svelte-70s021'); } }); diff --git a/packages/svelte/tests/runtime-legacy/samples/attribute-null-classnames-with-style/_config.js b/packages/svelte/tests/runtime-legacy/samples/attribute-null-classnames-with-style/_config.js index d2511a4403..2f4b6242cb 100644 --- a/packages/svelte/tests/runtime-legacy/samples/attribute-null-classnames-with-style/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/attribute-null-classnames-with-style/_config.js @@ -10,43 +10,43 @@ export default test({ }; }, - html: '
', + html: '
', test({ assert, component, target }) { const div = target.querySelector('div'); ok(div); - assert.equal(div.className, 'test1test2 svelte-x1o6ra'); + assert.equal(div.className, 'test1test2 svelte-70s021'); component.testName1 = null; component.testName2 = null; - assert.equal(div.className, '0 svelte-x1o6ra'); + assert.equal(div.className, '0 svelte-70s021'); component.testName1 = null; component.testName2 = 'test'; - assert.equal(div.className, 'nulltest svelte-x1o6ra'); + assert.equal(div.className, 'nulltest svelte-70s021'); component.testName1 = undefined; component.testName2 = 'test'; - assert.equal(div.className, 'undefinedtest svelte-x1o6ra'); + assert.equal(div.className, 'undefinedtest svelte-70s021'); component.testName1 = undefined; component.testName2 = undefined; - assert.equal(div.className, 'NaN svelte-x1o6ra'); + assert.equal(div.className, 'NaN svelte-70s021'); component.testName1 = null; component.testName2 = 1; - assert.equal(div.className, '1 svelte-x1o6ra'); + assert.equal(div.className, '1 svelte-70s021'); component.testName1 = undefined; component.testName2 = 1; - assert.equal(div.className, 'NaN svelte-x1o6ra'); + assert.equal(div.className, 'NaN svelte-70s021'); component.testName1 = null; component.testName2 = 0; - assert.equal(div.className, '0 svelte-x1o6ra'); + assert.equal(div.className, '0 svelte-70s021'); component.testName1 = undefined; component.testName2 = 0; - assert.equal(div.className, 'NaN svelte-x1o6ra'); + assert.equal(div.className, 'NaN svelte-70s021'); } }); diff --git a/packages/svelte/tests/runtime-legacy/samples/attribute-null-func-classname-with-style/_config.js b/packages/svelte/tests/runtime-legacy/samples/attribute-null-func-classname-with-style/_config.js index 081fceecf2..b06103992f 100644 --- a/packages/svelte/tests/runtime-legacy/samples/attribute-null-func-classname-with-style/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/attribute-null-func-classname-with-style/_config.js @@ -8,41 +8,41 @@ export default test({ }; }, - html: '
', + html: '
', test({ assert, component, target }) { const div = target.querySelector('div'); ok(div); - assert.equal(div.className, 'testClassName svelte-x1o6ra'); + assert.equal(div.className, 'testClassName svelte-70s021'); component.testName = null; - assert.equal(div.className, 'svelte-x1o6ra'); + assert.equal(div.className, 'svelte-70s021'); component.testName = undefined; - assert.equal(div.className, 'svelte-x1o6ra'); + assert.equal(div.className, 'svelte-70s021'); component.testName = undefined + ''; - assert.equal(div.className, 'undefined svelte-x1o6ra'); + assert.equal(div.className, 'undefined svelte-70s021'); component.testName = null + ''; - assert.equal(div.className, 'null svelte-x1o6ra'); + assert.equal(div.className, 'null svelte-70s021'); component.testName = 1; - assert.equal(div.className, '1 svelte-x1o6ra'); + assert.equal(div.className, '1 svelte-70s021'); component.testName = 0; - assert.equal(div.className, '0 svelte-x1o6ra'); + assert.equal(div.className, '0 svelte-70s021'); component.testName = false; - assert.equal(div.className, 'false svelte-x1o6ra'); + assert.equal(div.className, 'false svelte-70s021'); component.testName = true; - assert.equal(div.className, 'true svelte-x1o6ra'); + assert.equal(div.className, 'true svelte-70s021'); component.testName = {}; - assert.equal(div.className, 'svelte-x1o6ra'); + assert.equal(div.className, 'svelte-70s021'); component.testName = ''; - assert.equal(div.className, 'svelte-x1o6ra'); + assert.equal(div.className, 'svelte-70s021'); } }); diff --git a/packages/svelte/tests/runtime-legacy/samples/attribute-null-func-classnames-with-style/_config.js b/packages/svelte/tests/runtime-legacy/samples/attribute-null-func-classnames-with-style/_config.js index d55a0079c0..7fc3dd85f4 100644 --- a/packages/svelte/tests/runtime-legacy/samples/attribute-null-func-classnames-with-style/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/attribute-null-func-classnames-with-style/_config.js @@ -10,43 +10,43 @@ export default test({ }; }, - html: '
', + html: '
', async test({ assert, component, target }) { const div = target.querySelector('div'); ok(div); - assert.equal(div.className, 'test1test2 svelte-x1o6ra'); + assert.equal(div.className, 'test1test2 svelte-70s021'); component.testName1 = null; component.testName2 = null; - assert.equal(div.className, '0 svelte-x1o6ra'); + assert.equal(div.className, '0 svelte-70s021'); component.testName1 = null; component.testName2 = 'test'; - assert.equal(div.className, 'nulltest svelte-x1o6ra'); + assert.equal(div.className, 'nulltest svelte-70s021'); component.testName1 = undefined; component.testName2 = 'test'; - assert.equal(div.className, 'undefinedtest svelte-x1o6ra'); + assert.equal(div.className, 'undefinedtest svelte-70s021'); component.testName1 = undefined; component.testName2 = undefined; - assert.equal(div.className, 'NaN svelte-x1o6ra'); + assert.equal(div.className, 'NaN svelte-70s021'); component.testName1 = null; component.testName2 = 1; - assert.equal(div.className, '1 svelte-x1o6ra'); + assert.equal(div.className, '1 svelte-70s021'); component.testName1 = undefined; component.testName2 = 1; - assert.equal(div.className, 'NaN svelte-x1o6ra'); + assert.equal(div.className, 'NaN svelte-70s021'); component.testName1 = null; component.testName2 = 0; - assert.equal(div.className, '0 svelte-x1o6ra'); + assert.equal(div.className, '0 svelte-70s021'); component.testName1 = undefined; component.testName2 = 0; - assert.equal(div.className, 'NaN svelte-x1o6ra'); + assert.equal(div.className, 'NaN svelte-70s021'); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-svelte-class/_config.js b/packages/svelte/tests/runtime-runes/samples/custom-element-svelte-class/_config.js index 0069a7dfd7..930de12585 100644 --- a/packages/svelte/tests/runtime-runes/samples/custom-element-svelte-class/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/custom-element-svelte-class/_config.js @@ -3,7 +3,7 @@ import { test } from '../../test'; export default test({ async test({ assert, target, logs }) { const [my_element, my_element_1] = target.querySelectorAll('my-element'); - assert.equal(my_element.classList.contains('svelte-1koh33s'), true); - assert.equal(my_element_1.classList.contains('svelte-1koh33s'), true); + assert.equal(my_element.classList.contains('svelte-70s021'), true); + assert.equal(my_element_1.classList.contains('svelte-70s021'), true); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-element-custom-element-css-hash/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-element-custom-element-css-hash/_config.js index eae8101e6e..cb532b28b5 100644 --- a/packages/svelte/tests/runtime-runes/samples/svelte-element-custom-element-css-hash/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/svelte-element-custom-element-css-hash/_config.js @@ -1,14 +1,14 @@ import { ok, test } from '../../test'; export default test({ - html: ``, + html: ``, async test({ assert, target }) { const [el, el2] = target.querySelectorAll('custom-element'); ok(el); ok(el2); - assert.deepEqual(el.className, 'red svelte-p153w3'); - assert.deepEqual(el2.className, 'red svelte-p153w3'); + assert.deepEqual(el.className, 'red svelte-70s021'); + assert.deepEqual(el2.className, 'red svelte-70s021'); } }); diff --git a/packages/svelte/tests/server-side-rendering/samples/css-injected-options-minify/_expected.html b/packages/svelte/tests/server-side-rendering/samples/css-injected-options-minify/_expected.html index 60e974bd1a..7ba4de394e 100644 --- a/packages/svelte/tests/server-side-rendering/samples/css-injected-options-minify/_expected.html +++ b/packages/svelte/tests/server-side-rendering/samples/css-injected-options-minify/_expected.html @@ -1 +1 @@ -
foo
\ No newline at end of file +
foo
diff --git a/packages/svelte/tests/server-side-rendering/samples/css-injected-options-minify/_expected_head.html b/packages/svelte/tests/server-side-rendering/samples/css-injected-options-minify/_expected_head.html index 5350e77a49..99db8fc6dc 100644 --- a/packages/svelte/tests/server-side-rendering/samples/css-injected-options-minify/_expected_head.html +++ b/packages/svelte/tests/server-side-rendering/samples/css-injected-options-minify/_expected_head.html @@ -1 +1 @@ - \ No newline at end of file + diff --git a/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected.css b/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected.css index bddefdd00c..81c1111967 100644 --- a/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected.css +++ b/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected.css @@ -1,4 +1,4 @@ -.foo.svelte-sg04hs { +.foo.svelte-1nvcr6w { color: red; } diff --git a/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected.html b/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected.html index 1f0b2b95fe..a6eba00942 100644 --- a/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected.html +++ b/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected.html @@ -1 +1 @@ -
bar
foo
\ No newline at end of file +
bar
foo
diff --git a/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected_head.html b/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected_head.html index 6d795670ff..516a9576b3 100644 --- a/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected_head.html +++ b/packages/svelte/tests/server-side-rendering/samples/css-injected-options-nested/_expected_head.html @@ -1 +1 @@ - \ No newline at end of file + diff --git a/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected.css b/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected.css index 8882c6ec7e..eb1f3e7a9b 100644 --- a/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected.css +++ b/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected.css @@ -1,4 +1,4 @@ - .foo.svelte-sg04hs { + .foo.svelte-okauro { color: red; } diff --git a/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected.html b/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected.html index 8ebe1ad73e..01ebd79914 100644 --- a/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected.html +++ b/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected.html @@ -1 +1 @@ -
foo
\ No newline at end of file +
foo
diff --git a/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected_head.html b/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected_head.html index 9c4f8a8538..1a1511f55e 100644 --- a/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected_head.html +++ b/packages/svelte/tests/server-side-rendering/samples/css-injected-options/_expected_head.html @@ -1 +1 @@ - \ No newline at end of file + diff --git a/packages/svelte/tests/server-side-rendering/samples/css/_expected.css b/packages/svelte/tests/server-side-rendering/samples/css/_expected.css index 8882c6ec7e..ec86890478 100644 --- a/packages/svelte/tests/server-side-rendering/samples/css/_expected.css +++ b/packages/svelte/tests/server-side-rendering/samples/css/_expected.css @@ -1,4 +1,4 @@ - .foo.svelte-sg04hs { + .foo.svelte-e9omc { color: red; } diff --git a/packages/svelte/tests/server-side-rendering/samples/css/_expected.html b/packages/svelte/tests/server-side-rendering/samples/css/_expected.html index 8ebe1ad73e..dc9409fa03 100644 --- a/packages/svelte/tests/server-side-rendering/samples/css/_expected.html +++ b/packages/svelte/tests/server-side-rendering/samples/css/_expected.html @@ -1 +1 @@ -
foo
\ No newline at end of file +
foo
diff --git a/packages/svelte/tests/server-side-rendering/samples/css/_expected_head.html b/packages/svelte/tests/server-side-rendering/samples/css/_expected_head.html index 9c4f8a8538..941c1f13b4 100644 --- a/packages/svelte/tests/server-side-rendering/samples/css/_expected_head.html +++ b/packages/svelte/tests/server-side-rendering/samples/css/_expected_head.html @@ -1 +1 @@ - \ No newline at end of file + diff --git a/packages/svelte/tests/sourcemaps/samples/attached-sourcemap/_config.js b/packages/svelte/tests/sourcemaps/samples/attached-sourcemap/_config.js index ee9a3d92c4..3a292ff428 100644 --- a/packages/svelte/tests/sourcemaps/samples/attached-sourcemap/_config.js +++ b/packages/svelte/tests/sourcemaps/samples/attached-sourcemap/_config.js @@ -57,7 +57,7 @@ export default test({ { str: 'replace_me_script', strGenerated: 'done_replace_script_2' }, { str: 'done_replace_script_2', idxGenerated: 1 } ], - css: [{ str: '.replace_me_style', strGenerated: '.done_replace_style_2.svelte-o6vre' }], + css: [{ str: '.replace_me_style', strGenerated: '.done_replace_style_2.svelte-1vsrjd4' }], test({ assert, code_preprocessed, code_css }) { assert.equal( code_preprocessed.includes('\n/*# sourceMappingURL=data:application/json;base64,'), diff --git a/packages/svelte/tests/sourcemaps/samples/css/_config.js b/packages/svelte/tests/sourcemaps/samples/css/_config.js index df3c83c703..fce38d401c 100644 --- a/packages/svelte/tests/sourcemaps/samples/css/_config.js +++ b/packages/svelte/tests/sourcemaps/samples/css/_config.js @@ -1,5 +1,5 @@ import { test } from '../../test'; export default test({ - css: [{ str: '.foo', strGenerated: '.foo.svelte-sg04hs' }] + css: [{ str: '.foo', strGenerated: '.foo.svelte-1eyw86p' }] }); diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 97e6f0f5a3..9888de59b2 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -998,7 +998,7 @@ declare module 'svelte/compiler' { css?: 'injected' | 'external'; /** * A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. - * It defaults to returning `svelte-${hash(css)}`. + * It defaults to returning `svelte-${hash(filename ?? css)}`. * * @default undefined */ @@ -2933,7 +2933,7 @@ declare module 'svelte/types/compiler/interfaces' { css?: 'injected' | 'external'; /** * A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. - * It defaults to returning `svelte-${hash(css)}`. + * It defaults to returning `svelte-${hash(filename ?? css)}`. * * @default undefined */ From f09f25e6a3ba7aa3562f62713e7cdc00b267fac7 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Wed, 10 Sep 2025 16:29:31 -0600 Subject: [PATCH 09/68] fix: Don't destroy boundary contents on error unless the boundary is an error boundary (#16746) * fix: Don't destroy contents of boundaries on errors if they're not error boundaries * changeset * test * prettier * prettier * simplify test * oops --------- Co-authored-by: Rich Harris --- .changeset/tasty-trainers-sell.md | 5 +++++ .../internal/client/dom/blocks/boundary.js | 12 +++++------ .../_config.js | 20 +++++++++++++++++++ .../main.svelte | 19 ++++++++++++++++++ 4 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 .changeset/tasty-trainers-sell.md create mode 100644 packages/svelte/tests/runtime-runes/samples/non-error-boundary-preserve-on-error/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/non-error-boundary-preserve-on-error/main.svelte diff --git a/.changeset/tasty-trainers-sell.md b/.changeset/tasty-trainers-sell.md new file mode 100644 index 0000000000..4a2dd09fa6 --- /dev/null +++ b/.changeset/tasty-trainers-sell.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't destroy contents of `svelte:boundary` unless the boundary is an error boundary diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index b7f1803782..d4582024f7 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -285,6 +285,12 @@ export class Boundary { var onerror = this.#props.onerror; let failed = this.#props.failed; + // If we have nothing to capture the error, or if we hit an error while + // rendering the fallback, re-throw for another boundary to handle + if (this.#is_creating_fallback || (!onerror && !failed)) { + throw error; + } + if (this.#main_effect) { destroy_effect(this.#main_effect); this.#main_effect = null; @@ -346,12 +352,6 @@ export class Boundary { } }; - // If we have nothing to capture the error, or if we hit an error while - // rendering the fallback, re-throw for another boundary to handle - if (this.#is_creating_fallback || (!onerror && !failed)) { - throw error; - } - var previous_reaction = active_reaction; try { diff --git a/packages/svelte/tests/runtime-runes/samples/non-error-boundary-preserve-on-error/_config.js b/packages/svelte/tests/runtime-runes/samples/non-error-boundary-preserve-on-error/_config.js new file mode 100644 index 0000000000..7654cf1360 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-error-boundary-preserve-on-error/_config.js @@ -0,0 +1,20 @@ +import { test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + test({ assert, target }) { + const [button] = target.querySelectorAll('button'); + + assert.throws(() => { + flushSync(() => button.click()); + }, /oops/); + + assert.htmlEqual( + target.innerHTML, + ` + +

some content

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/non-error-boundary-preserve-on-error/main.svelte b/packages/svelte/tests/runtime-runes/samples/non-error-boundary-preserve-on-error/main.svelte new file mode 100644 index 0000000000..1c3f062b43 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/non-error-boundary-preserve-on-error/main.svelte @@ -0,0 +1,19 @@ + + + + + +

some content

+ + {#if should_throw} + {throw_error()} + {/if} +
From 2743cd0fe5a305586e87b65301dc3466b7c265f9 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Wed, 10 Sep 2025 17:20:04 -0700 Subject: [PATCH 10/68] fix: correctly analyze `` components (#16711) * fix: correctly analyze `` components * add test --------- Co-authored-by: Rich Harris --- .changeset/khaki-flies-remember.md | 5 +++++ packages/svelte/src/compiler/phases/scope.js | 2 +- .../samples/snippet-hoisting-4/Component.svelte | 1 + .../samples/snippet-hoisting-4/_config.js | 5 +++++ .../samples/snippet-hoisting-4/main.svelte | 13 +++++++++++++ 5 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 .changeset/khaki-flies-remember.md create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/main.svelte diff --git a/.changeset/khaki-flies-remember.md b/.changeset/khaki-flies-remember.md new file mode 100644 index 0000000000..16d0f79e0f --- /dev/null +++ b/.changeset/khaki-flies-remember.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly analyze `` components diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 76157d406f..887bc47c56 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -1032,7 +1032,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { }, Component: (node, context) => { - context.state.scope.reference(b.id(node.name), context.path); + context.state.scope.reference(b.id(node.name.split('.')[0]), context.path); Component(node, context); }, SvelteSelf: Component, diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/Component.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/Component.svelte new file mode 100644 index 0000000000..597ecf5fc4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/Component.svelte @@ -0,0 +1 @@ +

Hello world!

diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/_config.js new file mode 100644 index 0000000000..240263603d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: '

Hello world!

' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/main.svelte new file mode 100644 index 0000000000..d3130a99bd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-hoisting-4/main.svelte @@ -0,0 +1,13 @@ + + +{#snippet not_hoisted()} + +{/snippet} + +{@render not_hoisted()} From 558a3c963bca4423cdd7b93d9588d05546dfa629 Mon Sep 17 00:00:00 2001 From: dai Date: Thu, 11 Sep 2025 03:10:45 +0200 Subject: [PATCH 11/68] fix: transform input defaults from spread (#16481) * fix: transform input defaults from spread * chore: add changeset * fix: prevent duplicates * do not remove defaults if they are in spreads * fix spreading twice * tweak * tweak * drive-by: remove unused export * tweak * undo comment change, to minimise diff * oops * tweak --------- Co-authored-by: 7nik Co-authored-by: Rich Harris --- .changeset/quiet-planes-doubt.md | 5 ++++ .../client/visitors/RegularElement.js | 18 +++++++++-- .../client/visitors/shared/element.js | 5 +++- .../server/visitors/shared/element.js | 3 ++ packages/svelte/src/constants.js | 1 + .../client/dom/elements/attributes.js | 30 +++++++++++++++++-- packages/svelte/src/internal/client/index.js | 1 - packages/svelte/src/internal/server/index.js | 11 ++++++- .../form-default-value-from-spread/_config.js | 10 +++++++ .../main.svelte | 8 +++++ 10 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 .changeset/quiet-planes-doubt.md create mode 100644 packages/svelte/tests/runtime-runes/samples/form-default-value-from-spread/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/form-default-value-from-spread/main.svelte diff --git a/.changeset/quiet-planes-doubt.md b/.changeset/quiet-planes-doubt.md new file mode 100644 index 0000000000..bd895f00ef --- /dev/null +++ b/.changeset/quiet-planes-doubt.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: transform input defaults from spread diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 4296aa959e..e906061650 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -72,6 +72,7 @@ export function RegularElement(node, context) { let has_spread = node.metadata.has_spread; let has_use = false; + let should_remove_defaults = false; for (const attribute of node.attributes) { switch (attribute.type) { @@ -172,7 +173,12 @@ export function RegularElement(node, context) { bindings.has('group') || (!bindings.has('group') && has_value_attribute)) ) { - context.state.init.push(b.stmt(b.call('$.remove_input_defaults', context.state.node))); + if (has_spread) { + // remove_input_defaults will be called inside set_attributes + should_remove_defaults = true; + } else { + context.state.init.push(b.stmt(b.call('$.remove_input_defaults', context.state.node))); + } } } @@ -202,7 +208,15 @@ export function RegularElement(node, context) { bindings.has('checked'); if (has_spread) { - build_attribute_effect(attributes, class_directives, style_directives, context, node, node_id); + build_attribute_effect( + attributes, + class_directives, + style_directives, + context, + node, + node_id, + should_remove_defaults + ); } else { for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) { if (is_event_attribute(attribute)) { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js index 9143a57025..4b32dab82a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js @@ -16,6 +16,7 @@ import { build_expression, build_template_chunk, Memoizer } from './utils.js'; * @param {ComponentContext} context * @param {AST.RegularElement | AST.SvelteElement} element * @param {Identifier} element_id + * @param {boolean} [should_remove_defaults] */ export function build_attribute_effect( attributes, @@ -23,7 +24,8 @@ export function build_attribute_effect( style_directives, context, element, - element_id + element_id, + should_remove_defaults = false ) { /** @type {ObjectExpression['properties']} */ const values = []; @@ -91,6 +93,7 @@ export function build_attribute_effect( element.metadata.scoped && context.state.analysis.css.hash !== '' && b.literal(context.state.analysis.css.hash), + should_remove_defaults && b.true, is_ignored(element, 'hydration_attribute_changed') && b.true ) ) 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 7207564ef9..84692fca9c 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 @@ -11,6 +11,7 @@ import { import { regex_starts_with_newline } from '../../../../patterns.js'; import * as b from '#compiler/builders'; import { + ELEMENT_IS_INPUT, ELEMENT_IS_NAMESPACED, ELEMENT_PRESERVE_ATTRIBUTE_CASE } from '../../../../../../constants.js'; @@ -401,6 +402,8 @@ function build_element_spread_attributes( flags |= ELEMENT_IS_NAMESPACED | ELEMENT_PRESERVE_ATTRIBUTE_CASE; } else if (is_custom_element_node(element)) { flags |= ELEMENT_PRESERVE_ATTRIBUTE_CASE; + } else if (element.type === 'RegularElement' && element.name === 'input') { + flags |= ELEMENT_IS_INPUT; } const object = build_spread_object(element, attributes, context); diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index 69cd213940..63324c860f 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -28,6 +28,7 @@ export const HYDRATION_ERROR = {}; export const ELEMENT_IS_NAMESPACED = 1; export const ELEMENT_PRESERVE_ATTRIBUTE_CASE = 1 << 1; +export const ELEMENT_IS_INPUT = 1 << 2; export const UNINITIALIZED = Symbol(); diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index a5b7140f25..fb6a92cc82 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -268,10 +268,27 @@ export function set_custom_element_data(node, prop, value) { * @param {Record | undefined} prev * @param {Record} next New attributes - this function mutates this object * @param {string} [css_hash] + * @param {boolean} [should_remove_defaults] * @param {boolean} [skip_warning] * @returns {Record} */ -export function set_attributes(element, prev, next, css_hash, skip_warning = false) { +function set_attributes( + element, + prev, + next, + css_hash, + should_remove_defaults = false, + skip_warning = false +) { + if (hydrating && should_remove_defaults && element.tagName === 'INPUT') { + var input = /** @type {HTMLInputElement} */ (element); + var attribute = input.type === 'checkbox' ? 'defaultChecked' : 'defaultValue'; + + if (!(attribute in next)) { + remove_input_defaults(input); + } + } + var attributes = get_attributes(element); var is_custom_element = attributes[IS_CUSTOM_ELEMENT]; @@ -467,6 +484,7 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal * @param {Array<() => any>} sync * @param {Array<() => Promise>} async * @param {string} [css_hash] + * @param {boolean} [should_remove_defaults] * @param {boolean} [skip_warning] */ export function attribute_effect( @@ -475,6 +493,7 @@ export function attribute_effect( sync = [], async = [], css_hash, + should_remove_defaults = false, skip_warning = false ) { flatten(sync, async, (values) => { @@ -490,7 +509,14 @@ export function attribute_effect( block(() => { var next = fn(...values.map(get)); /** @type {Record} */ - var current = set_attributes(element, prev, next, css_hash, skip_warning); + var current = set_attributes( + element, + prev, + next, + css_hash, + should_remove_defaults, + skip_warning + ); if (inited && is_select && 'value' in next) { select_option(/** @type {HTMLSelectElement} */ (element), next.value); diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index c5b7bb845c..dbff5c4599 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -28,7 +28,6 @@ export { attach } from './dom/elements/attachments.js'; export { remove_input_defaults, set_attribute, - set_attributes, attribute_effect, set_custom_element_data, set_xlink_attribute, diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 62ee22d6fc..3aa44f2daa 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -8,7 +8,8 @@ import { subscribe_to_store } from '../../store/utils.js'; import { UNINITIALIZED, ELEMENT_PRESERVE_ATTRIBUTE_CASE, - ELEMENT_IS_NAMESPACED + ELEMENT_IS_NAMESPACED, + ELEMENT_IS_INPUT } from '../../constants.js'; import { escape_html } from '../../escaping.js'; import { DEV } from 'esm-env'; @@ -187,6 +188,7 @@ export function spread_attributes(attrs, css_hash, classes, styles, flags = 0) { const is_html = (flags & ELEMENT_IS_NAMESPACED) === 0; const lowercase = (flags & ELEMENT_PRESERVE_ATTRIBUTE_CASE) === 0; + const is_input = (flags & ELEMENT_IS_INPUT) !== 0; for (name in attrs) { // omit functions, internal svelte properties and invalid attribute names @@ -200,6 +202,13 @@ export function spread_attributes(attrs, css_hash, classes, styles, flags = 0) { name = name.toLowerCase(); } + if (is_input) { + if (name === 'defaultvalue' || name === 'defaultchecked') { + name = name === 'defaultvalue' ? 'value' : 'checked'; + if (attrs[name]) continue; + } + } + attr_str += attr(name, value, is_html && is_boolean_attribute(name)); } diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value-from-spread/_config.js b/packages/svelte/tests/runtime-runes/samples/form-default-value-from-spread/_config.js new file mode 100644 index 0000000000..3808ae6530 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value-from-spread/_config.js @@ -0,0 +1,10 @@ +import { test } from '../../test'; + +export default test({ + mode: ['server'], + html: ` + + + +` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value-from-spread/main.svelte b/packages/svelte/tests/runtime-runes/samples/form-default-value-from-spread/main.svelte new file mode 100644 index 0000000000..7c0ce9cbe3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value-from-spread/main.svelte @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file From 96a4b16842e62a2e4224b084425cac6a191e6c2d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 21:22:12 -0400 Subject: [PATCH 12/68] Version Packages (#16744) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/fast-boxes-sort.md | 5 ----- .changeset/khaki-flies-remember.md | 5 ----- .changeset/old-taxis-relate.md | 5 ----- .changeset/quiet-planes-doubt.md | 5 ----- .changeset/tasty-trainers-sell.md | 5 ----- packages/svelte/CHANGELOG.md | 14 ++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 8 files changed, 16 insertions(+), 27 deletions(-) delete mode 100644 .changeset/fast-boxes-sort.md delete mode 100644 .changeset/khaki-flies-remember.md delete mode 100644 .changeset/old-taxis-relate.md delete mode 100644 .changeset/quiet-planes-doubt.md delete mode 100644 .changeset/tasty-trainers-sell.md diff --git a/.changeset/fast-boxes-sort.md b/.changeset/fast-boxes-sort.md deleted file mode 100644 index 1edabf0582..0000000000 --- a/.changeset/fast-boxes-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: generate CSS hash using the filename diff --git a/.changeset/khaki-flies-remember.md b/.changeset/khaki-flies-remember.md deleted file mode 100644 index 16d0f79e0f..0000000000 --- a/.changeset/khaki-flies-remember.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: correctly analyze `` components diff --git a/.changeset/old-taxis-relate.md b/.changeset/old-taxis-relate.md deleted file mode 100644 index f38dc03f5e..0000000000 --- a/.changeset/old-taxis-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: clean up scheduling system diff --git a/.changeset/quiet-planes-doubt.md b/.changeset/quiet-planes-doubt.md deleted file mode 100644 index bd895f00ef..0000000000 --- a/.changeset/quiet-planes-doubt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: transform input defaults from spread diff --git a/.changeset/tasty-trainers-sell.md b/.changeset/tasty-trainers-sell.md deleted file mode 100644 index 4a2dd09fa6..0000000000 --- a/.changeset/tasty-trainers-sell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't destroy contents of `svelte:boundary` unless the boundary is an error boundary diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 20ae98a500..4e384d5697 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,19 @@ # svelte +## 5.38.9 + +### Patch Changes + +- chore: generate CSS hash using the filename ([#16740](https://github.com/sveltejs/svelte/pull/16740)) + +- fix: correctly analyze `` components ([#16711](https://github.com/sveltejs/svelte/pull/16711)) + +- fix: clean up scheduling system ([#16741](https://github.com/sveltejs/svelte/pull/16741)) + +- fix: transform input defaults from spread ([#16481](https://github.com/sveltejs/svelte/pull/16481)) + +- fix: don't destroy contents of `svelte:boundary` unless the boundary is an error boundary ([#16746](https://github.com/sveltejs/svelte/pull/16746)) + ## 5.38.8 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 02fa445b0e..d445e0acf8 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.8", + "version": "5.38.9", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 8cb69aac5b..edb787c00c 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.8'; +export const VERSION = '5.38.9'; export const PUBLIC_VERSION = '5'; From 0b5bcc891e1e4582131b3e5eca1c48229f1f57d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 21:29:26 -0400 Subject: [PATCH 13/68] chore(deps-dev): bump vite from 5.4.19 to 5.4.20 (#16749) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.19 to 5.4.20. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.4.20/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.4.20/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 5.4.20 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- playgrounds/sandbox/package.json | 2 +- pnpm-lock.yaml | 420 ++++++++++++++++++------------- 2 files changed, 252 insertions(+), 170 deletions(-) diff --git a/playgrounds/sandbox/package.json b/playgrounds/sandbox/package.json index f11da983f6..8100084832 100644 --- a/playgrounds/sandbox/package.json +++ b/playgrounds/sandbox/package.json @@ -19,7 +19,7 @@ "polka": "^1.0.0-next.25", "svelte": "workspace:*", "tinyglobby": "^0.2.12", - "vite": "^5.4.19", + "vite": "^5.4.20", "vite-plugin-inspect": "^0.8.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cad5fefdf8..1f6bff9bd4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -155,7 +155,7 @@ importers: devDependencies: '@sveltejs/vite-plugin-svelte': specifier: ^4.0.0-next.6 - version: 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + version: 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) polka: specifier: ^1.0.0-next.25 version: 1.0.0-next.25 @@ -166,11 +166,11 @@ importers: specifier: ^0.2.12 version: 0.2.12 vite: - specifier: ^5.4.19 - version: 5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + specifier: ^5.4.20 + version: 5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) vite-plugin-inspect: specifier: ^0.8.4 - version: 0.8.4(rollup@4.40.2)(vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + version: 0.8.4(rollup@4.50.1)(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) packages: @@ -417,6 +417,12 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -557,8 +563,8 @@ packages: cpu: [arm] os: [android] - '@rollup/rollup-android-arm-eabi@4.40.2': - resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==} + '@rollup/rollup-android-arm-eabi@4.50.1': + resolution: {integrity: sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==} cpu: [arm] os: [android] @@ -567,8 +573,8 @@ packages: cpu: [arm64] os: [android] - '@rollup/rollup-android-arm64@4.40.2': - resolution: {integrity: sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==} + '@rollup/rollup-android-arm64@4.50.1': + resolution: {integrity: sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==} cpu: [arm64] os: [android] @@ -577,8 +583,8 @@ packages: cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-arm64@4.40.2': - resolution: {integrity: sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==} + '@rollup/rollup-darwin-arm64@4.50.1': + resolution: {integrity: sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==} cpu: [arm64] os: [darwin] @@ -587,18 +593,18 @@ packages: cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.40.2': - resolution: {integrity: sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==} + '@rollup/rollup-darwin-x64@4.50.1': + resolution: {integrity: sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.40.2': - resolution: {integrity: sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==} + '@rollup/rollup-freebsd-arm64@4.50.1': + resolution: {integrity: sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.40.2': - resolution: {integrity: sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==} + '@rollup/rollup-freebsd-x64@4.50.1': + resolution: {integrity: sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==} cpu: [x64] os: [freebsd] @@ -607,8 +613,8 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.40.2': - resolution: {integrity: sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==} + '@rollup/rollup-linux-arm-gnueabihf@4.50.1': + resolution: {integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==} cpu: [arm] os: [linux] @@ -617,8 +623,8 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.40.2': - resolution: {integrity: sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==} + '@rollup/rollup-linux-arm-musleabihf@4.50.1': + resolution: {integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==} cpu: [arm] os: [linux] @@ -627,8 +633,8 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.40.2': - resolution: {integrity: sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==} + '@rollup/rollup-linux-arm64-gnu@4.50.1': + resolution: {integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==} cpu: [arm64] os: [linux] @@ -637,13 +643,13 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.40.2': - resolution: {integrity: sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==} + '@rollup/rollup-linux-arm64-musl@4.50.1': + resolution: {integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.40.2': - resolution: {integrity: sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==} + '@rollup/rollup-linux-loongarch64-gnu@4.50.1': + resolution: {integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==} cpu: [loong64] os: [linux] @@ -652,8 +658,8 @@ packages: cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': - resolution: {integrity: sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==} + '@rollup/rollup-linux-ppc64-gnu@4.50.1': + resolution: {integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==} cpu: [ppc64] os: [linux] @@ -662,13 +668,13 @@ packages: cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.40.2': - resolution: {integrity: sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==} + '@rollup/rollup-linux-riscv64-gnu@4.50.1': + resolution: {integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.40.2': - resolution: {integrity: sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==} + '@rollup/rollup-linux-riscv64-musl@4.50.1': + resolution: {integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==} cpu: [riscv64] os: [linux] @@ -677,8 +683,8 @@ packages: cpu: [s390x] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.40.2': - resolution: {integrity: sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==} + '@rollup/rollup-linux-s390x-gnu@4.50.1': + resolution: {integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==} cpu: [s390x] os: [linux] @@ -687,8 +693,8 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.40.2': - resolution: {integrity: sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==} + '@rollup/rollup-linux-x64-gnu@4.50.1': + resolution: {integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==} cpu: [x64] os: [linux] @@ -697,18 +703,23 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.40.2': - resolution: {integrity: sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==} + '@rollup/rollup-linux-x64-musl@4.50.1': + resolution: {integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==} cpu: [x64] os: [linux] + '@rollup/rollup-openharmony-arm64@4.50.1': + resolution: {integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==} + cpu: [arm64] + os: [openharmony] + '@rollup/rollup-win32-arm64-msvc@4.22.4': resolution: {integrity: sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.40.2': - resolution: {integrity: sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==} + '@rollup/rollup-win32-arm64-msvc@4.50.1': + resolution: {integrity: sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==} cpu: [arm64] os: [win32] @@ -717,8 +728,8 @@ packages: cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.40.2': - resolution: {integrity: sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==} + '@rollup/rollup-win32-ia32-msvc@4.50.1': + resolution: {integrity: sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==} cpu: [ia32] os: [win32] @@ -727,8 +738,8 @@ packages: cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.40.2': - resolution: {integrity: sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==} + '@rollup/rollup-win32-x64-msvc@4.50.1': + resolution: {integrity: sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==} cpu: [x64] os: [win32] @@ -785,8 +796,8 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -818,13 +829,25 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/project-service@8.43.0': + resolution: {integrity: sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/scope-manager@8.26.0': resolution: {integrity: sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.32.1': - resolution: {integrity: sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==} + '@typescript-eslint/scope-manager@8.43.0': + resolution: {integrity: sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.43.0': + resolution: {integrity: sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/type-utils@8.26.0': resolution: {integrity: sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==} @@ -837,8 +860,8 @@ packages: resolution: {integrity: sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.32.1': - resolution: {integrity: sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==} + '@typescript-eslint/types@8.43.0': + resolution: {integrity: sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@8.26.0': @@ -847,11 +870,11 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/typescript-estree@8.32.1': - resolution: {integrity: sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==} + '@typescript-eslint/typescript-estree@8.43.0': + resolution: {integrity: sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/utils@8.26.0': resolution: {integrity: sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==} @@ -860,19 +883,19 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.32.1': - resolution: {integrity: sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==} + '@typescript-eslint/utils@8.43.0': + resolution: {integrity: sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/visitor-keys@8.26.0': resolution: {integrity: sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.32.1': - resolution: {integrity: sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==} + '@typescript-eslint/visitor-keys@8.43.0': + resolution: {integrity: sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitest/coverage-v8@2.1.9': @@ -923,8 +946,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.14.1: - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} hasBin: true @@ -1099,6 +1122,15 @@ packages: supports-color: optional: true + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} @@ -1161,8 +1193,8 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - enhanced-resolve@5.18.1: - resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} enquirer@2.4.1: @@ -1241,6 +1273,10 @@ packages: resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@9.9.1: resolution: {integrity: sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1387,8 +1423,8 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - get-tsconfig@4.10.0: - resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -1862,6 +1898,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} @@ -1912,6 +1952,10 @@ packages: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1984,8 +2028,8 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rollup@4.40.2: - resolution: {integrity: sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==} + rollup@4.50.1: + resolution: {integrity: sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2139,8 +2183,8 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + tapable@2.2.3: + resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} engines: {node: '>=6'} term-size@2.2.1: @@ -2325,8 +2369,8 @@ packages: terser: optional: true - vite@5.4.19: - resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} + vite@5.4.20: + resolution: {integrity: sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2731,6 +2775,11 @@ snapshots: eslint: 9.9.1 eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.0(eslint@9.9.1)': + dependencies: + eslint: 9.9.1 + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.1': {} '@eslint/config-array@0.18.0': @@ -2880,126 +2929,129 @@ snapshots: optionalDependencies: rollup: 4.22.4 - '@rollup/pluginutils@5.1.0(rollup@4.40.2)': + '@rollup/pluginutils@5.1.0(rollup@4.50.1)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.40.2 + rollup: 4.50.1 '@rollup/rollup-android-arm-eabi@4.22.4': optional: true - '@rollup/rollup-android-arm-eabi@4.40.2': + '@rollup/rollup-android-arm-eabi@4.50.1': optional: true '@rollup/rollup-android-arm64@4.22.4': optional: true - '@rollup/rollup-android-arm64@4.40.2': + '@rollup/rollup-android-arm64@4.50.1': optional: true '@rollup/rollup-darwin-arm64@4.22.4': optional: true - '@rollup/rollup-darwin-arm64@4.40.2': + '@rollup/rollup-darwin-arm64@4.50.1': optional: true '@rollup/rollup-darwin-x64@4.22.4': optional: true - '@rollup/rollup-darwin-x64@4.40.2': + '@rollup/rollup-darwin-x64@4.50.1': optional: true - '@rollup/rollup-freebsd-arm64@4.40.2': + '@rollup/rollup-freebsd-arm64@4.50.1': optional: true - '@rollup/rollup-freebsd-x64@4.40.2': + '@rollup/rollup-freebsd-x64@4.50.1': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.22.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.40.2': + '@rollup/rollup-linux-arm-gnueabihf@4.50.1': optional: true '@rollup/rollup-linux-arm-musleabihf@4.22.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.40.2': + '@rollup/rollup-linux-arm-musleabihf@4.50.1': optional: true '@rollup/rollup-linux-arm64-gnu@4.22.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.40.2': + '@rollup/rollup-linux-arm64-gnu@4.50.1': optional: true '@rollup/rollup-linux-arm64-musl@4.22.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.40.2': + '@rollup/rollup-linux-arm64-musl@4.50.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.40.2': + '@rollup/rollup-linux-loongarch64-gnu@4.50.1': optional: true '@rollup/rollup-linux-powerpc64le-gnu@4.22.4': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': + '@rollup/rollup-linux-ppc64-gnu@4.50.1': optional: true '@rollup/rollup-linux-riscv64-gnu@4.22.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.40.2': + '@rollup/rollup-linux-riscv64-gnu@4.50.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.40.2': + '@rollup/rollup-linux-riscv64-musl@4.50.1': optional: true '@rollup/rollup-linux-s390x-gnu@4.22.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.40.2': + '@rollup/rollup-linux-s390x-gnu@4.50.1': optional: true '@rollup/rollup-linux-x64-gnu@4.22.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.40.2': + '@rollup/rollup-linux-x64-gnu@4.50.1': optional: true '@rollup/rollup-linux-x64-musl@4.22.4': optional: true - '@rollup/rollup-linux-x64-musl@4.40.2': + '@rollup/rollup-linux-x64-musl@4.50.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.50.1': optional: true '@rollup/rollup-win32-arm64-msvc@4.22.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.40.2': + '@rollup/rollup-win32-arm64-msvc@4.50.1': optional: true '@rollup/rollup-win32-ia32-msvc@4.22.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.40.2': + '@rollup/rollup-win32-ia32-msvc@4.50.1': optional: true '@rollup/rollup-win32-x64-msvc@4.22.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.40.2': + '@rollup/rollup-win32-x64-msvc@4.50.1': optional: true '@stylistic/eslint-plugin-js@1.8.0(eslint@9.9.1)': dependencies: '@types/eslint': 8.56.12 - acorn: 8.14.1 + acorn: 8.15.0 escape-string-regexp: 4.0.0 eslint: 9.9.1 eslint-visitor-keys: 3.4.3 @@ -3020,25 +3072,25 @@ snapshots: typescript: 5.5.4 typescript-eslint: 8.26.0(eslint@9.9.1)(typescript@5.5.4) - '@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': + '@sveltejs/vite-plugin-svelte-inspector@3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + '@sveltejs/vite-plugin-svelte': 4.0.0-next.6(svelte@packages+svelte)(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) debug: 4.4.0 svelte: link:packages/svelte - vite: 5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': + '@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + '@sveltejs/vite-plugin-svelte-inspector': 3.0.0-next.2(@sveltejs/vite-plugin-svelte@4.0.0-next.6(svelte@packages+svelte)(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)))(svelte@packages+svelte)(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) debug: 4.4.0 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 svelte: link:packages/svelte - vite: 5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) - vitefu: 0.2.5(vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) + vite: 5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vitefu: 0.2.5(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)) transitivePeerDependencies: - supports-color @@ -3053,14 +3105,14 @@ snapshots: '@types/eslint@8.56.12': dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 '@types/estree@1.0.5': {} '@types/estree@1.0.6': {} - '@types/estree@1.0.7': {} + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -3103,15 +3155,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.43.0(typescript@5.5.4)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.5.4) + '@typescript-eslint/types': 8.43.0 + debug: 4.4.1 + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.26.0': dependencies: '@typescript-eslint/types': 8.26.0 '@typescript-eslint/visitor-keys': 8.26.0 - '@typescript-eslint/scope-manager@8.32.1': + '@typescript-eslint/scope-manager@8.43.0': dependencies: - '@typescript-eslint/types': 8.32.1 - '@typescript-eslint/visitor-keys': 8.32.1 + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/visitor-keys': 8.43.0 + + '@typescript-eslint/tsconfig-utils@8.43.0(typescript@5.5.4)': + dependencies: + typescript: 5.5.4 '@typescript-eslint/type-utils@8.26.0(eslint@9.9.1)(typescript@5.5.4)': dependencies: @@ -3126,7 +3191,7 @@ snapshots: '@typescript-eslint/types@8.26.0': {} - '@typescript-eslint/types@8.32.1': {} + '@typescript-eslint/types@8.43.0': {} '@typescript-eslint/typescript-estree@8.26.0(typescript@5.5.4)': dependencies: @@ -3142,11 +3207,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.32.1(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@8.43.0(typescript@5.5.4)': dependencies: - '@typescript-eslint/types': 8.32.1 - '@typescript-eslint/visitor-keys': 8.32.1 - debug: 4.4.0 + '@typescript-eslint/project-service': 8.43.0(typescript@5.5.4) + '@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.5.4) + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/visitor-keys': 8.43.0 + debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -3167,12 +3234,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.32.1(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/utils@8.43.0(eslint@9.9.1)(typescript@5.5.4)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.9.1) - '@typescript-eslint/scope-manager': 8.32.1 - '@typescript-eslint/types': 8.32.1 - '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.5.4) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.9.1) + '@typescript-eslint/scope-manager': 8.43.0 + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.5.4) eslint: 9.9.1 typescript: 5.5.4 transitivePeerDependencies: @@ -3183,10 +3250,10 @@ snapshots: '@typescript-eslint/types': 8.26.0 eslint-visitor-keys: 4.2.0 - '@typescript-eslint/visitor-keys@8.32.1': + '@typescript-eslint/visitor-keys@8.43.0': dependencies: - '@typescript-eslint/types': 8.32.1 - eslint-visitor-keys: 4.2.0 + '@typescript-eslint/types': 8.43.0 + eslint-visitor-keys: 4.2.1 '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': dependencies: @@ -3250,13 +3317,13 @@ snapshots: dependencies: acorn: 8.14.0 - acorn-jsx@5.3.2(acorn@8.14.1): + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: - acorn: 8.14.1 + acorn: 8.15.0 acorn@8.14.0: {} - acorn@8.14.1: {} + acorn@8.15.0: {} agent-base@7.1.1: dependencies: @@ -3416,6 +3483,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.1: + dependencies: + ms: 2.1.3 + decimal.js@10.4.3: {} deep-eql@5.0.2: {} @@ -3464,10 +3535,10 @@ snapshots: emoji-regex@9.2.2: {} - enhanced-resolve@5.18.1: + enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 - tapable: 2.2.1 + tapable: 2.2.3 enquirer@2.4.1: dependencies: @@ -3519,7 +3590,7 @@ snapshots: eslint-plugin-es-x@7.8.0(eslint@9.9.1): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.9.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.9.1) '@eslint-community/regexpp': 4.12.1 eslint: 9.9.1 eslint-compat-utils: 0.5.1(eslint@9.9.1) @@ -3528,12 +3599,12 @@ snapshots: eslint-plugin-n@17.16.1(eslint@9.9.1)(typescript@5.5.4): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.9.1) - '@typescript-eslint/utils': 8.32.1(eslint@9.9.1)(typescript@5.5.4) - enhanced-resolve: 5.18.1 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.9.1) + '@typescript-eslint/utils': 8.43.0(eslint@9.9.1)(typescript@5.5.4) + enhanced-resolve: 5.18.3 eslint: 9.9.1 eslint-plugin-es-x: 7.8.0(eslint@9.9.1) - get-tsconfig: 4.10.0 + get-tsconfig: 4.10.1 globals: 15.15.0 ignore: 5.3.2 minimatch: 9.0.5 @@ -3575,6 +3646,8 @@ snapshots: eslint-visitor-keys@4.2.0: {} + eslint-visitor-keys@4.2.1: {} + eslint@9.9.1: dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.9.1) @@ -3624,8 +3697,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -3754,7 +3827,7 @@ snapshots: function-bind@1.1.2: {} - get-tsconfig@4.10.0: + get-tsconfig@4.10.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -4195,6 +4268,8 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.3: {} + pify@4.0.1: {} playwright-core@1.46.1: {} @@ -4221,9 +4296,9 @@ snapshots: dependencies: postcss: 8.5.3 - postcss-scss@4.0.9(postcss@8.5.3): + postcss-scss@4.0.9(postcss@8.5.6): dependencies: - postcss: 8.5.3 + postcss: 8.5.6 postcss-selector-parser@7.1.0: dependencies: @@ -4236,6 +4311,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prelude-ls@1.2.1: {} prettier-plugin-svelte@3.4.0(prettier@3.2.4)(svelte@packages+svelte): @@ -4309,30 +4390,31 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.22.4 fsevents: 2.3.3 - rollup@4.40.2: + rollup@4.50.1: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.40.2 - '@rollup/rollup-android-arm64': 4.40.2 - '@rollup/rollup-darwin-arm64': 4.40.2 - '@rollup/rollup-darwin-x64': 4.40.2 - '@rollup/rollup-freebsd-arm64': 4.40.2 - '@rollup/rollup-freebsd-x64': 4.40.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.40.2 - '@rollup/rollup-linux-arm-musleabihf': 4.40.2 - '@rollup/rollup-linux-arm64-gnu': 4.40.2 - '@rollup/rollup-linux-arm64-musl': 4.40.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.40.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.40.2 - '@rollup/rollup-linux-riscv64-gnu': 4.40.2 - '@rollup/rollup-linux-riscv64-musl': 4.40.2 - '@rollup/rollup-linux-s390x-gnu': 4.40.2 - '@rollup/rollup-linux-x64-gnu': 4.40.2 - '@rollup/rollup-linux-x64-musl': 4.40.2 - '@rollup/rollup-win32-arm64-msvc': 4.40.2 - '@rollup/rollup-win32-ia32-msvc': 4.40.2 - '@rollup/rollup-win32-x64-msvc': 4.40.2 + '@rollup/rollup-android-arm-eabi': 4.50.1 + '@rollup/rollup-android-arm64': 4.50.1 + '@rollup/rollup-darwin-arm64': 4.50.1 + '@rollup/rollup-darwin-x64': 4.50.1 + '@rollup/rollup-freebsd-arm64': 4.50.1 + '@rollup/rollup-freebsd-x64': 4.50.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.50.1 + '@rollup/rollup-linux-arm-musleabihf': 4.50.1 + '@rollup/rollup-linux-arm64-gnu': 4.50.1 + '@rollup/rollup-linux-arm64-musl': 4.50.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.50.1 + '@rollup/rollup-linux-ppc64-gnu': 4.50.1 + '@rollup/rollup-linux-riscv64-gnu': 4.50.1 + '@rollup/rollup-linux-riscv64-musl': 4.50.1 + '@rollup/rollup-linux-s390x-gnu': 4.50.1 + '@rollup/rollup-linux-x64-gnu': 4.50.1 + '@rollup/rollup-linux-x64-musl': 4.50.1 + '@rollup/rollup-openharmony-arm64': 4.50.1 + '@rollup/rollup-win32-arm64-msvc': 4.50.1 + '@rollup/rollup-win32-ia32-msvc': 4.50.1 + '@rollup/rollup-win32-x64-msvc': 4.50.1 fsevents: 2.3.3 rrweb-cssom@0.7.1: {} @@ -4455,15 +4537,15 @@ snapshots: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.0 espree: 10.1.0 - postcss: 8.5.3 - postcss-scss: 4.0.9(postcss@8.5.3) + postcss: 8.5.6 + postcss-scss: 4.0.9(postcss@8.5.6) postcss-selector-parser: 7.1.0 optionalDependencies: svelte: link:packages/svelte symbol-tree@3.2.4: {} - tapable@2.2.1: {} + tapable@2.2.3: {} term-size@2.2.1: {} @@ -4543,7 +4625,7 @@ snapshots: ts-declaration-location@1.0.7(typescript@5.5.4): dependencies: - picomatch: 4.0.2 + picomatch: 4.0.3 typescript: 5.5.4 type-check@0.4.0: @@ -4582,7 +4664,7 @@ snapshots: debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 1.1.2 - vite: 5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - '@types/node' - less @@ -4594,10 +4676,10 @@ snapshots: - supports-color - terser - vite-plugin-inspect@0.8.4(rollup@4.40.2)(vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)): + vite-plugin-inspect@0.8.4(rollup@4.50.1)(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)): dependencies: '@antfu/utils': 0.7.8 - '@rollup/pluginutils': 5.1.0(rollup@4.40.2) + '@rollup/pluginutils': 5.1.0(rollup@4.50.1) debug: 4.4.0 error-stack-parser-es: 0.1.1 fs-extra: 11.2.0 @@ -4605,7 +4687,7 @@ snapshots: perfect-debounce: 1.0.0 picocolors: 1.1.1 sirv: 2.0.4 - vite: 5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - rollup - supports-color @@ -4613,7 +4695,7 @@ snapshots: vite@5.4.14(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: esbuild: 0.21.5 - postcss: 8.5.3 + postcss: 8.5.6 rollup: 4.22.4 optionalDependencies: '@types/node': 20.12.7 @@ -4622,11 +4704,11 @@ snapshots: sass: 1.70.0 terser: 5.27.0 - vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): + vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: esbuild: 0.21.5 - postcss: 8.5.3 - rollup: 4.40.2 + postcss: 8.5.6 + rollup: 4.50.1 optionalDependencies: '@types/node': 20.12.7 fsevents: 2.3.3 @@ -4634,9 +4716,9 @@ snapshots: sass: 1.70.0 terser: 5.27.0 - vitefu@0.2.5(vite@5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)): + vitefu@0.2.5(vite@5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)): optionalDependencies: - vite: 5.4.19(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) + vite: 5.4.20(@types/node@20.12.7)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0) vitest@2.1.9(@types/node@20.12.7)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0): dependencies: From 72ce753278a3e1f0046d1d13171d3b86445a241a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 11 Sep 2025 10:58:56 -0400 Subject: [PATCH 14/68] fix: flush effects scheduled during boundary's pending phase (#16738) Alternative to #16721, partial alternative to #16709. Closes #16691, closes #16627, closes #16582 and #16651 as well. --- .changeset/ninety-pandas-move.md | 5 +++++ .../src/internal/client/dom/blocks/boundary.js | 2 +- .../src/internal/client/reactivity/batch.js | 2 +- .../samples/async-attachment/Inner.svelte | 10 ++++++++++ .../samples/async-attachment/_config.js | 18 ++++++++++++++++++ .../samples/async-attachment/main.svelte | 16 ++++++++++++++++ .../async-effect-after-await/Child.svelte | 7 +++++++ .../async-effect-after-await/_config.js | 9 +++++++++ .../async-effect-after-await/main.svelte | 9 +++++++++ 9 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 .changeset/ninety-pandas-move.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-attachment/Inner.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-attachment/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-attachment/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-after-await/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-after-await/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-after-await/main.svelte diff --git a/.changeset/ninety-pandas-move.md b/.changeset/ninety-pandas-move.md new file mode 100644 index 0000000000..65f57ddbbf --- /dev/null +++ b/.changeset/ninety-pandas-move.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: flush effects scheduled during boundary's pending phase diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index d4582024f7..2a9736553c 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -23,7 +23,7 @@ import { queue_micro_task } from '../task.js'; import * as e from '../../errors.js'; import * as w from '../../warnings.js'; import { DEV } from 'esm-env'; -import { Batch, effect_pending_updates } from '../../reactivity/batch.js'; +import { Batch, current_batch, effect_pending_updates } from '../../reactivity/batch.js'; import { internal_set, source } from '../../reactivity/sources.js'; import { tag } from '../../dev/tracing.js'; import { createSubscriber } from '../../../../reactivity/create-subscriber.js'; diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index c28617608e..e504ae2e3f 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -668,7 +668,7 @@ export function suspend() { batch.activate(); batch.decrement(); } else { - batch.deactivate(); + batch.flush(); } unset_context(); diff --git a/packages/svelte/tests/runtime-runes/samples/async-attachment/Inner.svelte b/packages/svelte/tests/runtime-runes/samples/async-attachment/Inner.svelte new file mode 100644 index 0000000000..b9b9d7a3d0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-attachment/Inner.svelte @@ -0,0 +1,10 @@ + + +

{test}

+
diff --git a/packages/svelte/tests/runtime-runes/samples/async-attachment/_config.js b/packages/svelte/tests/runtime-runes/samples/async-attachment/_config.js new file mode 100644 index 0000000000..f6b48b38b1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-attachment/_config.js @@ -0,0 +1,18 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await tick(); + assert.htmlEqual(target.innerHTML, '

foo

foo
'); + + const [toggle] = target.querySelectorAll('button'); + toggle.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ''); + + toggle.click(); + await tick(); + assert.htmlEqual(target.innerHTML, '

foo

foo
'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-attachment/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-attachment/main.svelte new file mode 100644 index 0000000000..6cef6e8f5c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-attachment/main.svelte @@ -0,0 +1,16 @@ + + + + + {#if show} + + {/if} + + {#snippet pending()} +

pending

+ {/snippet} +
diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/Child.svelte new file mode 100644 index 0000000000..682f7a0631 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/Child.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/_config.js new file mode 100644 index 0000000000..81548a25ea --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/_config.js @@ -0,0 +1,9 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, logs }) { + await tick(); + assert.deepEqual(logs, ['hello']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/main.svelte new file mode 100644 index 0000000000..d4b67f8803 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/main.svelte @@ -0,0 +1,9 @@ + + + + + + {#snippet pending()}{/snippet} + From df13be8727ab16319a4f01ac39ff2ab195e953ae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 11:30:50 -0400 Subject: [PATCH 15/68] Version Packages (#16754) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/ninety-pandas-move.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/ninety-pandas-move.md diff --git a/.changeset/ninety-pandas-move.md b/.changeset/ninety-pandas-move.md deleted file mode 100644 index 65f57ddbbf..0000000000 --- a/.changeset/ninety-pandas-move.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: flush effects scheduled during boundary's pending phase diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 4e384d5697..62f109c82f 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.38.10 + +### Patch Changes + +- fix: flush effects scheduled during boundary's pending phase ([#16738](https://github.com/sveltejs/svelte/pull/16738)) + ## 5.38.9 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index d445e0acf8..6c91ec6bbb 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.9", + "version": "5.38.10", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index edb787c00c..9bfa7a5421 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.9'; +export const VERSION = '5.38.10'; export const PUBLIC_VERSION = '5'; From 808fbb4989d0e1687577cb4534e4d8350fb8971d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 11 Sep 2025 11:49:14 -0400 Subject: [PATCH 16/68] chore: inline `suspend` (#16755) * chore: inline `suspend` * invert condition --- packages/svelte/src/internal/client/index.js | 2 +- .../src/internal/client/reactivity/async.js | 21 +++++++++++++++--- .../src/internal/client/reactivity/batch.js | 22 ------------------- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index dbff5c4599..3c5409bcfe 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -103,7 +103,7 @@ export { save, track_reactivity_loss } from './reactivity/async.js'; -export { flushSync as flush, suspend } from './reactivity/batch.js'; +export { flushSync as flush } from './reactivity/batch.js'; export { async_derived, user_derived as derived, diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index b7a5d5cdb7..a109a1f4d8 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -11,7 +11,7 @@ import { set_active_effect, set_active_reaction } from '../runtime.js'; -import { current_batch, suspend } from './batch.js'; +import { Batch, current_batch } from './batch.js'; import { async_derived, current_async_effect, @@ -178,7 +178,13 @@ export function unset_context() { * @param {() => Promise} fn */ export async function async_body(fn) { - var unsuspend = suspend(); + var boundary = get_boundary(); + var batch = /** @type {Batch} */ (current_batch); + var pending = boundary.is_pending(); + + boundary.update_pending_count(1); + if (!pending) batch.increment(); + var active = /** @type {Effect} */ (active_effect); try { @@ -188,6 +194,15 @@ export async function async_body(fn) { invoke_error_boundary(error, active); } } finally { - unsuspend(); + boundary.update_pending_count(-1); + + if (pending) { + batch.flush(); + } else { + batch.activate(); + batch.decrement(); + } + + unset_context(); } } diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index e504ae2e3f..3d234f5bba 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -653,28 +653,6 @@ export function schedule_effect(signal) { queued_root_effects.push(effect); } -export function suspend() { - var boundary = get_boundary(); - var batch = /** @type {Batch} */ (current_batch); - var pending = boundary.is_pending(); - - boundary.update_pending_count(1); - if (!pending) batch.increment(); - - return function unsuspend() { - boundary.update_pending_count(-1); - - if (!pending) { - batch.activate(); - batch.decrement(); - } else { - batch.flush(); - } - - unset_context(); - }; -} - /** * Forcibly remove all current batches, to prevent cross-talk between tests */ From a0598014d2b634566d8a62acca6b0c7601054e18 Mon Sep 17 00:00:00 2001 From: Aaron Ajose Date: Sun, 14 Sep 2025 22:09:40 +0300 Subject: [PATCH 17/68] docs: Fix some inaccuracies (#16759) * docs: Fix some inaccuracies * Apply suggestions from code review --------- Co-authored-by: Rich Harris --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0653b08b76..e940252892 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ The [Open Source Guides](https://opensource.guide/) website has a collection of ## Get involved -There are many ways to contribute to Svelte, and many of them do not involve writing any code. Here's a few ideas to get started: +There are many ways to contribute to Svelte, and many of them do not involve writing any code. Here are a few ideas to get started: - Simply start using Svelte. Go through the [Getting Started](https://svelte.dev/docs#getting-started) guide. Does everything work as expected? If not, we're always looking for improvements. Let us know by [opening an issue](#reporting-new-issues). - Look through the [open issues](https://github.com/sveltejs/svelte/issues). A good starting point would be issues tagged [good first issue](https://github.com/sveltejs/svelte/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). Provide workarounds, ask for clarification, or suggest labels. Help [triage issues](#triaging-issues-and-pull-requests). @@ -90,9 +90,9 @@ A good test plan has the exact commands you ran and their output, provides scree #### Writing tests -All tests are located in `/test` folder. +All tests are located in the `/tests` folder. -Test samples are kept in `/test/xxx/samples` folder. +Test samples are kept in `/tests/xxx/samples` folders. #### Running tests From 8b4e1fcb7aa321c15382652b60d430bcd0bda960 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 15 Sep 2025 10:00:45 -0400 Subject: [PATCH 18/68] chore: extract a couple of drive-by fixes from other branch (#16772) * chore: extract a couple of drive-by fixes from other branch * more --- packages/svelte/src/internal/client/dom/operations.js | 6 +++--- packages/svelte/src/internal/client/dom/template.js | 3 +++ packages/svelte/src/internal/client/reactivity/batch.js | 3 --- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index abc29a7670..c527ca23e3 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -130,11 +130,11 @@ export function child(node, is_text) { /** * Don't mark this as side-effect-free, hydration needs to walk all nodes - * @param {DocumentFragment | TemplateNode[]} fragment - * @param {boolean} is_text + * @param {DocumentFragment | TemplateNode | TemplateNode[]} fragment + * @param {boolean} [is_text] * @returns {Node | null} */ -export function first_child(fragment, is_text) { +export function first_child(fragment, is_text = false) { if (!hydrating) { // when not hydrating, `fragment` is a `DocumentFragment` (the result of calling `open_frag`) var first = /** @type {DocumentFragment} */ (get_first_child(/** @type {Node} */ (fragment))); diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 265a52262f..135ca86610 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -316,6 +316,9 @@ export function text(value = '') { return node; } +/** + * @returns {TemplateNode | DocumentFragment} + */ export function comment() { // we're not delegating to `template` here for performance reasons if (hydrating) { diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 3d234f5bba..35aff7d4c9 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -10,12 +10,10 @@ import { INERT, RENDER_EFFECT, ROOT_EFFECT, - USER_EFFECT, MAYBE_DIRTY } from '#client/constants'; import { async_mode_flag } from '../../flags/index.js'; import { deferred, define_property } from '../../shared/utils.js'; -import { get_boundary } from '../dom/blocks/boundary.js'; import { active_effect, is_dirty, @@ -30,7 +28,6 @@ import { DEV } from 'esm-env'; import { invoke_error_boundary } from '../error-handling.js'; import { old_values } from './sources.js'; import { unlink_effect } from './effects.js'; -import { unset_context } from './async.js'; /** @type {Set} */ const batches = new Set(); From 8b106b94f41a87ebfff251f70d602dda70265dc9 Mon Sep 17 00:00:00 2001 From: 7nik Date: Mon, 15 Sep 2025 20:24:31 +0300 Subject: [PATCH 19/68] fix: correctly SSR hidden="until-found" (#16773) --- .changeset/pink-gifts-sell.md | 5 +++++ packages/svelte/src/internal/shared/attributes.js | 4 ++++ packages/svelte/src/utils.js | 1 - .../samples/attribute-spread-hidden-2/_expected.html | 1 + .../samples/attribute-spread-hidden-2/main.svelte | 3 +++ 5 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .changeset/pink-gifts-sell.md create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/_expected.html create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/main.svelte diff --git a/.changeset/pink-gifts-sell.md b/.changeset/pink-gifts-sell.md new file mode 100644 index 0000000000..f3f91ff7d9 --- /dev/null +++ b/.changeset/pink-gifts-sell.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly SSR hidden="until-found" diff --git a/packages/svelte/src/internal/shared/attributes.js b/packages/svelte/src/internal/shared/attributes.js index c8758c1d4d..a96e71ff6f 100644 --- a/packages/svelte/src/internal/shared/attributes.js +++ b/packages/svelte/src/internal/shared/attributes.js @@ -23,6 +23,10 @@ const replacements = { */ export function attr(name, value, is_boolean = false) { if (value == null || (!value && is_boolean)) return ''; + // attribute hidden for values other than "until-found" behaves like a boolean attribute + if (name === 'hidden' && value !== 'until-found') { + is_boolean = true; + } const normalized = (name in replacements && replacements[name].get(value)) || value; const assignment = is_boolean ? '' : `="${escape_html(normalized, true)}"`; return ` ${name}${assignment}`; diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index f8c39253ac..f8a7e8d46d 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -154,7 +154,6 @@ const DOM_BOOLEAN_ATTRIBUTES = [ 'default', 'disabled', 'formnovalidate', - 'hidden', 'indeterminate', 'inert', 'ismap', diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/_expected.html b/packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/_expected.html new file mode 100644 index 0000000000..80937efaee --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/_expected.html @@ -0,0 +1 @@ +
A
diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/main.svelte new file mode 100644 index 0000000000..6738b34054 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/main.svelte @@ -0,0 +1,3 @@ +
A
+
B
+
C
\ No newline at end of file From 8c982f61013a517bec2edf68a82ce1a1188fcd8f Mon Sep 17 00:00:00 2001 From: 7nik Date: Tue, 16 Sep 2025 11:45:18 +0300 Subject: [PATCH 20/68] fix: correct wrong fix, get test to actually do something (#16779) #16773 added a test with _expected.html to a wrong suit, so it was ignored, and this allowed to slip in a new bug of printing the falsy hidden attribute as hidden (shortened enabled form). That fix wasn't released yet, so no changeset. --- packages/svelte/src/internal/shared/attributes.js | 2 +- .../samples/attribute-spread-hidden}/_expected.html | 0 .../samples/attribute-spread-hidden}/main.svelte | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename packages/svelte/tests/{runtime-runes/samples/attribute-spread-hidden-2 => server-side-rendering/samples/attribute-spread-hidden}/_expected.html (100%) rename packages/svelte/tests/{runtime-runes/samples/attribute-spread-hidden-2 => server-side-rendering/samples/attribute-spread-hidden}/main.svelte (100%) diff --git a/packages/svelte/src/internal/shared/attributes.js b/packages/svelte/src/internal/shared/attributes.js index a96e71ff6f..4ad550e8d6 100644 --- a/packages/svelte/src/internal/shared/attributes.js +++ b/packages/svelte/src/internal/shared/attributes.js @@ -22,11 +22,11 @@ const replacements = { * @returns {string} */ export function attr(name, value, is_boolean = false) { - if (value == null || (!value && is_boolean)) return ''; // attribute hidden for values other than "until-found" behaves like a boolean attribute if (name === 'hidden' && value !== 'until-found') { is_boolean = true; } + if (value == null || (!value && is_boolean)) return ''; const normalized = (name in replacements && replacements[name].get(value)) || value; const assignment = is_boolean ? '' : `="${escape_html(normalized, true)}"`; return ` ${name}${assignment}`; diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/_expected.html b/packages/svelte/tests/server-side-rendering/samples/attribute-spread-hidden/_expected.html similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/_expected.html rename to packages/svelte/tests/server-side-rendering/samples/attribute-spread-hidden/_expected.html diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/main.svelte b/packages/svelte/tests/server-side-rendering/samples/attribute-spread-hidden/main.svelte similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/attribute-spread-hidden-2/main.svelte rename to packages/svelte/tests/server-side-rendering/samples/attribute-spread-hidden/main.svelte From b8fd326d96c3a57feff3a4fafcd2d1651001b109 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Wed, 17 Sep 2025 15:49:57 -0600 Subject: [PATCH 21/68] feat: async SSR (#16748) * feat: First pass at payload * first crack * snapshots * checkpoint * fix: cloning * add test option * big dumb * today's hard work; few tests left to fix * improve * tests passing no wayyyyy yo * lots of progress, couple of failing tests around selects * meh * solve async tree stuff * fix select/option stuff * whoop, tests * simplify * feat: hoisting * fix: `$effect.pending` sends updates to incorrect boundary * changeset * stuff from upstream * feat: first hydrationgaa * remove docs * snapshots * silly fix * checkpoint * meh * ALKASJDFALSKDFJ the test passes * chore: Update a bunch of tests for hydration markers * chore: remove snippet and is_async * naming * better errors for sync-in-async * test improvements * idk man * merge local branches (#16757) * use fragment as async hoist boundary * remove async_hoist_boundary * only dewaterfall when necessary * unused * simplify/fix * de-waterfall awaits in separate elements * update snapshots * remove unnecessary wrapper * fix * fix * remove suspends_without_fallback --------- Co-authored-by: Rich Harris * Update payload.js Co-authored-by: Rich Harris * checkpoint * got the extra children to go away * just gonna go ahead and merge this as the review comments take up too much space * chore: remove hoisted_promises (#16766) * chore: remove hoisted_promises * WIP optimise promises * WIP * fix with await in prop * tweak * fix type error * Update packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteHead.js * chore: fix hydration treeshaking (#16767) * chore: fix hydration treeshaking * fix * remove await_outside_boundary error (#16762) * chore: remove unused analysis.boundary (#16763) * chore: simplify slots (#16765) * chore: simplify slots * unused * Apply suggestions from code review * chore: remove metadata.pending (#16764) * Update packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js * put this back where it was, keep the diff small * Update packages/svelte/src/compiler/phases/types.d.ts Co-authored-by: Rich Harris * chore: remove analysis.state.title (#16771) * chore: remove analysis.state.title * unused * chore: remove is_async (#16769) * chore: remove is_async * unused * Apply suggestions from code review Co-authored-by: Rich Harris * cleanup * lint * clean up payload a bit * compiler work * run ssr on sync and async * prettier * inline capture * Update packages/svelte/src/compiler/phases/3-transform/server/visitors/EachBlock.js * chore: simplify build_template (#16780) * small tweak to aid greppability * chore: fix SSR context (#16781) * at least passing * cleanup * fix * remove push/pop from exports, not needed with payload * I think this is better but tbh not sure * async SSR * qualification * errors: * I have lost the plot * finally * ugh * tweak error codes to better align with existing conventions, such as they are * tweak messages * remove unused args * DRY out a bit * unused * unused * unused * simplify - we can enforce readonly at a type level * unused * simplify * avoid magical accessors * simplify algorithm * unused * unused * reduce indirection * TreeState -> SSRState * mark deprecated methods * grab this.local from parent directly * rename render -> fn per conventions (fn indicates 'arbitrary code') * reduce indirection * Revert "reduce indirection" This reverts commit 3ec461baad451654db6d518734aeeb7b366f7c2e. * tweak * okay works this time * no way chat, it works * fix context stuff * tweak * make it chainable * lint * clean up * lint * Update packages/svelte/src/internal/server/types.d.ts Co-authored-by: Rich Harris * sunset html for async * types * we use 'deprecated' in other messages * oops --------- Co-authored-by: Rich Harris --- .changeset/forty-insects-cheat.md | 5 + .prettierignore | 1 + .../98-reference/.generated/server-errors.md | 14 + .../.generated/server-warnings.md | 9 + .../98-reference/.generated/shared-errors.md | 20 - eslint.config.js | 1 + .../svelte/messages/server-errors/errors.md | 15 + .../messages/server-errors/lifecycle.md | 5 - .../messages/server-warnings/warnings.md | 5 + .../svelte/messages/shared-errors/errors.md | 18 - .../svelte/scripts/process-messages/index.js | 1 + .../templates/server-warnings.js | 20 + .../compiler/phases/1-parse/state/element.js | 3 +- .../src/compiler/phases/2-analyze/index.js | 15 +- .../src/compiler/phases/2-analyze/types.d.ts | 5 + .../2-analyze/visitors/AwaitExpression.js | 122 +++- .../2-analyze/visitors/CallExpression.js | 1 + .../phases/2-analyze/visitors/ConstTag.js | 6 +- .../2-analyze/visitors/RegularElement.js | 2 +- .../2-analyze/visitors/VariableDeclarator.js | 6 + .../3-transform/client/transform-client.js | 2 - .../phases/3-transform/client/types.d.ts | 8 +- .../client/visitors/AwaitExpression.js | 106 +-- .../client/visitors/CallExpression.js | 4 +- .../3-transform/client/visitors/ConstTag.js | 9 +- .../client/visitors/RegularElement.js | 41 +- .../client/visitors/VariableDeclaration.js | 14 +- .../3-transform/server/transform-server.js | 26 +- .../3-transform/server/visitors/AwaitBlock.js | 38 +- .../server/visitors/AwaitExpression.js | 22 +- .../3-transform/server/visitors/EachBlock.js | 27 +- .../3-transform/server/visitors/IfBlock.js | 22 +- .../server/visitors/RegularElement.js | 76 ++- .../server/visitors/SlotElement.js | 27 +- .../server/visitors/SvelteBoundary.js | 26 +- .../server/visitors/TitleElement.js | 4 +- .../server/visitors/shared/component.js | 60 +- .../server/visitors/shared/utils.js | 149 +++-- .../svelte/src/compiler/phases/types.d.ts | 4 +- .../svelte/src/compiler/types/template.d.ts | 2 + packages/svelte/src/compiler/utils/ast.js | 16 + .../svelte/src/compiler/utils/builders.js | 12 +- packages/svelte/src/index-server.js | 8 +- .../internal/client/dom/blocks/boundary.js | 106 +-- .../src/internal/client/reactivity/async.js | 3 +- packages/svelte/src/internal/client/render.js | 55 +- .../src/internal/server/blocks/snippet.js | 2 +- .../svelte/src/internal/server/context.js | 58 +- packages/svelte/src/internal/server/dev.js | 46 +- packages/svelte/src/internal/server/errors.js | 24 + packages/svelte/src/internal/server/index.js | 232 ++++--- .../svelte/src/internal/server/payload.js | 625 ++++++++++++++++-- .../src/internal/server/payload.test.ts | 364 ++++++++++ .../svelte/src/internal/server/types.d.ts | 23 +- .../svelte/src/internal/server/warnings.js | 17 + packages/svelte/src/internal/shared/errors.js | 16 - packages/svelte/src/legacy/legacy-server.js | 1 + .../samples/binding-input/_expected.html | 2 +- .../_override.html | 2 +- .../dynamic-text-changed/_expected.html | 2 +- .../_expected.html | 2 +- .../_expected.html | 2 +- .../_expected.html | 2 +- .../_expected.html | 2 +- .../element-attribute-added/_expected.html | 2 +- .../element-attribute-changed/_expected.html | 2 +- .../element-attribute-removed/_expected.html | 2 +- .../if-block-mismatch-2/_expected.html | 2 +- .../samples/if-block-mismatch/_expected.html | 2 +- .../_expected.html | 2 +- .../input-value-changed/_expected.html | 2 +- .../hydration/samples/noscript/_expected.html | 2 +- .../pre-first-node-newline/_expected.html | 4 +- .../_expected.html | 2 +- .../repair-mismatched-a-href/_expected.html | 2 +- .../samples/safari-borking/_override.html | 2 +- .../hydration/samples/script/_expected.html | 2 +- .../snippet-raw-hydrate/_expected.html | 2 +- .../standalone-component/_expected.html | 2 +- .../samples/standalone-snippet/_expected.html | 2 +- .../surrounding-whitespace/_expected.html | 2 +- .../surrounding-whitespace/_override.html | 2 +- .../samples/text-empty-2/_expected.html | 2 +- .../samples/text-empty/_expected.html | 2 +- .../whitespace-at-block-start/_override.html | 4 +- .../tests/runtime-browser/driver-ssr.js | 6 +- .../after-render-prevents-loop/_config.js | 2 +- .../after-render-triggers-update/_config.js | 2 +- .../before-render-prevents-loop/_config.js | 2 +- .../_config.js | 18 +- .../_config.js | 6 +- .../samples/binding-select-late-2/_config.js | 6 +- .../samples/binding-select-late-3/_config.js | 6 +- .../samples/binding-select-late/_config.js | 6 +- .../binding-select-unmatched-2/_config.js | 30 +- .../binding-select-unmatched-3/_config.js | 4 +- .../_config.js | 2 +- .../samples/textarea-content/_config.js | 4 +- .../svelte/tests/runtime-legacy/shared.ts | 53 +- .../async-no-pending-throws-sync/_config.js | 7 + .../async-no-pending-throws-sync/main.svelte | 1 + .../samples/async-no-pending/_config.js | 17 + .../samples/async-no-pending/main.svelte | 1 + .../samples/async-ondestroy-ordering/A.svelte | 9 + .../samples/async-ondestroy-ordering/B.svelte | 9 + .../samples/async-ondestroy-ordering/C.svelte | 5 + .../async-ondestroy-ordering/_config.js | 14 + .../async-ondestroy-ordering/destroyed.js | 4 + .../async-ondestroy-ordering/main.svelte | 13 + .../samples/snippet-slot-let-error/_config.js | 1 + .../_config.js | 6 + .../main.svelte | 7 + .../_expected.html | 1 + .../async-each-fallback-hoisting/main.svelte | 5 + .../async-each-hoisting/_expected.html | 1 + .../samples/async-each-hoisting/main.svelte | 9 + .../A.svelte | 9 + .../B.svelte | 7 + .../_config.js | 3 + .../_expected.html | 0 .../_expected_head.html | 1 + .../main.svelte | 27 + .../_expected.html | 1 + .../async-if-alternate-hoisting/main.svelte | 5 + .../samples/async-if-hoisting/_expected.html | 1 + .../samples/async-if-hoisting/main.svelte | 5 + .../_expected.html | 3 + .../main.svelte | 3 + .../_expected.html | 3 + .../main.svelte | 3 + .../Option.svelte | 5 + .../_expected.html | 1 + .../async-select-value-component/main.svelte | 8 + .../_expected.html | 1 + .../main.svelte | 13 + .../_expected.html | 1 + .../main.svelte | 5 + .../samples/comment-preserve/_expected.html | 2 +- .../samples/context/Child.svelte | 8 + .../samples/context/_config.js | 5 + .../samples/context/_expected.html | 3 + .../samples/context/main.svelte | 11 + .../_expected.html | 2 +- .../_expected.html | 2 + .../invalid-nested-svelte-element/_config.js | 4 +- .../tests/server-side-rendering/test.ts | 146 ++-- .../async-each-fallback-hoisting/_config.js | 3 + .../_expected/client/index.svelte.js | 35 + .../_expected/server/index.svelte.js | 25 + .../async-each-fallback-hoisting/index.svelte | 5 + .../samples/async-each-hoisting/_config.js | 3 + .../_expected/client/index.svelte.js | 24 + .../_expected/server/index.svelte.js | 23 + .../samples/async-each-hoisting/index.svelte | 9 + .../async-if-alternate-hoisting/_config.js | 3 + .../_expected/client/index.svelte.js | 30 + .../_expected/server/index.svelte.js | 16 + .../async-if-alternate-hoisting/index.svelte | 5 + .../samples/async-if-hoisting/_config.js | 3 + .../_expected/client/index.svelte.js | 30 + .../_expected/server/index.svelte.js | 16 + .../samples/async-if-hoisting/index.svelte | 5 + .../_expected/server/index.svelte.js | 4 +- .../_expected/server/index.svelte.js | 8 +- .../_expected/server/index.svelte.js | 64 +- .../_expected/server/index.svelte.js | 8 +- .../_expected/server/main.svelte.js | 2 +- .../_expected/server/index.svelte.js | 8 +- .../_expected/server/index.svelte.js | 8 +- .../_expected/server/index.svelte.js | 2 +- .../_expected/server/index.svelte.js | 2 +- .../_expected/server/index.svelte.js | 2 +- .../hmr/_expected/server/index.svelte.js | 2 +- .../_expected/server/index.svelte.js | 2 +- .../_expected/server/index.svelte.js | 21 +- .../purity/_expected/server/index.svelte.js | 4 +- .../_expected/server/index.svelte.js | 2 +- .../_expected/server/index.svelte.js | 2 +- .../_expected/server/index.svelte.js | 2 +- packages/svelte/types/index.d.ts | 4 +- playgrounds/sandbox/package.json | 3 +- 181 files changed, 2638 insertions(+), 901 deletions(-) create mode 100644 .changeset/forty-insects-cheat.md create mode 100644 documentation/docs/98-reference/.generated/server-warnings.md create mode 100644 packages/svelte/messages/server-errors/errors.md delete mode 100644 packages/svelte/messages/server-errors/lifecycle.md create mode 100644 packages/svelte/messages/server-warnings/warnings.md create mode 100644 packages/svelte/scripts/process-messages/templates/server-warnings.js create mode 100644 packages/svelte/src/internal/server/payload.test.ts create mode 100644 packages/svelte/src/internal/server/warnings.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-no-pending-throws-sync/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-no-pending-throws-sync/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-no-pending/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-no-pending/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-ondestroy-ordering/A.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-ondestroy-ordering/B.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-ondestroy-ordering/C.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-ondestroy-ordering/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-ondestroy-ordering/destroyed.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-ondestroy-ordering/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-context-throws-after-await/_config.js create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-context-throws-after-await/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-each-fallback-hoisting/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-each-fallback-hoisting/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-each-hoisting/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-each-hoisting/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-head-multiple-title-order-preserved/A.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-head-multiple-title-order-preserved/B.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-head-multiple-title-order-preserved/_config.js create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-head-multiple-title-order-preserved/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-head-multiple-title-order-preserved/_expected_head.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-head-multiple-title-order-preserved/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-option-implicit-complex-value/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-option-implicit-complex-value/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-option-implicit-simple-value/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-option-implicit-simple-value/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-select-value-component/Option.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-select-value-component/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-select-value-component/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value-complex/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value-complex/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-select-value-implicit-value/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/context/Child.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/context/_config.js create mode 100644 packages/svelte/tests/server-side-rendering/samples/context/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/context/main.svelte create mode 100644 packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/index.svelte create mode 100644 packages/svelte/tests/snapshot/samples/async-each-hoisting/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-hoisting/index.svelte create mode 100644 packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/index.svelte create mode 100644 packages/svelte/tests/snapshot/samples/async-if-hoisting/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-hoisting/index.svelte diff --git a/.changeset/forty-insects-cheat.md b/.changeset/forty-insects-cheat.md new file mode 100644 index 0000000000..993a8fdb24 --- /dev/null +++ b/.changeset/forty-insects-cheat.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: experimental async SSR diff --git a/.prettierignore b/.prettierignore index 5e1d9b1aa7..9cf9a4bfe1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,6 +15,7 @@ packages/svelte/src/internal/client/warnings.js packages/svelte/src/internal/shared/errors.js packages/svelte/src/internal/shared/warnings.js packages/svelte/src/internal/server/errors.js +packages/svelte/src/internal/server/warnings.js packages/svelte/tests/migrate/samples/*/output.svelte packages/svelte/tests/**/*.svelte packages/svelte/tests/**/_expected* diff --git a/documentation/docs/98-reference/.generated/server-errors.md b/documentation/docs/98-reference/.generated/server-errors.md index c3e8b53c31..6263032212 100644 --- a/documentation/docs/98-reference/.generated/server-errors.md +++ b/documentation/docs/98-reference/.generated/server-errors.md @@ -1,5 +1,19 @@ +### await_invalid + +``` +Encountered asynchronous work while rendering synchronously. +``` + +You (or the framework you're using) called [`render(...)`](svelte-server#render) with a component containing an `await` expression. Either `await` the result of `render` or wrap the `await` (or the component containing it) in a [``](svelte-boundary) with a `pending` snippet. + +### html_deprecated + +``` +The `html` property of server render results has been deprecated. Use `body` instead. +``` + ### lifecycle_function_unavailable ``` diff --git a/documentation/docs/98-reference/.generated/server-warnings.md b/documentation/docs/98-reference/.generated/server-warnings.md new file mode 100644 index 0000000000..26b3628be9 --- /dev/null +++ b/documentation/docs/98-reference/.generated/server-warnings.md @@ -0,0 +1,9 @@ + + +### experimental_async_ssr + +``` +Attempted to use asynchronous rendering without `experimental.async` enabled +``` + +Set `experimental.async: true` in your compiler options (usually in `svelte.config.js`) to use async server rendering. This render ran synchronously. diff --git a/documentation/docs/98-reference/.generated/shared-errors.md b/documentation/docs/98-reference/.generated/shared-errors.md index de34b3f5da..6c31aaafd0 100644 --- a/documentation/docs/98-reference/.generated/shared-errors.md +++ b/documentation/docs/98-reference/.generated/shared-errors.md @@ -1,25 +1,5 @@ -### await_outside_boundary - -``` -Cannot await outside a `` with a `pending` snippet -``` - -The `await` keyword can only appear in a `$derived(...)` or template expression, or at the top level of a component's ``); -} - -export function reset_elements() { - let old_parent = parent; - parent = null; - return () => { - parent = old_parent; - }; + payload.child( + (payload) => payload.push(``), + 'head' + ); } /** @@ -58,10 +52,12 @@ export function reset_elements() { * @param {number} column */ export function push_element(payload, tag, line, column) { - var filename = /** @type {Component} */ (current_component).function[FILENAME]; - var child = { tag, parent, filename, line, column }; + var context = /** @type {SSRContext} */ (ssr_context); + var filename = context.function[FILENAME]; + var parent = context.element; + var element = { tag, parent, filename, line, column }; - if (parent !== null) { + if (parent !== undefined) { var ancestor = parent.parent; var ancestors = [parent.tag]; @@ -86,11 +82,11 @@ export function push_element(payload, tag, line, column) { } } - parent = child; + set_ssr_context({ ...context, p: context, element }); } export function pop_element() { - parent = /** @type {Element} */ (parent).parent; + set_ssr_context(/** @type {SSRContext} */ (ssr_context).p); } /** @@ -100,7 +96,7 @@ export function validate_snippet_args(payload) { if ( typeof payload !== 'object' || // for some reason typescript consider the type of payload as never after the first instanceof - !(payload instanceof Payload || /** @type {any} */ (payload) instanceof HeadPayload) + !(payload instanceof Payload) ) { e.invalid_snippet_arguments(); } diff --git a/packages/svelte/src/internal/server/errors.js b/packages/svelte/src/internal/server/errors.js index 458937218f..bde49fe935 100644 --- a/packages/svelte/src/internal/server/errors.js +++ b/packages/svelte/src/internal/server/errors.js @@ -2,6 +2,30 @@ export * from '../shared/errors.js'; +/** + * Encountered asynchronous work while rendering synchronously. + * @returns {never} + */ +export function await_invalid() { + const error = new Error(`await_invalid\nEncountered asynchronous work while rendering synchronously.\nhttps://svelte.dev/e/await_invalid`); + + error.name = 'Svelte error'; + + throw error; +} + +/** + * The `html` property of server render results has been deprecated. Use `body` instead. + * @returns {never} + */ +export function html_deprecated() { + const error = new Error(`html_deprecated\nThe \`html\` property of server render results has been deprecated. Use \`body\` instead.\nhttps://svelte.dev/e/html_deprecated`); + + error.name = 'Svelte error'; + + throw error; +} + /** * `%name%(...)` is not available on the server * @param {string} name diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 3aa44f2daa..a2cf222da6 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -1,6 +1,7 @@ -/** @import { ComponentType, SvelteComponent } from 'svelte' */ -/** @import { Component, RenderOutput } from '#server' */ +/** @import { ComponentType, SvelteComponent, Component } from 'svelte' */ +/** @import { RenderOutput, SSRContext } from '#server' */ /** @import { Store } from '#shared' */ +/** @import { AccumulatedContent } from './payload.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'; @@ -13,13 +14,14 @@ import { } from '../../constants.js'; import { escape_html } from '../../escaping.js'; import { DEV } from 'esm-env'; -import { current_component, pop, push } from './context.js'; +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 { reset_elements } from './dev.js'; -import { Payload } from './payload.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'; // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // https://infra.spec.whatwg.org/#noncharacter @@ -34,102 +36,48 @@ const INVALID_ATTR_NAME_CHAR_REGEX = * @returns {void} */ export function element(payload, tag, attributes_fn = noop, children_fn = noop) { - payload.out.push(''); + payload.push(''); if (tag) { - payload.out.push(`<${tag}`); + payload.push(`<${tag}`); attributes_fn(); - payload.out.push(`>`); + payload.push(`>`); if (!is_void(tag)) { children_fn(); if (!is_raw_text_element(tag)) { - payload.out.push(EMPTY_COMMENT); + payload.push(EMPTY_COMMENT); } - payload.out.push(``); + payload.push(``); } } - payload.out.push(''); + payload.push(''); } -/** - * Array of `onDestroy` callbacks that should be called at the end of the server render function - * @type {Function[]} - */ -export let on_destroy = []; - /** * Only available on the server and when compiling with the `server` option. * Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app. * @template {Record} Props - * @param {import('svelte').Component | ComponentType>} component + * @param {Component | ComponentType>} component * @param {{ props?: Omit; context?: Map; idPrefix?: string }} [options] * @returns {RenderOutput} */ export function render(component, options = {}) { - try { - const payload = new Payload(options.idPrefix ? options.idPrefix + '-' : ''); - - const prev_on_destroy = on_destroy; - on_destroy = []; - payload.out.push(BLOCK_OPEN); - - let reset_reset_element; - - if (DEV) { - // prevent parent/child element state being corrupted by a bad render - reset_reset_element = reset_elements(); - } - - if (options.context) { - push(); - /** @type {Component} */ (current_component).c = options.context; - } - - // @ts-expect-error - component(payload, options.props ?? {}, {}, {}); - - if (options.context) { - pop(); - } - - if (reset_reset_element) { - reset_reset_element(); - } - - payload.out.push(BLOCK_CLOSE); - for (const cleanup of on_destroy) cleanup(); - on_destroy = prev_on_destroy; - - let head = payload.head.out.join('') + payload.head.title; - - for (const { hash, code } of payload.css) { - head += ``; - } - - const body = payload.out.join(''); - - return { - head, - html: body, - body: body - }; - } finally { - abort(); - } + return Payload.render(/** @type {Component} */ (component), options); } /** * @param {Payload} payload - * @param {(head_payload: Payload['head']) => void} fn + * @param {(payload: Payload) => Promise | void} fn * @returns {void} */ export function head(payload, fn) { - const head_payload = payload.head; - head_payload.out.push(BLOCK_OPEN); - fn(head_payload); - head_payload.out.push(BLOCK_CLOSE); + payload.child((payload) => { + payload.push(BLOCK_OPEN); + payload.child(fn); + payload.push(BLOCK_CLOSE); + }, 'head'); } /** @@ -144,21 +92,21 @@ export function css_props(payload, is_html, props, component, dynamic = false) { const styles = style_object_to_string(props); if (is_html) { - payload.out.push(``); + payload.push(``); } else { - payload.out.push(``); + payload.push(``); } if (dynamic) { - payload.out.push(''); + payload.push(''); } component(); if (is_html) { - payload.out.push(``); + payload.push(``); } else { - payload.out.push(``); + payload.push(``); } } @@ -451,13 +399,13 @@ export function bind_props(props_parent, props_now) { */ function await_block(payload, promise, pending_fn, then_fn) { if (is_promise(promise)) { - payload.out.push(BLOCK_OPEN); + payload.push(BLOCK_OPEN); promise.then(null, noop); if (pending_fn !== null) { pending_fn(); } } else if (then_fn !== null) { - payload.out.push(BLOCK_OPEN_ELSE); + payload.push(BLOCK_OPEN_ELSE); then_fn(promise); } } @@ -503,8 +451,8 @@ export function once(get_value) { * @returns {string} */ export function props_id(payload) { - const uid = payload.uid(); - payload.out.push(''); + const uid = payload.global.uid(); + payload.push(''); return uid; } @@ -512,12 +460,10 @@ export { attr, clsx }; export { html } from './blocks/html.js'; -export { push, pop } from './context.js'; +export { save } from './context.js'; export { push_element, pop_element, validate_snippet_args } from './dev.js'; -export { assign_payload, copy_payload } from './payload.js'; - export { snapshot } from '../shared/clone.js'; export { fallback, to_array } from '../shared/utils.js'; @@ -531,8 +477,6 @@ export { export { escape_html as escape }; -export { await_outside_boundary } from '../shared/errors.js'; - /** * @template T * @param {()=>T} fn @@ -557,29 +501,117 @@ export function derived(fn) { /** * * @param {Payload} payload - * @param {*} value + * @param {unknown} value */ export function maybe_selected(payload, value) { - return value === payload.select_value ? ' selected' : ''; + return value === payload.local.select_value ? ' selected' : ''; } /** + * When an `option` element has no `value` attribute, we need to treat the child + * 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 {() => void} children + * @param {((payload: Payload) => void | Promise)} children * @returns {void} */ export function valueless_option(payload, children) { - var i = payload.out.length; + const i = payload.length; + + // prior to children, `payload` has some combination of string/unresolved payload that ends in `