From ef4b97dfabfd7a23b27933e18f7393587c343d66 Mon Sep 17 00:00:00 2001 From: quyentonndbs Date: Thu, 14 May 2026 07:31:42 -0500 Subject: [PATCH 1/9] fix: duplicated "of" in events.js comment (#18217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One-line typo fix in `packages/svelte/src/internal/client/dom/elements/events.js`: "removal or moving of of the DOM" → "removal or moving of the DOM". No code/behavior change. --------- Co-authored-by: Kai Tanaka <275430420+quyentonndbs@users.noreply.github.com> Co-authored-by: Rich Harris --- packages/svelte/src/internal/client/dom/elements/events.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index e598a78949..5aa41e1c4d 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -237,9 +237,9 @@ export function handle_event_propagation(event) { }); // This started because of Chromium issue https://chromestatus.com/feature/5128696823545856, - // where removal or moving of of the DOM can cause sync `blur` events to fire, which can cause logic + // where removal or moving of the DOM can cause sync `blur` events to fire, which can cause logic // to run inside the current `active_reaction`, which isn't what we want at all. However, on reflection, - // it's probably best that all event handled by Svelte have this behaviour, as we don't really want + // it's probably best that all events handled by Svelte have this behaviour, as we don't really want // an event handler to run in the context of another reaction or effect. var previous_reaction = active_reaction; var previous_effect = active_effect; From a10e8e47a5946623a60a1e36b9023c23926eae87 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 14 May 2026 13:31:31 -0400 Subject: [PATCH 2/9] fix: keep dependencies of `$state.eager`/`pending` (alternative approach) (#18218) #18108, with two differences: - we use a global map - we use the parent reaction as the key, rather than traversing upwards for a branch I think this has the same outcome? ### Before submitting the PR, please make sure you do the following - [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`. - [x] This message body should clearly illustrate what problems it solves. - [x] Ideally, include a test that fails without this PR but passes with it. - [x] If this PR changes code within `packages/svelte/src`, add a changeset (`npx changeset`). ### Tests and linting - [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` --------- Co-authored-by: Simon Holthausen Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/breezy-laws-train.md | 5 +++ .../src/internal/client/reactivity/batch.js | 22 +++++++++++-- .../samples/async-eager-block/_config.js | 23 +++++++++++++ .../samples/async-eager-block/main.svelte | 20 ++++++++++++ .../samples/async-eager-each-block/_config.js | 32 +++++++++++++++++++ .../async-eager-each-block/main.svelte | 24 ++++++++++++++ 6 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 .changeset/breezy-laws-train.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-eager-block/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-eager-block/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-eager-each-block/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-eager-each-block/main.svelte diff --git a/.changeset/breezy-laws-train.md b/.changeset/breezy-laws-train.md new file mode 100644 index 0000000000..5e88998a0a --- /dev/null +++ b/.changeset/breezy-laws-train.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: keep dependencies of `$state.eager/pending` diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index d822834324..a5c9a51eec 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -16,7 +16,8 @@ import { EAGER_EFFECT, ERROR_VALUE, MANAGED_EFFECT, - REACTION_RAN + REACTION_RAN, + DESTROYING } from '#client/constants'; import { async_mode_flag } from '../../flags/index.js'; import { deferred, define_property, includes } from '../../shared/utils.js'; @@ -33,7 +34,7 @@ import { flush_tasks, queue_micro_task } from '../dom/task.js'; import { DEV } from 'esm-env'; import { invoke_error_boundary } from '../error-handling.js'; import { flush_eager_effects, old_values, set_eager_effects, source, update } from './sources.js'; -import { eager_effect, unlink_effect } from './effects.js'; +import { eager_effect, teardown, unlink_effect } from './effects.js'; import { defer_effect } from './utils.js'; import { UNINITIALIZED } from '../../../constants.js'; import { set_signal_status } from './status.js'; @@ -1234,6 +1235,9 @@ function eager_flush() { }); } +/** @type {Map>} */ +var version_map = new Map(); + /** * Implementation of `$state.eager(fn())` * @template T @@ -1241,10 +1245,22 @@ function eager_flush() { * @returns {T} */ export function eager(fn) { - var version = source(0); var initial = true; var value = /** @type {T} */ (undefined); + if (active_reaction === null) { + return fn(); + } + + let parent = active_reaction; + + let version = version_map.get(parent) ?? source(0); + version_map.set(parent, version); + + teardown(() => { + if (parent.f & DESTROYING) version_map.delete(parent); + }); + get(version); eager_effect(() => { diff --git a/packages/svelte/tests/runtime-runes/samples/async-eager-block/_config.js b/packages/svelte/tests/runtime-runes/samples/async-eager-block/_config.js new file mode 100644 index 0000000000..b6d283c8a5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-eager-block/_config.js @@ -0,0 +1,23 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await tick(); + const [increment, resolve] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ' 0

loading...

' + ); + + resolve.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ' 1

1

' + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-eager-block/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-eager-block/main.svelte new file mode 100644 index 0000000000..61f4705d06 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-eager-block/main.svelte @@ -0,0 +1,20 @@ + + + + + +{await delay(count)} +{#if $state.eager(count) !== count} +

loading...

+{:else} +

{count}

+{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/async-eager-each-block/_config.js b/packages/svelte/tests/runtime-runes/samples/async-eager-each-block/_config.js new file mode 100644 index 0000000000..a4921a5115 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-eager-each-block/_config.js @@ -0,0 +1,32 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + await tick(); + const [increment, resolve] = target.querySelectorAll('button'); + logs.length = 0; + + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` +
  • 0 / 0
  • 0 / loading...
  • 0 / 0
` + ); + + resolve.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` +
  • 0 / 0
  • 1 / 1
  • 0 / 0
` + ); + + assert.equal( + logs.some((l) => l.toString().includes('0 ') || l.toString().includes('2')), + false, + 'only the second $state.eager should have been evaluated' + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-eager-each-block/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-eager-each-block/main.svelte new file mode 100644 index 0000000000..f27e256e9f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-eager-each-block/main.svelte @@ -0,0 +1,24 @@ + + + + + +
    + {#each counts as count, i} +
  • + {await delay(count)} / + {#if console.log(i) || $state.eager(count) !== count} + loading... + {:else} + {count} + {/if} +
  • + {/each} +
From 55f9c85c09d625c3dd80c71ce7542f57386fafb4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 14:03:20 -0400 Subject: [PATCH 3/9] Version Packages (#18158) This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## svelte@5.55.6 ### Patch Changes - fix: leave stale promises to wait for a later resolution, instead of rejecting ([#18180](https://github.com/sveltejs/svelte/pull/18180)) - fix: keep dependencies of `$state.eager/pending` ([#18218](https://github.com/sveltejs/svelte/pull/18218)) - fix: reapply context after transforming error during SSR ([#18099](https://github.com/sveltejs/svelte/pull/18099)) - fix: don't rebase just-created batches ([#18117](https://github.com/sveltejs/svelte/pull/18117)) - chore: allow `null` for `pending` in typings ([#18201](https://github.com/sveltejs/svelte/pull/18201)) - fix: flush eager effects in production ([#18107](https://github.com/sveltejs/svelte/pull/18107)) - fix: rethrow error of failed iterable after calling `return()` ([#18169](https://github.com/sveltejs/svelte/pull/18169)) - fix: account for proxified instance when updating `bind:this` ([#18147](https://github.com/sveltejs/svelte/pull/18147)) - fix: ensure scheduled batch is flushed if not obsolete ([#18131](https://github.com/sveltejs/svelte/pull/18131)) - fix: resolve stale deriveds with latest value ([#18167](https://github.com/sveltejs/svelte/pull/18167)) - chore: remove unnecessary `increment_pending` calls ([#18183](https://github.com/sveltejs/svelte/pull/18183)) - fix: correctly compile component member expressions for SSR ([#18192](https://github.com/sveltejs/svelte/pull/18192)) - fix: reset `source.updated` stack traces after `flush` ([#18196](https://github.com/sveltejs/svelte/pull/18196)) - fix: replacing async 'blocking' strategy with 'merging' ([#18205](https://github.com/sveltejs/svelte/pull/18205)) - fix: allow `@debug` tags to reference awaited variables ([#18138](https://github.com/sveltejs/svelte/pull/18138)) - fix: re-run fallback props if dependencies update ([#18146](https://github.com/sveltejs/svelte/pull/18146)) - fix: abort running obsolete async branches ([#18118](https://github.com/sveltejs/svelte/pull/18118)) - fix: ignore comments when reading CSS values ([#18153](https://github.com/sveltejs/svelte/pull/18153)) - fix: wrap `Promise.all` in `save` during SSR ([#18178](https://github.com/sveltejs/svelte/pull/18178)) - fix: ignore false-positive errors of `$inspect` dependencies ([#18106](https://github.com/sveltejs/svelte/pull/18106)) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/big-webs-sing.md | 5 ---- .changeset/breezy-laws-train.md | 5 ---- .changeset/cruel-boxes-serve.md | 5 ---- .changeset/easy-singers-retire.md | 5 ---- .changeset/fine-bushes-marry.md | 5 ---- .changeset/flat-shrimps-worry.md | 5 ---- .changeset/fresh-stars-grin.md | 5 ---- .changeset/full-waves-tease.md | 5 ---- .changeset/many-pandas-add.md | 5 ---- .changeset/modern-tables-fetch.md | 5 ---- .changeset/public-mammals-float.md | 5 ---- .changeset/quiet-teams-pick.md | 5 ---- .changeset/red-crabs-ring.md | 5 ---- .changeset/shiny-squids-whisper.md | 5 ---- .changeset/small-tools-walk.md | 5 ---- .changeset/smooth-poems-tap.md | 5 ---- .changeset/stupid-baboons-fall.md | 5 ---- .changeset/three-pears-build.md | 5 ---- .changeset/tough-knives-smell.md | 5 ---- .changeset/twelve-cooks-speak.md | 5 ---- packages/svelte/CHANGELOG.md | 44 ++++++++++++++++++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 23 files changed, 46 insertions(+), 102 deletions(-) delete mode 100644 .changeset/big-webs-sing.md delete mode 100644 .changeset/breezy-laws-train.md delete mode 100644 .changeset/cruel-boxes-serve.md delete mode 100644 .changeset/easy-singers-retire.md delete mode 100644 .changeset/fine-bushes-marry.md delete mode 100644 .changeset/flat-shrimps-worry.md delete mode 100644 .changeset/fresh-stars-grin.md delete mode 100644 .changeset/full-waves-tease.md delete mode 100644 .changeset/many-pandas-add.md delete mode 100644 .changeset/modern-tables-fetch.md delete mode 100644 .changeset/public-mammals-float.md delete mode 100644 .changeset/quiet-teams-pick.md delete mode 100644 .changeset/red-crabs-ring.md delete mode 100644 .changeset/shiny-squids-whisper.md delete mode 100644 .changeset/small-tools-walk.md delete mode 100644 .changeset/smooth-poems-tap.md delete mode 100644 .changeset/stupid-baboons-fall.md delete mode 100644 .changeset/three-pears-build.md delete mode 100644 .changeset/tough-knives-smell.md delete mode 100644 .changeset/twelve-cooks-speak.md diff --git a/.changeset/big-webs-sing.md b/.changeset/big-webs-sing.md deleted file mode 100644 index 946a41d881..0000000000 --- a/.changeset/big-webs-sing.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: leave stale promises to wait for a later resolution, instead of rejecting diff --git a/.changeset/breezy-laws-train.md b/.changeset/breezy-laws-train.md deleted file mode 100644 index 5e88998a0a..0000000000 --- a/.changeset/breezy-laws-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: keep dependencies of `$state.eager/pending` diff --git a/.changeset/cruel-boxes-serve.md b/.changeset/cruel-boxes-serve.md deleted file mode 100644 index 592cec4d01..0000000000 --- a/.changeset/cruel-boxes-serve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: reapply context after transforming error during SSR diff --git a/.changeset/easy-singers-retire.md b/.changeset/easy-singers-retire.md deleted file mode 100644 index 4420286e13..0000000000 --- a/.changeset/easy-singers-retire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't rebase just-created batches diff --git a/.changeset/fine-bushes-marry.md b/.changeset/fine-bushes-marry.md deleted file mode 100644 index ccba53babe..0000000000 --- a/.changeset/fine-bushes-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: allow `null` for `pending` in typings diff --git a/.changeset/flat-shrimps-worry.md b/.changeset/flat-shrimps-worry.md deleted file mode 100644 index a5f76a0f9d..0000000000 --- a/.changeset/flat-shrimps-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: flush eager effects in production diff --git a/.changeset/fresh-stars-grin.md b/.changeset/fresh-stars-grin.md deleted file mode 100644 index 3d56792d1e..0000000000 --- a/.changeset/fresh-stars-grin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: rethrow error of failed iterable after calling `return()` diff --git a/.changeset/full-waves-tease.md b/.changeset/full-waves-tease.md deleted file mode 100644 index 3915334bf7..0000000000 --- a/.changeset/full-waves-tease.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: account for proxified instance when updating `bind:this` diff --git a/.changeset/many-pandas-add.md b/.changeset/many-pandas-add.md deleted file mode 100644 index 85de7acb35..0000000000 --- a/.changeset/many-pandas-add.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure scheduled batch is flushed if not obsolete diff --git a/.changeset/modern-tables-fetch.md b/.changeset/modern-tables-fetch.md deleted file mode 100644 index 89543910fa..0000000000 --- a/.changeset/modern-tables-fetch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: resolve stale deriveds with latest value diff --git a/.changeset/public-mammals-float.md b/.changeset/public-mammals-float.md deleted file mode 100644 index d890c9e070..0000000000 --- a/.changeset/public-mammals-float.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: remove unnecessary `increment_pending` calls diff --git a/.changeset/quiet-teams-pick.md b/.changeset/quiet-teams-pick.md deleted file mode 100644 index ed046168be..0000000000 --- a/.changeset/quiet-teams-pick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: correctly compile component member expressions for SSR diff --git a/.changeset/red-crabs-ring.md b/.changeset/red-crabs-ring.md deleted file mode 100644 index 82b53c5464..0000000000 --- a/.changeset/red-crabs-ring.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: reset `source.updated` stack traces after `flush` diff --git a/.changeset/shiny-squids-whisper.md b/.changeset/shiny-squids-whisper.md deleted file mode 100644 index a8d2d7378c..0000000000 --- a/.changeset/shiny-squids-whisper.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"svelte": patch ---- - -fix: replacing async 'blocking' strategy with 'merging' diff --git a/.changeset/small-tools-walk.md b/.changeset/small-tools-walk.md deleted file mode 100644 index 2b275368b3..0000000000 --- a/.changeset/small-tools-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: allow `@debug` tags to reference awaited variables diff --git a/.changeset/smooth-poems-tap.md b/.changeset/smooth-poems-tap.md deleted file mode 100644 index ac160656cf..0000000000 --- a/.changeset/smooth-poems-tap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: re-run fallback props if dependencies update diff --git a/.changeset/stupid-baboons-fall.md b/.changeset/stupid-baboons-fall.md deleted file mode 100644 index 66895ad015..0000000000 --- a/.changeset/stupid-baboons-fall.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: abort running obsolete async branches diff --git a/.changeset/three-pears-build.md b/.changeset/three-pears-build.md deleted file mode 100644 index 8a638149a3..0000000000 --- a/.changeset/three-pears-build.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ignore comments when reading CSS values diff --git a/.changeset/tough-knives-smell.md b/.changeset/tough-knives-smell.md deleted file mode 100644 index 7687188c1a..0000000000 --- a/.changeset/tough-knives-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: wrap `Promise.all` in `save` during SSR diff --git a/.changeset/twelve-cooks-speak.md b/.changeset/twelve-cooks-speak.md deleted file mode 100644 index d4fcd5c339..0000000000 --- a/.changeset/twelve-cooks-speak.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ignore false-positive errors of `$inspect` dependencies diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 8e0f8a0916..e5ba5737c4 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,49 @@ # svelte +## 5.55.6 + +### Patch Changes + +- fix: leave stale promises to wait for a later resolution, instead of rejecting ([#18180](https://github.com/sveltejs/svelte/pull/18180)) + +- fix: keep dependencies of `$state.eager/pending` ([#18218](https://github.com/sveltejs/svelte/pull/18218)) + +- fix: reapply context after transforming error during SSR ([#18099](https://github.com/sveltejs/svelte/pull/18099)) + +- fix: don't rebase just-created batches ([#18117](https://github.com/sveltejs/svelte/pull/18117)) + +- chore: allow `null` for `pending` in typings ([#18201](https://github.com/sveltejs/svelte/pull/18201)) + +- fix: flush eager effects in production ([#18107](https://github.com/sveltejs/svelte/pull/18107)) + +- fix: rethrow error of failed iterable after calling `return()` ([#18169](https://github.com/sveltejs/svelte/pull/18169)) + +- fix: account for proxified instance when updating `bind:this` ([#18147](https://github.com/sveltejs/svelte/pull/18147)) + +- fix: ensure scheduled batch is flushed if not obsolete ([#18131](https://github.com/sveltejs/svelte/pull/18131)) + +- fix: resolve stale deriveds with latest value ([#18167](https://github.com/sveltejs/svelte/pull/18167)) + +- chore: remove unnecessary `increment_pending` calls ([#18183](https://github.com/sveltejs/svelte/pull/18183)) + +- fix: correctly compile component member expressions for SSR ([#18192](https://github.com/sveltejs/svelte/pull/18192)) + +- fix: reset `source.updated` stack traces after `flush` ([#18196](https://github.com/sveltejs/svelte/pull/18196)) + +- fix: replacing async 'blocking' strategy with 'merging' ([#18205](https://github.com/sveltejs/svelte/pull/18205)) + +- fix: allow `@debug` tags to reference awaited variables ([#18138](https://github.com/sveltejs/svelte/pull/18138)) + +- fix: re-run fallback props if dependencies update ([#18146](https://github.com/sveltejs/svelte/pull/18146)) + +- fix: abort running obsolete async branches ([#18118](https://github.com/sveltejs/svelte/pull/18118)) + +- fix: ignore comments when reading CSS values ([#18153](https://github.com/sveltejs/svelte/pull/18153)) + +- fix: wrap `Promise.all` in `save` during SSR ([#18178](https://github.com/sveltejs/svelte/pull/18178)) + +- fix: ignore false-positive errors of `$inspect` dependencies ([#18106](https://github.com/sveltejs/svelte/pull/18106)) + ## 5.55.5 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index a4ae208913..51aed56a3a 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.55.5", + "version": "5.55.6", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 04b0b0398a..b11e7635c7 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.55.5'; +export const VERSION = '5.55.6'; export const PUBLIC_VERSION = '5'; From 547853e2406a2147ad7fb5ffeba95b01bd9642da Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 14 May 2026 20:51:28 +0200 Subject: [PATCH 4/9] Merge commit from fork * fix: disallow empty attribute names during SSR * Update packages/svelte/tests/server-side-rendering/samples/spread-attributes-event-handler-xss/main.svelte Co-authored-by: Conduitry --------- Co-authored-by: Elliott Johnson Co-authored-by: Conduitry --- .changeset/famous-webs-flash.md | 5 +++++ packages/svelte/src/internal/server/index.js | 2 +- .../spread-attributes-event-handler-xss/main.svelte | 7 ++++++- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .changeset/famous-webs-flash.md diff --git a/.changeset/famous-webs-flash.md b/.changeset/famous-webs-flash.md new file mode 100644 index 0000000000..75a01f689a --- /dev/null +++ b/.changeset/famous-webs-flash.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: disallow empty attribute names during SSR diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 34d0133a31..12f76f188e 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -151,7 +151,7 @@ export function attributes(attrs, css_hash, classes, styles, flags = 0) { // omit functions, internal svelte properties and invalid attribute names if (typeof attrs[name] === 'function') continue; if (name[0] === '$' && name[1] === '$') continue; // faster than name.startsWith('$$') - if (INVALID_ATTR_NAME_CHAR_REGEX.test(name)) continue; + if (name === '' || INVALID_ATTR_NAME_CHAR_REGEX.test(name)) continue; var value = attrs[name]; var lower = name.toLowerCase(); diff --git a/packages/svelte/tests/server-side-rendering/samples/spread-attributes-event-handler-xss/main.svelte b/packages/svelte/tests/server-side-rendering/samples/spread-attributes-event-handler-xss/main.svelte index 2a8c17f048..e47a19408e 100644 --- a/packages/svelte/tests/server-side-rendering/samples/spread-attributes-event-handler-xss/main.svelte +++ b/packages/svelte/tests/server-side-rendering/samples/spread-attributes-event-handler-xss/main.svelte @@ -5,7 +5,12 @@ onclick: 'alert(1)', onerror: 'alert(1)', onfocus: 'alert(1)', - onmouseover: 'alert(1)' + onmouseover: 'alert(1)', + ' onload': 'alert(1)', + '\tonload': 'alert(1)', + '\u00a0onload': 'alert(1)', + "\t": "/onmouseover=alert(1)//", + "": "/onmouseover=alert(1)//" }; From d2375e2ebcab5c88feb5652f1a9d621b8f06b259 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 14 May 2026 20:51:56 +0200 Subject: [PATCH 5/9] Merge commit from fork --- .changeset/proud-dogs-go.md | 5 ++++ packages/svelte/src/utils.js | 2 +- packages/svelte/src/utils.test.ts | 50 +++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 .changeset/proud-dogs-go.md create mode 100644 packages/svelte/src/utils.test.ts diff --git a/.changeset/proud-dogs-go.md b/.changeset/proud-dogs-go.md new file mode 100644 index 0000000000..c728466138 --- /dev/null +++ b/.changeset/proud-dogs-go.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: harden regex diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index 57561e6dc7..54757a6f13 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -491,7 +491,7 @@ export function is_raw_text_element(name) { // Rejects strings containing whitespace, quotes, angle brackets, slashes, equals, // or other characters that could break out of a tag-name token and enable markup injection. export const REGEX_VALID_TAG_NAME = - /^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9.\-_\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}]+)*$/u; + /^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9.\-_\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}]*)?$/u; /** * Prevent devtools trying to make `location` a clickable link by inserting a zero-width space diff --git a/packages/svelte/src/utils.test.ts b/packages/svelte/src/utils.test.ts new file mode 100644 index 0000000000..02175ae93f --- /dev/null +++ b/packages/svelte/src/utils.test.ts @@ -0,0 +1,50 @@ +import { expect, test } from 'vitest'; +import { REGEX_VALID_TAG_NAME } from './utils'; + +test('REGEX_VALID_TAG_NAME accepts common HTML tag names', () => { + const common_html_tag_names = ['div', 'span', 'button', 'input', 'svg', 'math', 'a']; + + for (const tag_name of common_html_tag_names) { + expect(REGEX_VALID_TAG_NAME.test(tag_name)).toBe(true); + } +}); + +test('REGEX_VALID_TAG_NAME accepts basic custom element names', () => { + const valid_custom_tag_names = ['my-element', 'x-foo', 'todo-item', 'my-element2']; + + for (const tag_name of valid_custom_tag_names) { + expect(REGEX_VALID_TAG_NAME.test(tag_name)).toBe(true); + } +}); + +test('REGEX_VALID_TAG_NAME accepts spec-allowed custom element characters', () => { + const valid_custom_tag_names = [ + 'x-foo.bar', + 'x-foo_bar', + 'x-foo\u00B7bar', + 'x-foo\u00FCbar', + 'x-foo\u{1F600}bar', + 'x-' + ]; + + for (const tag_name of valid_custom_tag_names) { + expect(REGEX_VALID_TAG_NAME.test(tag_name)).toBe(true); + } +}); + +test('REGEX_VALID_TAG_NAME rejects invalid tag names', () => { + const invalid_tag_names = ['', '1', 'x\u00FC', '-x-foo', '1foo', 'x-foo bar', 'x-foo/', 'x-foo>']; + + for (const tag_name of invalid_tag_names) { + expect(REGEX_VALID_TAG_NAME.test(tag_name)).toBe(false); + } +}); + +test('REGEX_VALID_TAG_NAME no ReDoS', () => { + const before = performance.now(); + REGEX_VALID_TAG_NAME.test('a-----------------------------------!'); + const after = performance.now(); + if (after - before > 10) { + throw new Error(`REGEX_VALID_TAG_NAME is vulnerable to ReDoS`); + } +}); From a16ebc67bbcf8f708360195687e1b2719463e1a4 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Thu, 14 May 2026 12:52:15 -0600 Subject: [PATCH 6/9] Merge commit from fork https://github.com/sveltejs/svelte/security/advisories/GHSA-f3cj-j4f6-wq85 Co-authored-by: Simon Holthausen --- .changeset/blue-lions-call.md | 5 +++ .../svelte/src/internal/server/hydratable.js | 9 +++-- .../src/internal/server/hydratable.test.ts | 33 +++++++++++++++++++ playgrounds/sandbox/ssr-dev.js | 5 +-- playgrounds/sandbox/ssr-prod.js | 5 +-- 5 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 .changeset/blue-lions-call.md create mode 100644 packages/svelte/src/internal/server/hydratable.test.ts diff --git a/.changeset/blue-lions-call.md b/.changeset/blue-lions-call.md new file mode 100644 index 0000000000..f9ddb88505 --- /dev/null +++ b/.changeset/blue-lions-call.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent XSS on `hydratable` from user contents diff --git a/packages/svelte/src/internal/server/hydratable.js b/packages/svelte/src/internal/server/hydratable.js index 81f59ab2fd..be75288604 100644 --- a/packages/svelte/src/internal/server/hydratable.js +++ b/packages/svelte/src/internal/server/hydratable.js @@ -3,7 +3,6 @@ import { async_mode_flag } from '../flags/index.js'; import { get_render_context } from './render-context.js'; import * as e from './errors.js'; import * as devalue from 'devalue'; -import { get_stack } from '../shared/dev.js'; import { DEV } from 'esm-env'; import { get_user_code_location } from './dev.js'; @@ -65,7 +64,13 @@ function encode(key, value, unresolved) { const placeholder = `"${uid++}"`; const p = value .then((v) => { - entry.serialized = entry.serialized.replace(placeholder, `r(${uneval(v)})`); + entry.serialized = entry.serialized.replace( + placeholder, + // use the function form here to prevent any string replacement characters from being interpreted + // in `v`, as it's potentially user-controlled and therefore potentially malicious. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_the_replacement + () => `r(${uneval(v)})` + ); }) .catch((devalue_error) => e.hydratable_serialization_failed( diff --git a/packages/svelte/src/internal/server/hydratable.test.ts b/packages/svelte/src/internal/server/hydratable.test.ts new file mode 100644 index 0000000000..8dd54c8f1d --- /dev/null +++ b/packages/svelte/src/internal/server/hydratable.test.ts @@ -0,0 +1,33 @@ +import { afterAll, beforeAll, expect, test } from 'vitest'; +import { Renderer } from './renderer.js'; +import type { Component } from 'svelte'; +import { disable_async_mode_flag, enable_async_mode_flag } from '../flags/index.js'; +import { hydratable } from './hydratable.js'; + +beforeAll(() => { + enable_async_mode_flag(); +}); + +afterAll(() => { + disable_async_mode_flag(); +}); + +test('treats replacement tokens in hydratable promise values as literals', async () => { + const component = (renderer: Renderer) => { + hydratable('key', () => Promise.resolve(`$'`)); + renderer.child(async () => { + await Promise.resolve(); + }); + renderer.push('ok'); + }; + + const { head } = await Renderer.render(component as unknown as Component); + const script_match = head.match(/]*)?>([\s\S]*)<\/script>/); + + expect(script_match, 'expected hydratable script in head output').toBeTruthy(); + + const script_content = script_match![1]; + expect(script_content).toContain('const h = (window.__svelte ??= {}).h ??= new Map();'); + expect(script_content).toContain('r("$\'")'); + expect(script_content).toMatch(/\[\s*"key"\s*,\s*r\("\$'"\)\s*\]/); +}); diff --git a/playgrounds/sandbox/ssr-dev.js b/playgrounds/sandbox/ssr-dev.js index 8a0c063d47..b770848ad1 100644 --- a/playgrounds/sandbox/ssr-dev.js +++ b/playgrounds/sandbox/ssr-dev.js @@ -29,8 +29,9 @@ polka() const { head, body } = await render(App); const html = transformed_template - .replace(``, head) - .replace(``, body) + // use function form to prevent any string replacement characters from being interpreted + .replace(``, () => head) + .replace(``, () => body) // check that Safari doesn't break hydration .replaceAll('+636-555-3226', '+636-555-3226'); diff --git a/playgrounds/sandbox/ssr-prod.js b/playgrounds/sandbox/ssr-prod.js index 0c760188d4..e3ece4d5be 100644 --- a/playgrounds/sandbox/ssr-prod.js +++ b/playgrounds/sandbox/ssr-prod.js @@ -9,8 +9,9 @@ const { head, body } = await render(App); const rendered = fs .readFileSync(path.resolve('./dist/client/index.html'), 'utf-8') - .replace(``, body) - .replace(``, head); + // use function form to prevent any string replacement characters from being interpreted + .replace(``, () => body) + .replace(``, () => head); const types = { '.js': 'application/javascript', From e1cbbd96441e82c9eb8a23a2903c0d06d3cda991 Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Thu, 14 May 2026 12:59:01 -0600 Subject: [PATCH 7/9] Merge commit from fork * fix: move Svelte runtime properties to symbols Prevents DOM clobbering * shorten names --------- Co-authored-by: Simon Holthausen --- .changeset/six-snails-sell.md | 5 ++++ .../svelte/src/internal/client/constants.js | 5 ++++ .../client/dom/elements/attributes.js | 23 +++++++++++++------ .../client/dom/elements/bindings/shared.js | 10 ++++---- .../src/internal/client/dom/elements/class.js | 7 +++--- .../src/internal/client/dom/elements/misc.js | 4 ++-- .../src/internal/client/dom/elements/style.js | 7 +++--- .../src/internal/client/dom/operations.js | 23 ++++++++++--------- packages/svelte/src/internal/client/render.js | 9 ++++---- .../_config.js | 23 +++++++++++++++++++ .../main.svelte | 20 ++++++++++++++++ 11 files changed, 97 insertions(+), 39 deletions(-) create mode 100644 .changeset/six-snails-sell.md create mode 100644 packages/svelte/tests/runtime-browser/samples/dom-clobbering-cache-symbols-spread/_config.js create mode 100644 packages/svelte/tests/runtime-browser/samples/dom-clobbering-cache-symbols-spread/main.svelte diff --git a/.changeset/six-snails-sell.md b/.changeset/six-snails-sell.md new file mode 100644 index 0000000000..6c61546e86 --- /dev/null +++ b/.changeset/six-snails-sell.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: move Svelte runtime properties to symbols diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index f92fba73be..043b50b4b2 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -63,6 +63,11 @@ export const STATE_SYMBOL = Symbol('$state'); export const LEGACY_PROPS = Symbol('legacy props'); export const LOADING_ATTR_SYMBOL = Symbol(''); export const PROXY_PATH_SYMBOL = Symbol('proxy path'); +export const ATTRIBUTES_CACHE = Symbol('attributes'); +export const CLASS_CACHE = Symbol('class'); +export const STYLE_CACHE = Symbol('style'); +export const TEXT_CACHE = Symbol('text'); +export const FORM_RESET_HANDLER = Symbol('form reset'); /** An anchor might change, via this symbol on the original anchor we can tell HMR about the updated anchor */ export const HMR_ANCHOR = Symbol('hmr anchor'); diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 0cec01191a..a193a70dd5 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -5,7 +5,12 @@ import { get_descriptors, get_prototype_of } from '../../../shared/utils.js'; import { create_event, delegate, delegated, event, event_symbol } from './events.js'; import { add_form_reset_listener, autofocus } from './misc.js'; import * as w from '../../warnings.js'; -import { IS_XHTML, LOADING_ATTR_SYMBOL } from '#client/constants'; +import { + ATTRIBUTES_CACHE, + FORM_RESET_HANDLER, + IS_XHTML, + LOADING_ATTR_SYMBOL +} from '#client/constants'; import { queue_micro_task } from '../task.js'; import { is_capture_event, can_delegate_event, normalize_attribute } from '../../../../utils.js'; import { @@ -69,8 +74,7 @@ export function remove_input_defaults(input) { } }; - // @ts-expect-error - input.__on_r = remove_defaults; + /** @type {any} */ (input)[FORM_RESET_HANDLER] = remove_defaults; queue_micro_task(remove_defaults); add_form_reset_listener(); } @@ -561,8 +565,7 @@ export function attribute_effect( */ function get_attributes(element) { return /** @type {Record} **/ ( - // @ts-expect-error - element.__attributes ??= { + /** @type {any} */ (element)[ATTRIBUTES_CACHE] ??= { [IS_CUSTOM_ELEMENT]: element.nodeName.includes('-'), [IS_HTML]: element.namespaceURI === NAMESPACE_HTML } @@ -583,13 +586,19 @@ function get_setters(element) { var proto = element; // In the case of custom elements there might be setters on the instance var element_proto = Element.prototype; - // Stop at Element, from there on there's only unnecessary setters we're not interested in + // Stop at Element, from there on there's only unnecessary (and dangerous, like innerHTML) setters we're not interested in // Do not use constructor.name here as that's unreliable in some browser environments while (element_proto !== proto) { descriptors = get_descriptors(proto); for (var key in descriptors) { - if (descriptors[key].set) { + if ( + descriptors[key].set && + // better safe than sorry, we don't want spread attributes to mess with HTML content + key !== 'innerHTML' && + key !== 'textContent' && + key !== 'innerText' + ) { setters.push(key); } } diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/shared.js b/packages/svelte/src/internal/client/dom/elements/bindings/shared.js index aa083776a5..303890adec 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/shared.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/shared.js @@ -5,6 +5,7 @@ import { set_active_effect, set_active_reaction } from '../../../runtime.js'; +import { FORM_RESET_HANDLER } from '../../../constants.js'; import { add_form_reset_listener } from '../misc.js'; /** @@ -58,18 +59,15 @@ export function without_reactive_context(fn) { */ export function listen_to_event_and_reset_event(element, event, handler, on_reset = handler) { element.addEventListener(event, () => without_reactive_context(handler)); - // @ts-expect-error - const prev = element.__on_r; + const prev = /** @type {any} */ (element)[FORM_RESET_HANDLER]; if (prev) { // special case for checkbox that can have multiple binds (group & checked) - // @ts-expect-error - element.__on_r = () => { + /** @type {any} */ (element)[FORM_RESET_HANDLER] = () => { prev(); on_reset(true); }; } else { - // @ts-expect-error - element.__on_r = () => on_reset(true); + /** @type {any} */ (element)[FORM_RESET_HANDLER] = () => on_reset(true); } add_form_reset_listener(); diff --git a/packages/svelte/src/internal/client/dom/elements/class.js b/packages/svelte/src/internal/client/dom/elements/class.js index 038ce33f3e..6478707095 100644 --- a/packages/svelte/src/internal/client/dom/elements/class.js +++ b/packages/svelte/src/internal/client/dom/elements/class.js @@ -1,4 +1,5 @@ import { to_class } from '../../../shared/attributes.js'; +import { CLASS_CACHE } from '../../constants.js'; import { hydrating } from '../hydration.js'; /** @@ -11,8 +12,7 @@ import { hydrating } from '../hydration.js'; * @returns {Record | undefined} */ export function set_class(dom, is_html, value, hash, prev_classes, next_classes) { - // @ts-expect-error need to add __className to patched prototype - var prev = dom.__className; + var prev = /** @type {any} */ (dom)[CLASS_CACHE]; if ( hydrating || @@ -35,8 +35,7 @@ export function set_class(dom, is_html, value, hash, prev_classes, next_classes) } } - // @ts-expect-error need to add __className to patched prototype - dom.__className = value; + /** @type {any} */ (dom)[CLASS_CACHE] = value; } else if (next_classes && prev_classes !== next_classes) { for (var key in next_classes) { var is_present = !!next_classes[key]; diff --git a/packages/svelte/src/internal/client/dom/elements/misc.js b/packages/svelte/src/internal/client/dom/elements/misc.js index f350d1df8c..ad34ab9c1d 100644 --- a/packages/svelte/src/internal/client/dom/elements/misc.js +++ b/packages/svelte/src/internal/client/dom/elements/misc.js @@ -1,6 +1,7 @@ import { hydrating } from '../hydration.js'; import { clear_text_content, get_first_child } from '../operations.js'; import { queue_micro_task } from '../task.js'; +import { FORM_RESET_HANDLER } from '../../constants.js'; /** * @param {HTMLElement} dom @@ -45,8 +46,7 @@ export function add_form_reset_listener() { Promise.resolve().then(() => { if (!evt.defaultPrevented) { for (const e of /**@type {HTMLFormElement} */ (evt.target).elements) { - // @ts-expect-error - e.__on_r?.(); + /** @type {any} */ (e)[FORM_RESET_HANDLER]?.(); } } }); diff --git a/packages/svelte/src/internal/client/dom/elements/style.js b/packages/svelte/src/internal/client/dom/elements/style.js index 3e05eec30e..740c225f29 100644 --- a/packages/svelte/src/internal/client/dom/elements/style.js +++ b/packages/svelte/src/internal/client/dom/elements/style.js @@ -1,4 +1,5 @@ import { to_style } from '../../../shared/attributes.js'; +import { STYLE_CACHE } from '../../constants.js'; import { hydrating } from '../hydration.js'; /** @@ -28,8 +29,7 @@ function update_styles(dom, prev = {}, next, priority) { * @param {Record | [Record, Record]} [next_styles] */ export function set_style(dom, value, prev_styles, next_styles) { - // @ts-expect-error - var prev = dom.__style; + var prev = /** @type {any} */ (dom)[STYLE_CACHE]; if (hydrating || prev !== value) { var next_style_attr = to_style(value, next_styles); @@ -42,8 +42,7 @@ export function set_style(dom, value, prev_styles, next_styles) { } } - // @ts-expect-error - dom.__style = value; + /** @type {any} */ (dom)[STYLE_CACHE] = value; } else if (next_styles) { if (Array.isArray(next_styles)) { update_styles(dom, prev_styles?.[0], next_styles[0]); diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index 4036aa2d61..f6d05162ca 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -5,7 +5,14 @@ import { init_array_prototype_warnings } from '../dev/equality.js'; import { get_descriptor, is_extensible } from '../../shared/utils.js'; import { active_effect } from '../runtime.js'; import { async_mode_flag } from '../../flags/index.js'; -import { TEXT_NODE, REACTION_RAN } from '#client/constants'; +import { + ATTRIBUTES_CACHE, + CLASS_CACHE, + REACTION_RAN, + STYLE_CACHE, + TEXT_CACHE, + TEXT_NODE +} from '#client/constants'; import { eager_block_effects } from '../reactivity/batch.js'; import { NAMESPACE_HTML } from '../../../constants.js'; @@ -48,21 +55,15 @@ export function init_operations() { if (is_extensible(element_prototype)) { // the following assignments improve perf of lookups on DOM nodes - // @ts-expect-error - element_prototype.__click = undefined; - // @ts-expect-error - element_prototype.__className = undefined; - // @ts-expect-error - element_prototype.__attributes = null; - // @ts-expect-error - element_prototype.__style = undefined; + /** @type {any} */ (element_prototype)[CLASS_CACHE] = undefined; + /** @type {any} */ (element_prototype)[ATTRIBUTES_CACHE] = null; + /** @type {any} */ (element_prototype)[STYLE_CACHE] = undefined; // @ts-expect-error element_prototype.__e = undefined; } if (is_extensible(text_prototype)) { - // @ts-expect-error - text_prototype.__t = undefined; + /** @type {any} */ (text_prototype)[TEXT_CACHE] = undefined; } if (DEV) { diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index cb152ed9c1..50832fb3ff 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -23,7 +23,7 @@ import * as w from './warnings.js'; import * as e from './errors.js'; import { assign_nodes } from './dom/template.js'; import { is_passive_event } from '../../utils.js'; -import { COMMENT_NODE, STATE_SYMBOL } from './constants.js'; +import { COMMENT_NODE, STATE_SYMBOL, TEXT_CACHE } from './constants.js'; import { boundary } from './dom/blocks/boundary.js'; /** @@ -46,10 +46,9 @@ export function set_should_intro(value) { export function set_text(text, value) { // For objects, we apply string coercion (which might make things like $state array references in the template reactive) before diffing var str = value == null ? '' : typeof value === 'object' ? `${value}` : value; - // @ts-expect-error - if (str !== (text.__t ??= text.nodeValue)) { - // @ts-expect-error - text.__t = str; + // prettier-ignore + if (str !== (/** @type {any} */ (text)[TEXT_CACHE] ??= text.nodeValue)) { + /** @type {any} */ (text)[TEXT_CACHE] = str; text.nodeValue = `${str}`; } } diff --git a/packages/svelte/tests/runtime-browser/samples/dom-clobbering-cache-symbols-spread/_config.js b/packages/svelte/tests/runtime-browser/samples/dom-clobbering-cache-symbols-spread/_config.js new file mode 100644 index 0000000000..62248851db --- /dev/null +++ b/packages/svelte/tests/runtime-browser/samples/dom-clobbering-cache-symbols-spread/_config.js @@ -0,0 +1,23 @@ +import { assert_ok, test } from '../../assert'; + +export default test({ + async test({ assert, target, waitUntil, window }) { + const form = target.querySelector('form'); + const button = target.querySelector('button'); + const [i1, i2, i3] = target.querySelectorAll('input'); + assert_ok(form); + assert_ok(button); + + assert.equal(form.id, 'initial-form'); + assert.equal(form.className, 'first'); + assert.equal(window.getComputedStyle(form).backgroundColor, 'rgb(255, 0, 0)'); + + button.click(); + await waitUntil(() => form.id === 'updated-form'); + + assert.equal(form.id, 'updated-form'); + assert.equal(form.className, 'second'); + assert.equal(i3.id, '', 'input clobbered form'); + assert.equal(window.getComputedStyle(form).backgroundColor, 'rgb(0, 0, 255)'); + } +}); diff --git a/packages/svelte/tests/runtime-browser/samples/dom-clobbering-cache-symbols-spread/main.svelte b/packages/svelte/tests/runtime-browser/samples/dom-clobbering-cache-symbols-spread/main.svelte new file mode 100644 index 0000000000..60e9b80e81 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/samples/dom-clobbering-cache-symbols-spread/main.svelte @@ -0,0 +1,20 @@ + + + + +
+ + + + +
From 05523088173e10af0753877af6936088de924833 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 14 May 2026 15:00:51 -0400 Subject: [PATCH 8/9] chore: bump devalue (#18219) --- .changeset/eight-things-join.md | 5 +++++ packages/svelte/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 .changeset/eight-things-join.md diff --git a/.changeset/eight-things-join.md b/.changeset/eight-things-join.md new file mode 100644 index 0000000000..678d63db33 --- /dev/null +++ b/.changeset/eight-things-join.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: bump devalue diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 51aed56a3a..7e9521bb57 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -176,7 +176,7 @@ "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", - "devalue": "^5.6.4", + "devalue": "^5.8.1", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e2be208b6..1bfee5c769 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,8 +96,8 @@ importers: specifier: ^2.1.1 version: 2.1.1 devalue: - specifier: ^5.6.4 - version: 5.6.4 + specifier: ^5.8.1 + version: 5.8.1 esm-env: specifier: ^1.2.1 version: 1.2.1 @@ -1418,8 +1418,8 @@ packages: engines: {node: '>=0.10'} hasBin: true - devalue@5.6.4: - resolution: {integrity: sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==} + devalue@5.8.1: + resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==} dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} @@ -3786,7 +3786,7 @@ snapshots: detect-libc@1.0.3: optional: true - devalue@5.6.4: {} + devalue@5.8.1: {} dir-glob@3.0.1: dependencies: From 4d8f99a2709e3c02e48d8bc6c77458f4ba49d0e3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 13:06:06 -0600 Subject: [PATCH 9/9] Version Packages (#18220) This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## svelte@5.55.7 ### Patch Changes - fix: prevent XSS on `hydratable` from user contents ([`a16ebc67bbcf8f708360195687e1b2719463e1a4`](https://github.com/sveltejs/svelte/commit/a16ebc67bbcf8f708360195687e1b2719463e1a4)) - chore: bump devalue ([#18219](https://github.com/sveltejs/svelte/pull/18219)) - fix: disallow empty attribute names during SSR ([`547853e2406a2147ad7fb5ffeba95b01bd9642da`](https://github.com/sveltejs/svelte/commit/547853e2406a2147ad7fb5ffeba95b01bd9642da)) - fix: harden regex ([`d2375e2ebcab5c88feb5652f1a9d621b8f06b259`](https://github.com/sveltejs/svelte/commit/d2375e2ebcab5c88feb5652f1a9d621b8f06b259)) - fix: move Svelte runtime properties to symbols ([`e1cbbd96441e82c9eb8a23a2903c0d06d3cda991`](https://github.com/sveltejs/svelte/commit/e1cbbd96441e82c9eb8a23a2903c0d06d3cda991)) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/blue-lions-call.md | 5 ----- .changeset/eight-things-join.md | 5 ----- .changeset/famous-webs-flash.md | 5 ----- .changeset/proud-dogs-go.md | 5 ----- .changeset/six-snails-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/blue-lions-call.md delete mode 100644 .changeset/eight-things-join.md delete mode 100644 .changeset/famous-webs-flash.md delete mode 100644 .changeset/proud-dogs-go.md delete mode 100644 .changeset/six-snails-sell.md diff --git a/.changeset/blue-lions-call.md b/.changeset/blue-lions-call.md deleted file mode 100644 index f9ddb88505..0000000000 --- a/.changeset/blue-lions-call.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: prevent XSS on `hydratable` from user contents diff --git a/.changeset/eight-things-join.md b/.changeset/eight-things-join.md deleted file mode 100644 index 678d63db33..0000000000 --- a/.changeset/eight-things-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: bump devalue diff --git a/.changeset/famous-webs-flash.md b/.changeset/famous-webs-flash.md deleted file mode 100644 index 75a01f689a..0000000000 --- a/.changeset/famous-webs-flash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: disallow empty attribute names during SSR diff --git a/.changeset/proud-dogs-go.md b/.changeset/proud-dogs-go.md deleted file mode 100644 index c728466138..0000000000 --- a/.changeset/proud-dogs-go.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: harden regex diff --git a/.changeset/six-snails-sell.md b/.changeset/six-snails-sell.md deleted file mode 100644 index 6c61546e86..0000000000 --- a/.changeset/six-snails-sell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: move Svelte runtime properties to symbols diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index e5ba5737c4..1255d8c6a0 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,19 @@ # svelte +## 5.55.7 + +### Patch Changes + +- fix: prevent XSS on `hydratable` from user contents ([`a16ebc67bbcf8f708360195687e1b2719463e1a4`](https://github.com/sveltejs/svelte/commit/a16ebc67bbcf8f708360195687e1b2719463e1a4)) + +- chore: bump devalue ([#18219](https://github.com/sveltejs/svelte/pull/18219)) + +- fix: disallow empty attribute names during SSR ([`547853e2406a2147ad7fb5ffeba95b01bd9642da`](https://github.com/sveltejs/svelte/commit/547853e2406a2147ad7fb5ffeba95b01bd9642da)) + +- fix: harden regex ([`d2375e2ebcab5c88feb5652f1a9d621b8f06b259`](https://github.com/sveltejs/svelte/commit/d2375e2ebcab5c88feb5652f1a9d621b8f06b259)) + +- fix: move Svelte runtime properties to symbols ([`e1cbbd96441e82c9eb8a23a2903c0d06d3cda991`](https://github.com/sveltejs/svelte/commit/e1cbbd96441e82c9eb8a23a2903c0d06d3cda991)) + ## 5.55.6 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 7e9521bb57..2f0743641f 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.55.6", + "version": "5.55.7", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index b11e7635c7..6b0ed0aea8 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.55.6'; +export const VERSION = '5.55.7'; export const PUBLIC_VERSION = '5';