Each `<svelte:boundary>` with:
```svelte
{#snippet failed(error)}
...
{/snippet}
```
generated a function named failed. Sibling boundaries placed both
functions in the same SSR scope:
```js
{
function failed() {}
function failed() {} // Identifier `failed` has already been declared
}
```
Resulting in a `[PARSE_ERROR] Identifier `failed` has already been
declared` error during build time.
A let declaration such as `{const x = 0}` can create a nested lexical
block, exposing the collision.
The fix gives each boundary its own scope:
```js
{
function failed() {}
$$renderer.boundary({ failed }, ...);
}
{
function failed() {}
$$renderer.boundary({ failed }, ...);
}
``
`render` returns `RenderOutput` and takes `csp: Csp`, but both are
declared in `src/internal/server/types.d.ts` and never exported. The
generated `svelte/server` block has them without `export`, so nothing
outside the repo can import them and the docs page names `RenderOutput`
as the return type with nothing on the page defining it.
Moves them into `src/server/public.d.ts` the way `svelte/motion` went in
#17967, rather than re-exporting internals.
Fixes#18440
`hmr()` in `packages/svelte/src/internal/client/dev/hmr.js` wrapped a
component in a `block` effect containing a `branch` effect. It forwarded
the inner effect's `nodes` object to the outer block:
This shared the same reference, meaning `outer_block.nodes ===
inner_branch.nodes`. When `pause_children` collected transitions for
unmounting, Each collected transition had `out(check)` called multiple times,
starting multiple animations and firing `outroend` multiple times.
Fix by only copying `start`/`end` (the DOM range info the outer block needs)
without sharing the transitions array:
`trace_references` recreated a fresh seen set for every CallExpression,
so `touch` re-walked the same transitive assignment graph once per call.
For N calls reaching an N-deep binding chain, this was $O(N^2)$.
Share one seen set per trace_references invocation. Use separate sets
for the write-directed CallExpression touches and the read-directed
ReturnStatement touches. The shared set ensures each assignment-value
expression is walked at most once, making the traversal linear. `touch`
only ever adds to a fixed target set, so skipping an already-seen
expression never drops a binding: compiler output is byte-identical.
Complements #18548.
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
`to_style()` strips CSS comments from an inline `style` value with
`/\s*\/\*.*?\*\/\s*/g`. The leading `\s*` makes the match retry from
every position, so a long run of whitespace backtracks in O(n^2). When a
dynamic `style={value}` sits on an element that also has a `style:`
directive, that regex runs on `value`, so a large whitespace string can
stall rendering (server) or the main thread (client).
### Fix
Drop the surrounding `\s*` and match only the comment: `/\/\*.*?\*\//g` to prevent quadratic regex.
The surrounding whitespace was already removed by the `.trim()`
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Fixes#18568
`last_propagated_event` was added in #16527 to stop Firefox from garbage collecting the event wrapper mid-propagation. The problem with this is that the even is now retained until the next event, which could be a while. This removes it after a macrotask.
Co-authored-by: rmurphy <rmurphy@fortressinfosec.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Fixes#7916. Context lookup starts at the current component, not the
closest parent. `setContext` followed by `getContext` with the same key
in the same component returns the value.
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
The server copy of `createContext` shipped without the `missing_context`
throw and #17580 had to hand-mirror the client body back in, so this
duplication has already cost a bug. This moves the realm-independent
parts, the `createContext` tuple, `get_parent_context`, and
`get_or_init_context_map`, into `internal/shared/context.js`, and each
realm keeps its public context functions as thin wrappers over its own
state.
Fixes#18609
A `<an+b> of <selector>` CSS statement does not need whitespace after `of` if it's followed by a CSS-known syntax (like a `.` which marks a class identifier). Adjust the regex accordingly.
Fixes#18612.
A concise arrow body does not contain a `ReturnStatement` which we traverse in full calculate_blockers (else we bail on functions), so short-cut to `touch` there.
---------
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
Fixes#18288
Check the surrounding branch effect's start node to retrieve the correct root node instead of just the anchor, since the latter could come from each.js/branch.js and be a text node that is never going to get connected
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
Fixes#18404
`<svelte:head>` combined with two or more components that each use `bind:` on a child component's prop corrupts a renderer's `.type` during SSR, misplacing the head hydration marker in `<body>` instead of `<head>`.
Reason: `copy()` (`packages/svelte/src/internal/server/renderer.js`) rebuilds via `new Renderer(this.global, this.#parent)`. That constructor defaults `.type` from the *parent's* current type, not from `this.type`. To fix it we therefore reassign it (like already do for the boundary elsewhere)
Fixes#18677.
- preserve namespace prefixes on CSS `TypeSelector` AST nodes
- accept wildcard local names in `svg|*` and `*|*`
- retain namespaces when printing modern ASTs
- keep namespaced universal selectors intact while adding scoped CSS
selectors
- add compile and print regressions covering all four namespace forms
---------
Co-authored-by: svelte-triage-bot <team@svelte.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Fixes#18664
`print()` was writing selector names back out verbatim. The parser
decodes CSS escape sequences when it reads them — `\31` becomes `1`,
`\a` becomes a newline — so the printer produced selectors that either
failed to re-parse (`#123`) or silently meant something different
(`#line\nbreak` turns into a descendant combinator).
A small re-escaper (`escape_identifier`) is now used when printing type,
class, id, pseudo, attribute and at-rule names.
Also fixes a backslash parser bug in the process.
---------
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
Hi! This PR fixes#18666
### 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#18592.
Logical assignments to private `$state` fields were compiled as
unconditional setter calls. This preserves JavaScript short-circuiting
by evaluating the setter only when `||=`, `&&=`, or `??=` should assign.
The regression sample covers the no-assignment branch for all three
operators, alongside the existing assignment-path coverage.
### 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#18304
The parent of a declaration could be the Program, on which HTML comments are added, which subsequently crashes the funciton. Ignore such comments.
---------
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
Fixes#18608.
When `set_attributes` normalized removed spread attributes, it also
copied its internal `$$` listener bookkeeping keys into `next` with a
`null` value. This cleared the stored callback before event cleanup, so
removing a capture handler passed `null` to `removeEventListener` and
left the listener attached.
Skip internal `$$` keys during missing-attribute normalization. The
updated runtime-runes sample verifies replacing and then removing a
spread capture handler in both client and hydration modes.
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Fixes#18619
Make sure to not override old values with newer values that are part of the same flush
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Fixes https://github.com/sveltejs/svelte/issues/18607.
For server output, `build_getter` reused a derived binding's declaration
identifier as the generated call callee. Esrap therefore associated
leading declaration comments with an earlier reference, potentially
emitting them immediately after `return` and triggering automatic
semicolon insertion. The getter returned `undefined` instead of the
derived value.
Fix it by avoiding the double-transform.
---------
Co-authored-by: svelte-triage-bot[bot] <316883489+svelte-triage-bot[bot]@users.noreply.github.com>
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
- preserve whitespace around elements if it leads to a line break
- preserve whitespace for whitespace-sensitive elements (pre, textarea,
title)
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Adds a new `comments` array to the stylesheet node which CSS comments
are added to. Is subsequently used in `print` to see them in the output.
Alternative to #18475
Fixes#18610
### Problem
In async mode, a controlled keyed `{#each}` throws `TypeError: Cannot
read properties of undefined (reading 'e')` when its collection becomes
empty while an earlier batch is still pending on the same block.
The fast path in `pause_effects` cleared `state.items` and then called
`destroy_effects`, which walks pending batch keys and reads
`state.items.get(key).e`. Those EachItems are still needed for the
pending batch (preserved offscreen), so clearing the map makes the
dereference throw and aborts the commit mid-flight.
### Fix
Only take the controlled-each fast path when `state.pending.size === 0`,
so pending batches keep their items until they commit or discard.
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.8
### Patch Changes
- fix: call `onerror` and provide a working `reset` when hydrating a
failed boundary
([#18556](https://github.com/sveltejs/svelte/pull/18556))
- fix: preserve select selection when spread attributes omit value
([#18561](https://github.com/sveltejs/svelte/pull/18561))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Fixes#18555
A boundary that failed during SSR hydrates via
`#hydrate_failed_content`, which never calls `onerror` and passes the
`failed` snippet a no-op `reset`. Both came in with #17672, whose docs
say `onerror` "will be called upon hydration with the deserialized error
object". Once hydrated as failed, the boundary can never leave that
state. Downstream this is what keeps SvelteKit's `+error.svelte` mounted
after navigating away from a server-rendered error page
(sveltejs/kit#16345).
This extracts the reset/onerror machinery from `#handle_error` into
`#create_reset` and uses it in the hydration path too. `onerror` is
invoked in a microtask because it may mutate state, which is disallowed
while hydrating. `#handle_error` already invokes it asynchronously, so
the timing matches the error path.
The tests flip a `recovered` flag before calling `reset`, since the
child would otherwise throw again. That is the intended retry pattern,
and the same one kit uses when it resets route boundaries on navigation.
Verified against kit end to end, hydrating a server-rendered error page
and navigating away now tears it down with no kit changes needed.
Fixes#18557
A `<select>` with spread attributes initializes the option mutation
observer even when those attributes never provide a `value`. In that
case the element has no internal `__value`, but the observer previously
treated the missing property as an explicit `undefined` value and
deselected every option after an option was added or removed.
Only reapply the programmatic selection when `__value` is actually
present. This preserves the browser's current selection for spreads
without a value while keeping the existing behavior for bindings,
including an explicitly stored `undefined` value.
A runtime-legacy regression fixture covers both adding and removing an
option in client and hydration modes.
### Before submitting
- [x] References the existing issue
- [x] Uses a `fix:` title
- [x] Includes a regression test
- [x] Includes a patch changeset for `svelte`
### Test plan
- [x] `FILTER=select-spread-preserve-selection pnpm test runtime-legacy
--maxWorkers=1` (2 tests passed: client and hydrate)
- [x] `git diff --check`
- [ ] Full `pnpm test` was not run because the shared host was under
resource pressure and broad test processes were explicitly avoided
- [ ] `pnpm lint` and `pnpm check` were not run for the same reason
- [ ] Targeted repository Prettier check was attempted, but could not
start because `prettier-plugin-svelte` required the ungenerated
`packages/svelte/compiler/index.js`; no broad build or dependency
reinstall was run
### AI disclosure
AI-assisted: the implementation, regression test, and pull request text
were prepared with OpenAI Codex and reviewed by the contributor.
Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>
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.7
### Patch Changes
- chore: provide `indent` option for `print`
([#18474](https://github.com/sveltejs/svelte/pull/18474))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Relevant for https://github.com/sveltejs/cli/pull/1138.
This is basically just an option from `esrap` that we pass through. That
will allow tools like `sv migrate` to provide a guessed indent based on
the other file contents and therefore allow us to produce way smaller
diffs. Since we are just passing an option, there is no need for a test
here.
Technically a `feat:` but i dont think this is relevant enough.
### 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.
- [ ] 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`
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.6
### Patch Changes
- perf: skip unnecessary blocker analysis when compiling components
without top-level await
([#18548](https://github.com/sveltejs/svelte/pull/18548))
- fix: rerun derived that had an abort controller on reconnection
([#18551](https://github.com/sveltejs/svelte/pull/18551))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Skip function reference tracing in `calculate_blockers` when a component
has no top-level `await`.
Blockers only represent dependencies on top-level async statements.
Without a top-level `await`, no binding can have a blocker, so tracing
every top-level function cannot affect the generated output. In large
components with many functions and transitive assignments, that
unnecessary work can become quadratic.
Follow-up to #18400 - we need to mark a derived with an abort signal
that is frozen as dirty so it is guaranteed to rerun when it
reconnects/is re-requested. Else you could return a stale value, or
worse, you returned a promise from the derived which you aborted, and
it's now in the rejected state until you update one of its dependencies.
During my explorations I collected these new tests which are currently
failing. Two of them work on the incremental-batches branch, all of them
(adjusted for new behavior) pass on my uncommitted entangle batches
branch, and all of them also pass on another uncommitted experimental
branch of overlaying deriveds which will likely not land.
Fixes https://github.com/sveltejs/svelte/issues/18301
---------
Co-authored-by: Fedor Nezhivoi <f.nezhivoi@corp.vk.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
Fixes#18501.
Problem is that is_updating_effect was also set to true for branch/root effects which are not reactive
---------
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
fixes#18438
Currently the `mark` method inside `#merge` for `earlier_batch` will
schedule effect for undirty derived. Add the condition for derived to
prevent unnecessary effect be scheduled.
```
if ((flags & DERIVED) !== 0) {
mark(/** @type {Derived} */ (reaction));
}
```
## Summary
Fixes#18491 — the compiler discards an upstream plugin's sourcemap when
it's generated without a `source` option (e.g. `new
MagicString(code).generateMap()`), producing wrong devtools/stack-trace
positions.
### Root cause
`compile()` already supports composing an incoming sourcemap into its
output via `options.sourcemap` (`merge_with_preprocessor_map` →
`apply_preprocessor_sourcemap` → `combine_sourcemaps` in
`packages/svelte/src/compiler/utils/mapped_code.js`). This is the same
mechanism `preprocess()` uses internally, and it's the documented
contract for tools that transform a `.svelte` file before compiling it
(see `CompileOptions.sourcemap`'s doc comment).
`combine_sourcemaps` composes maps by matching `sourcefile === filename`
(the basename of the file being compiled). A sourcemap produced by `new
MagicString(code).generateMap()` **without** a `source` option — which
is exactly what the reporter's Vite plugin does — has `sources: ['']`.
That empty string never equals `filename`, so `remapping()` treats the
node as a leaf (the "original" file) instead of a branch to keep
chaining through, and the whole incoming map is silently dropped. The
result: every position that flows through that segment resolves to `{
source: null, line: null, column: null }`, which is what produces the
wrong/missing devtools mapping described in the issue.
Vite itself already has to handle this exact ambiguity: in
`pluginContainer.ts`'s `_getCombinedSourcemap`, an empty `sources[0]`
from a MagicString-based transform is patched to refer to the file being
transformed before Vite uses it internally. This PR applies the same
normalization on svelte's side, so the contract holds regardless of
whether the caller happens to pass a `source` option to `generateMap()`.
### Fix
In `apply_preprocessor_sourcemap`, normalize an incoming map's `sources:
['']` (or `[null]`/`[undefined]`) to `[filename]` before calling
`combine_sourcemaps`, so the chain-matching step can actually find it.
Closes#18506
Declaration tags in the scope of an each block were considered "parts"
and transformed to use their value instead of the signal itself. We can
safely check on the `kind` of the binding since a `kind` of `state`,
`state_raw` or `derived` in the each scope needs to be a declaration tag
(and it's a stable reference so we can use them directly)
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.4
### Patch Changes
- fix: include wrapping parentheses in `{@const}` declarator `end`
position ([#18436](https://github.com/sveltejs/svelte/pull/18436))
- fix: always unset reactivity context after restoring it
([#18453](https://github.com/sveltejs/svelte/pull/18453))
- fix: don't notify `searchParams` subscribers when the URL changes
without affecting the search string
([#18425](https://github.com/sveltejs/svelte/pull/18425))
- fix: strip `?` from optional parameters in `<script lang="ts">` so
generated JavaScript is valid
([#18448](https://github.com/sveltejs/svelte/pull/18448))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
When calling `save`, we restore the context after the promise resolves.
But we do not unset it after the subsequent synchronous execution. That
means that until `unset_context` runs in `async_derived` we will not
have the correct (nulled) context. That causes problems if the `save`
isn't the last promise contributing to the `async_derived`, because it
means the context is not properly unset until the promise _after_ the
`save` within the `async_derived` settles. This can cause all sorts of
mixups, including a wrong mutation error.
fixes#18441
`{@const x = (a)}` (and any `{@const}` whose initializer is wrapped in
parentheses, e.g. `(a = b)`, `({ ... })`) produced a
`VariableDeclarator` whose `end` fell before the closing `)`.
`read_expression` strips the wrapping parens from the returned node but
advances the parser past them, so `init.end` excludes the `)` while the
parser position does not. Use the parser position for the declarator
`end` instead. Fixes source slices / tooling that rely on the node range
(the range previously yielded invalid, unbalanced source).
<img width="2064" height="1746" alt="image"
src="https://github.com/user-attachments/assets/64153ee5-d543-442d-8018-d3668acfd7a7"
/>
### 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
- [ ] Run the tests with `pnpm test` and lint the project with `pnpm
lint`
`SvelteURLSearchParams` has two notification bugs that surface through
`SvelteURL`.
The first is over-notification. Setting `url.href` always rebuilds the
params and bumps their version signal, even when the search string
didn't change at all. Effects that read `size` (or any params method)
re-run on every unrelated `href` write, which is #17218, where it caused
infinite effect loops in a router. The fix is a guard in the internal
replace path that bails out when the incoming params serialize
identically to the current ones.
The second is under-notification, and it's sneakier. Every read method
tracks the version signal, `get`, `getAll`, `has`, `keys`, `values`,
`entries`, `toString`, `size`, the iterator... except `forEach`, which
was never overridden. The inherited platform method reads the internal
list directly and subscribes to nothing, so a template that renders
params via `forEach` never updates:
```js
$effect(() => {
url.searchParams.forEach((value, key) => entries.push(`${key}=${value}`)); // never re-runs
});
```
We found this one in the wild while making `page.url` a `SvelteURL` in
sveltejs/kit#16031. Kit's own test suite renders query params with
`forEach`, and the coarse object-identity signal that previously masked
the gap went away, leaving stale UI after client-side navigations. The
fix is the same one-line tracking pattern every other read method
already uses.
Fixes#17218
---
- [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`
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.25.11 to
0.28.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/evanw/esbuild/releases">esbuild's
releases</a>.</em></p>
<blockquote>
<h2>v0.28.1</h2>
<ul>
<li>
<p>Disallow <code>\</code> in local development server HTTP requests (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr">GHSA-g7r4-m6w7-qqqr</a>)</p>
<p>This release fixes a security issue where HTTP requests to esbuild's
local development server could traverse outside of the serve directory
on Windows using a <code>\</code> backslash character. It happened due
to the use of Go's <code>path.Clean()</code> function, which only
handles Unix-style <code>/</code> characters. HTTP requests with paths
containing <code>\</code> are no longer allowed.</p>
<p>Thanks to <a
href="https://github.com/dellalibera"><code>@dellalibera</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Add integrity checks to the Deno API (<a
href="https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr">GHSA-gv7w-rqvm-qjhr</a>)</p>
<p>The previous release of esbuild added integrity checks to esbuild's
npm install script. This release also adds integrity checks to esbuild's
Deno install script. Now esbuild's Deno API will also fail with an error
if the downloaded esbuild binary contains something other than the
expected content.</p>
<p>Note that esbuild's Deno API installs from
<code>registry.npmjs.org</code> by default, but allows the
<code>NPM_CONFIG_REGISTRY</code> environment variable to override this
with a custom package registry. This change means that the esbuild
executable served by <code>NPM_CONFIG_REGISTRY</code> must now match the
expected content.</p>
<p>Thanks to <a
href="https://github.com/sondt99"><code>@sondt99</code></a> for
reporting this issue.</p>
</li>
<li>
<p>Avoid inlining <code>using</code> and <code>await using</code>
declarations (<a
href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>)</p>
<p>Previously esbuild's minifier sometimes incorrectly inlined
<code>using</code> and <code>await using</code> declarations into
subsequent uses of that declaration, which then fails to dispose of the
resource correctly. This bug happened because inlining was done for
<code>let</code> and <code>const</code> declarations by avoiding doing
it for <code>var</code> declarations, which no longer worked when more
declaration types were added. Here's an example:</p>
<pre lang="js"><code>// Original code
{
using x = new Resource()
x.activate()
}
<p>// Old output (with --minify)<br />
new Resource().activate();</p>
<p>// New output (with --minify)<br />
{using e=new Resource;e.activate()}<br />
</code></pre></p>
</li>
<li>
<p>Fix module evaluation when an error is thrown (<a
href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>,
<a
href="https://redirect.github.com/evanw/esbuild/pull/4467">#4467</a>)</p>
<p>If an error is thrown during module evaluation, esbuild previously
didn't preserve the state of the module for subsequent module
references. This was observable if <code>import()</code> or
<code>require()</code> is used to import a module multiple times. The
thrown error is supposed to be thrown by every call to
<code>import()</code> or <code>require()</code>, not just the first.
With this release, esbuild will now throw the same error every time you
call <code>import()</code> or <code>require()</code> on a module that
throws during its evaluation.</p>
</li>
<li>
<p>Fix some edge cases around the <code>new</code> operator (<a
href="https://redirect.github.com/evanw/esbuild/issues/4477">#4477</a>)</p>
<p>Previously esbuild incorrectly printed certain edge cases involving
complex expressions inside the target of a <code>new</code> expression
(specifically an optional chain and/or a tagged template literal). The
generated code for the <code>new</code> target was not correctly wrapped
with parentheses, and either contained a syntax error or had different
semantics. These edge cases have been fixed so that they now correctly
wrap the <code>new</code> target in parentheses. Here is an example of
some affected code:</p>
<pre lang="js"><code>// Original code
new (foo()`bar`)()
new (foo()?.bar)()
<p>// Old output<br />
new foo()<code>bar</code>();<br />
new (foo())?.bar();</p>
<p></code></pre></p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/evanw/esbuild/blob/main/CHANGELOG-2025.md">esbuild's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog: 2025</h1>
<p>This changelog documents all esbuild versions published in the year
2025 (versions 0.25.0 through 0.27.2).</p>
<h2>0.27.2</h2>
<ul>
<li>
<p>Allow import path specifiers starting with <code>#/</code> (<a
href="https://redirect.github.com/evanw/esbuild/pull/4361">#4361</a>)</p>
<p>Previously the specification for <code>package.json</code> disallowed
import path specifiers starting with <code>#/</code>, but this
restriction <a
href="https://redirect.github.com/nodejs/node/pull/60864">has recently
been relaxed</a> and support for it is being added across the JavaScript
ecosystem. One use case is using it for a wildcard pattern such as
mapping <code>#/*</code> to <code>./src/*</code> (previously you had to
use another character such as <code>#_*</code> instead, which was more
confusing). There is some more context in <a
href="https://redirect.github.com/nodejs/node/issues/49182">nodejs/node#49182</a>.</p>
<p>This change was contributed by <a
href="https://github.com/hybrist"><code>@hybrist</code></a>.</p>
</li>
<li>
<p>Automatically add the <code>-webkit-mask</code> prefix (<a
href="https://redirect.github.com/evanw/esbuild/issues/4357">#4357</a>,
<a
href="https://redirect.github.com/evanw/esbuild/issues/4358">#4358</a>)</p>
<p>This release automatically adds the <code>-webkit-</code> vendor
prefix for the <a
href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/mask"><code>mask</code></a>
CSS shorthand property:</p>
<pre lang="css"><code>/* Original code */
main {
mask: url(x.png) center/5rem no-repeat
}
<p>/* Old output (with --target=chrome110) */<br />
main {<br />
mask: url(x.png) center/5rem no-repeat;<br />
}</p>
<p>/* New output (with --target=chrome110) */<br />
main {<br />
-webkit-mask: url(x.png) center/5rem no-repeat;<br />
mask: url(x.png) center/5rem no-repeat;<br />
}<br />
</code></pre></p>
<p>This change was contributed by <a
href="https://github.com/BPJEnnova"><code>@BPJEnnova</code></a>.</p>
</li>
<li>
<p>Additional minification of <code>switch</code> statements (<a
href="https://redirect.github.com/evanw/esbuild/issues/4176">#4176</a>,
<a
href="https://redirect.github.com/evanw/esbuild/issues/4359">#4359</a>)</p>
<p>This release contains additional minification patterns for reducing
<code>switch</code> statements. Here is an example:</p>
<pre lang="js"><code>// Original code
switch (x) {
case 0:
foo()
break
case 1:
default:
bar()
}
</code></pre>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="bb9db84c02"><code>bb9db84</code></a>
publish 0.28.1 to npm</li>
<li><a
href="9ff053e53b"><code>9ff053e</code></a>
security: add integrity checks to the Deno API</li>
<li><a
href="0a9bf2135b"><code>0a9bf21</code></a>
enforce non-negative size in gzip parser</li>
<li><a
href="e2a1a71320"><code>e2a1a71</code></a>
security: forbid <code>\\</code> in local dev server requests</li>
<li><a
href="83a2cbfc35"><code>83a2cbf</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4482">#4482</a>:
don't inline <code>using</code> declarations</li>
<li><a
href="308ad745d8"><code>308ad74</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4471">#4471</a>:
renaming of nested <code>var</code> declarations</li>
<li><a
href="f013f5f99a"><code>f013f5f</code></a>
fix some typos</li>
<li><a
href="aafd6e48b1"><code>aafd6e4</code></a>
chore: fix some minor issues in comments (<a
href="https://redirect.github.com/evanw/esbuild/issues/4462">#4462</a>)</li>
<li><a
href="15300c30b5"><code>15300c3</code></a>
follow up: cjs evaluation fixes</li>
<li><a
href="1bda0c31d7"><code>1bda0c3</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4461">#4461</a>,
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4467">#4467</a>:
esm evaluation fixes</li>
<li>Additional commits viewable in <a
href="https://github.com/evanw/esbuild/compare/v0.25.11...v0.28.1">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for esbuild since your current version.</p>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/sveltejs/svelte/network/alerts).
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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>
## 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>
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
If a branch is removed from the visible dom, it may be kept around
because a subsequent batch will intro it again. If we don't resume the
effects it will stay inert and therefore not react to updates anymore
Several constant lookup tables in `utils.js` were arrays searched with
`Array.prototype.includes`, which is O(n). They're queried often — per
attribute during attribute setup and SSR, per event during event
delegation, and per identifier during compilation. Switching them to
`Set` makes each lookup O(1) without changing any public behaviour.
Ref: https://github.com/sveltejs/svelte/issues/15100
Adds the tag name to the `a11y_click_events_have_key_events` warning
given when a non-interactive element has a click handler but no keyboard
events.
This already happens in `a11y_no_static_element_interactions`, and
should probably happen in many more messages.
Fixes#14413.
This keeps the temporary raw-text hydration sentinel used by dynamic
`<svelte:element>` from becoming part of the final DOM. Hydration still
gets a marker to advance through for raw-text children, but the marker
is removed after the child renderer runs, so `<style>` and `<script>`
contents stay identical to SSR output.
Tests:
- `pnpm test hydration -t svelte-head-dynamic-style`
- `pnpm test hydration`
- `pnpm test runtime-runes -t svelte-element`
- `pnpm lint`
- `git diff --check`
---------
Co-authored-by: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
## Summary
The propagation walk in `handle_event_propagation` already calls
`event.composedPath()` at the start to find the entry index, but then
re-derives the same chain step-by-step via `current_target.assignedSlot
|| current_target.parentNode || .host`. Three property reads per
iteration is measurable on the hot event path.
Walk the captured `path` array by index instead.
## Notes on behavior
`composedPath()` is the spec-compliant snapshot of the dispatch chain:
- Same shadow-DOM crossings (slots and shadow roots are included for
composed events).
- Same `host` traversal (composed-path crosses shadow boundaries when
appropriate).
- Differs from the previous walk in one edge case: if a handler removes
a parent mid-dispatch, the snapshot-based walk continues through the
captured chain (matches native browser semantics — the previous
`parentNode` walk would have stopped at a null parent).
## Performance
Measured in real Chromium on a click through a 30-deep tree with five
delegated handlers: **~245k hz → ~277k hz** (~+13%, ~−12% per-event
time).
## Test plan
- [x] All 6006 runtime tests pass (runtime-runes + runtime-legacy +
runtime-browser)
- [x] Native shadow-DOM event tests (in runtime-browser) pass unchanged
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A committing/merging batch can have promises that were rejected (e.g. as
obsolete). We gotta "forward" this rejection, too, instead of just the
successful promise. At best it results in a uncaught rejection
(`async-branch-merge-obsolete`), at worst it means error boundaries are
not correctly displayed (`async-later-promise-fails-first`).
Solves the reproduction in
https://github.com/sveltejs/svelte/issues/18221#issuecomment-4507803845
This fixes the issue in
https://github.com/sveltejs/svelte/issues/18221#issuecomment-4497918414
where an error can create follup-up invariant errors. The batch errors,
has no chance to run otherwise (no more pending work) and is therefore
"dead". That means we need to unlink it otherwise it's becoming a
"zombie" and hangs around, causing unnecessary and potentially buggy (as
seen in the reproduction) merge/commit work.
I was not able to reduce the reproduction down to a test case that fails
without the fix, but it does make a related error test from #17888 work
more correctly.
While looking at
https://github.com/sveltejs/svelte/issues/18221#issuecomment-4497918414
and trying to understand how the invariant can happen I noticed that we
are not correctly filtering during commit.
- we were not ignoring deriveds
- we were not comparing the correct values (checking `source.v` instead
of the saved value) and not checking if their "is a derived" state
differs
I'm not able to come up with a test where something fails without these
(possibly because it's more about an optimization to do less reruns and
not about correctness) fixes, but they do make sense.
While looking at the reproduction in
https://github.com/sveltejs/svelte/issues/18221#issuecomment-4497918414
I immediately got greeted with a runtime error when running it in the
playground (weirdly not in the Stackblitz version). The error was that a
component expected a binding to be set in onMount, but the timing of
onMount was wrong.
Turns out it's because our logic to determine whether or not to defer
top level effects is flawed. `REACTION_RAN`, which was used previously,
is already set if the initialized component is inside an async block. We
instead check for `component_context.i` which is set to `true` on
`pop()`.
An effect could be gated behind a branch. If we don't defer + transfer
them upon merge, the branch would still be marked clean but the effect
behind it is dirty but no longer reachable. It's not reachable via mark
either because that one only concerns itself with block/async effects,
and the branch gating the effect is not guaranteed to be touched by
that.
Fixes#18249
Little sad side-effect: Since we cannot reliably know _before_ traversal
if we have no blocking pending work left (the traversal could mark an if
block falsy which contains the last blocker), we gotta undo a
performance optimization.
Thanks to
https://github.com/sveltejs/svelte/issues/17940#issuecomment-4480016550
I was finally able to isolate and reproduce a false-positive invariant
error. I had a hunch this could happen and this shows it. Essentially,
you can end up in situations where two batches are scheduled to run in
the same microtask queue flush, and if the first rebases the second the
invariant will throw, which is wrong. We can avoid this by checking if a
decrement is queued.
This started out as an investigation to get rid of the runtime code for
`{#await ...}` that deactivated a batch prior to reading the promise
function. That can result in a new batch being created if the promise
invocation happens to write a source.
Through that I discovered two other bugs:
1. The way we handle `{#await await ...}` was flawed around
SSR/hydration: On the server it would await the expression (which it
should not; `{#await await ...}` is kind of a weird special case here)
and during hydration it did not produce matching nodes, leading to
hydration fails.
2. When reading a dependency after an await expression which we add to
the current reaction, we did not deduplicate those reads. That can lead
to duplicate dependencies, which in turn can lead to bugs when
`remove_reaction` later runs. Conside this: You have `deps = [count,
unrelated, count]`. Now you do `remove_reactions(deps, 1)`, i.e. "remove
all reactions after the first one". That means the "disconnect these
from each other" logic runs for `count`, too, because it's also in the
third position, but that is wrong because it is also in the first
position, i.e. the connection should be kept.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.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. Fixes#10031.
- [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`).
### What this changes
Server attribute template generation currently wraps each dynamic
expression in `$.stringify`, even when the compiler can prove the
expression is a string or a known constant. This reuses the existing
scope evaluation metadata so server output can avoid `$.stringify` for
proven string/constant chunks while keeping it for possibly nullish
unknown values.
The updated snapshot covers a mixed attribute with a known string,
mutable state, `null`, numeric/undefined constants, a known
string-producing `typeof`, and an unknown prop value.
### Tests and linting
- [x] `pnpm test snapshot -t nullish-coallescence-omittance`
- [x] `pnpm test snapshot`
- [x] `pnpm --filter svelte check`
- [x] `pnpm lint`
- [x] `pnpm prettier --check .changeset/slow-bikes-serve.md`
---------
Co-authored-by: Rich Harris <hello@rich-harris.dev>
Fixes#18206 and fixes#18207 — both are printer bugs in
`packages/svelte/src/compiler/print/index.js`.
## Changes
### Fix 1: `svelte:body` crashes the printer (#18206)
`SvelteBody` was missing from the visitor map, causing a crash with
`Error: Not implemented: SvelteBody`. Added the handler (same one-liner
pattern as `SvelteDocument`, `SvelteHead`, etc.) and added `SvelteBody`
to the `is_block_element` check so whitespace is handled consistently.
### Fix 2: Keyframe percent stops print as `0%%` (#18207)
`Percentage.value` already includes the `%` sign (captured by
`/\d+(\.\d+)?%/y`), but the printer was writing `` `${node.value}%` `` —
appending a second `%`. Changed to `context.write(node.value)` to match
the `Nth` printer pattern directly above it.
Also updated the existing `style` snapshot which had `50%%` (the bug was
silently baked in), and added dedicated test samples for both fixes.
This isn't _really_ a fix since these should never surface to the user,
but it's useful for debugging when they do, as in
https://github.com/sveltejs/kit/pull/15779. Instead of seeing `Symbol()`
we see e.g. `Symbol(uninitialized)` which makes it easier to understand
where a bug is coming from.
The logic was flawed - a teardown effect only has a teardown function
but not `fn` property, but unfreeze thought that everything with a
`teardown` needs to be unfreezed
Helps with #18221 (though likely doesn't fix it completely, at least not
the more general `batch.#roots`problems)
Turns out there are a few unavoidable cases where we have to execute the
derived even if we otherwise wouldn't, because of its lazy nature.
Fixes#18139
Our `run` function which executes top level awaits (and synchronous
statements in-between/after) did not unset the context in time in case
the function returns an async value. In that case the context was still
around until the that promise resolves, which can be too late because
unrelated things can be intertwined with the batch.
The test shows this: Without the fix, the unrelated count incrementation
would not update the view until the top level awaits in the child are
done. In the test this just shows as a delayed visual update, but it
also can result in stale roots as shown in
https://github.com/sveltejs/svelte/issues/18221#issuecomment-4470921077
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`](a16ebc67bb))
- chore: bump devalue
([#18219](https://github.com/sveltejs/svelte/pull/18219))
- fix: disallow empty attribute names during SSR
([`547853e2406a2147ad7fb5ffeba95b01bd9642da`](547853e240))
- fix: harden regex
([`d2375e2ebcab5c88feb5652f1a9d621b8f06b259`](d2375e2ebc))
- fix: move Svelte runtime properties to symbols
([`e1cbbd96441e82c9eb8a23a2903c0d06d3cda991`](e1cbbd9644))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
#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 <simon.holthausen@vercel.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
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 <hello@rich-harris.dev>
This is another attempt to address some of the tricky edge cases that
arise when async batches resolve out of order. The idea is this:
Essentially, when a batch resolves:
1. we find the latest batch it shares changes with
2. if none exists (either because this is the earliest batch, or because
it is independent of any earlier batches):
- we commit it: effects are flushed, `oncommit` callbacks are run
- we restart any async work in later batches that depends on both the
committed values and the later batch's changes
3. otherwise, we don't commit the batch. instead:
- we merge the changes from the later batch onto the earlier batch. in
some cases this may mean restarting async work on the earlier batch, but
we avoid doing so unnecessarily.
- we then `#process()` the earlier batch. if it resolves, goto 1
This feels like it ought to work. There are still two failing tests,
which I'm currently looking into.
Notable changes:
- Instead of having a `batches` set, we have a linked list. When a batch
resolves, this makes it easy to find the batch that it should be merged
into
- The `#is_deferred` logic now takes account of skipped effects —
there's no need to wait for a promise inside a falsy `if` block (this
accounts for the change in the `async-inner-after-outer` test)
- We no longer care about `#blockers`
I would like to believe that this approach will allow us to simplify and
delete some code, for example the `rebase` logic, though that remains to
be seen. It also feels like some version of #18035 would be helpful.
- closes#18189
- closes#18162
- fixes https://github.com/sveltejs/kit/issues/15431
### 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 H <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
Realised that we can make the logic in `#process()` a little easier to
follow by early-returning if a batch is deferred (or blocked) after
processing, since nothing that happens after the `if` block applies in
that case
Noticed that we're not actually doing anything with `source_stacks` — we
shadow the module-level declaration in `flush`, which means we just keep
appending to it and then clearing a different (and empty) set. As a
result, any source that ever gets an `updated` property never gets rid
of it. This probably causes a memory leak?
Anyway, this fixes it.
### 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`
Fixes a regression of #18170 (not released yet therefore no changeset).
`current_batch` is nulled out if the `#commit` rebases other branches,
and that can lead to nullpointers down the line.
No test right now but it's part of getting the failing SvelteKit test
passing.
This incorporates some of the fixes and insights from #18177, but gets
rid of the `skip` logic.
Instead, we differentiate between _stale_ and _obsolete_ promises. A
promise is stale if it has been overtaken by a subsequent update, and
was rejected with `STALE_REACTION`:
```ts
async function search(query: string) {
return fetch(`/search?q=${query}`, { signal: getAbortSignal() }).then((r) => r.json());
}
```
In this case, if we start typing `pot`, and then finish typing `potato`,
the first promise will eventually resolve with the results for
`/search?q=potato`, instead of the batch entering a weird limbo/zombie
state.
A promise is obsolete if it belongs to a now-destroyed effect, meaning
that toggling `show` doesn't result in an accumulation of
never-resolving batches:
```svelte
{#if show}
{await neverResolves()}
{/if}
```
Fixes part of https://github.com/sveltejs/kit/issues/15431
---------
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
We shouldn't continue executing async work where we know the surrounding
branch is destroyed already, it can leave to noisy "derived inter"
warnings or even runtime errors ("cannot stringify symbol" when running
a template effect with an uninitialized source). Neither should we warn
about waterfalls on an already-destroyed async effect.
Fixes#18097 (though strictly speaking that particular instance is also
fixed by #18117 which fixes the underlying cause for the reruns; this
one is necessary in itself though, as shown by the new test)
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
These might have been necessary at one point, but I'm confident they're
unnecessary now — `increment_pending` happens (if necessary) inside
`flatten`, which is called inside `async` and
`deferred_template_effect`, so there's no need to call it inside those
functions as well.
Don't have a test for it and no bug report but I stumbled upon this and
I'm very certain not restoring context here is wrong since it means the
failed snippet rendering gets the wrong context.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
This is a regression from #18117 - we moved `this.#commit()` higher up
but that means that `current_batch` could be nulled out / overridden
through `batch.activate/deactivate` / blocker runs inside `#commit()`.
Therefore restore the previous value afterwards. No changest because
#18117 is not released yet.
Fixes the other part of the failing SvelteKit `query.live` test.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>