Merge remote-tracking branch 'upstream/main' into snippet-dev-arg-warnings

pull/15521/head
ComputerGuy 1 year ago
commit 471f479305

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: invalidate parent effects when child effects update parent dependencies

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: correctly match `:has()` selector during css pruning

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: replace `undefined` with `void 0` to avoid edge case

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: allow global-like pseudo-selectors refinement

@ -1,5 +0,0 @@
---
'svelte': patch
---
chore: don't distribute unused types definitions

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure clearing of old values happens independent of root flushes

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: add `files` and `group` to HTMLInputAttributes in elements.d.ts

@ -2,7 +2,6 @@ import fs from 'node:fs';
import path from 'node:path';
import { execSync, fork } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { benchmarks } from '../benchmarks.js';
// if (execSync('git status --porcelain').toString().trim()) {
// console.error('Working directory is not clean');

@ -1,7 +1,7 @@
import { benchmarks } from '../benchmarks.js';
import { reactivity_benchmarks } from '../benchmarks/reactivity/index.js';
const results = [];
for (const benchmark of benchmarks) {
for (const benchmark of reactivity_benchmarks) {
const result = await benchmark();
console.error(result.benchmark);
results.push(result);

@ -2,7 +2,7 @@
title: Getting started
---
We recommend using [SvelteKit](../kit), the official application framework from the Svelte team powered by [Vite](https://vite.dev/):
We recommend using [SvelteKit](../kit), which lets you [build almost anything](../kit/project-types). It's the official application framework from the Svelte team and powered by [Vite](https://vite.dev/). Create a new project with:
```bash
npx sv create myapp
@ -15,7 +15,9 @@ Don't worry if you don't know Svelte yet! You can ignore all the nice features S
## Alternatives to SvelteKit
You can also use Svelte directly with Vite by running `npm create vite@latest` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS and CSS files inside the `dist` directory using [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte). In most cases, you will probably need to [choose a routing library](faq#Is-there-a-router) as well.
You can also use Svelte directly with Vite by running `npm create vite@latest` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS, and CSS files inside the `dist` directory using [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte). In most cases, you will probably need to [choose a routing library](faq#Is-there-a-router) as well.
>[!NOTE] Vite is often used in standalone mode to build [single page apps (SPAs)](../kit/glossary#SPA), which you can also [build with SvelteKit](../kit/single-page-apps).
There are also plugins for [Rollup](https://github.com/sveltejs/rollup-plugin-svelte), [Webpack](https://github.com/sveltejs/svelte-loader) [and a few others](https://sveltesociety.dev/packages?category=build-plugins), but we recommend Vite.

@ -52,6 +52,48 @@ Anything read synchronously inside the `$derived` expression (or `$derived.by` f
To exempt a piece of state from being treated as a dependency, use [`untrack`](svelte#untrack).
## Overriding derived values
Derived expressions are recalculated when their dependencies change, but you can temporarily override their values by reassigning them (unless they are declared with `const`). This can be useful for things like _optimistic UI_, where a value is derived from the 'source of truth' (such as data from your server) but you'd like to show immediate feedback to the user:
```svelte
<script>
let { post, like } = $props();
let likes = $derived(post.likes);
async function onclick() {
// increment the `likes` count immediately...
likes += 1;
// and tell the server, which will eventually update `post`
try {
await like();
} catch {
// failed! roll back the change
likes -= 1;
}
}
</script>
<button {onclick}>🧡 {likes}</button>
```
> [!NOTE] Prior to Svelte 5.25, deriveds were read-only.
## Deriveds and reactivity
Unlike `$state`, which converts objects and arrays to [deeply reactive proxies]($state#Deep-state), `$derived` values are left as-is. For example, [in a case like this](/playground/untitled#H4sIAAAAAAAAE4VU22rjMBD9lUHd3aaQi9PdstS1A3t5XvpQ2Ic4D7I1iUUV2UjjNMX431eS7TRdSosxgjMzZ45mjt0yzffIYibvy0ojFJWqDKCQVBk2ZVup0LJ43TJ6rn2aBxw-FP2o67k9oCKP5dziW3hRaUJNjoYltjCyplWmM1JIIAn3FlL4ZIkTTtYez6jtj4w8WwyXv9GiIXiQxLVs9pfTMR7EuoSLIuLFbX7Z4930bZo_nBrD1bs834tlfvsBz9_SyX6PZXu9XaL4gOWn4sXjeyzftv4ZWfyxubpzxzg6LfD4MrooxELEosKCUPigQCMPKCZh0OtQE1iSxcsmdHuBvCiHZXALLXiN08EL3RRkaJ_kDVGle0HcSD5TPEeVtj67O4Nrg9aiSNtBY5oODJkrL5QsHtN2cgXp6nSJMWzpWWGasdlsGEMbzi5jPr5KFr0Ep7pdeM2-TCelCddIhDxAobi1jqF3cMaC1RKp64bAW9iFAmXGIHfd4wNXDabtOLN53w8W53VvJoZLh7xk4Rr3CoL-UNoLhWHrT1JQGcM17u96oES5K-kc2XOzkzqGCKL5De79OUTyyrg1zgwXsrEx3ESfx4Bz0M5UjVMHB24mw9SuXtXFoN13fYKOM1tyUT3FbvbWmSWCZX2Er-41u5xPoml45svRahl9Wb9aasbINJixDZwcPTbyTLZSUsAvrg_cPuCR7s782_WU8343Y72Qtlb8OYatwuOQvuN13M_hJKNfxann1v1U_B1KZ_D_mzhzhz24fw85CSz2irtN9w9HshBK7AQAAA==)...
```svelte
let items = $state([...]);
let index = $state(0);
let selected = $derived(items[index]);
```
...you can change (or `bind:` to) properties of `selected` and it will affect the underlying `items` array. If `items` was _not_ deeply reactive, mutating `selected` would have no effect.
## Update propagation
Svelte uses something called _push-pull reactivity_ — when state is updated, everything that depends on the state (whether directly or indirectly) is immediately notified of the change (the 'push'), but derived values are not re-evaluated until they are actually read (the 'pull').

@ -74,6 +74,8 @@ Teardown functions also run when the effect is destroyed, which happens when its
`$effect` automatically picks up any reactive values (`$state`, `$derived`, `$props`) that are _synchronously_ read inside its function body (including indirectly, via function calls) and registers them as dependencies. When those dependencies change, the `$effect` schedules a re-run.
If `$state` and `$derived` are used directly inside the `$effect` (for example, during creation of a [reactive class](https://svelte.dev/docs/svelte/$state#Classes)), those values will _not_ be treated as dependencies.
Values that are read _asynchronously_ — after an `await` or inside a `setTimeout`, for example — will not be tracked. Here, the canvas will be repainted when `color` changes, but not when `size` changes ([demo](/playground/untitled#H4sIAAAAAAAAE31T246bMBD9lZF3pWSlBEirfaEQqdo_2PatVIpjBrDkGGQPJGnEv1e2IZfVal-wfHzmzJyZ4cIqqdCy9M-F0blDlnqArZjmB3f72XWRHVCRw_bc4me4aDWhJstSlllhZEfbQhekkMDKfwg5PFvihMvX5OXH_CJa1Zrb0-Kpqr5jkiwC48rieuDWQbqgZ6wqFLRcvkC-hYvnkWi1dWqa8ESQTxFRjfQWsOXiWzmr0sSLhEJu3p1YsoJkNUcdZUnN9dagrBu6FVRQHAM10sJRKgUG16bXcGxQ44AGdt7SDkTDdY02iqLHnJVU6hedlWuIp94JW6Tf8oBt_8GdTxlF0b4n0C35ZLBzXb3mmYn3ae6cOW74zj0YVzDNYXRHFt9mprNgHfZSl6mzml8CMoLvTV6wTZIUDEJv5us2iwMtiJRyAKG4tXnhl8O0yhbML0Wm-B7VNlSSSd31BG7z8oIZZ6dgIffAVY_5xdU9Qrz1Bnx8fCfwtZ7v8Qc9j3nB8PqgmMWlHIID6-bkVaPZwDySfWtKNGtquxQ23Qlsq2QJT0KIqb8dL0up6xQ2eIBkAg_c1FI_YqW0neLnFCqFpwmreedJYT7XX8FVOBfwWRhXstZrSXiwKQjUhOZeMIleb5JZfHWn2Yq5pWEpmR7Hv-N_wEqT8hEEAAA=)):
```ts
@ -252,6 +254,8 @@ In general, `$effect` is best considered something of an escape hatch — useful
> [!NOTE] For things that are more complicated than a simple expression like `count * 2`, you can also use `$derived.by`.
If you're using an effect because you want to be able to reassign the derived value (to build an optimistic UI, for example) note that [deriveds can be directly overridden]($derived#Overriding-derived-values) as of Svelte 5.25.
You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Don't use effects for this ([demo](/playground/untitled#H4sIAAAAAAAACpVRy26DMBD8FcvKgUhtoIdeHBwp31F6MGSJkBbHwksEQvx77aWQqooq9bgzOzP7mGTdIHipPiZJowOpGJAv0po2VmfnDv4OSBErjYdneHWzBJaCjcx91TWOToUtCIEE3cig0OIty44r5l1oDtjOkyFIsv3GINQ_CNYyGegd1DVUlCR7oU9iilDUcP8S8roYs9n8p2wdYNVFm4csTx872BxNCcjr5I11fdgonEkXsjP2CoUUZWMv6m6wBz2x7yxaM-iJvWeRsvSbSVeUy5i0uf8vKA78NIeJLSZWv1I8jQjLdyK4XuTSeIdmVKJGGI4LdjVOiezwDu1yG74My8PLCQaSiroe5s_5C2PHrkVGAgAA)):
```svelte
@ -280,7 +284,7 @@ You might be tempted to do something convoluted with effects to link one value t
</label>
```
Instead, use callbacks where possible ([demo](/playground/untitled#H4sIAAAAAAAACo1SMW6EMBD8imWluFMSIEUaDiKlvy5lSOHjlhOSMRZeTiDkv8deMEEJRcqdmZ1ZjzzxqpZgePo5cRw18JQA_sSVaPz0rnVk7iDRYxdhYA8vW4Wg0NnwzJRdrfGtUAVKQIYtCsly9pIkp4AZ7cQOezAoEA7JcWUkVBuCdol0dNWrEutWsV5fHfnhPQ5wZJMnCwyejxCh6G6A0V3IHk4zu_jOxzzPBxBld83PTr7xXrb3rUNw8PbiYJ3FP22oTIoLSComq5XuXTeu8LzgnVA3KDgj13wiQ8taRaJ82rzXskYM-URRlsXktejjgNLoo9e4fyf70_8EnwncySX1GuunX6kGRwnzR_BgaPNaGy3FmLJKwrCUeBM6ZUn0Cs2mOlp3vwthQJ5i14P9st9vZqQlsQIAAA==)):
Instead, use `oninput` callbacks or — better still — [function bindings](bind#Function-bindings) where possible ([demo](/playground/untitled#H4sIAAAAAAAAE51SsW6DMBT8FcvqABINdOhCIFKXTt06lg4GHpElYyz8iECIf69tcIIipo6-u3f3fPZMJWuBpvRzkBXyTpKSy5rLq6YRbbgATdOfmeKkrMgCBt9GPpQ66RsItFjJNBzhVScRJBobmumq5wovhSxQABLskAmSk7ckOXtMKyM22ItGhhAk4Z0R0OwIN-tIQzd-90HVhvy2HsGNiQFCMltBgd7XoecV2xzXNV7XaEcth7ZfRv7kujnsTX2Qd7USb5rFjwZkJlgJwpWRcakG04cpOS9oz-QVCuoeInXW-RyEJL-sG0b7Wy6kZWM-u7CFxM5tdrIl9qg72vB74H-y7T2iXROHyVb0CLanp1yNk4D1A1jQ91hzrQSbUtIIGLcir0ylJDm9Q7urz42bX4UwIk2xH2D5Xf4A7SeMcMQCAAA=)):
```svelte
<script>
@ -288,54 +292,26 @@ Instead, use callbacks where possible ([demo](/playground/untitled#H4sIAAAAAAAAC
let spent = $state(0);
let left = $state(total);
function updateSpent(e) {
spent = +e.target.value;
function updateSpent(value) {
spent = value;
left = total - spent;
}
function updateLeft(e) {
left = +e.target.value;
function updateLeft(value) {
left = value;
spent = total - left;
}
</script>
<label>
<input type="range" value={spent} oninput={updateSpent} max={total} />
<input type="range" bind:value={() => spent, updateSpent} max={total} />
{spent}/{total} spent
</label>
<label>
<input type="range" value={left} oninput={updateLeft} max={total} />
<input type="range" bind:value={() => left, updateLeft} max={total} />
{left}/{total} left
</label>
```
If you need to use bindings, for whatever reason (for example when you want some kind of "writable `$derived`"), consider using getters and setters to synchronise state ([demo](/playground/untitled#H4sIAAAAAAAACpWRwW6DMBBEf8WyekikFOihFwcq9TvqHkyyQUjGsfCCQMj_XnvBNKpy6Qn2DTOD1wu_tRocF18Lx9kCFwT4iRvVxenT2syNoDGyWjl4xi93g2AwxPDSXfrW4oc0EjUgwzsqzSr2VhTnxJwNHwf24lAhHIpjVDZNwy1KS5wlNoGMSg9wOCYksQccerMlv65p51X0p_Xpdt_4YEy9yTkmV3z4MJT579-bUqsaNB2kbI0dwlnCgirJe2UakJzVrbkKaqkWivasU1O1ULxnOVk3JU-Uxti0p_-vKO4no_enbQ_yXhnZn0aHs4b1jiJMK7q2zmo1C3bTMG3LaZQVrMjeoSPgaUtkDxePMCEX2Ie6b_8D4WyJJEwCAAA=)):
```svelte
<script>
let total = 100;
let spent = $state(0);
let left = {
get value() {
return total - spent;
},
set value(v) {
spent = total - v;
}
};
</script>
<label>
<input type="range" bind:value={spent} max={total} />
{spent}/{total} spent
</label>
<label>
<input type="range" bind:value={left.value} max={total} />
{left.value}/{total} left
</label>
```
If you absolutely have to update `$state` within an effect and run into an infinite loop because you read and write to the same `$state`, use [untrack](svelte#untrack).

@ -2,129 +2,137 @@
title: Context
---
<!-- - get/set/hasContext
- how to use, best practises (like encapsulating them) -->
Context allows components to access values owned by parent components without passing them down as props (potentially through many layers of intermediate components, known as 'prop-drilling'). The parent component sets context with `setContext(key, value)`...
Most state is component-level state that lives as long as its component lives. There's also section-wide or app-wide state however, which also needs to be handled somehow.
The easiest way to do that is to create global state and just import that.
```svelte
<!--- file: Parent.svelte --->
<script>
import { setContext } from 'svelte';
```ts
/// file: state.svelte.js
export const myGlobalState = $state({
user: {
/* ... */
}
/* ... */
});
setContext('my-context', 'hello from Parent.svelte');
</script>
```
...and the child retrieves it with `getContext`:
```svelte
<!--- file: App.svelte --->
<!--- file: Child.svelte --->
<script>
import { myGlobalState } from './state.svelte.js';
// ...
import { getContext } from 'svelte';
const message = getContext('my-context');
</script>
<h1>{message}, inside Child.svelte</h1>
```
This has a few drawbacks though:
This is particularly useful when `Parent.svelte` is not directly aware of `Child.svelte`, but instead renders it as part of a `children` [snippet](snippet) ([demo](/playground/untitled#H4sIAAAAAAAAE42Q3W6DMAyFX8WyJgESK-oto6hTX2D3YxcM3IIUQpR40yqUd58CrCXsp7tL7HNsf2dAWXaEKR56yfTBGOOxFWQwfR6Qz8q1XAHjL-GjUhvzToJd7bU09FO9ctMkG0wxM5VuFeeFLLjtVK8ZnkpNkuGo-w6CTTJ9Z3PwsBAemlbUF934W8iy5DpaZtOUcU02-ZLcaS51jHEkTFm_kY1_wfOO8QnXrb8hBzDEc6pgZ4gFoyz4KgiD7nxfTe8ghqAhIfrJ46cTzVZBbkPlODVJsLCDO6V7ZcJoncyw1yRr0hd1GNn_ZbEM3I9i1bmVxOlWElUvDUNHxpQngt3C4CXzjS1rtvkw22wMrTRtTbC8Lkuabe7jvthPPe3DofYCAAA=)):
```svelte
<Parent>
<Child />
</Parent>
```
- it only safely works when your global state is only used client-side - for example, when you're building a single page application that does not render any of your components on the server. If your state ends up being managed and updated on the server, it could end up being shared between sessions and/or users, causing bugs
- it may give the false impression that certain state is global when in reality it should only used in a certain part of your app
The key (`'my-context'`, in the example above) and the context itself can be any JavaScript value.
To solve these drawbacks, Svelte provides a few `context` primitives which alleviate these problems.
In addition to [`setContext`](svelte#setContext) and [`getContext`](svelte#getContext), Svelte exposes [`hasContext`](svelte#hasContext) and [`getAllContexts`](svelte#getAllContexts) functions.
## Setting and getting context
## Using context with state
To associate an arbitrary object with the current component, use `setContext`.
You can store reactive state in context ([demo](/playground/untitled#H4sIAAAAAAAAE41R0W6DMAz8FSuaBNUQdK8MkKZ-wh7HHihzu6hgosRMm1D-fUpSVNq12x4iEvvOx_kmQU2PIhfP3DCCJGgHYvxkkYid7NCI_GUS_KUcxhVEMjOelErNB3bsatvG4LW6n0ZsRC4K02qpuKqpZtmrQTNMYJA3QRAs7PTQQxS40eMCt3mX3duxnWb-lS5h7nTI0A4jMWoo4c44P_Hku-zrOazdy64chWo-ScfRkRgl8wgHKrLTH1OxHZkHgoHaTraHcopXUFYzPPVfuC_hwQaD1GrskdiNCdQwJljJqlvXfyqVsA5CGg0uRUQifHw56xFtciO75QrP07vo_JXf_tf8yK2ezDKY_ZWt_1y2qqYzv7bI1IW1V_sN19m-07wCAAA=))...
```svelte
<script>
import { setContext } from 'svelte';
import Child from './Child.svelte';
setContext('key', value);
let counter = $state({
count: 0
});
setContext('counter', counter);
</script>
<button onclick={() => counter.count += 1}>
increment
</button>
<Child />
<Child />
<Child />
```
The context is then available to children of the component (including slotted content) with `getContext`.
...though note that if you _reassign_ `counter` instead of updating it, you will 'break the link' — in other words instead of this...
```svelte
<script>
import { getContext } from 'svelte';
const value = getContext('key');
</script>
<button onclick={() => counter = { count: 0 }}>
reset
</button>
```
`setContext` and `getContext` solve the above problems:
...you must do this:
- the state is not global, it's scoped to the component. That way it's safe to render your components on the server and not leak state
- it's clear that the state is not global but rather scoped to a specific component tree and therefore can't be used in other parts of your app
```svelte
<button onclick={() => +++counter.count = 0+++}>
reset
</button>
```
> [!NOTE] `setContext`/`getContext` must be called during component initialisation.
Svelte will warn you if you get it wrong.
Context is not inherently reactive. If you need reactive values in context then you can pass a `$state` object into context, whose properties _will_ be reactive.
## Type-safe context
```svelte
<!--- file: Parent.svelte --->
<script>
import { setContext } from 'svelte';
A useful pattern is to wrap the calls to `setContext` and `getContext` inside helper functions that let you preserve type safety:
let value = $state({ count: 0 });
setContext('counter', value);
</script>
```js
/// file: context.js
// @filename: ambient.d.ts
interface User {}
<button onclick={() => value.count++}>increment</button>
```
// @filename: index.js
// ---cut---
import { getContext, setContext } from 'svelte';
```svelte
<!--- file: Child.svelte --->
<script>
import { getContext } from 'svelte';
let key = {};
const value = getContext('counter');
</script>
/** @param {User} user */
export function setUserContext(user) {
setContext(key, user);
}
<p>Count is {value.count}</p>
export function getUserContext() {
return /** @type {User} */ (getContext(key));
}
```
To check whether a given `key` has been set in the context of a parent component, use `hasContext`.
## Replacing global state
```svelte
<script>
import { hasContext } from 'svelte';
When you have state shared by many different components, you might be tempted to put it in its own module and just import it wherever it's needed:
if (hasContext('key')) {
// do something
```js
/// file: state.svelte.js
export const myGlobalState = $state({
user: {
// ...
}
</script>
// ...
});
```
You can also retrieve the whole context map that belongs to the closest parent component using `getAllContexts`. This is useful, for example, if you programmatically create a component and want to pass the existing context to it.
In many cases this is perfectly fine, but there is a risk: if you mutate the state during server-side rendering (which is discouraged, but entirely possible!)...
```svelte
<!--- file: App.svelte ---->
<script>
import { getAllContexts } from 'svelte';
import { myGlobalState } from 'svelte';
const contexts = getAllContexts();
let { data } = $props();
if (data.user) {
myGlobalState.user = data.user;
}
</script>
```
## Encapsulating context interactions
The above methods are very unopinionated about how to use them. When your app grows in scale, it's worthwhile to encapsulate setting and getting the context into functions and properly type them.
```ts
// @errors: 2304
import { getContext, setContext } from 'svelte';
let userKey = Symbol('user');
export function setUserContext(user: User) {
setContext(userKey, user);
}
export function getUserContext(): User {
return getContext(userKey) as User;
}
```
...then the data may be accessible by the _next_ user. Context solves this problem because it is not shared between requests.

@ -10,13 +10,13 @@ You don't have to migrate to the new syntax right away - Svelte 5 still supports
At the heart of Svelte 5 is the new runes API. Runes are basically compiler instructions that inform Svelte about reactivity. Syntactically, runes are functions starting with a dollar-sign.
### let -> $state
### let $state
In Svelte 4, a `let` declaration at the top level of a component was implicitly reactive. In Svelte 5, things are more explicit: a variable is reactive when created using the `$state` rune. Let's migrate the counter to runes mode by wrapping the counter in `$state`:
```svelte
<script>
let count = +++$state(+++0+++)+++;
let count = +++$state(0)+++;
</script>
```
@ -25,14 +25,14 @@ Nothing else changes. `count` is still the number itself, and you read and write
> [!DETAILS] Why we did this
> `let` being implicitly reactive at the top level worked great, but it meant that reactivity was constrained - a `let` declaration anywhere else was not reactive. This forced you to resort to using stores when refactoring code out of the top level of components for reuse. This meant you had to learn an entirely separate reactivity model, and the result often wasn't as nice to work with. Because reactivity is more explicit in Svelte 5, you can keep using the same API outside the top level of components. Head to [the tutorial](/tutorial) to learn more.
### $: -> $derived/$effect
### $: $derived/$effect
In Svelte 4, a `$:` statement at the top level of a component could be used to declare a derivation, i.e. state that is entirely defined through a computation of other state. In Svelte 5, this is achieved using the `$derived` rune:
```svelte
<script>
let count = +++$state(+++0+++)+++;
---$:--- +++const+++ double = +++$derived(+++count * 2+++)+++;
let count = $state(0);
---$:--- +++const+++ double = +++$derived(count * 2)+++;
</script>
```
@ -42,7 +42,8 @@ A `$:` statement could also be used to create side effects. In Svelte 5, this is
```svelte
<script>
let count = +++$state(+++0+++)+++;
let count = $state(0);
---$:---+++$effect(() =>+++ {
if (count > 5) {
alert('Count is too high!');
@ -73,14 +74,14 @@ Note that [when `$effect` runs is different]($effect#Understanding-dependencies)
> - executing dependencies as needed and therefore being immune to ordering problems
> - being TypeScript-friendly
### export let -> $props
### export let $props
In Svelte 4, properties of a component were declared using `export let`. Each property was one declaration. In Svelte 5, all properties are declared through the `$props` rune, through destructuring:
```svelte
<script>
---export let optional = 'unset';
export let required;---
---export let optional = 'unset';---
---export let required;---
+++let { optional = 'unset', required } = $props();+++
</script>
```
@ -105,8 +106,8 @@ In Svelte 5, the `$props` rune makes this straightforward without any additional
```svelte
<script>
---let klass = '';
export { klass as class};---
---let klass = '';---
---export { klass as class};---
+++let { class: klass, ...rest } = $props();+++
</script>
<button class={klass} {...---$$restProps---+++rest+++}>click me</button>
@ -192,9 +193,9 @@ This function is deprecated in Svelte 5. Instead, components should accept _call
```svelte
<!--- file: Pump.svelte --->
<script>
---import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
---
---import { createEventDispatcher } from 'svelte';---
---const dispatch = createEventDispatcher();---
+++let { inflate, deflate } = $props();+++
let power = $state(5);
</script>
@ -466,11 +467,11 @@ By now you should have a pretty good understanding of the before/after and how t
We thought the same, which is why we provide a migration script to do most of the migration automatically. You can upgrade your project by using `npx sv migrate svelte-5`. This will do the following things:
- bump core dependencies in your `package.json`
- migrate to runes (`let` -> `$state` etc)
- migrate to event attributes for DOM elements (`on:click` -> `onclick`)
- migrate slot creations to render tags (`<slot />` -> `{@render children()}`)
- migrate slot usages to snippets (`<div slot="x">...</div>` -> `{#snippet x()}<div>...</div>{/snippet}`)
- migrate obvious component creations (`new Component(...)` -> `mount(Component, ...)`)
- migrate to runes (`let` `$state` etc)
- migrate to event attributes for DOM elements (`on:click` `onclick`)
- migrate slot creations to render tags (`<slot />` `{@render children()}`)
- migrate slot usages to snippets (`<div slot="x">...</div>` `{#snippet x()}<div>...</div>{/snippet}`)
- migrate obvious component creations (`new Component(...)` `mount(Component, ...)`)
You can also migrate a single component in VS Code through the `Migrate Component to Svelte 5 Syntax` command, or in our Playground through the `Migrate` button.

@ -96,7 +96,7 @@ However, you can use any router library. A lot of people use [page.js](https://g
If you prefer a declarative HTML approach, there's the isomorphic [svelte-routing](https://github.com/EmilTholin/svelte-routing) library and a fork of it called [svelte-navigator](https://github.com/mefechoel/svelte-navigator) containing some additional functionality.
If you need hash-based routing on the client side, check out [svelte-spa-router](https://github.com/ItalyPaleAle/svelte-spa-router) or [abstract-state-router](https://github.com/TehShrike/abstract-state-router/).
If you need hash-based routing on the client side, check out the [hash option](https://svelte.dev/docs/kit/configuration#router) in SvelteKit, [svelte-spa-router](https://github.com/ItalyPaleAle/svelte-spa-router), or [abstract-state-router](https://github.com/TehShrike/abstract-state-router/).
[Routify](https://routify.dev) is another filesystem-based router, similar to SvelteKit's router. Version 3 supports Svelte's native SSR.

@ -122,38 +122,39 @@ Property descriptors defined on `$state` objects must contain `value` and always
Cannot set prototype of `$state` object
```
### state_unsafe_local_read
```
Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state
```
### state_unsafe_mutation
```
Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
```
This error is thrown in a situation like this:
This error occurs when state is updated while evaluating a `$derived`. You might encounter it while trying to 'derive' two pieces of state in one go:
```svelte
<script>
let count = $state(0);
let multiple = $derived.by(() => {
const result = count * 2;
if (result > 10) {
count = 0;
}
return result;
});
let count = $state(0);
let even = $state(true);
let odd = $derived.by(() => {
even = count % 2 === 0;
return !even;
});
</script>
<button onclick={() => count++}>{count} / {multiple}</button>
<button onclick={() => count++}>{count}</button>
<p>{count} is even: {even}</p>
<p>{count} is odd: {odd}</p>
```
Here, the `$derived` updates `count`, which is `$state` and therefore forbidden to do. It is forbidden because the reactive graph could become unstable as a result, leading to subtle bugs, like values being stale or effects firing in the wrong order. To prevent this, Svelte errors when detecting an update to a `$state` variable.
This is forbidden because it introduces instability: if `<p>{count} is even: {even}</p>` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived:
```js
let count = 0;
// ---cut---
let even = $derived(count % 2 === 0);
let odd = $derived(!even);
```
To fix this:
- See if it's possible to refactor your `$derived` such that the update becomes unnecessary
- Think about why you need to update `$state` inside a `$derived` in the first place. Maybe it's because you're using `bind:`, which leads you down a bad code path, and separating input and output path (by splitting it up to an attribute and an event, or by using [Function bindings](bind#Function-bindings)) makes it possible avoid the update
- If it's unavoidable, you may need to use an [`$effect`]($effect) instead. This could include splitting parts of the `$derived` into an [`$effect`]($effect) which does the updates
If side-effects are unavoidable, use [`$effect`]($effect) instead.

@ -660,6 +660,12 @@ Cannot access a computed property of a rune
`%name%` is not a valid rune
```
### rune_invalid_spread
```
`%rune%` cannot be called with a spread argument
```
### rune_invalid_usage
```

@ -1,5 +1,103 @@
# svelte
## 5.25.6
### Patch Changes
- fix: ignore generic type arguments while creating AST ([#15659](https://github.com/sveltejs/svelte/pull/15659))
- fix: better consider component and its snippets during css pruning ([#15630](https://github.com/sveltejs/svelte/pull/15630))
## 5.25.5
### Patch Changes
- fix: add setters to `$derived` class properties ([#15628](https://github.com/sveltejs/svelte/pull/15628))
- fix: silence assignment warning on more function bindings ([#15644](https://github.com/sveltejs/svelte/pull/15644))
- fix: make sure CSS is preserved during SSR with bindings ([#15645](https://github.com/sveltejs/svelte/pull/15645))
## 5.25.4
### Patch Changes
- fix: support TS type assertions ([#15642](https://github.com/sveltejs/svelte/pull/15642))
- fix: ensure `undefined` class still applies scoping class, if necessary ([#15643](https://github.com/sveltejs/svelte/pull/15643))
## 5.25.3
### Patch Changes
- fix: prevent state runes from being called with spread ([#15585](https://github.com/sveltejs/svelte/pull/15585))
## 5.25.2
### Patch Changes
- feat: migrate reassigned deriveds to `$derived` ([#15581](https://github.com/sveltejs/svelte/pull/15581))
## 5.25.1
### Patch Changes
- fix: prevent dev server from throwing errors when attempting to retrieve the proxied value of an iframe's contentWindow ([#15577](https://github.com/sveltejs/svelte/pull/15577))
## 5.25.0
### Minor Changes
- feat: make deriveds writable ([#15570](https://github.com/sveltejs/svelte/pull/15570))
## 5.24.1
### Patch Changes
- fix: use `get` in constructor for deriveds ([#15300](https://github.com/sveltejs/svelte/pull/15300))
- fix: ensure toStore root effect is connected to correct parent effect ([#15574](https://github.com/sveltejs/svelte/pull/15574))
## 5.24.0
### Minor Changes
- feat: allow state created in deriveds/effects to be written/read locally without self-invalidation ([#15553](https://github.com/sveltejs/svelte/pull/15553))
### Patch Changes
- fix: check if DOM prototypes are extensible ([#15569](https://github.com/sveltejs/svelte/pull/15569))
- Keep inlined trailing JSDoc comments of properties when running svelte-migrate ([#15567](https://github.com/sveltejs/svelte/pull/15567))
- fix: simplify set calls for proxyable values ([#15548](https://github.com/sveltejs/svelte/pull/15548))
- fix: don't depend on deriveds created inside the current reaction ([#15564](https://github.com/sveltejs/svelte/pull/15564))
## 5.23.2
### Patch Changes
- fix: don't hoist listeners that access non hoistable snippets ([#15534](https://github.com/sveltejs/svelte/pull/15534))
## 5.23.1
### Patch Changes
- fix: invalidate parent effects when child effects update parent dependencies ([#15506](https://github.com/sveltejs/svelte/pull/15506))
- fix: correctly match `:has()` selector during css pruning ([#15277](https://github.com/sveltejs/svelte/pull/15277))
- fix: replace `undefined` with `void 0` to avoid edge case ([#15511](https://github.com/sveltejs/svelte/pull/15511))
- fix: allow global-like pseudo-selectors refinement ([#15313](https://github.com/sveltejs/svelte/pull/15313))
- chore: don't distribute unused types definitions ([#15473](https://github.com/sveltejs/svelte/pull/15473))
- fix: add `files` and `group` to HTMLInputAttributes in elements.d.ts ([#15492](https://github.com/sveltejs/svelte/pull/15492))
- fix: throw rune_invalid_arguments_length when $state.raw() is used with more than 1 arg ([#15516](https://github.com/sveltejs/svelte/pull/15516))
## 5.23.0
### Minor Changes

@ -80,34 +80,37 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long
> Cannot set prototype of `$state` object
## state_unsafe_local_read
> Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state
## state_unsafe_mutation
> Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
This error is thrown in a situation like this:
This error occurs when state is updated while evaluating a `$derived`. You might encounter it while trying to 'derive' two pieces of state in one go:
```svelte
<script>
let count = $state(0);
let multiple = $derived.by(() => {
const result = count * 2;
if (result > 10) {
count = 0;
}
return result;
});
let count = $state(0);
let even = $state(true);
let odd = $derived.by(() => {
even = count % 2 === 0;
return !even;
});
</script>
<button onclick={() => count++}>{count} / {multiple}</button>
<button onclick={() => count++}>{count}</button>
<p>{count} is even: {even}</p>
<p>{count} is odd: {odd}</p>
```
Here, the `$derived` updates `count`, which is `$state` and therefore forbidden to do. It is forbidden because the reactive graph could become unstable as a result, leading to subtle bugs, like values being stale or effects firing in the wrong order. To prevent this, Svelte errors when detecting an update to a `$state` variable.
This is forbidden because it introduces instability: if `<p>{count} is even: {even}</p>` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived:
```js
let count = 0;
// ---cut---
let even = $derived(count % 2 === 0);
let odd = $derived(!even);
```
To fix this:
- See if it's possible to refactor your `$derived` such that the update becomes unnecessary
- Think about why you need to update `$state` inside a `$derived` in the first place. Maybe it's because you're using `bind:`, which leads you down a bad code path, and separating input and output path (by splitting it up to an attribute and an event, or by using [Function bindings](bind#Function-bindings)) makes it possible avoid the update
- If it's unavoidable, you may need to use an [`$effect`]($effect) instead. This could include splitting parts of the `$derived` into an [`$effect`]($effect) which does the updates
If side-effects are unavoidable, use [`$effect`]($effect) instead.

@ -162,6 +162,10 @@ This turned out to be buggy and unpredictable, particularly when working with de
> `%name%` is not a valid rune
## rune_invalid_spread
> `%rune%` cannot be called with a spread argument
## rune_invalid_usage
> Cannot use `%rune%` rune in non-runes mode

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.23.0",
"version": "5.25.6",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
@ -156,7 +156,7 @@
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",
"esm-env": "^1.2.1",
"esrap": "^1.4.3",
"esrap": "^1.4.6",
"is-reference": "^3.0.3",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",

@ -383,6 +383,16 @@ export function rune_invalid_name(node, name) {
e(node, 'rune_invalid_name', `\`${name}\` is not a valid rune\nhttps://svelte.dev/e/rune_invalid_name`);
}
/**
* `%rune%` cannot be called with a spread argument
* @param {null | number | NodeLike} node
* @param {string} rune
* @returns {never}
*/
export function rune_invalid_spread(node, rune) {
e(node, 'rune_invalid_spread', `\`${rune}\` cannot be called with a spread argument\nhttps://svelte.dev/e/rune_invalid_spread`);
}
/**
* Cannot use `%rune%` rune in non-runes mode
* @param {null | number | NodeLike} node

@ -952,7 +952,7 @@ const instance_script = {
const reassigned_bindings = bindings.filter((b) => b?.reassigned);
if (
reassigned_bindings.length === 0 &&
node.body.expression.right.type !== 'Literal' &&
!bindings.some((b) => b?.kind === 'store_sub') &&
node.body.expression.left.type !== 'MemberExpression'
) {
@ -1592,7 +1592,6 @@ function extract_type_and_comment(declarator, state, path) {
const comment_start = /** @type {any} */ (comment_node)?.start;
const comment_end = /** @type {any} */ (comment_node)?.end;
let comment = comment_node && str.original.substring(comment_start, comment_end);
if (comment_node) {
str.update(comment_start, comment_end, '');
}
@ -1673,6 +1672,11 @@ function extract_type_and_comment(declarator, state, path) {
state.has_type_or_fallback = true;
const match = /@type {(.+)}/.exec(comment_node.value);
if (match) {
// try to find JSDoc comments after a hyphen `-`
const jsdoc_comment = /@type {.+} (?:\w+|\[.*?\]) - (.+)/.exec(comment_node.value);
if (jsdoc_comment) {
cleaned_comment += jsdoc_comment[1]?.trim();
}
return {
type: match[1],
comment: cleaned_comment,
@ -1693,7 +1697,6 @@ function extract_type_and_comment(declarator, state, path) {
};
}
}
return {
type: 'any',
comment: state.uses_ts ? comment : cleaned_comment,

@ -24,6 +24,7 @@ const visitors = {
// until that day comes, we just delete them so they don't confuse esrap
delete n.typeAnnotation;
delete n.typeParameters;
delete n.typeArguments;
delete n.returnType;
delete n.accessibility;
},
@ -94,6 +95,9 @@ const visitors = {
TSTypeAliasDeclaration() {
return b.empty;
},
TSTypeAssertion(node, context) {
return context.visit(node.expression);
},
TSEnumDeclaration(node) {
e.typescript_invalid_feature(node, 'enums');
},

@ -251,7 +251,11 @@ function apply_combinator(relative_selector, rest_selectors, rule, node, directi
let sibling_matched = false;
for (const possible_sibling of siblings.keys()) {
if (possible_sibling.type === 'RenderTag' || possible_sibling.type === 'SlotElement') {
if (
possible_sibling.type === 'RenderTag' ||
possible_sibling.type === 'SlotElement' ||
possible_sibling.type === 'Component'
) {
// `{@render foo()}<p>foo</p>` with `:global(.x) + p` is a match
if (rest_selectors.length === 1 && rest_selectors[0].metadata.is_global) {
sibling_matched = true;
@ -814,10 +818,10 @@ function get_element_parent(node) {
* @param {Direction} direction
* @param {boolean} adjacent_only
* @param {Set<Compiler.AST.SnippetBlock>} seen
* @returns {Map<Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.SlotElement | Compiler.AST.RenderTag, NodeExistsValue>}
* @returns {Map<Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.SlotElement | Compiler.AST.RenderTag | Compiler.AST.Component, NodeExistsValue>}
*/
function get_possible_element_siblings(node, direction, adjacent_only, seen = new Set()) {
/** @type {Map<Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.SlotElement | Compiler.AST.RenderTag, NodeExistsValue>} */
/** @type {Map<Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.SlotElement | Compiler.AST.RenderTag | Compiler.AST.Component, NodeExistsValue>} */
const result = new Map();
const path = node.metadata.path;
@ -847,14 +851,18 @@ function get_possible_element_siblings(node, direction, adjacent_only, seen = ne
}
// Special case: slots, render tags and svelte:element tags could resolve to no siblings,
// so we want to continue until we find a definite sibling even with the adjacent-only combinator
} else if (is_block(node)) {
if (node.type === 'SlotElement') {
} else if (is_block(node) || node.type === 'Component') {
if (node.type === 'SlotElement' || node.type === 'Component') {
result.set(node, NODE_PROBABLY_EXISTS);
}
const possible_last_child = get_possible_nested_siblings(node, direction, adjacent_only);
add_to_map(possible_last_child, result);
if (adjacent_only && has_definite_elements(possible_last_child)) {
if (
adjacent_only &&
node.type !== 'Component' &&
has_definite_elements(possible_last_child)
) {
return result;
}
} else if (node.type === 'SvelteElement') {
@ -907,7 +915,7 @@ function get_possible_element_siblings(node, direction, adjacent_only, seen = ne
}
/**
* @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement | Compiler.AST.SnippetBlock} node
* @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement | Compiler.AST.SnippetBlock | Compiler.AST.Component} node
* @param {Direction} direction
* @param {boolean} adjacent_only
* @param {Set<Compiler.AST.SnippetBlock>} seen
@ -942,6 +950,10 @@ function get_possible_nested_siblings(node, direction, adjacent_only, seen = new
seen.add(node);
fragments.push(node.body);
break;
case 'Component':
fragments.push(node.fragment, ...[...node.metadata.snippets].map((s) => s.body));
break;
}
/** @type {Map<Compiler.AST.RegularElement | Compiler.AST.SvelteElement, NodeExistsValue>} NodeMap */

@ -183,6 +183,15 @@ function get_delegated_event(event_name, handler, context) {
const binding = scope.get(reference);
const local_binding = context.state.scope.get(reference);
// if the function access a snippet that can't be hoisted we bail out
if (
local_binding !== null &&
local_binding.initial?.type === 'SnippetBlock' &&
!local_binding.initial.metadata.can_hoist
) {
return unhoisted;
}
// If we are referencing a binding that is shadowed in another scope then bail out.
if (local_binding !== null && binding !== null && local_binding.node !== binding.node) {
return unhoisted;

@ -17,6 +17,14 @@ export function CallExpression(node, context) {
const rune = get_rune(node, context.state.scope);
if (rune && rune !== '$inspect') {
for (const arg of node.arguments) {
if (arg.type === 'SpreadElement') {
e.rune_invalid_spread(node, rune);
}
}
}
switch (rune) {
case null:
if (!is_safe_identifier(node.callee, context.state.scope)) {
@ -117,7 +125,7 @@ export function CallExpression(node, context) {
if ((rune === '$derived' || rune === '$derived.by') && node.arguments.length !== 1) {
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
} else if (rune === '$state' && node.arguments.length > 1) {
} else if (node.arguments.length > 1) {
e.rune_invalid_arguments_length(node, rune, 'zero or one arguments');
}

@ -21,10 +21,6 @@ export function validate_assignment(node, argument, state) {
const binding = state.scope.get(argument.name);
if (state.analysis.runes) {
if (binding?.kind === 'derived') {
e.constant_assignment(node, 'derived state');
}
if (binding?.node === state.analysis.props_id) {
e.constant_assignment(node, '$props.id()');
}
@ -38,25 +34,6 @@ export function validate_assignment(node, argument, state) {
e.snippet_parameter_assignment(node);
}
}
if (
argument.type === 'MemberExpression' &&
argument.object.type === 'ThisExpression' &&
(((argument.property.type === 'PrivateIdentifier' || argument.property.type === 'Identifier') &&
state.derived_state.some(
(derived) =>
derived.name === /** @type {PrivateIdentifier | Identifier} */ (argument.property).name &&
derived.private === (argument.property.type === 'PrivateIdentifier')
)) ||
(argument.property.type === 'Literal' &&
argument.property.value &&
typeof argument.property.value === 'string' &&
state.derived_state.some(
(derived) =>
derived.name === /** @type {Literal} */ (argument.property).value && !derived.private
)))
) {
e.constant_assignment(node, 'derived state');
}
}
/**
@ -81,7 +58,6 @@ export function validate_no_const_assignment(node, argument, scope, is_binding)
} else if (argument.type === 'Identifier') {
const binding = scope.get(argument.name);
if (
binding?.kind === 'derived' ||
binding?.declaration_kind === 'import' ||
(binding?.declaration_kind === 'const' && binding.kind !== 'each')
) {
@ -96,12 +72,7 @@ export function validate_no_const_assignment(node, argument, scope, is_binding)
// );
// TODO have a more specific error message for assignments to things like `{:then foo}`
const thing =
binding.declaration_kind === 'import'
? 'import'
: binding.kind === 'derived'
? 'derived state'
: 'constant';
const thing = binding.declaration_kind === 'import' ? 'import' : 'constant';
if (is_binding) {
e.constant_binding(node, thing);

@ -219,7 +219,10 @@ export function client_component(analysis, options) {
for (const [name, binding] of analysis.instance.scope.declarations) {
if (binding.kind === 'legacy_reactive') {
legacy_reactive_declarations.push(
b.const(name, b.call('$.mutable_state', undefined, analysis.immutable ? b.true : undefined))
b.const(
name,
b.call('$.mutable_source', undefined, analysis.immutable ? b.true : undefined)
)
);
}
if (binding.kind === 'store_sub') {

@ -30,7 +30,7 @@ export interface ClientTransformState extends TransformState {
/** turn `foo` into e.g. `$.get(foo)` */
read: (id: Identifier) => Expression;
/** turn `foo = bar` into e.g. `$.set(foo, bar)` */
assign?: (node: Identifier, value: Expression) => Expression;
assign?: (node: Identifier, value: Expression, proxy?: boolean) => Expression;
/** turn `foo.bar = baz` into e.g. `$.mutate(foo, $.get(foo).bar = baz);` */
mutate?: (node: Identifier, mutation: AssignmentExpression | UpdateExpression) => Expression;
/** turn `foo++` into e.g. `$.update(foo)` */

@ -45,14 +45,6 @@ export function build_getter(node, state) {
return node;
}
/**
* @param {Expression} value
* @param {Expression} previous
*/
export function build_proxy_reassignment(value, previous) {
return dev ? b.call('$.proxy', value, b.null, previous) : b.call('$.proxy', value);
}
/**
* @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
* @param {ComponentContext} context

@ -1,5 +1,4 @@
/** @import { Location } from 'locate-character' */
/** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Literal, MemberExpression, Pattern } from 'estree' */
/** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Pattern } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { Context } from '../types.js' */
import * as b from '../../../../utils/builders.js';
@ -8,8 +7,8 @@ import {
get_attribute_expression,
is_event_attribute
} from '../../../../utils/ast.js';
import { dev, filename, is_ignored, locate_node, locator } from '../../../../state.js';
import { build_proxy_reassignment, should_proxy } from '../utils.js';
import { dev, is_ignored, locate_node } from '../../../../state.js';
import { should_proxy } from '../utils.js';
import { visit_assignment_expression } from '../../shared/assignments.js';
/**
@ -65,21 +64,12 @@ function build_assignment(operator, left, right, context) {
context.visit(build_assignment_value(operator, left, right))
);
if (
const needs_proxy =
private_state.kind === 'state' &&
is_non_coercive_operator(operator) &&
should_proxy(value, context.state.scope)
) {
value = build_proxy_reassignment(value, b.member(b.this, private_state.id));
}
if (context.state.in_constructor) {
// inside the constructor, we can assign to `this.#foo.v` rather than using `$.set`,
// since nothing is tracking the signal at this point
return b.assignment(operator, /** @type {Pattern} */ (context.visit(left)), value);
}
should_proxy(value, context.state.scope);
return b.call('$.set', left, value);
return b.call('$.set', left, value, needs_proxy && b.true);
}
}
@ -113,20 +103,18 @@ function build_assignment(operator, left, right, context) {
context.visit(build_assignment_value(operator, left, right))
);
if (
return transform.assign(
object,
value,
!is_primitive &&
binding.kind !== 'prop' &&
binding.kind !== 'bindable_prop' &&
binding.kind !== 'raw_state' &&
binding.kind !== 'store_sub' &&
context.state.analysis.runes &&
should_proxy(right, context.state.scope) &&
is_non_coercive_operator(operator)
) {
value = build_proxy_reassignment(value, object);
}
return transform.assign(object, value);
binding.kind !== 'prop' &&
binding.kind !== 'bindable_prop' &&
binding.kind !== 'raw_state' &&
binding.kind !== 'store_sub' &&
context.state.analysis.runes &&
should_proxy(right, context.state.scope) &&
is_non_coercive_operator(operator)
);
}
// mutation
@ -177,7 +165,9 @@ function build_assignment(operator, left, right, context) {
path.at(-1) === 'SvelteComponent' ||
(path.at(-1) === 'ArrowFunctionExpression' &&
path.at(-2) === 'SequenceExpression' &&
(path.at(-3) === 'Component' || path.at(-3) === 'SvelteComponent'))
(path.at(-3) === 'Component' ||
path.at(-3) === 'SvelteComponent' ||
path.at(-3) === 'BindDirective'))
) {
should_transform = false;
}

@ -5,7 +5,7 @@ import { dev, is_ignored } from '../../../../state.js';
import * as b from '../../../../utils/builders.js';
import { regex_invalid_identifier_chars } from '../../../patterns.js';
import { get_rune } from '../../../scope.js';
import { build_proxy_reassignment, should_proxy } from '../utils.js';
import { should_proxy } from '../utils.js';
/**
* @param {ClassBody} node
@ -142,39 +142,17 @@ export function ClassBody(node, context) {
// get foo() { return this.#foo; }
body.push(b.method('get', definition.key, [], [b.return(b.call('$.get', member))]));
if (field.kind === 'state') {
// set foo(value) { this.#foo = value; }
const value = b.id('value');
const prev = b.member(b.this, field.id);
body.push(
b.method(
'set',
definition.key,
[value],
[b.stmt(b.call('$.set', member, build_proxy_reassignment(value, prev)))]
)
);
}
if (field.kind === 'raw_state') {
// set foo(value) { this.#foo = value; }
const value = b.id('value');
body.push(
b.method('set', definition.key, [value], [b.stmt(b.call('$.set', member, value))])
);
}
// set foo(value) { this.#foo = value; }
const val = b.id('value');
if (dev && (field.kind === 'derived' || field.kind === 'derived_by')) {
body.push(
b.method(
'set',
definition.key,
[b.id('_')],
[b.throw_error(`Cannot update a derived property ('${name}')`)]
)
);
}
body.push(
b.method(
'set',
definition.key,
[val],
[b.stmt(b.call('$.set', member, val, field.kind === 'state' && b.true))]
)
);
}
continue;
}

@ -11,7 +11,9 @@ export function MemberExpression(node, context) {
if (node.property.type === 'PrivateIdentifier') {
const field = context.state.private_state.get(node.property.name);
if (field) {
return context.state.in_constructor ? b.member(node, 'v') : b.call('$.get', node);
return context.state.in_constructor && (field.kind === 'raw_state' || field.kind === 'state')
? b.member(node, 'v')
: b.call('$.get', node);
}
}

@ -299,7 +299,7 @@ function create_state_declarators(declarator, { scope, analysis }, value) {
return [
b.declarator(
declarator.id,
b.call('$.mutable_state', value, analysis.immutable ? b.true : undefined)
b.call('$.mutable_source', value, analysis.immutable ? b.true : undefined)
)
];
}
@ -314,7 +314,7 @@ function create_state_declarators(declarator, { scope, analysis }, value) {
return b.declarator(
path.node,
binding?.kind === 'state'
? b.call('$.mutable_state', value, analysis.immutable ? b.true : undefined)
? b.call('$.mutable_source', value, analysis.immutable ? b.true : undefined)
: value
);
})

@ -24,8 +24,8 @@ export function add_state_transformers(context) {
) {
context.state.transform[name] = {
read: binding.declaration_kind === 'var' ? (node) => b.call('$.safe_get', node) : get_value,
assign: (node, value) => {
let call = b.call('$.set', node, value);
assign: (node, value, proxy = false) => {
let call = b.call('$.set', node, value, proxy && b.true);
if (context.state.scope.get(`$${node.name}`)?.kind === 'store_sub') {
call = b.call('$.store_unsub', call, b.literal(`$${node.name}`), b.id('$$stores'));

@ -186,12 +186,10 @@ export function server_component(analysis, options) {
...snippets,
b.let('$$settled', b.true),
b.let('$$inner_payload'),
b.stmt(
b.function(
b.id('$$render_inner'),
[b.id('$$payload')],
b.block(/** @type {Statement[]} */ (rest))
)
b.function_declaration(
b.id('$$render_inner'),
[b.id('$$payload')],
b.block(/** @type {Statement[]} */ (rest))
),
b.do_while(
b.unary('!', b.id('$$settled')),

@ -20,6 +20,7 @@ export const LEGACY_DERIVED_PROP = 1 << 17;
export const INSPECT_EFFECT = 1 << 18;
export const HEAD_EFFECT = 1 << 19;
export const EFFECT_HAS_DERIVED = 1 << 20;
export const EFFECT_IS_UPDATING = 1 << 21;
export const STATE_SYMBOL = Symbol('$state');
export const STATE_SYMBOL_METADATA = Symbol('$state metadata');

@ -14,7 +14,11 @@ export function set_class(dom, is_html, value, hash, prev_classes, next_classes)
// @ts-expect-error need to add __className to patched prototype
var prev = dom.__className;
if (hydrating || prev !== value) {
if (
hydrating ||
prev !== value ||
prev === undefined // for edge case of `class={undefined}`
) {
var next_class_name = to_class(value, hash, next_classes);
if (!hydrating || next_class_name !== dom.getAttribute('class')) {

@ -2,7 +2,7 @@
import { hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
import { DEV } from 'esm-env';
import { init_array_prototype_warnings } from '../dev/equality.js';
import { get_descriptor } from '../../shared/utils.js';
import { get_descriptor, is_extensible } from '../../shared/utils.js';
// export these for reference in the compiled code, making global name deduplication unnecessary
/** @type {Window} */
@ -34,26 +34,31 @@ export function init_operations() {
var element_prototype = Element.prototype;
var node_prototype = Node.prototype;
var text_prototype = Text.prototype;
// @ts-ignore
first_child_getter = get_descriptor(node_prototype, 'firstChild').get;
// @ts-ignore
next_sibling_getter = get_descriptor(node_prototype, 'nextSibling').get;
// the following assignments improve perf of lookups on DOM nodes
// @ts-expect-error
element_prototype.__click = undefined;
// @ts-expect-error
element_prototype.__className = undefined;
// @ts-expect-error
element_prototype.__attributes = null;
// @ts-expect-error
element_prototype.__style = undefined;
// @ts-expect-error
element_prototype.__e = undefined;
// @ts-expect-error
Text.prototype.__t = undefined;
if (is_extensible(element_prototype)) {
// the following assignments improve perf of lookups on DOM nodes
// @ts-expect-error
element_prototype.__click = undefined;
// @ts-expect-error
element_prototype.__className = undefined;
// @ts-expect-error
element_prototype.__attributes = null;
// @ts-expect-error
element_prototype.__style = undefined;
// @ts-expect-error
element_prototype.__e = undefined;
}
if (is_extensible(text_prototype)) {
// @ts-expect-error
text_prototype.__t = undefined;
}
if (DEV) {
// @ts-expect-error

@ -307,21 +307,6 @@ export function state_prototype_fixed() {
}
}
/**
* Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state
* @returns {never}
*/
export function state_unsafe_local_read() {
if (DEV) {
const error = new Error(`state_unsafe_local_read\nReading state that was created inside the same derived is forbidden. Consider using \`untrack\` to read locally created state\nhttps://svelte.dev/e/state_unsafe_local_read`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/state_unsafe_local_read`);
}
}
/**
* Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
* @returns {never}

@ -102,7 +102,7 @@ export {
text,
props_id
} from './dom/template.js';
export { derived, derived_safe_equal } from './reactivity/deriveds.js';
export { user_derived as derived, derived_safe_equal } from './reactivity/deriveds.js';
export {
effect_tracking,
effect_root,
@ -114,7 +114,7 @@ export {
user_effect,
user_pre_effect
} from './reactivity/effects.js';
export { mutable_state, mutate, set, state, update, update_pre } from './reactivity/sources.js';
export { mutable_source, mutate, set, state, update, update_pre } from './reactivity/sources.js';
export {
prop,
rest_props,

@ -1,6 +1,6 @@
/** @import { ProxyMetadata, Source } from '#client' */
import { DEV } from 'esm-env';
import { get, active_effect } from './runtime.js';
import { get, active_effect, active_reaction, set_active_reaction } from './runtime.js';
import { component_context } from './context.js';
import {
array_prototype,
@ -10,26 +10,23 @@ import {
object_prototype
} from '../shared/utils.js';
import { check_ownership, widen_ownership } from './dev/ownership.js';
import { source, set } from './reactivity/sources.js';
import { state as source, set } from './reactivity/sources.js';
import { STATE_SYMBOL, STATE_SYMBOL_METADATA } from './constants.js';
import { UNINITIALIZED } from '../../constants.js';
import * as e from './errors.js';
import { get_stack } from './dev/tracing.js';
import { tracing_mode_flag } from '../flags/index.js';
/** @type {ProxyMetadata | null} */
var parent_metadata = null;
/**
* @template T
* @param {T} value
* @param {ProxyMetadata | null} [parent]
* @param {Source<T>} [prev] dev mode only
* @returns {T}
*/
export function proxy(value, parent = null, prev) {
/** @type {Error | null} */
var stack = null;
if (DEV && tracing_mode_flag) {
stack = get_stack('CreatedAt');
}
export function proxy(value, prev) {
// if non-proxyable, or is already a proxy, return `value`
if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) {
return value;
@ -46,6 +43,33 @@ export function proxy(value, parent = null, prev) {
var is_proxied_array = is_array(value);
var version = source(0);
var stack = DEV && tracing_mode_flag ? get_stack('CreatedAt') : null;
var reaction = active_reaction;
/**
* @template T
* @param {() => T} fn
*/
var with_parent = (fn) => {
var previous_reaction = active_reaction;
set_active_reaction(reaction);
/** @type {T} */
var result;
if (DEV) {
var previous_metadata = parent_metadata;
parent_metadata = metadata;
result = fn();
parent_metadata = previous_metadata;
} else {
result = fn();
}
set_active_reaction(previous_reaction);
return result;
};
if (is_proxied_array) {
// We need to create the length source eagerly to ensure that
// mutations to the array are properly synced with our proxy
@ -57,7 +81,7 @@ export function proxy(value, parent = null, prev) {
if (DEV) {
metadata = {
parent,
parent: parent_metadata,
owners: null
};
@ -69,7 +93,7 @@ export function proxy(value, parent = null, prev) {
metadata.owners = prev_owners ? new Set(prev_owners) : null;
} else {
metadata.owners =
parent === null
parent_metadata === null
? component_context !== null
? new Set([component_context.function])
: null
@ -95,10 +119,13 @@ export function proxy(value, parent = null, prev) {
var s = sources.get(prop);
if (s === undefined) {
s = source(descriptor.value, stack);
s = with_parent(() => source(descriptor.value, stack));
sources.set(prop, s);
} else {
set(s, proxy(descriptor.value, metadata));
set(
s,
with_parent(() => proxy(descriptor.value))
);
}
return true;
@ -109,7 +136,10 @@ export function proxy(value, parent = null, prev) {
if (s === undefined) {
if (prop in target) {
sources.set(prop, source(UNINITIALIZED, stack));
sources.set(
prop,
with_parent(() => source(UNINITIALIZED, stack))
);
}
} else {
// When working with arrays, we need to also ensure we update the length when removing
@ -143,7 +173,7 @@ export function proxy(value, parent = null, prev) {
// create a source, but only if it's an own property and not a prototype property
if (s === undefined && (!exists || get_descriptor(target, prop)?.writable)) {
s = source(proxy(exists ? target[prop] : UNINITIALIZED, metadata), stack);
s = with_parent(() => source(proxy(exists ? target[prop] : UNINITIALIZED), stack));
sources.set(prop, s);
}
@ -211,7 +241,7 @@ export function proxy(value, parent = null, prev) {
(active_effect !== null && (!has || get_descriptor(target, prop)?.writable))
) {
if (s === undefined) {
s = source(has ? proxy(target[prop], metadata) : UNINITIALIZED, stack);
s = with_parent(() => source(has ? proxy(target[prop]) : UNINITIALIZED, stack));
sources.set(prop, s);
}
@ -238,7 +268,7 @@ export function proxy(value, parent = null, prev) {
// If the item exists in the original, we need to create a uninitialized source,
// else a later read of the property would result in a source being created with
// the value of the original item at that index.
other_s = source(UNINITIALIZED, stack);
other_s = with_parent(() => source(UNINITIALIZED, stack));
sources.set(i + '', other_s);
}
}
@ -250,13 +280,19 @@ export function proxy(value, parent = null, prev) {
// object property before writing to that property.
if (s === undefined) {
if (!has || get_descriptor(target, prop)?.writable) {
s = source(undefined, stack);
set(s, proxy(value, metadata));
s = with_parent(() => source(undefined, stack));
set(
s,
with_parent(() => proxy(value))
);
sources.set(prop, s);
}
} else {
has = s.v !== UNINITIALIZED;
set(s, proxy(value, metadata));
set(
s,
with_parent(() => proxy(value))
);
}
if (DEV) {
@ -330,8 +366,18 @@ function update_version(signal, d = 1) {
* @param {any} value
*/
export function get_proxied_value(value) {
if (value !== null && typeof value === 'object' && STATE_SYMBOL in value) {
return value[STATE_SYMBOL];
try {
if (value !== null && typeof value === 'object' && STATE_SYMBOL in value) {
return value[STATE_SYMBOL];
}
} catch {
// the above if check can throw an error if the value in question
// is the contentWindow of an iframe on another domain, in which
// case we want to just return the value (because it's definitely
// not a proxied value) so we don't break any JavaScript interacting
// with that iframe (such as various payment companies client side
// JavaScript libraries interacting with their iframes on the same
// domain)
}
return value;

@ -8,7 +8,8 @@ import {
skip_reaction,
update_reaction,
increment_write_version,
set_active_effect
set_active_effect,
push_reaction_value
} from '../runtime.js';
import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js';
@ -61,6 +62,19 @@ export function derived(fn) {
return signal;
}
/**
* @template V
* @param {() => V} fn
* @returns {Derived<V>}
*/
export function user_derived(fn) {
const d = derived(fn);
push_reaction_value(d);
return d;
}
/**
* @template V
* @param {() => V} fn

@ -11,11 +11,12 @@ import {
untrack,
increment_write_version,
update_effect,
derived_sources,
set_derived_sources,
reaction_sources,
set_reaction_sources,
check_dirtiness,
untracking,
is_destroying_effect
is_destroying_effect,
push_reaction_value
} from '../runtime.js';
import { equals, safe_equals } from './equality.js';
import {
@ -27,12 +28,14 @@ import {
UNOWNED,
MAYBE_DIRTY,
BLOCK_EFFECT,
ROOT_EFFECT
ROOT_EFFECT,
EFFECT_IS_UPDATING
} from '../constants.js';
import * as e from '../errors.js';
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
import { get_stack } from '../dev/tracing.js';
import { component_context, is_runes } from '../context.js';
import { proxy } from '../proxy.js';
export let inspect_effects = new Set();
export const old_values = new Map();
@ -50,6 +53,7 @@ export function set_inspect_effects(v) {
* @param {Error | null} [stack]
* @returns {Source<V>}
*/
// TODO rename this to `state` throughout the codebase
export function source(v, stack) {
/** @type {Value} */
var signal = {
@ -72,9 +76,14 @@ export function source(v, stack) {
/**
* @template V
* @param {V} v
* @param {Error | null} [stack]
*/
export function state(v) {
return push_derived_source(source(v));
export function state(v, stack) {
const s = source(v, stack);
push_reaction_value(s);
return s;
}
/**
@ -99,33 +108,6 @@ export function mutable_source(initial_value, immutable = false) {
return s;
}
/**
* @template V
* @param {V} v
* @param {boolean} [immutable]
* @returns {Source<V>}
*/
export function mutable_state(v, immutable = false) {
return push_derived_source(mutable_source(v, immutable));
}
/**
* @template V
* @param {Source<V>} source
*/
/*#__NO_SIDE_EFFECTS__*/
function push_derived_source(source) {
if (active_reaction !== null && !untracking && (active_reaction.f & DERIVED) !== 0) {
if (derived_sources === null) {
set_derived_sources([source]);
} else {
derived_sources.push(source);
}
}
return source;
}
/**
* @template V
* @param {Value<V>} source
@ -143,22 +125,23 @@ export function mutate(source, value) {
* @template V
* @param {Source<V>} source
* @param {V} value
* @param {boolean} [should_proxy]
* @returns {V}
*/
export function set(source, value) {
export function set(source, value, should_proxy = false) {
if (
active_reaction !== null &&
!untracking &&
is_runes() &&
(active_reaction.f & (DERIVED | BLOCK_EFFECT)) !== 0 &&
// If the source was created locally within the current derived, then
// we allow the mutation.
(derived_sources === null || !derived_sources.includes(source))
!reaction_sources?.includes(source)
) {
e.state_unsafe_mutation();
}
return internal_set(source, value);
let new_value = should_proxy ? proxy(value, source) : value;
return internal_set(source, new_value);
}
/**

@ -22,7 +22,8 @@ import {
ROOT_EFFECT,
LEGACY_DERIVED_PROP,
DISCONNECTED,
BOUNDARY_EFFECT
BOUNDARY_EFFECT,
EFFECT_IS_UPDATING
} from './constants.js';
import { flush_tasks } from './dom/task.js';
import { internal_set, old_values } from './reactivity/sources.js';
@ -87,17 +88,28 @@ export function set_active_effect(effect) {
}
/**
* When sources are created within a derived, we record them so that we can safely allow
* local mutations to these sources without the side-effect error being invoked unnecessarily.
* When sources are created within a reaction, reading and writing
* them should not cause a re-run
* @type {null | Source[]}
*/
export let derived_sources = null;
export let reaction_sources = null;
/**
* @param {Source[] | null} sources
*/
export function set_derived_sources(sources) {
derived_sources = sources;
export function set_reaction_sources(sources) {
reaction_sources = sources;
}
/** @param {Value} value */
export function push_reaction_value(value) {
if (active_reaction !== null && active_reaction.f & EFFECT_IS_UPDATING) {
if (reaction_sources === null) {
set_reaction_sources([value]);
} else {
reaction_sources.push(value);
}
}
}
/**
@ -367,6 +379,9 @@ function schedule_possible_effect_self_invalidation(signal, effect, root = true)
for (var i = 0; i < reactions.length; i++) {
var reaction = reactions[i];
if (reaction_sources?.includes(signal)) continue;
if ((reaction.f & DERIVED) !== 0) {
schedule_possible_effect_self_invalidation(/** @type {Derived} */ (reaction), effect, false);
} else if (effect === reaction) {
@ -391,9 +406,10 @@ export function update_reaction(reaction) {
var previous_untracked_writes = untracked_writes;
var previous_reaction = active_reaction;
var previous_skip_reaction = skip_reaction;
var prev_derived_sources = derived_sources;
var previous_reaction_sources = reaction_sources;
var previous_component_context = component_context;
var previous_untracking = untracking;
var flags = reaction.f;
new_deps = /** @type {null | Value[]} */ (null);
@ -403,11 +419,13 @@ export function update_reaction(reaction) {
(flags & UNOWNED) !== 0 && (untracking || !is_updating_effect || active_reaction === null);
active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null;
derived_sources = null;
reaction_sources = null;
set_component_context(reaction.ctx);
untracking = false;
read_version++;
reaction.f |= EFFECT_IS_UPDATING;
try {
var result = /** @type {Function} */ (0, reaction.fn)();
var deps = reaction.deps;
@ -477,9 +495,11 @@ export function update_reaction(reaction) {
untracked_writes = previous_untracked_writes;
active_reaction = previous_reaction;
skip_reaction = previous_skip_reaction;
derived_sources = prev_derived_sources;
reaction_sources = previous_reaction_sources;
set_component_context(previous_component_context);
untracking = previous_untracking;
reaction.f ^= EFFECT_IS_UPDATING;
}
}
@ -672,6 +692,7 @@ function flush_queued_root_effects() {
var collected_effects = process_effects(root_effects[i]);
flush_queued_effects(collected_effects);
}
old_values.clear();
}
} finally {
is_flushing = false;
@ -681,7 +702,6 @@ function flush_queued_root_effects() {
if (DEV) {
dev_effect_stack = [];
}
old_values.clear();
}
}
@ -866,24 +886,23 @@ export function get(signal) {
// Register the dependency on the current reaction signal.
if (active_reaction !== null && !untracking) {
if (derived_sources !== null && derived_sources.includes(signal)) {
e.state_unsafe_local_read();
}
var deps = active_reaction.deps;
if (signal.rv < read_version) {
signal.rv = read_version;
// If the signal is accessing the same dependencies in the same
// order as it did last time, increment `skipped_deps`
// rather than updating `new_deps`, which creates GC cost
if (new_deps === null && deps !== null && deps[skipped_deps] === signal) {
skipped_deps++;
} else if (new_deps === null) {
new_deps = [signal];
} else if (!skip_reaction || !new_deps.includes(signal)) {
// Normally we can push duplicated dependencies to `new_deps`, but if we're inside
// an unowned derived because skip_reaction is true, then we need to ensure that
// we don't have duplicates
new_deps.push(signal);
if (!reaction_sources?.includes(signal)) {
var deps = active_reaction.deps;
if (signal.rv < read_version) {
signal.rv = read_version;
// If the signal is accessing the same dependencies in the same
// order as it did last time, increment `skipped_deps`
// rather than updating `new_deps`, which creates GC cost
if (new_deps === null && deps !== null && deps[skipped_deps] === signal) {
skipped_deps++;
} else if (new_deps === null) {
new_deps = [signal];
} else if (!skip_reaction || !new_deps.includes(signal)) {
// Normally we can push duplicated dependencies to `new_deps`, but if we're inside
// an unowned derived because skip_reaction is true, then we need to ensure that
// we don't have duplicates
new_deps.push(signal);
}
}
}
} else if (

@ -49,6 +49,7 @@ export function copy_payload({ out, css, head, uid }) {
*/
export function assign_payload(p1, p2) {
p1.out = p2.out;
p1.css = p2.css;
p1.head = p2.head;
p1.uid = p2.uid;
}

@ -10,6 +10,7 @@ export var get_descriptors = Object.getOwnPropertyDescriptors;
export var object_prototype = Object.prototype;
export var array_prototype = Array.prototype;
export var get_prototype_of = Object.getPrototypeOf;
export var is_extensible = Object.isExtensible;
/**
* @param {any} thing

@ -6,6 +6,12 @@ import {
} from '../internal/client/reactivity/effects.js';
import { get, writable } from './shared/index.js';
import { createSubscriber } from '../reactivity/create-subscriber.js';
import {
active_effect,
active_reaction,
set_active_effect,
set_active_reaction
} from '../internal/client/runtime.js';
export { derived, get, readable, readonly, writable } from './shared/index.js';
@ -39,19 +45,34 @@ export { derived, get, readable, readonly, writable } from './shared/index.js';
* @returns {Writable<V> | Readable<V>}
*/
export function toStore(get, set) {
let init_value = get();
var effect = active_effect;
var reaction = active_reaction;
var init_value = get();
const store = writable(init_value, (set) => {
// If the value has changed before we call subscribe, then
// we need to treat the value as already having run
let ran = init_value !== get();
var ran = init_value !== get();
// TODO do we need a different implementation on the server?
const teardown = effect_root(() => {
render_effect(() => {
const value = get();
if (ran) set(value);
var teardown;
// Apply the reaction and effect at the time of toStore being called
var previous_reaction = active_reaction;
var previous_effect = active_effect;
set_active_reaction(reaction);
set_active_effect(effect);
try {
teardown = effect_root(() => {
render_effect(() => {
const value = get();
if (ran) set(value);
});
});
});
} finally {
set_active_reaction(previous_reaction);
set_active_effect(previous_effect);
}
ran = true;

@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
export const VERSION = '5.23.0';
export const VERSION = '5.25.6';
export const PUBLIC_VERSION = '5';

@ -1,8 +0,0 @@
import { test } from '../../test';
export default test({
error: {
code: 'constant_assignment',
message: 'Cannot assign to derived state'
}
});

@ -1,5 +0,0 @@
<script>
const a = $state(0);
let b = $derived(a);
b = 1;
</script>

@ -1,8 +0,0 @@
import { test } from '../../test';
export default test({
error: {
code: 'constant_binding',
message: 'Cannot bind to derived state'
}
});

@ -1,6 +0,0 @@
<script>
let a = $state(0);
let b = $derived({ a });
</script>
<input type="number" bind:value={b} />

@ -1,8 +0,0 @@
import { test } from '../../test';
export default test({
error: {
code: 'constant_assignment',
message: 'Cannot assign to derived state'
}
});

@ -1,10 +0,0 @@
<script>
class Counter {
count = $state();
#doubled = $derived(this.count * 2);
nope() {
this.#doubled += 1;
}
}
</script>

@ -1,8 +0,0 @@
import { test } from '../../test';
export default test({
error: {
code: 'constant_assignment',
message: 'Cannot assign to derived state'
}
});

@ -1,10 +0,0 @@
<script>
class Counter {
count = $state();
#doubled = $derived(this.count * 2);
nope() {
this.#doubled++;
}
}
</script>

@ -1,8 +0,0 @@
import { test } from '../../test';
export default test({
error: {
code: 'constant_assignment',
message: 'Cannot assign to derived state'
}
});

@ -1,5 +0,0 @@
<script>
const a = $state(0);
let b = $derived(a);
b++;
</script>

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
error: {
code: 'rune_invalid_arguments_length',
message: '`$state.raw` must be called with zero or one arguments'
}
});

@ -0,0 +1,3 @@
<script>
const foo = $state.raw(1, 2, 3);
</script>

@ -5,32 +5,20 @@ export default test({
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b ~ .c"',
start: { character: 137, column: 1, line: 11 },
end: { character: 144, column: 8, line: 11 }
start: { character: 191, column: 1, line: 13 },
end: { character: 198, column: 8, line: 13 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".c ~ .f"',
start: { character: 162, column: 1, line: 12 },
end: { character: 169, column: 8, line: 12 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".f ~ .g"',
start: { character: 187, column: 1, line: 13 },
end: { character: 194, column: 8, line: 13 }
start: { character: 216, column: 1, line: 14 },
end: { character: 223, column: 8, line: 14 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b ~ .f"',
start: { character: 212, column: 1, line: 14 },
end: { character: 219, column: 8, line: 14 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b ~ .g"',
start: { character: 237, column: 1, line: 15 },
end: { character: 244, column: 8, line: 15 }
start: { character: 241, column: 1, line: 15 },
end: { character: 248, column: 8, line: 15 }
}
]
});

@ -2,10 +2,10 @@
.d.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
.f.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .b ~ .c { color: red; }*/
/* (unused) .c ~ .f { color: red; }*/
/* (unused) .f ~ .g { color: red; }*/
/* (unused) .b ~ .f { color: red; }*/
/* (unused) .b ~ .g { color: red; }*/

@ -6,13 +6,13 @@
.d ~ .e { color: green; }
.a ~ .g { color: green; }
.a ~ .b { color: green; }
.f ~ .g { color: green; }
.b ~ .g { color: green; }
/* no match */
.b ~ .c { color: red; }
.c ~ .f { color: red; }
.f ~ .g { color: red; }
.b ~ .f { color: red; }
.b ~ .g { color: red; }
</style>
<div class="a"></div>

@ -0,0 +1,5 @@
<script>
let { foo } = $props();
</script>
{@render foo()}

@ -0,0 +1,20 @@
import { test } from '../../test';
export default test({
warnings: [
{
code: 'css_unused_selector',
message: 'Unused CSS selector "n + m"',
end: {
character: 468,
column: 6,
line: 36
},
start: {
character: 463,
column: 1,
line: 36
}
}
]
});

@ -0,0 +1,8 @@
x.svelte-xyz + y:where(.svelte-xyz) { color: green; }
x.svelte-xyz + v:where(.svelte-xyz) { color: green; }
x.svelte-xyz + z:where(.svelte-xyz) { color: green; }
y.svelte-xyz + z:where(.svelte-xyz) { color: green; }
v.svelte-xyz + z:where(.svelte-xyz) { color: green; }
.component + z.svelte-xyz { color: green; }
/* (unused) n + m { color: red; }*/

@ -0,0 +1,37 @@
<script>
import Child from './Child.svelte';
</script>
<div>
<x></x>
<Child>
<y></y>
{#snippet foo()}
<v></v>
{/snippet}
</Child>
<z></z>
<Child>
<span>
<n></n>
</span>
{#snippet foo()}
<span>
<n></n>
</span>
{/snippet}
</Child>
<m></m>
</div>
<style>
x + y { color: green; }
x + v { color: green; }
x + z { color: green; }
y + z { color: green; }
v + z { color: green; }
:global(.component) + z { color: green; }
n + m { color: red; }
</style>

@ -5,14 +5,8 @@ export default test({
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".b + .c"',
start: { character: 110, column: 1, line: 10 },
end: { character: 117, column: 8, line: 10 }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ".c + .f"',
start: { character: 135, column: 1, line: 11 },
end: { character: 142, column: 8, line: 11 }
start: { character: 137, column: 1, line: 11 },
end: { character: 144, column: 8, line: 11 }
}
]
});

@ -1,7 +1,7 @@
.d.svelte-xyz + .e:where(.svelte-xyz) { color: green; }
.a.svelte-xyz + .b:where(.svelte-xyz) { color: green; }
.c.svelte-xyz + .f:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .b + .c { color: red; }*/
/* (unused) .c + .f { color: red; }*/

@ -5,10 +5,10 @@
<style>
.d + .e { color: green; }
.a + .b { color: green; }
.c + .f { color: green; }
/* no match */
.b + .c { color: red; }
.c + .f { color: red; }
</style>
<div class="a"></div>

@ -1 +1,2 @@
<p class="svelte-xyz">Foo</p>
<p class="svelte-xyz">Foo</p>
<p class="svelte-xyz">Bar</p>

@ -1,3 +1,4 @@
<style>p { color: red; }</style>
<p class={undefined}>Foo</p>
<p class={undefined}>Foo</p>
<p class="{undefined}">Bar</p>

@ -86,7 +86,7 @@ export function normalize_html(
clean_children(node);
return node.innerHTML;
} catch (err) {
throw new Error(`Failed to normalize HTML:\n${html}`);
throw new Error(`Failed to normalize HTML:\n${html}\nCause: ${err}`);
}
}

@ -21,6 +21,9 @@
*/
export let type_no_comment;
/** @type {boolean} type_with_comment - One-line declaration with comment */
export let type_with_comment;
/**
* This is optional
*/
@ -40,4 +43,10 @@
export let inline_multiline_trailing_comment = 'world'; /*
* this is a same-line trailing multiline comment
**/
/** @type {number} [default_value=1] */
export let default_value = 1;
/** @type {number} [comment_default_value=1] - This has a comment and an optional value. */
export let comment_default_value = 1;
</script>

@ -9,12 +9,18 @@
/**
* @typedef {Object} Props
* @property {string} comment - My wonderful comment
@ -22,11 +28,14 @@
* @property {any} one_line - one line comment
* @property {any} no_comment
* @property {boolean} type_no_comment
* @property {boolean} type_with_comment - One-line declaration with comment
* @property {any} [optional] - This is optional
* @property {any} inline_commented - this should stay a comment
* @property {any} inline_commented_merged - This comment should be merged - with this inline comment
* @property {string} [inline_multiline_leading_comment] - this is a same-line leading multiline comment
* @property {string} [inline_multiline_trailing_comment] - this is a same-line trailing multiline comment
* @property {number} [default_value]
* @property {number} [comment_default_value] - This has a comment and an optional value.
*/
/** @type {Props} */
@ -36,10 +45,13 @@
one_line,
no_comment,
type_no_comment,
type_with_comment,
optional = {stuff: true},
inline_commented,
inline_commented_merged,
inline_multiline_leading_comment = 'world',
inline_multiline_trailing_comment = 'world'
inline_multiline_trailing_comment = 'world',
default_value = 1,
comment_default_value = 1
} = $props();
</script>

@ -0,0 +1,10 @@
<script>
let name = 'world';
$: upper = name.toUpperCase();
</script>
<input bind:value={name} />
<input bind:value={upper} />
{upper}

@ -0,0 +1,10 @@
<script>
let name = $state('world');
let upper = $derived(name.toUpperCase());
</script>
<input bind:value={name} />
<input bind:value={upper} />
{upper}

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
html: `<p>state,derived state,derived.by derived state</p>`
});

@ -0,0 +1,18 @@
<script>
class Foo {
#state = $state('state')
#derived = $derived('derived ' + this.#state);
#derivedBy = $derived.by(() => {
return 'derived.by ' + this.#derived
});
initial
constructor() {
this.initial = [this.#state, this.#derived, this.#derivedBy]
}
}
const foo = new Foo()
</script>
<p>{foo.initial}</p>

@ -10,6 +10,6 @@ export default test({
flushSync(() => {
b1.click();
});
assert.deepEqual(logs, ['init 0', 'cleanup 2', null, 'init 2', 'cleanup 4', null, 'init 4']);
assert.deepEqual(logs, ['init 0']);
}
});

@ -0,0 +1,12 @@
<script>
import { onMount } from 'svelte'
let thing = $state(0)
onMount(() => {
thing = 1;
return () => {
console.log(thing);
}
})
</script>

@ -0,0 +1,7 @@
import { test } from '../../test';
export default test({
async test({ assert, logs }) {
assert.deepEqual(logs, [1]);
}
});

@ -0,0 +1,14 @@
<script>
import { onMount } from "svelte"
import Component from './Component.svelte';
let key = $state(0);
onMount(() => {
key = 1;
})
</script>
{#key key}
<Component />
{/key}

@ -6,7 +6,7 @@ export default test({
dev: true
},
html: `<button>items: null</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2">`,
html: `<button>items: null</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2"><input>`,
test({ assert, target, warnings }) {
const btn = target.querySelector('button');
@ -15,13 +15,13 @@ export default test({
flushSync(() => btn.click());
assert.htmlEqual(
target.innerHTML,
`<button>items: []</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2">`
`<button>items: []</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2"><input>`
);
flushSync(() => btn.click());
assert.htmlEqual(
target.innerHTML,
`<button>items: [0]</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2">`
`<button>items: [0]</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2"><input>`
);
const input = target.querySelector('input');
@ -30,7 +30,7 @@ export default test({
flushSync(() => input.dispatchEvent(new Event('change', { bubbles: true })));
assert.deepEqual(warnings, [
'Assignment to `items` property (main.svelte:8:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.'
'Assignment to `items` property (main.svelte:9:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.'
]);
}
});

@ -3,6 +3,7 @@
let entries = $state([]);
let object = $state({ items: null, group: [] });
let elementFunBind = $state();
</script>
<button onclick={() => (object.items ??= []).push(object.items.length)}>
@ -13,6 +14,12 @@
<div bind:this={entries[0]}>x</div>
<input type="checkbox" value=1 bind:group={object.group}>
<input type="checkbox" value=2 bind:group={object.group}>
<Test bind:this={entries[1]}></Test>
<Test bind:this={() => entries[2], (v) => (entries[2] = v)}></Test>
<Test bind:x={entries[3]}></Test>
{#snippet funBind(context)}
<input bind:this={() => {}, (e) => (context.element = e)} />
{/snippet}
{@render funBind({ set element(e) { elementFunBind = e } })}

@ -0,0 +1,16 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
let btn = target.querySelector('button');
btn?.click();
flushSync();
assert.htmlEqual(
target.innerHTML,
`<div>Count 1!</div><div>Count from store 1!</div><button>Add 1</button>`
);
}
});

@ -0,0 +1,11 @@
<script>
import { toStore } from "svelte/store";
let counter = $state(0);
const count = toStore(() => counter, value => counter = value);
</script>
<div>Count {counter}!</div>
<div>Count from store {$count}!</div>
<button onclick={() => counter+=1}>Add 1</button>

@ -8,7 +8,7 @@
console.log(this);
}
function foo(): string {
function foo<T>(): string {
return ""!;
}
@ -45,6 +45,8 @@
export type { Hello };
const TypedFoo = Foo<true>;
const typeAssertion = <boolean>true;
const x = foo<true>();
</script>
<script>

@ -0,0 +1,12 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, errors }) {
const button = target.querySelector('button');
flushSync(() => {
button?.click();
});
assert.deepEqual(errors, []);
}
});

@ -0,0 +1,12 @@
<script>
const log = () => {
console.log(snip);
}
let x = $state(0);
</script>
<button onclick={log}></button>
{#snippet snip()}
snippet {x}
{/snippet}

@ -0,0 +1,20 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
test({ assert, target, logs }) {
const button = target.querySelector('button');
flushSync(() => button?.click());
assert.htmlEqual(
target.innerHTML,
`
<button>increment</button>
<p>1/2</p
`
);
assert.deepEqual(logs, [0, 0]);
}
});

@ -0,0 +1,26 @@
<script>
class Foo {
value = $state(0);
double = $derived(this.value * 2);
constructor() {
console.log(this.value, this.double);
}
increment() {
this.value++;
}
}
let foo = $state();
$effect(() => {
foo = new Foo();
});
</script>
<button onclick={() => foo.increment()}>increment</button>
{#if foo}
<p>{foo.value}/{foo.double}</p>
{/if}

@ -0,0 +1,46 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: `
<input type="range"><input type="range"><p>0 * 2 = 0</p>
`,
ssrHtml: `
<input type="range" value="0"><input type="range" value="0"><p>0 * 2 = 0</p>
`,
test({ assert, target, window }) {
const [input1, input2] = target.querySelectorAll('input');
flushSync(() => {
input1.value = '10';
input1.dispatchEvent(new window.Event('input', { bubbles: true }));
});
assert.htmlEqual(
target.innerHTML,
`<input type="range"><input type="range"><p>10 * 2 = 20</p>`
);
flushSync(() => {
input2.value = '99';
input2.dispatchEvent(new window.Event('input', { bubbles: true }));
});
assert.htmlEqual(
target.innerHTML,
`<input type="range"><input type="range"><p>10 * 2 = 99</p>`
);
flushSync(() => {
input1.value = '20';
input1.dispatchEvent(new window.Event('input', { bubbles: true }));
});
assert.htmlEqual(
target.innerHTML,
`<input type="range"><input type="range"><p>20 * 2 = 40</p>`
);
}
});

@ -0,0 +1,9 @@
<script>
let count = $state(0);
let double = $derived(count * 2);
</script>
<input type="range" bind:value={count} />
<input type="range" bind:value={double} />
<p>{count} * 2 = {double}</p>

@ -487,6 +487,26 @@ describe('signals', () => {
};
});
test('schedules rerun when updating deeply nested value', (runes) => {
if (!runes) return () => {};
const value = proxy({ a: { b: { c: 0 } } });
user_effect(() => {
value.a.b.c += 1;
});
return () => {
let errored = false;
try {
flushSync();
} catch (e: any) {
assert.include(e.message, 'effect_update_depth_exceeded');
errored = true;
}
assert.equal(errored, true);
};
});
test('schedules rerun when writing to signal before reading it', (runes) => {
if (!runes) return () => {};
@ -958,14 +978,30 @@ describe('signals', () => {
};
});
test('deriveds cannot depend on state they own', () => {
test('deriveds do not depend on state they own', () => {
return () => {
let s;
const d = derived(() => {
const s = state(0);
s = state(0);
return $.get(s);
});
assert.throws(() => $.get(d), 'state_unsafe_local_read');
assert.equal($.get(d), 0);
set(s!, 1);
assert.equal($.get(d), 0);
};
});
test('effects do not depend on state they own', () => {
user_effect(() => {
const value = state(0);
set(value, $.get(value) + 1);
});
return () => {
flushSync();
};
});

@ -23,7 +23,7 @@ export default function Bind_component_snippet($$anchor) {
return $.get(value);
},
set value($$value) {
$.set(value, $.proxy($$value));
$.set(value, $$value, true);
}
});

@ -23,7 +23,7 @@ export default function Bind_component_snippet($$payload) {
});
$$payload.out += `<!----> value: ${$.escape(value)}`;
};
}
do {
$$settled = true;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save