- 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)