`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>
Small typo in `CONTRIBUTING.md` — "the most important and active ones
repositories" reads like a leftover from an edit. Removed the dangling
"ones".
```diff
-PRs to the most important and active ones repositories get reviewed more quickly
+PRs to the most important and active repositories get reviewed more quickly
```
Closes#18022
This updates the context docs to show the getter pattern for state that
may be reassigned, such as primitive state. The new example passes a
function through context so children read the current value instead of
capturing the initial value.
This is a docs-only clarification and does not change runtime behavior.
Validation:
- `git diff --check`
- `pnpm exec prettier --check
documentation/docs/06-runtime/02-context.md`
Full `pnpm test`/`pnpm lint` were not run because this only changes
documentation.
---------
Co-authored-by: Rich Harris <hello@rich-harris.dev>
### 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>