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>
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>
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`
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>
The fix in #17966 wasn't quite right, because we gotta rethrow in case
the iterator stopped because of an error. Fixes part of the SvelteKit
`query.live` test failure.
---------
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Closes#18168
Not sure if there's a deeper issue in play because the error it's only
really there if you also add a title to the component. I think the issue
is that with multiple arguments the top level `Promise.all` is not
wrapped in `save` and that probably causes a race condition with `title`
that sets the context back to `null` in a `finally`.
One issue is that now the generated code looks like this
```js
const [$$0, $$1] = (await $.save(Promise.all([
(async () => (await $.save(user()))().name)(),
(async () => (await $.save(user()))().image)()
])))();
```
which seems a bit redundant, but I'm not sure if we can get rid of the
inner `save` since they are indeed awaiting something.
While looking into #18162 I found an adjacent bug. Currently, if an
async derived resolves in batch 2 before it resolves in batch 1, we
reject the promise belonging to batch 1 and by extension the batch
itself. This means that any other changes in batch 1 are silently
discarded, incorrectly.
The fix is almost comically simple: rather than rejecting the earlier
promise, we just resolve it with the latest value.
I have a hunch that this might also enable us to simplify the rebase
logic, though I haven't investigated that in this PR.
### 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`
We had logic in place to ignore errors of `$inspect` effects that are
about to destroy, but we didn't take into account that we can get these
transient errors while checking for `is_dirty` in preparation for
running the effect, too. Now effects are marked as dirty in case an
error occurs while evaluating their dependencies, which guarantees we
will see the error again but we can then handle it properly.
Fixes#15741
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
While working on #18106 I noticed that we're not adding eager effects
inside `mark_reactions` when `DEV` is `false`. As a result production
build could have `$state.eager` or `$state.pending` not working
correctly.
~No test because I can't get Vitest to not run with `DEV` being `true`.~
added a test
It's possible to rebase just-created batches.
Case A:
- batch A runs effects
- one of these effects writes to a source. This creates a new batch B
- an effect _after_ that (still part of "flush effects of batch A")
executes a derived. This creates an entry in the `current` Map in batch
B
- batch A commits after processing batch B (`next_batch` etc logic),
batch B is pending. Due to derived being part of batchB.current batch A
can wrongfully think these are connected and try to rerun/add effects
etc on batch B
Case B:
- like case A but with an additional await inside a pending snippet
Case C:
- batch A with source a and b, it flushes effects
- one of these effects schedules batch B with b and c scheduling an
async effect
- batch B is deferred
- batch A commits. Due to the a/b/c partial overlap it will needlessly
rerun the just scheduled async effect
All these cases are wrong. We fix it like this:
1. we call `this.#commit()` _before_ running the new batches, which may
stick around due to having pending work, and we don't want to rebase
these. This fixes case A and C
2. we capture derived values in `previous_batch` if it exists, because
it means we're currently flushing effects, and derived writes belong to
that batch and not a new one that might have been scheduled already.
This fixes case B
Discovered this while working on #18097
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Fix#18132
This PR treat lazy fallbacks on `prop()` as derived. Now a default
function that uses a $state is recalculated whenever its dependents
changes. This change implies that this lazy functions cannot mutate a
state anymore (because it is derived), causing a
`state_unsafe_mutation`error. This implies on a breaking change, but
reasonable.
---
### New breaking change here
- **Who does this affect**: Everyone that has updated a $state on a
default lazy prop. Example:
```html
<script>
let myValue = $state(0);
let callCount = $state(0);
function getValue() {
callCount++; // causes a state_unsafe_mutation error
return myValue; // returning a state doesn't cause an error, and now it is tracked as a dependency
}
let { value = getValue() } = $props();
</script>
```
**Why make this breaking change**
This encourages people to not update states on a function that
fundamentaly, is readonly. When someone wants to use a default function
expecting that it should be tracked, its not likely that this function
will change some state. It is anti-pattern to change some state inside a
getter function.
But what if someone wants to do it, like in the code above?
The code above doesn't make sense before this PR, the old way to
calculate lazy functions is to execute it one time, and only one, so the
`callCount` variable will never change. But let's assume that someone
did it, how to migrate?
The migration in same example is easy, since the `callCount` is executed
only once, it will not be executed after the component is mounted. So
the `callCount` doesn't need to be a state, the `callCount` will be in a
valid state when the component is created. So here is the migrated code:
```html
<script>
let myValue = $state(0);
let callCount = 0;
function getValue() {
callCount++; // doesn't causes an error
return myValue;
}
let { value = getValue() } = $props();
</script>
```
As we can see, there is no reason for the variable `callCount` in this
example (before this PR), and if someone did it, it is more likely that
they used a constant instead:
```html
<script>
let myValue = $state(0);
let callCount = 1;
function getValue() {
return myValue;
}
let { value = getValue() } = $props();
</script>
```
There is another example that causes the `state_unsafe_mutation` and how
to fix (this happened on the tests that i changed):
```html
<script>
let log = $state([]);
function fallbackExample => {
log.push('fallback called');
return 1; // any value, just to show the issue with the log
}
let { value = fallbackExample() } = $props();
</script>
```
Here, we can see that `log` variable is a state. Before this PR, as i
said, this function `fallbackExample` will be executed once. So the logs
will be computed when the component is mounted. So there is no reason to
make the `log` a state. The simplest way to fix this is to make it a
normal variable:
```html
<script>
let log = [];
</script>
```
But with this PR, the function might be recalculated at some point, and
the `log` with a state makes sense now, so how to migrate in this case?
As i said, changing a state inside a lazy prop function is not a good
practice, we can think in a way to invert this dependency, and change
the approach from push (imperative mutation) to pull (declarative
derivation).
If a developer really needs to track how many times a fallback is
executed or react to its changes, they should use a $derived or an
$effect that observes the same dependencies as the fallback, or simply
observe the property itself:
```html
<script>
let { value = fallbackExample() } = $props();
let log = $state([]);
$effect(() => {
const message = `${value}`;
untrack(() => { // we don't want to track changes on the log variable
log.push(message);
});
});
</script>
```
### After all, how to migrate?
1. **If there is no state mutation inside the prop function, no need to
changes**;
2. **If there is a state mutation inside the prop function, but the
value muted is declared inside the same component:** remove the state
from it. Before this PR the method will be executed only once, and to
get the same result, you do not need the variable to be a state;
**Before**
```html
<script>
let log = $state([]);
const fallback_fn = () => {
log.push('fallback_fn');
return 1;
}
const { myProp = fallback_fn() } = $props();
</script>
```
**After**
```html
<script>
let log = [];
const fallback_fn = () => {
log.push('fallback_fn');
return 1;
}
const { myProp = fallback_fn() } = $props();
</script>
```
3. **If there is a state mutation inside the prop function, and the
value is read in multiple places:** change your approach, use a effect
to detect the change on the prop, and apply your mutation inside the
effect;
Before:
```html
<script>
import { setLog } from './logs.js'; // setLog apply a mutation on a state
const fallback_fn = () => {
setLog('fallback_fn');
return 1;
}
const { myProp = fallback_fn() } = $props();
</script>
```
After
```html
<script>
import { setLog } from './logs.js'; // setLog apply a mutation on a state
const fallback_fn = () => {
return 1;
}
const { myProp = fallback_fn() } = $props();
$effect(() => {
const message = `${myProp}`;
untrack(() => {
setLog(message);
});
});
</script>
```
### Severity (number of people affected x effort): Low
- **Affected Users:** Minimal. Mutating state inside a property
initializer is a rare edge case and considered an anti-pattern (because
its a side effect inside a getter). Most users use constants or pure
functions for fallbacks.
- **Migration Effort:** Low. As demonstrated in the examples above, the
fix usually involves either removing an unnecessary $state or moving the
side effect to its proper place, the $effect
### Conclusion
This PR encourages users to program in a better way. Forcing a clean
separation between data and their side effects. The developer can use
this new feature mainly in i18n services, providing better usability and
experience. Also, this PR makes the properties more predictable, since
the expected behavior is that it works reactively, eliminating this bug
for future developers.
Even though this PR adds a breaking change, it's easily solvable, and
the chance of any user facing this problem is low.
**Full example to test reactivity in props** (won't work on web, you can
get the PR and test localy to see it working):
https://svelte.dev/playground/a6608434d8c642179f0e2b72468c74d7?version=latest
*A unit test for this reactivity was created:
runtime-runes/props-default-value-reactivity*.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Rich Harris <hello@rich-harris.dev>
The logic of checking that the current batch is still the generated one
is flawed. If microtasks align the current batch can be a different
value even if the batch still need to be flushed. This therefore
switches the heuristic to what it actually should express: "has this
batch run already?"
Fixes#18126 (because the batch isn't running, it later runs into the
invariant)
Fixes#18134.
## Problem
`read_value()` in
`packages/svelte/src/compiler/phases/1-parse/read/style.js` has no logic
to skip CSS block comments (`/* ... */`). When the parser encounters an
apostrophe inside a comment, it sets `quote_mark = "'"` — treating it as
the start of a string literal — then never finds a matching closing
quote, and ultimately throws `unexpected_eof` at the end of the style
block.
Minimal repro:
```svelte
<style>
/* it's a comment */
.foo { color: red; }
</style>
```
→ `Error: Unexpected end of input`
## Fix
Add a `/* ... */` skip path inside the `read_value` loop, mirroring the
same pattern already used in `allow_comment_or_whitespace`. When `/*` is
detected outside a string or url context, the parser advances past the
entire comment without adding its content to the value string.
---------
Co-authored-by: Dor Alagem <doralagem@MacBook-Pro-sl-Dor.local>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Fixes#18145
I wonder why nulling happens after updating `bind:this` but not before,
which would fix the issue as well, though not as efficiently.
### 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`
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.5
### Patch Changes
- fix: don't mark deriveds while an effect is updating
([#18124](https://github.com/sveltejs/svelte/pull/18124))
- fix: do not dispatch introstart event with animation of animate
directive ([#18122](https://github.com/sveltejs/svelte/pull/18122))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Fixes#18123
This makes setting state inside effects slightly slower theoretically
(since they hit the new guard), but I verified that the original issue
for which we introduced this (#16658) is still fast with this change.
The more we add logic to this the more I think we should investigate
switching to a different mechanism. I tried using a `Set` previously but
it did hurt the benchmarks a bit - might try to revisit a variant of
this.
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
closes#18056
related: #17567 and #14009
# Changes
move `dispatch_event()` calls in `transitions.js` out of `animate()`
function using an additional `on_begin()` callback parameter. Doing so
makes it possible to dispatch the `introstart` and `outrostart` events
only from `transition()`.
# Testing
add a test checking that svelte dispatches no event when it runs an
animation
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.4
### Patch Changes
- fix: never mark a child effect root as inert
([#18111](https://github.com/sveltejs/svelte/pull/18111))
- fix: reset context after waiting on blockers of `@const` expressions
([#18100](https://github.com/sveltejs/svelte/pull/18100))
- fix: keep flushing new eager effects
([#18102](https://github.com/sveltejs/svelte/pull/18102))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
A nested `$effect.root` was marked `INERT` during `pause_children`,
which caused it to stay in that state indefinetly after the rest of the
parent tree was destroyed. Consequently deriveds inside no longer update
and cause warnings.
This fixes it by not marking nested `$effect.root`s as inert, just like
nested `$effect.root`s are not destryoed and instead become a new root.
Fixes#18097
Regression from #18039 - we need to have each await expression (and
waiting on blockers is one) in its own entry of `(renderer.)run`. Else
context is not restored correctly and if the synchronous expression
afterwards requires it stuff breaks.
Fixes#18098
The previous code "swallowed" new additions to the array of eager
effects that happened while flushing since `eager_flush` did not clear
the array before running, only afterwards. Now it clears the beforehand,
causing newly added eager effects to run, too.
An example where this can happen is `$state.eager` and `$effect.pending`
in combination: first `$state.eager` is flushed, then due to `flushSync`
the `queue_micro_task` inside `boundary.js` that flushes
`$effect.pending` is triggered synchronously, adding new entries to the
`eager_versions` array. If they're only cleared at the end of
`eager_flush`, new entries are swallowed.
Related to #18095 (but not fixing it yet)
Avoids two categories of false positives for reactivity loss warning:
1. if you have synchronously read signals already as part of invoking
the async_derived function, then it shouldn't warn when these signals
are read after an await if we know they haven't changed (we check the
write version for that)
2. `track_reactivity_loss` kept the `reactivity_loss_tracker` around
indefinitely, both when invoking the async operation as well as when
it's finished. The former is buggy because while the async operation
happens unrelated reads as part of other reactivity work can happen, the
latter is buggy because if it's the last in a chain of awaits it's kept
around until the next async work starts.
Fixes https://github.com/sveltejs/kit/issues/15654
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Per
https://github.com/sveltejs/svelte/pull/17862#issuecomment-4049752548,
this freezes the value of a derived if it was created inside a parent
effect that is now destroyed. This prevents the sort of bug where a
derived reads `foo.bar` even though `foo` is now `undefined`.
If the derived is dirty, a warning will be printed.
This PR also gets rid of some weirdness around `derived.parent` — it can
only ever be an `Effect | null`, and there's no need for
`get_derived_parent_effect`.
Blocked on https://github.com/sveltejs/kit/pull/15533 and a follow-up
that switches remote functions to use `$effect.root` (since this
effectively undoes #17171), hence draft.
### 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: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Move the calculation of blockers into the analysis phase and then only
push the right thunks in the transform phase - similar to how we already
do it with top level `$.run`
Fixes#18024
The solution is a tiny bit brittle (not much more than the top level one
we already have) and I tried to make it a bit more robust but ended up
in a rabbit hole in #18032 - we can revisit that solution once all the
old stuff is gone. Until then this is the most pragmatic/non-invasive
change.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
The BranchManager in `branches.js` does create a temporary anchor when
creating a new branch offscreen, and deletes it once the branch is
committed. Normally this is fine, but the combination of HMR and dynamic
components leads to a bug: Since `svelte-component.js` passes the
temporary anchor along to the component it generates, which is the HMR
wrapper, this wrapper will have an obsolete, disconnected anchor on
updates, leading to the content disappearing.
The fix is to add a dev-only symbol which we set on the original (then
obsolete) anchor to tell about the updated anchor that should be used
for HMR updates.
Fixes https://github.com/sveltejs/kit/issues/14699Fixes#17211
RIght now, when an async error occurs inside a fork, the error UI will
show immediately. This change defers the removal of the current content
etc until the fork is committed. The BranchManager logic cannot be
reused here since this is not about pending work, and we cannot wait for
other pending work to complete if the fork commits. Instead, batches now
have a "on fork commit" callback set which the boundary pushes to. The
boundary's effects are skipped in the fork until it's committing, at
which point we "resume" the error logic (calling onerror, transforming
it, etc)
Fixes#18060
While looking into #17954 I realised that a) our code for handling
parentheses in expressions is unnecessarily convoluted and b) it doesn't
handle the case where you have an opening parent outside the first
comment — this fails to parse:
```svelte
{(/**/ 42)}
```
This fixes it and simplifies the code a good bit.
### 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#17571 where the situation is the following:
A derived creates a new query. That query initializes loading with true.
This means the if block is marked for destruction (therefore effects
inside branch are skipped), but it's not doing that yet because the
query promise is pending. Then query resolves and loading is set back to
false right before resolving, but it's not the same tick so
`loading=false` is a separate thing. Because that later batch doesn't
see any overlap with an earlier batch (the earlier batch did set loading
to true but not via set but indirectly via recreating the query) it
doesn't wait on it and flushes right away. Now the if block is marked as
visible again but the earlier batch doesn't know that if noone unskips
its branch. If we don't do that the render effect that is now dirty as
part of that batch will not run.
Fixes part of #17940 (the hydration->error thing still needs a repro).
Essentially in sync mode render effects are executed during traversing
the effect tree, and when flushSync is called during that it can cause
the timing of things getting out of sync such that you end up wanting to
rebase another branch, which is never needed in sync mode.
So we just skip that logic in sync mode. Another way would be to adjust
flushSync such that it does appends itself to the end of the current
flushing cycle if it notices processing is already ongoing. This will
not work when you pass a callback function to `flushSync` though -
another indicator that we should probably remove that callback argument.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Rich Harris <hello@rich-harris.dev>
If a new branch is created (e.g. if block becomes truthy) inside a fork
and a derived is read inside the new branch for the first time, it will
get reactions but its true value will stay uninitialized due to the
fork. If the fork then commits, it will NOT change its value because it
will not reexecute: there is no effect running after commit telling it
to do that, because it was only read inside new effects which already
ran while still inside the fork.
Now, when that branch is removed (e.g. if block becomes falsy), the
derived loses its reactions again and becomes disconnected, at which
point the status is reset. So we end up with a maybe_dirty or clean
derived and an uninitialized value, causing breakage if it's called
again afterwards.
The fix is to not reset the status in this case.
Fixes https://github.com/sveltejs/kit/issues/15126
Fixes https://github.com/sveltejs/kit/issues/15318
Fixes https://github.com/sveltejs/kit/issues/15061
`<details>` elements fire `ontoggle` as ToggleEvents
([source](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/toggle_event)),
but they're currently just typed as Event.
### 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:`.
- [ ] 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 started out as me implementing
https://github.com/sveltejs/svelte/pull/17998/changes#r3018047965, but
then I realised that I'd also fixed the bug that #17998 addresses. So I
guess it's an alternative to that PR
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
## Description
**While investigating the compiler analysis phase for errors or missing
lines, I located an explicit `TODO fix the message here` comment in the
source code
(`packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js`).**
### Triggering Code
```svelte
<script module>
import { foo } from './somewhere.js';
</script>
<script>
let foo = 'conflict'; // Triggers the duplicate module import error
</script>
```
## The Bug
When a component author creates a local `let` declaration in the
instance `<script>` block that shadows a variable imported in the
`<script module>` block, the compiler emits a
`declaration_duplicate_module_import` error.
However, the error message text was misleading. It stated:
```diff
- Cannot declare a variable with the same name as an import inside `<script module>`
+ Cannot declare a variable with the same name as an import from `<script module>`
```
This incorrectly implies that the duplicate declaration itself is
happening *inside* the module script block, rather than colliding with
an import *from* that block.
## Changes Made
| Action | Target (File / Function) | Description |
| :--- | :--- | :--- |
| **Template Fix** | `packages/svelte/messages/compile-errors/script.md`
| Changed "inside" to "from" to accurately reflect the nature of the
shadow collision. |
| **Regenerated Errors** | `packages/svelte/src/compiler/errors.js` |
Ran `pnpm generate` to apply the updated markdown template into the
dictionary list. |
| **Removed TODO** | `ensure_no_module_import_conflict` function |
Removed the original `// TODO fix the message here` notation from the
validator. |
| **Test Adjustments** | `illegal-variable-declaration/errors.json` |
Updated the validator test suite snapshots to assert against the correct
error message format. |
## Validation Results
* The error correctly throws when a user replicates this component
structure.
* Vitest suite `tests/validator/test.ts` successfully asserts the new,
clearer message.
---
### 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`
# THANK YOU
Closes#18012
We were removing `superTypeArguments` but the AST showed
`superTypeParameters`.
Luckily, I was able to pinpoint `esrap@2.2.4` as the cause... I've only
bumped `esrap` and that made the test fail (so that I could fix it)
And of course it was that...there's literally a commit that explicitly
print them lol
f9137c4101
If a batch creates a new branch (e.g. through an if block becoming true)
the previous batches so far do not know about the new effects created
through that. This can lead to stale values being shown. We therefore
schedule those new effects on prior batches if they are touched by a
`current` value of that batch
Fixes#17099
extracted from #17971
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.0
### Minor Changes
- feat: export TweenOptions, SpringOptions, SpringUpdateOptions and
Updater from svelte/motion
([#17967](https://github.com/sveltejs/svelte/pull/17967))
### Patch Changes
- fix: ensure HMR wrapper forwards correct start/end nodes to active
effect ([#17985](https://github.com/sveltejs/svelte/pull/17985))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Fixes#17982 (two issues reported there)
Adding the start/end statically at the end once does not work because
it's going to be stale when there's a HMR reload of the wrapped
component. For reasons not completely clear to me it also fails in
another case.
So instead of wrapping the HMR with comments we just forward the nodes
of the inner effect to the outer active effect, pretending the wrapper
isn't there from a "remove dom nodes"-perspective.
Exports `TweenedOptions`, `SpringOpts`, `SpringUpdateOpts`, and
`Updater` from `svelte/motion`.
These types are required for the public method signatures of `spring`
and `tweened` (e.g., as parameters for `.set()` and `.update()`). This
PR makes them accessible to TypeScript users, following the established
pattern in modules like `svelte/store` and `svelte/transition`.
Internal implementation details like `TickContext` remain private as
they do not appear in any public-facing signatures.
Fixes#16151
### 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: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Rich Harris <hello@rich-harris.dev>
Fixes#17720
## Problem
The `{@const}` tag was being printed with a trailing semicolon,
producing invalid Svelte syntax like:
{@const a = 1;}
This happened because the `ConstTag` visitor in the printer was
delegating to esrap's `VariableDeclaration` handler, which always
appends a semicolon (correct for JS, but wrong for Svelte template
syntax).
## Solution
Instead of delegating to `VariableDeclaration`, the `ConstTag` visitor
now manually prints the tag by:
- Writing `{@const ` directly
- Iterating through declarators and visiting each one
- Separating multiple declarations with commas
- Closing with `}` — no trailing semicolon
## Before
{@const a = 1;}
{@const a = 1, b = 2;}
## After
{@const a = 1}
{@const a = 1, b = 2}
## Changes
- `packages/svelte/src/compiler/print/index.js` — fixed `ConstTag`
visitor
- `packages/svelte/tests/print/samples/const-tag/output.svelte` —
updated expected test output
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Rich Harris <hello@rich-harris.dev>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
## Fix: #17881
### Root Cause
When a dynamic component switches (e.g. `<Component />`), branch effects
are destroyed via `destroy_effect()`.
However, the `effect.b` (Boundary reference) field was not cleared
alongside other references (`next`, `prev`, `ctx`, `deps`, `fn`,
`nodes`, `ac`).
As a result, destroyed effects retained a reference to the `Boundary`
instance, which holds references to component state and child effects.
This prevented destroyed component subtrees from being garbage
collected, causing memory usage to grow during repeated component
switching.
### Solution
Clear the boundary reference during effect destruction.
```diff
effect.next =
effect.prev =
effect.teardown =
effect.ctx =
effect.deps =
effect.fn =
effect.nodes =
effect.ac =
+ effect.b =
null;
```
### Test Plan
* All existing tests pass:
* runtime-runes: 2,459
* runtime-legacy: 3,294
* signals: 96
* Total: 5,849 tests
* Added a dynamic component switching test that toggles components
repeatedly and verifies correct DOM output.
### Validation
Reproduced the issue using the example from #17881.
After applying the fix:
* Memory usage stabilizes during repeated component switching
* Destroyed components are properly reclaimed by the garbage collector
* No behavioral regressions observed
### Impact
* Fixes memory leak in dynamic component switching
* Minimal, safe change
* No API or behavior changes
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
We were just putting each statement into its own promise. Besides this
being bad for perf, it also introduces subtle timing issues - the
execution order of the code could change in bad ways. Fixes#17940
Closes#17972
Claude found this fix I had a look and I think it makes sense (we are
injecting a new comment which messes up the marching during hydration).
I'm slightly confused why this only applied to `css_props` but I guess
that's because there's an "hidden" element there which makes it special.
I don't super-like the `if` situation, but I guess it is what it is.
Also the test was using `hmr` without `dev` and this brought to my
attention that we were just assuming components return something while
it's not the case...I guess it doesn't really matter unless you are
using `hmr` in prod which is not a thing but fixing this is simple so we
might just as well doing it
This fixes an awkward bug with `each` blocks containing `await`,
especially keyed `each` blocks.
If you do `array.push(...)` multiple times in distinct batches,
something weird happens — to the `each` block, the array looks this...
```js
[1]
```
...then this...
```js
[undefined, 2]
```
...then this...
```js
[undefined, undefined, 3]
```
...and so on. That's because as far as Svelte's reactivity is concerned,
what we're _really_ doing is assigning to `array[0]` then `array[1]`
then `array[2]`. Those (along with `array.length`) are each backed by
independent sources, which we can rewind individually when we need to
'apply' a batch. When it comes to sources that actually _are_
independent, this is useful, since we apply the changes from batch B to
the DOM while we're still waiting for a promise in batch A to resolve.
But in this case it's not ideal, because you would expect these changes
to accumulate.
In particular, this fails with keyed `each` blocks because duplicate
keys are disallowed (assuming the key function doesn't break on
`undefined` _before_ the duplicate check happens).
This PR fixes it by stacking batches that are interconnected.
Specifically, if a later batch has some (but not all) sources in common
with an earlier batch, then when we apply the batch we include the
sources from the earlier batch, and block it until the earlier batch
commits. When the earlier batch commits, it will check to see if doing
so unblocks any later batches, and if so process them.
In the course of working on this I realised that `SvelteSet` and
`SvelteMap` aren't async-ready — will follow up this PR with ones for
those.
Fixes#17050
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.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.54.0
### Minor Changes
- feat: allow `css`, `runes`, `customElement` compiler options to be
functions ([#17951](https://github.com/sveltejs/svelte/pull/17951))
### Patch Changes
- fix: reinstate reactivity loss tracking
([#17801](https://github.com/sveltejs/svelte/pull/17801))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Alternative to #17950. Closes#17952
The goal of this is to allow svelte.config.js to contain functions for
setting certain options, so that there's a single source of truth for
everything that needs to interact with Svelte config (plugins, editor
extensions, etc):
```js
// svelte.config.js
export default {
compilerOptions: {
css: ({ filename }) => filename.endsWith('/OG.svelte') ? 'injected' : 'external',
experimental: {
async: true
},
runes: ({ filename }) => !filename.split(/\/\\/).includes('node_modules')
}
};
```
Once this ships, we can deprecate `dynamicCompileOptions` in
`vite-plugin-svelte`.
### 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`
We commented out this code in #17038 because it was broken. I suspect it
was broken because we weren't correctly calling `unset_context` inside
`run`, leading to false positives — this is now fixed, and as such I
_think_ we can safely reinstate it.
One small change — I got rid of the `was_read` check. I assume this
existed to prevent duplicate warnings, but it actually causes false
negatives in the case where you read a signal while the reaction is
being tracked then again while it's untracked.
### 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`
---------
Co-authored-by: Tee Ming <chewteeming01@gmail.com>
The offscreen branch was missing the "resume inert effects" logic that
was just below; it never reached that because of the early continue.
Fixes#17851
Fixes
https://github.com/sveltejs/svelte/issues/17918#issuecomment-4054067024.
The issue here was that in `boundary.#render`, if `this.#pending_count >
0` we yoink the content out of the DOM so we can replace it with the
`pending` fragment. This works by taking everything from
`effect.nodes.start` to `effect.nodes.end` and putting it in a
`DocumentFragment`.
With HMR, that doesn't work, because the effect with the nodes is buried
inside the HMR effect. This fixes it.
Draft because I'd like to try this out in a few more places before
merging.
Our "hey this is a thunk invoking a function, let's flatten that" logic
caused a bug where lazily-initialized functions where eagerly referenced
in the template effect. That causes a nullpointer.
Ensuring these variables are always referenced in a closure fixes#17404
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Batches that are made stale (because of a `STALE_REACTION`) can end up
sticking around indefinitely, forcing every subsequent batch into
time-traveling mode and causing incorrect `previous` values to be
rendered.
This partially fixes it, by discarding any older batches that are
subsets of a batch currently being committed. It's not a complete fix,
though — if an earlier batch is stale but is _not_ a subset of the
committed batch, it becomes a zombie, and its changes will never be
applied. Haven't quite figured out how to think about that yet.
### 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`
Before #17805, all batches drew from the same `queued_root_effects` and
did reset them to the empty array when starting a flush. After the
refactoring roots are scheduled per batch. This introduces a possible
race condition where the same root is scheduled multiple times. It was
possible because of the rebase logic in `#commit` not clearing the array
of roots, so if you somehow flush that same batch later, you will end up
traversing a clean root.
(it is possible a bug like this always existed with rebasing it was just
impossible hard to trigger it before because everyone drew from the same
root effects array)
The fix is a bit more complicated than just checking if new roots where
added, we gotta check if we actually created async work before
traversing.
Fixes#17918
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Fixes#17924. This also DRYs stuff a bit by making `operator` an
argument to the runtime helper function, which means we only need two
variants of it: regular and async. It also makes it so that `=`
assignments don't use the getter, because they don't need to be done
lazily.
I've added `skip_no_async` to the new test, but I'm not entirely clear
on why it was failing the TestNoAsync run to begin with.
### 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>
With this, we can add invariants to the codebase so we can identify
problems like 'this batch already has roots scheduled', which indicate a
bug somewhere, without a) needing tests for scenarios that are
inherently hard to anticipate, or b) cluttering people's prod bundles
## Summary
Fixes#17148
When a `<select>` is focused inside an async boundary, the
`bind_select_value` effect gets deferred by the batch system, leaving
`select.__value` stale. If options then change dynamically (e.g. via
`{#each}`), the `MutationObserver` in `init_select` uses the stale
`__value`, snapping the select to the wrong option.
- Update `__value` in the change handler so it's always current, even
when the effect is deferred
- Update `__value` in the effect's early-return path (defensive fix for
when the effect runs but skips the DOM update)
## Test plan
- Added `select-dynamic-options-while-focused` test that renders a
`<select>` with dynamic `{#each}` options inside an async boundary,
selects a non-initial option while focused, adds another option, and
verifies the select retains the user's choice
- Verified existing `async-binding-update-while-focused-3` test still
passes
- All 7151 tests pass (`pnpm test`)
---------
Co-authored-by: Tee Ming <chewteeming01@gmail.com>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Rich Harris <hello@rich-harris.dev>
capture derived updates aswell so they become part of current/previous
so that `batch_values` computation is correct when e.g. using
`$state.eager` with a derived. Fixes#17849
Fixes#17907. When hydrating, we were resolving the boundary in the
hydration batch rather than the batch created inside the
`queue_micro_task` inside `#hydrate_pending_content`. This meant that
effects got scheduled inside a batch that was already resolved.
### 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`
https://github.com/sveltejs/svelte/pull/17680#issuecomment-3888440736.
Errors that occur during traversal (not inside a template effect etc)
can leave dirty effects inside the effect tree, but with clean parents.
This means that
a) subsequent changes to their dependencies won't schedule them to
re-run
b) subsequent batch flushes won't 'reach' them unless a sibling effect
happens to be made dirty
The easiest way to fix this is to just repair the tree if traversal
fails. If you had a truly ginormous tree this could conceivably take a
noticeable amount of time, but that's probably better than the app just
being broken.
Note that this doesn't apply to errors that occur inside an error
boundary, because in that case the offending subtree gets destroyed.
This is just for errors that bubble all the way to the root.
Closes#17680, closes#17679.
Closes#17899 by importing `untrack` from the actual file instead of the
`index-client.js`. Verified by packing the library and launching a build
with it.
Fixes#17904 by wrapping the RHS in a `() =>`.
### 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`
This is part of me trying to figure out #17162. It feels less confusing
to rebase other branches after the current batch has been processed,
rather than sort of doing it in the middle (which is an artifact of
historical constraints that no longer apply).
No test because it doesn't change any user-observable behaviour (but I
added a changeset just in case)
### 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.53.10
### Patch Changes
- fix: re-process batch if new root effects were scheduled
([#17895](https://github.com/sveltejs/svelte/pull/17895))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
In some cases a new branch might create effects which via
reading/writing reschedule an effect, causing `this.#roots` to become
populated again. In this case we need to re-process the batch. Most of
the time this will just result in a cleanup of the dirtied branches
since other work is already handled via running the effects etc. - it's
still crucial, else the reactive graph becomes frozen since no new root
effects are scheduled.
Fixes#17891
---------
Co-authored-by: Rich Harris <rich.harris@vercel.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.53.9
### Patch Changes
- fix: better `bind:this` cleanup timing
([#17885](https://github.com/sveltejs/svelte/pull/17885))
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This removes the `queue_micro_task`-workaround we employed in
`bind:this` in favor of a search for the nearest component effect /
effect that is still getting destroyed, whichever comes first.
We used `queue_micro_task` mainly due to timing issues with components
wanting to access the bound property on teardown still, and when nulling
it out on cleanup of the bind-this-effect itself, that was too early.
The microtask is too late though in some cases, when accessing
properties of objects that are no longer there. The targeted
upwards-walk solves this while keeping the binding around as long as
needed.
For that I had to add a new `DESTROYING` flag. We _could_ have done it
without one and by deleting code in `props.js` where we don't do
`get(d)` when the prop derived is destroyed, but I wanted to keep that
because you could still run into an access error if you e.g. access the
property in a timeout.
Alternative to #17862
In #17837 we added logic to not schedule another batch during
resumption. The logic in there turns out to be flawed - it's dangerous
to keep accessing inert block effects, because if they're nested they
could access properties that no longer exist (because the outer if makes
the inner if obsolete).
So this PR basically reverts #17837 and instead schedules another batch
again under the assumption that this will only happen during the commit
phase, and all that's gonna happen is that it will schedule another
batch, which is safe.
Fixes#17866Fixes#17878
This reverts commit 2f12b60701.
Co-authored-by: Rich Harris <rich.harris@vercel.com>
This simplifies the scheduling logic and will likely improve performance
in some cases. Previously, there was a global `queued_root_effects`
array, and we would cycle through the batch flushing logic as long as it
was non-empty. This was a very loosey-goosey approach that was
appropriate in the pre-async world, but has gradually become a source of
confusion.
Now, effects are scheduled within the context of a specific batch. The
lifecycle is more rigorous and debuggable. This opens the door to
explorations of alternative approaches, such as only scheduling effects
when we call `batch.flush()`, which _may_ be better than the eager
status quo.
The layout of the `Batch` class is extremely chaotic —
public/private/static fields/methods are all jumbled up together — and I
would like to get a grip of it. In the interests of minimising diff
noise that ought to be a follow-up rather than part of this PR.
### 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`
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
When an async value is updated inside the boundary while the pending
snippet is shown, we previously didn't notice that update and instead
showed an outdated value once it resolved. This fixes that by rejecting
all deferreds inside an async_derived while the pending snippet is
shown.
---------
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Fixes `{@html}` content duplication when used inside a contenteditable
element.
When `{@html content}` is inside a contenteditable element and the user
types, the browser inserts DOM nodes directly into the {@html} managed
region. On re-render (e.g. triggered by a blur handler setting `content
= e.currentTarget.innerText`, the `{@html} `block only removed nodes it
previously created via` effect.nodes`, leaving browser-inserted nodes in
place. This caused content to appear twice — once as leftover text nodes
and once as the new `{@html}` output.
The fix tracks the boundary node (`previousSibling `of the anchor at
init) and removes all nodes between the boundary and the anchor on
re-render, ensuring externally-added nodes are also cleaned up.
Closes: #16993
---------
Co-authored-by: 7nik <kfiiranet@gmail.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>