feat: add parent hierarchy to __svelte_meta
objects at dev time
parent
property to the __svelte_meta
properties that are added to elements at dev time. This property represents the closest non-element parent the element is related to. For example for {#if ...}<div>foo</div>{/if}
the parent
of the div would be the line/column of the if block. The parent is recursive and goes upwards (through component boundaries) until the root component is reached, which has no parent.
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.
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
feat: add print(...)
function
esrap
pluggable, so that it can be used to print any AST composed of { type: string, ... }
nodes rather than just estree
and its TypeScript extensions. That includes Svelte ASTs.
The main motivation for exposing this is so that we can make it easier to write preprocessors. Historically, Svelte exposed a preprocess
API, but it's all strings and duct tape, and it's difficult to integrate preprocessors cleanly with bundlers as we've seen with enhanced-img
.
When the preprocessor API was introduced, things looked very different. Preprocessing was necessary to support things like TypeScript and Sass. Today, TypeScript is supported natively, and CSS is sufficiently capable that Sass is little more than a historical curiosity.
In the long term, we'd therefore like to move away from the preprocessor API in favour of providing more robust lower-level utilities. For example enhanced-img
, which can only be used in a Vite context, really should just be a Vite plugin:
import { parse, print } from 'svelte/compiler';
import { walk } from 'zimmerframe';
function transform(code) {
const ast = parse(code);
const transformed = walk(ast, null, {
RegularElement(node, context) {
if (node.name !== 'enhanced:img') return;
// ...
}
});
return print(ast);
}
There are other potential uses, such as migrations or sv add
or having a 'format' button in the playground.
As a side-effect, quality of compiler output will be slightly better in certain cases, such as when encountering comments inside nodes. (Today, we attach leadingComments
and trailingComments
to each node, but this is a brittle and not-very-widely-used convention. The new esrap
API expects an array of comments
to be passed instead.)
This functionality already exists in svelte-ast-print
(thank you @xeho91!), but having it in core means we can re-use the esrap
version that's already installed alongside svelte
, and will help ensure it stays current with new features.
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
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.
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
feat: support using
, link top-level using
declarations in components to lifecycle
using
declarations at the top level of a component are only disposed when the component unmounts.
I think I prefer this — it's more useful and more intuitive. Demo here.
On the server it doesn't transform anything, meaning resources are correctly disposed of once rendering is complete. Obviously all this is of limited use until support is sufficiently widespread, but I don't see any reason to hold it back until then. (The demo above actually works in all browsers, because top-level using
gets compiled away for client code, but that doesn't apply to server code or non-top-level using
.)
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
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.
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
fix: update indirectly-affected bindings on mutation
#16200 13 days ago • Jun 18, 2025<script>
export let selected;
export let tasks;
</script>
<select bind:value={selected}>
{#each tasks as task}
<option value='{task}'>{task.description}</option>
{/each}
</select>
<label>
<input type='checkbox' bind:checked={selected.done}> {selected.description}
</label>
<h2>Pending tasks</h2>
{#each tasks.filter(t => !t.done) as task}
<p>{task.description}</p>
{/each}
...updating selected
should also update tasks
, because the bindings are linked. I think we might be able to use this mechanism for each blocks as well and end up with simpler compiler code, though I don't have time to finish it right now and my brain needs a rest anyway because this stuff is confusing as hell. Can't wait to be able to delete all this legacy gubbins.
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
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.
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
feat: add onAnimationFrame
lifecycle function
onFrame
function? It would mean for example that this demo could be written like this:
$effect(() => {
const context = canvas.getContext('2d');
onFrame(() => {
paint(context, Date.now());
});
});
Alternatively we might want to have some sort of reactive time primitive (other than SvelteDate
) so that it could be used in deriveds too. Either way not wedded to it, just an idle thought that I thought was worth a PR
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
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.
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
docs: update store documentation
#14083 Nov 6, 2024svelte/store
on /docs/svelte/stores
page and then again on /docs/svelte/svelte-store
. The first should describe the store contract at a high level, while the second should detail the things you can import from the module.
This is draft because we really need to fix the way overloads are handled first — it's too confusing at the momentfix: more hydration mismatch stuff
#12791 Aug 10, 2024feat:
, 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.
Tests and linting
Run the tests with pnpm test
and lint the project with pnpm lint
Asynchronous Svelte
#15845 7 days ago • Jun 24, 2025await
keyword in Svelte — in your <script>
, inside $derived
expressions, and in your markup — by installing the async
branch...
npm i https://pkg.pr.new/svelte@async
...and adding the experimental.async
option to your svelte.config.js
(or wherever you configure Svelte):
// svelte.config.js
export default {
compilerOptions: {
experimental: {
async: true
}
},
kit: {
// ...
}
};
You can also try things out in the async playground.
You will find bugs! This is in no way production-ready. The PR that accompanies this discussion is at https://github.com/sveltejs/svelte/pull/15844.
Background
In olden times, we did asynchronous work like fetching data inside onMount
or an {#await ...}
block. This works but it's quite verbose, and offers no coordination — if two components are both fetching stuff, then they will likely have independently managed error/loading states, often resulting in a janky UI with multiple spinners.
Frameworks like SvelteKit (or Remix or pre-App-Router Next.js, etc) offer an alternative: instead of fetching inside the component, we do our asynchronous work outside the component, for example in load
functions. This typically results in better user experience (we can server-render the data, preload it, and coordinate everything) but it too has problems — prop-drilling, type shenanigans, coarse-grained invalidation, and logic that's hard to delete because it's often not obvious whether something in your load
function is even being used.
In recent years component frameworks have explored a third way: putting asynchronous work back inside components, but in a way that is coordinated:
React has a variety of primitives — <Suspense>
, startTransition
, useTransition
, use
and React Server Components, that together allow you to manage async updates
Solid has a createResource
API that can be used with <Suspense>
and startTransition
/useTransition
for the same purpose
Vue allows you to use await
inside a <script setup>
in a component inside a <Suspense>
boundary, though this experimental API only works for component creation, not subsequent updates
We believe Svelte's compiler-centric nature offers us a way to have component-level async work with significantly better ergonomics and fewer drawbacks than existing approaches.
Requirements
minimal ceremony — we want to just write code, ideally using the await
keyword. Avoid framework-specific APIs and idioms to the extent possible
flexibility — it should be possible to use asynchronous values anywhere (in the <script>
, inside $derived
expressions, in the template, in attributes and component props, in control flow like {#if ...}
blocks, or wherever else)
parallel by default — since expressions in the template can be safely assumed to be pure, sequential await
expressions need not result in sequential async work. Similarly, asynchronous work in two sibling components (for example) should happen simultaneously
automatic coördination — if foo
changes, and await bar(foo)
is (or will be) visible on the page, any occurrences of foo
in the UI should not update until bar(foo)
resolves
overlapping updates — a state change that happens while asynchronous work is ongoing should be visible immediately without waiting for the work to complete (unless it results in more async work)
We also want this to have minimal impact on performance and memory for existing codebases, and to cause little or no breakage for existing apps.
Design
In a nutshell: you can now use await
in three places that will today result in a syntax error:
at the top level of a component <script>
in a $derived
expression
in a template expression (i.e. in a component's markup)
We also introduce a pending
snippet to <svelte:boundary>
, which allows you to provide UI when await
expressions inside the boundary are first resolving. For now, await
expressions must be inside a boundary with a pending snippet (however deeply nested — for example you might have a single boundary at the very root of your app), though this constraint will likely be relaxed in future.
If state is read inside a $derived
or template expression with an await
, changes to that state will not be reflected in the UI until the expression resolves. For example in a situation like this...
<h1>Weather forecast for {city}</h1>
<p>{await getWeatherForecast(city)}</p>
...the <h1>
will not update until the <p>
does. (If getWeatherForecast
were to fail, it would activate the nearest error boundary.)
To know if an update is currently pending, you can use $effect.pending()
:
{#if $effect.pending()}
<p>loading new data...</p>
{/if}
If unrelated state changes while an update is pending, it will be visible immediately (assuming it doesn't separately cause asynchronous updates).
Use cases
The most obvious use case is loading data (which could be as simple as fetch
, but in many cases will likely involve yet-to-be-designed utilities for client-server communication), but others include:
demo preloading images to avoid jank (<img alt="..." src={await preload('...')}>
)
demo lazily importing modules and components as needed
demo moving expensive computation to a web worker
Avoiding waterfalls
As much as framework authors like to witter on about rendering performance, the thing that really slows apps down is latency. In some cases you can reduce latency by speeding up your back end, or prefetching content you expect to need, or serving content from somewhere close to the user, but those things aren't always possible. What you can do is mitigate the effect of latency by minimising the number of round trips between the browser and the server.
Svelte helps by doing as much work as possible in parallel. For example here...
<li>apples {await getPrice('apple')} each</li>
<li>bananas {await getPrice('banana')} each</li>
<li>canteloupes {await getPrice('canteloupe')} each</li>
...the three calls to getPrice
will happen simultaneously, even though they appear in sequence. Similarly, the three wise monkeys will do all their async work together:
<WiseMonkey verb="see" />
<WiseMonkey verb="hear" />
<WiseMonkey verb="speak" />
Not all work can be parallelized. Any await
expressions inside the <script>
run before expressions in the template, and in the sequence you would expect...
// `a` will be calculated and _then_ `b` will be calculated
let a = $derived(await foo(x));
let b = $derived(await bar(x));
...though note that if x
subsequently updates, a
and b
will be recomputed in parallel. You can of course avoid the initial waterfall like so:
let aPromise = $derived(foo(x));
let bPromise = $derived(bar(x));
let a = $derived(await aPromise);
let b = $derived(await bPromise);
Promise.all
here, because you need to know artist.id
before you can call getTrackListing
:
let artist = $derived(await search(query));
let tracks = $derived(await getTrackListing(artist.id));
It's better if you can do both fetches on the server, near your database. There are two ways to solve this: either anticipate the need for the track listing when you do the search...
let [artist, tracks] = $derived(await searchAndGetTrackListing(query));
...or use a mechanism like React Server Components, where the rendering happens on the server. Option 1 has less-than-ideal ergonomics, though it's basically equivalent to SvelteKit's load
function. Option 2 is optimal from a waterfall prevention perspective, but we feel RSCs have significant trade-offs.
We anticipate that opinionated data-fetching patterns will emerge over time, along with new approaches inspired by RSCs, to solve this problem.
<svelte:boundary>
with a pending
snippet is encountered during SSR, the pending
snippet will be rendered.
It would be better to render the content instead, but this requires that SSR become an asynchronous operation.
In an ideal world we would also be able to stream the result where appropriate. This is complicated by the fact that you ideally need to know what's in the <head>
before you get to the <body>
. We have some ideas for how to design this sensibly, but it's not a current priority.
Once we get to this part, it's likely that the requirement for await
expressions to be contained in a <svelte:boundary>
will go away.
Forking
In SvelteKit we preload the code and data necessary for a navigation before it even occurs: when you hover over a link, or tap it, we import any modules needed by the route and begin running whichever load
functions need to run. (This behaviour is configurable, of course.)
To make this work in a world where asynchronous work happens inside components, we need to be able to pretend that a state change occurred, and do the resulting updates 'off-screen'. The result can either be applied or discarded.
Colloquially, we've been describing this as a 'fork'. You could imagine several unresolved forks coexisting simultaneously until one reality is chosen (by the user clicking on one of several links they've been close to interacting with, for example). Most likely, we'll add an API to Svelte that allows frameworks like SvelteKit (but also particularly ambitious application authors) to enter the multiverse.
This all sounds very complicated, but in reality it's not all that different to how asynchronous updates already work. Nevertheless, it will take some finagling to get right, and in the interests of shipping we haven't included this work in the current PR.
Client-server utilities
Unless we want to create server endpoints for everything we want to fetch
inside a component, we'll need tools for interacting with the server. Those tools will need to consider security, HTTP caching, mutations and invalidation, optimistic UI, batching, streaming, custom serialization, type safety and lots of other things.
We're at the early stages of figuring out what this all looks like, but we're very confident that the foundation we're laying here will allow us to design something really good.
Improving SvelteKit
Anticipating everyone's reaction: no, we're not going to suddenly make you rewrite all your SvelteKit apps. But taking all the other stuff together, a picture starts to emerge: a version of SvelteKit that's a thinner layer on top of Svelte. For example, as proud as we are of our zero-effort type safety, it's something you no longer need if you're fetching data directly inside your component.
Meanwhile the framework internals could potentially get simpler, because we'd be able to rely on common primitives inside Svelte itself. (Today, we can't reuse that much code between the server and client portions of SvelteKit, and we rely a lot on virtual modules and generated code. Some of that is for historical reasons, but some is necessary because anything asynchronous has to happen outside Svelte.)
It seems likely that a lot of interesting new possibilities will open up as a result of this work, and it seems at least plausible that we'll collectively find ourselves drifting away from primitives like load
. Exactly what this would look like is to be determined, and it's something that we'll figure out together as a community.
Nuances
No startTransition
/useTransition
If you've used React or Solid's suspense implementation, you will have encountered startTransition
and useTransition
. These functions allows you to update state in such a way that the nearest suspense boundary doesn't show its fallback while waiting for any async work that is downstream of the state change.
In Svelte, we don't do this. Instead, state changes that result in asynchronous work always have their effects deferred until the asynchronous work is complete. While that work is ongoing, $effect.pending()
is true
. Any pending
snippets are only shown when a boundary is first being created.
This does create the possibility that something distantly connected to a given piece of state has the power to delay (or prevent!) changes to that state from being reflected in the UI. We think this is preferable to the alternative (in which updates outside a useTransition
or similar cause unwanted fallback UI to appear), though it may be necessary to develop techniques for identifying these chains.
Overlapping updates that touch the same state must be applied in linear order
In a case like this...
<p>{a} + {b} = {await fetch(`/add/${a}/${b}`).then((r) => r.json())}</p>
...it's possible to imagine a scenario in which a change to a
is followed by a change to b
while the first fetch
is ongoing. If the second fetch
somehow finishes first, we don't want the first fetch
to be applied afterwards, since the resulting UI would show stale values.
As such, a given async expression must only resolve after its previous versions have also resolved.
Async deriveds are eager
Normally in Svelte, $derived
expressions use 'push-pull' reactivity — they are invalidated when their dependencies change (the 'push') but are not re-evaluated until something reads them (the 'pull'). This doesn't work for async deriveds, because if we waited until the value was read the resulting promise would never resolve in time.
Instead, an 'async derived' is really just an effect and a source signal in a trenchcoat. We evalute the expression in the effect, and when the promise resolves, set the source value (unless the effect fired again in the interim).
For the most part, you don't need to think about this. There is one important implication though — while it's possible to create an 'unowned derived' (in other words, a derived that is not part of the 'effect tree', such as one created in an event handler) it is not possible to create an unowned async derived, because effects can only be created inside other effects. (Don't worry if you didn't follow this — it's inside baseball stuff, and describes an edge case you're unlikely to encounter. Svelte will tell you if you get this wrong.)
Context is preserved in expressions
When a reaction (i.e. an effect or derived) runs, we track its dependencies by seeing which values are read during its execution. In pseudo-code:
let dependencies = null;
function get(source) {
dependencies?.add(source);
return source.value;
}
function update_reaction(reaction) {
dependencies = new Set();
reaction.fn();
reaction.dependencies = dependencies;
dependencies = null;
}
(This is simplified and wrong, but you get the idea.)
For asynchronous functions, this presents a challenge. To understand why, consider the order in which things happen here:
function a() {
console.log('a 1');
b();
console.log('a 2');
}
async function b() {
console.log('b 1');
await 0;
console.log('b 2');
}
a();
This will log a 1
, b 1
, a 2
, b 2
— in other words despite the fact that b
isn't even awaiting an asynchronous value, b 2
isn't logged until after it has returned to a
.
In Svelte terms, this would mean that $derived(await a + b)
or an equivalent {await a + b}
template expression would register a dependency on a
but not on b
. But since we're a compiler we have a trick up our sleeves: we wrap the await
expression in a function that restores the effect context, so that b
is treated as a dependency.
On one level this is spooky compiler magic. But on another, it's just making the system work how a reasonable person would expect it to work. It does mean that if you extract the logic out into a function — {await a_plus_b()}
— it will be treated differently, but during development Svelte will catch those cases and help you fix them.
No async $derived.by
While you can do this...
let x = $derived.by(async () => await y);
...x
will then be a Promise
, not the value of await y
. There's no sensible place to put the await
that would result in x
not being a Promise
. (This is why it's pretty great that we're a compiler and can make the expression form the default, even though it makes other framework authors mad.)
If you have sufficiently complex logic that you need to use a function block, just declare the function and call it:
async function add(p1: Promise<number>, p2: Promise<number>) {
return await p1 + await p2;
}
let sum = $derived(await add(p1, p2));
Note the aforementioned caveat around context preservation — make sure you pass state into the function, rather than having the function close over the state it references, so that dependencies are correctly attached.
Focused inputs are not overridden
Normally, state does not update in the UI until everything that depends on it has finished resolving. This does not apply to a focused <input>
element, which in many cases will be the source of the state change in question — instead, the rest of the UI 'catches up' to the input.
Breaking changes
Because of how async reactivity works, there is one small and unavoidable breaking change: beforeUpdate
and $effect.pre
callbacks no longer run before control flow blocks are updated. If you're using those callbacks to set state that control flow logic depends on — which you absolutely shouldn't be — then you may experience breakage. As a result, we may have to wait until 6.0 for a non-experimental release of this stuff.
Ok so
That was a lot; congratulations to those of you still here. I am sure you have questions, and that's a good thing. This is the place to ask them, or to offer feedback on the design. Let's go!Svelte Toolbar
#15910 May 30, 2025svelte/toolbar
.
First party tools added automatically via svelte/toolbar
or other packages within the org (eg bundler plugins)
customizable
While we are going to provide first party tools, we also want to provide an api to add third party tools and allow customization
add tools
libraries or packages for svelte can ship with dedicated tools
community can add useful generic tools
you can add custom made ones for your app
configure
The toolbar itself and each tool has a configuration that can be modified by invoking a function provided on svelte/toolbar
The configuration is going to be stored locally so you can tailor it to your needs and it stays that way. Possible storage locations would be localstorage, indexeddb or .env.local files.
mode aware
Most tools are going to be available during dev, but some can also be used in a production build.
powerful
access svelte build-time information from the svelte compiler
access svelte runtime information
integrate with dev server functionality
Planned tools
first release
svelte inspector
to be ported from vite-plugin-svelte , its functionality is going to remain the same, but instead of hardcoding how file editors are opened by a call to a vite middleware, it is going to leverage the devserver integration
toolbar config editor
visual editor for the toolbar config, with options to persist
future releases
reactivity graph viewer
$inspect is nice and surgical, but it would be even nicer if there was a way to visualize the reactivity graph and more easily follow dependencies through your app. Solid already has a tool for this and so should we
sveltekit route viewer
params, matchers, layout groups, the file system tree can become a bit messy. A dedicated ui with links to open the relevant files can help understand and work with your application structure.
component hierarchy viewer
The original svelte devtools browser extension had this view, and we can bring it back.
your idea here
please share ideas for tools you'd like to have/build and any apis they might need. now is the time to shape the foundation
the owl
The toolbar
The toolbar itself is going to provide basic common features to the tools and renders them with provided name&icon. Of course it is going to be a svelte component and to avoid colliding with your application it will be mounted on document.documentElement outside of body by default, which should avoid collisions with your app in most cases.
A tool
A tool needs to be registered with the toolbar and receives api & configuration. Optionally it can provide its own svelte component for ui. If the toolbar is going to add this component or the tools is responsible for mounting it is still up for discussion.
How to add it
Ideally, bundler plugins are going to inject the toolbar to avoid the need for putting code into your app yourself (like svelte-inspector is added today) and the tools just show up as configured.
But manually putting them in your root layout can work as well.
The hardest one is adding them for production, this might require a web extension and building with a special flag so it can aquire a reference to the toolbar api.
Feedback & Ideas wanted!
If you want to build a tool, what api do you prefer. Should a tool always be a svelte component or should it be more similar to a vite plugin with an initializer function and hooks?
Do you think tools should be aware of each other and able to collaborate?
What are the most important tools you want to use?
Would you prefer a web extension with ui separately in the devtools tab or is rendering in the apps window ok?Tenets
#10085 Nov 12, 2024Remove auto-parenthesis logic for MediaQuery
System Info
-
Severity
annoyanceImproved error when attempting to export $derived state as a let?
#15842 Apr 28, 2025export const foo = $derived(...)
to attempt to expose derived state on a component instance, you get a helpful error message about instead needing to export a function that returns the derived state. However, if you write export let foo = $derived(...)
instead, the compiler thinks you are trying to use the export
syntax to define a prop, and directs you to use $props()
instead.
Adjacently, something like export const foo = 42
(in runes mode) is allowed by the compiler, which seems to just expose that value on the component instance like export function foo() { ... }
would, and I don't know whether that was intentionally left in as a feature.
Describe the proposed solution
I'm not sure, entirely. There certainly are situations where the best thing would be to continue to throw the error about how this isn't the right way to define props anymore. But I think people using let
s for $derived()
s isn't that uncommon, and so using let
vs const
to decide what's happening here isn't ideal.
Importance
nice to have