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.56.3
### Patch Changes
- fix: ignore errors that occur in destroyed effects
([#18384](https://github.com/sveltejs/svelte/pull/18384))
- fix: type BigInts in `$state.snapshot(...)` return values
([#18388](https://github.com/sveltejs/svelte/pull/18388))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
fixes#18383
I traced the issue to
`packages/svelte/src/internal/client/error-handling.js`, specifically
the `invoke_error_boundary` function. The problem is at line 56 where
`effect.b.error(error)` is called without checking if `effect.b` exists.
What happens is: when an async `$derived` rejects inside a
`<svelte:boundary>`, the error is handled correctly the first time. But
if the boundary is destroyed before the rejection fully settles (e.g.
component unmounts), a subsequent settle tries to call
`effect.b.error()` on a destroyed boundary where `effect.b` is `null`,
causing a `TypeError`.
The fix adds a null check for `effect.b` before calling `error()`. If
the boundary has been destroyed, we skip it and continue bubbling up to
the parent boundary. This way the error still gets handled properly
instead of crashing.
This could also be tested by creating a component with an async
`$derived` that rejects after the component unmounts, but I wanted to
get the fix up first for review.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Fixes#18385
## Problem
`$state.snapshot` infers BigInt properties as `never` because `bigint`
is missing
from the `Primitive` type definition. BigInt is a valid JavaScript
primitive and is
supported by `structuredClone`.
## Solution
Added `bigint` to the `Primitive` type in both
`packages/svelte/src/ambient.d.ts` and
`packages/svelte/types/index.d.ts`.
## Before
```ts
const object = $state({ number: 1n });
const snapshot = $state.snapshot(object);
// snapshot.number is inferred as never ❌
After
const object = $state({ number: 1n });
const snapshot = $state.snapshot(object);
// snapshot.number is correctly inferred as bigint ✅
Tests and linting
- [x] This is a type-only change, no runtime behavior affected
---------
Co-authored-by: Rich Harris <hello@rich-harris.dev>
By checking that current/previous batch are null we filter out false
positives. `reactivity_loss_tracker` is only reset after a microtask, so
if a flush happens before that, we get warnings for things we shouldn't
warn on.
Fixes#18370
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
If a block has a sole component child, we apply an optimization to not
need a template. But if that sole child is wrapped in `$.async`, it can
happens that the block's effect is assigned the wrong end node.
In the reproduction this happens because we have two components inside a
Child which is rendered only after an async block resolves. In
`$.append` we have logic to not reassign nodes when the active effect
didn't already run, which it has by the time we come across it (because
the async work had to resolve first; we added this in
https://github.com/sveltejs/svelte/pull/17120 to prevent the opposite,
nodes being "rewinded" to an earlier position) and so the second sibling
component's `$.append` will not set its end node to the effect. As a
result the end node is wrong (too early).
To fix this we assign the nodes in `$.async`. We could also use
`compareDocumentPosition` in `$.append` to only ever go forward, but
that feels a bit heavier performance-wise.
---------
Co-authored-by: paoloricciuti <ricciutipaolo@gmail.com>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
## Problem
The `{#each}` docs page has a runnable "chess board" example whose
`<style>` block uses an invalid CSS property:
```css
.chess-board {
display: grid;
grid-template-columns: repeat(8, 1fr);
rows: repeat(8, 1fr); /* ← not a real CSS property */
...
}
```
There is no `rows` shorthand or longhand in CSS, so this line is
silently dropped by every browser. The example clearly intends an 8×8
grid (note the matching `grid-template-columns` line directly above).
## Fix
`rows` → `grid-template-rows`. One line, in
`documentation/docs/03-template-syntax/03-each.md`. No prose changes.
## Summary
Bumps `vitest` and `@vitest/coverage-v8` from `^2.1.9` to `^4.1.7` (two
major versions). Three small test-harness updates compensate for vitest
4 behavior changes; all 7569 tests still pass.
- **`packages/svelte/tests/runtime-browser/test.ts`** — vitest 4 removed
the deprecated `describe(name, fn, opts)` signature. Pass options as the
second argument.
- **`packages/svelte/tests/runtime-legacy/shared.ts`** — vitest 4's
jsdom env binds `virtualConsole` to the original `globalThis.console`
reference *before* vitest wraps the console, so inline-`<script>` logs
and `jsdomError` events no longer reach per-test `console.{log,error}`
overrides. Restore vitest 2's behavior by re-routing the virtual console
through the live `console` in `beforeAll`. Also promote the window-error
listener to a named function and remove it in `finally` — previously
leaked listeners from earlier tests kept writing to module-level
`unhandled_rejection`, polluting later tests.
- **`vitest.config.js`** — bump `testTimeout` to 10s. The 5s default
trips a handful of dev-mode tests that exercise
`effect_update_depth_exceeded`, whose ~1000 Error-stack captures per
flush are slower under vitest 4's deeper async stacks.
## Test plan
- [x] `pnpm test` — 7569 passed, 63 skipped, 0 failed
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
The rejects all async deriveds of a batch as `OBSOLETE`, so they don't
hang around and bail early without triggering the batch.
If we don't do this, an async derived can trigger the already done
batch, which schedules an effect that is never flushed. Because it is
never flushed the branches it touched on its way up are never cleared,
and so anything else in that subtree is now unreactive.
Mimic what `parseExpressionAt` does
---------
Co-authored-by: hjaber <hjaber@users.noreply.github.com>
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
Detect TypeScript `type` declarations by actually parsing instead of by
character-class blacklists, so that expressions like `{type === 'all' ?
a : b}` or `{type instanceof Foo}` aren't misclassified as malformed
declarations.
Closes#18328
---------
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
### 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`
Fixes#18332.
When spread props put `value` before a later `type="hidden"` on an
`<input>`, `set_attributes` currently writes the value while the element
is still a text input. Browsers sanitize text input values by removing
newlines, so the hidden input permanently loses them before `type` is
applied.
This sets an input's `type` first whenever the same spread update also
includes `value` or `__value`, so the existing value handling runs with
the final input type. The new runtime-browser sample covers both the
problematic spread-first order and the already-working type-first order.
Validation:
- `corepack pnpm lint`
- `CI=1 corepack pnpm test`
- `corepack pnpm vitest run
packages/svelte/tests/runtime-browser/test.ts -t
input-type-before-value-spread`
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Rich Harris <hello@rich-harris.dev>
## Summary
- detect `SvelteURLSearchParams#set` changes by comparing duplicate
value lists instead of joined strings
- update reactive subscribers when duplicate params collapse to a single
value with the same concatenated text
- cover both `SvelteURLSearchParams` and `SvelteURL.searchParams`
synchronization
## Tests
- pnpm exec vitest run
packages/svelte/src/reactivity/url-search-params.test.ts -t
"URLSearchParams.set updates when duplicate values collapse to the same
joined string"
- pnpm exec vitest run
packages/svelte/src/reactivity/url-search-params.test.ts
packages/svelte/src/reactivity/url.test.ts
---------
Co-authored-by: Rich Harris <hello@rich-harris.dev>
The text in https://svelte.dev/docs/svelte/browser-support is quite
redundant and has a lot of AI smell. I did my best to make it more
concise and remove some slop comments in the generation script.
Requires a companion PR in svelte.dev:
https://github.com/sveltejs/svelte.dev/pull/2013
<img width="620" height="809" alt="image"
src="https://github.com/user-attachments/assets/7be9d5ff-1623-4897-a2d4-9a601b20f54d"
/>
### Before submitting the PR, please make sure you do the following
- [ ] 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.
- [ ] 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: Rich Harris <rich.harris@vercel.com>
Byte-identical templates hoisted from different fragments, elements, or
branches now share a single `$.from_html` factory instead of each
emitting its own module-scope variable - this shrinks generated output
and avoids redundant runtime template parsing. Dedup is keyed on
`(content, flags)` and is skipped in dev mode (templates there are
wrapped in `$.add_locations`, which embeds per-call-site info).
### Before submitting the PR, please make sure you do the following
- [ ] 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: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
The compiler emitted an inline string array as the second argument to
`$.rest_props(...)`, and the runtime did a linear
`Array.prototype.includes` on it on every property access via the
rest-props Proxy.
The exclude list only depends on the component definition, not on the
instance, so it can be hoisted to module scope and shared by every
instance. Switching it to a `Set` at the same time makes each lookup
O(1).
For a component like `<Button ...rest />` rendered N times, this turns
one per-instance allocation (plus a linear search on every rest-prop
access) into one module-scope allocation plus O(1) lookups.
The legacy `$$restProps` path is unchanged — it mutates the exclude list
in its `deleteProperty` trap, so it can't share a hoisted Set across
instances.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
The `\b` in the regex matched on `}`, too, so you would get a very
confusing "invalid declaration tag" error on `{type}`. Match on
whitespace instead (because that's what has to come afterwards)
---------
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Rich-Harris <hello@rich-harris.dev>
i don't know how to stem the flood of slop PRs but maybe this will help
a tiny bit
---------
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Simon thinks it was because he thought that committing the fork != committing the batch, and that this could result in an error boundary showing up too late. But that's not the case
## Summary
The current wrapper always calls `document.createElementNS(namespace ??
NAMESPACE_HTML, tag, options)` — even for HTML elements (the >99% case),
and even when `options` would be `undefined`. Two effects compound:
1. **Route HTML elements through `createElement`** — Blink has a fast
path that skips the namespace lookup `createElementNS` always performs.
2. **Omit the trailing `undefined` argument** — V8/Blink take a slower
path for `createElementNS(ns, tag, undefined)` (and `createElement(tag,
undefined)`) than for the bare 2-arg form. This applies symmetrically to
the SVG/MathML branch, where the wrapper now also avoids the `undefined`
3rd arg.
The wrapper dispatches to the fastest call shape for every input —
`{HTML, non-HTML}` × `{with is, without is}` × no `undefined` ever.
## Affects
Every place Svelte constructs a DOM element internally:
`<svelte:element>`, `run_scripts`, the per-component `<style>` injector,
the `{@html}` wrapper, and the `<template>` element used to clone string
templates.
## Numbers
Measured in headless Chromium (Chromium 145) using the browser bench
harness from #18261, 2–3 runs, median. Lower per-call is better; higher
hz is better.
### Raw call shapes (what each shape costs in the browser)
| | hz | per-call |
| ------------------------------------------- | -------: | -------: |
| `createElement(tag)` | ~1,210k | ~0.83 µs |
| `createElement(tag, undefined)` | ~901k | ~1.11 µs |
| `createElementNS(NS_HTML, tag)` | ~913k | ~1.10 µs |
| `createElementNS(NS_HTML, tag, undefined)` | ~630k | ~1.59 µs |
| `createElement(tag, { is })` | ~484k | ~2.07 µs |
| `createElementNS(SVG_NS, tag)` | ~333k | ~3.01 µs |
| `createElementNS(SVG_NS, tag, undefined)` | ~303k | ~3.30 µs |
| `createElementNS(SVG_NS, tag, { is })` | ~245k | ~4.08 µs |
Two stable effects fall out:
- **Trailing `undefined` is consistently slower** than the bare form —
~26% on `createElement`, ~31% on `createElementNS` (HTML), ~10% on
`createElementNS` (SVG).
- **`createElement` skips a namespace lookup** that `createElementNS`
always performs — ~32% delta for equal-shape calls (`createElement(tag)`
vs `createElementNS(NS_HTML, tag)`).
### Per-case impact of this PR
| Case (namespace, `is`) | Old wrapper call | Old hz | New wrapper call
| New hz | Speedup |
| ----------------------------- |
--------------------------------------------- | --------: |
------------------------------ | --------: | ------: |
| HTML, no `is` (dominant path) | `createElementNS(NS_HTML, tag,
undefined)` | ~630k | `createElement(tag)` | ~1,210k | **~92%** (1.92×)
|
| HTML, with `is` | `createElementNS(NS_HTML, tag, { is })` | (slow) |
`createElement(tag, { is })` | ~484k | similar to above (just the
NS-skip) |
| SVG/MathML, no `is` | `createElementNS(ns, tag, undefined)` | ~303k |
`createElementNS(ns, tag)` | ~333k | **~10%** |
| SVG/MathML, with `is` | `createElementNS(ns, tag, { is })` | ~245k |
same | ~245k | no change |
No measurable change in JSDOM (both shapes route through the same JS
implementation there).
The headline gain — **~92% on the dominant HTML-no-`is` path** —
combines both effects roughly equally: dropping the `undefined` (~46%)
and switching to `createElement` (~32%).
Wrapper-vs-raw was also verified: the new wrapper measured to within ±2%
of raw `document.createElement(tag)` for the HTML-no-`is` case, so the
function-call indirection adds no measurable overhead.
See #18261 for the benchmark and methodology.
## Test plan
- [x] All 5881 runtime tests still pass (runtime-runes + runtime-legacy)
- [x] `<svelte:element xmlns={null}>` correctly falls back to HTML
(covered by existing `dynamic-element-dynamic-namespace` test)
- [x] SVG/MathML namespaces still go through `createElementNS`
- [x] Custom-element `is` option still honoured on both branches
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Adds a [Browser
support](https://github.com/MathiasWP/svelte/blob/document-pipeline-draft/documentation/docs/07-misc/05-browser-support.md)
page listing the minimum browser versions Svelte's runtime and compiler
output require. The page (headline floor + per-feature requirements) is
auto-generated by `packages/svelte/scripts/generate-browser-support.js`,
which scans the code Svelte actually ships against the
[web-features](https://github.com/web-platform-dx/web-features) Baseline
dataset and resolves results to concrete browser versions via
[baseline-browser-mapping](https://github.com/web-platform-dx/baseline-browser-mapping).
CI fails if a PR moves the floor without regenerating the page.
For 5.55.9 the floor is **Baseline 2020**: Chrome/Edge 87, Firefox 83,
Safari 14.
## How it works
1. **Headline floor.** Each runtime entry from `pkg.exports` is bundled
with rollup using production conditions, then walked with TypeScript's
compiler API + TypeChecker. The walker (`browser-support.detector.js`)
flags any web-features ID the runtime references; the highest Baseline
year among them is the floor.
2. **Per-feature requirements.** Every user-facing surface is enumerated
programmatically — every `bind:*` from `binding_properties`, every
public subpackage export discovered via `pkg.exports` + `import * as
ns`, every rune from `RUNES`, and directives (`transition:` / `animate:`
/ `use:` / `@attach` / `{@html}` / custom elements). Each gets a
fixture, compiled and bundled like user code, then scanned. A row
appears only when the fixture's floor exceeds the runtime floor.
3. **Supplemental rules.** A handful of APIs `web-features` doesn't
catalogue (`getComputedStyle(...).zoom`, `box:
'device-pixel-content-box'`) are detected with the same type-aware
walker via a small `register_extra_rules` list with inline
justifications.
4. **Ignore sets.** `SAFE_TO_IGNORE` covers dataset bugs and
feature-detected APIs with graceful fallbacks. `BEHAVIORAL_IGNORE` hides
APIs reached only via specific user code from the headline (they still
surface as conditional rows); staleness-checked, so CI fails if an entry
no longer exists in the runtime.
Resolves#18198. Provides an answer to the long-standing #558.
## Test plan
- [ ] \`pnpm --filter svelte generate:browser-support\` regenerates the
page idempotently
- [ ] CI fails when a code change bumps the floor without regenerating
the page
- [ ] Page renders correctly on svelte.dev
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
`current_sources` tracks the sources created within the active reaction
so that reading or writing them during that same reaction doesn't
trigger a re-run. It was an `Array` checked with
`Array.prototype.includes.call(...)` in three hot places: the
`state_unsafe_mutation` guard, `set()`'s destruction check, and
`schedule_possible_effect_self_invalidation`.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Merged #18298 a bit too soon - there's still a situation which can
corrput the linked list: If we merge one batch into another, it will
unlink the batch immediately. But `discard` also calls it, running the
unlink logic again, which can corrupt the linked list.
Sketch:
1. Batch A is pending.
2. Batch B starts later, intersects A, resolves first, and merges into
A.
3. B is unlinked immediately, but `A.oncommit(() => B.discard())`
remains.
4. Independent batch C is created while A is still pending and is linked
after A.
5. A commits, calls `B.discard()`, and B's stale `#prev`/`#next` can set
`A.#next = null` / `last_batch = A`, disconnecting C.
Not able to produce a failing test from it but it's definitely a fix we
need to make.
Also moved `#link` into a constructor, because it is (and should be)
used only once.
Also made the action after an error `discard` instead of just `#unlink`
because this batch is done for and e.g. pending `settled` should
resolve, too.
We incorrectly restore effect context after an`$derived(await ...)` if
it occurs inside an async function, but only for those, no other `await`
expressions. This is buggy and wrong. We only want to restore context
_inside_ an async derived expression (including template expressions)`
so that `await a + b` works correctly.
It's possible that this will be a breaking change (albeit not
semver-violating, because `experimental`) for some people if they are
creating effects after an `await` (other than at the top level of a
component). This is regrettable, but it should never have worked in the
first place.
Right now, if you have the following:
```svelte
{@const data = await foo}
<p>{(() => data)()}<p>
```
It will blow up during CSR, because it tries to read `data` before it
exists. The solution to this is to consider references inside closures
as blockers for those closures. This _does_ mean we'll overblock in some
circumstances, such as:
```svelte
{@const data = await foo}
<button onclick={() => data}>foo<button>
```
But I wonder if that's actually incorrect? If the user were to click
`onclick` before `data` is ready, it would blow up.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Follow-up to #18273 (not merged yet hence no changeset here): We can run
into a null-pointer when wanting to increment/decrement inside an effect
root that is outside a the component tree. Similarly, if not using the
component logic to unset context at the right time we gotta do it
"manually" inside `save`.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
## Summary
The `script_unknown_attribute` warning message used American English
(`Unrecognized`) while every other user-facing error/warning in the
compiler uses British English (`Unrecognised`):
- `options_unrecognised` error: `Unrecognised compiler option…`
- `unknown_code` warning: `is not a recognised code`
This change updates `script_unknown_attribute` to match, then
regenerates `warnings.js` and the reference documentation from the
source message file via `node scripts/process-messages`.
## Test plan
- Spelling change is isolated to the `script_unknown_attribute` warning
message
- `warnings.js` and generated docs were updated by running `node
scripts/process-messages`
- All three occurrences (source, generated JS, generated docs) are
consistent