diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2bcb088480b..c0e1d3676041 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ env: jobs: Tests: + permissions: {} runs-on: ${{ matrix.os }} timeout-minutes: 15 strategy: @@ -27,6 +28,8 @@ jobs: os: ubuntu-latest - node-version: 22 os: ubuntu-latest + - node-version: 24 + os: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -41,6 +44,7 @@ jobs: env: CI: true Lint: + permissions: {} runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -61,6 +65,7 @@ jobs: if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); } Benchmarks: + permissions: {} runs-on: ubuntu-latest timeout-minutes: 15 steps: diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml index ce7bf04136ac..71df3242e8f1 100644 --- a/.github/workflows/ecosystem-ci-trigger.yml +++ b/.github/workflows/ecosystem-ci-trigger.yml @@ -9,6 +9,7 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'sveltejs/svelte' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run') steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - uses: actions/github-script@v6 with: script: | diff --git a/.github/workflows/pkg.pr.new-comment.yml b/.github/workflows/pkg.pr.new-comment.yml index 1698a456d3df..3f1fca5a0bea 100644 --- a/.github/workflows/pkg.pr.new-comment.yml +++ b/.github/workflows/pkg.pr.new-comment.yml @@ -6,11 +6,15 @@ on: types: - completed +permissions: + pull-requests: write + jobs: build: name: 'Update comment' runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Download artifact uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml index 99f8153517f9..b1ba217e5a0f 100644 --- a/.github/workflows/pkg.pr.new.yml +++ b/.github/workflows/pkg.pr.new.yml @@ -3,19 +3,16 @@ on: [push, pull_request] jobs: build: + permissions: {} + runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: install corepack - run: npm i -g corepack@0.31.0 - - - run: corepack enable + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x cache: pnpm - name: Install dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1daef0b89cc3..6debe5662a88 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,7 @@ jobs: name: Release runs-on: ubuntu-latest steps: + - uses: GitHubSecurityLab/actions-permissions/monitor@v1 - name: Checkout Repo uses: actions/checkout@v4 with: diff --git a/.prettierignore b/.prettierignore index d5c124353c37..72cd10aca8e7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,6 +7,7 @@ packages/**/config/*.js # packages/svelte packages/svelte/messages/**/*.md +packages/svelte/scripts/_bundle.js packages/svelte/src/compiler/errors.js packages/svelte/src/compiler/warnings.js packages/svelte/src/internal/client/errors.js @@ -25,8 +26,7 @@ packages/svelte/tests/hydration/samples/*/_expected.html packages/svelte/tests/hydration/samples/*/_override.html packages/svelte/types packages/svelte/compiler/index.js -playgrounds/sandbox/input/**.svelte -playgrounds/sandbox/output +playgrounds/sandbox/src/* # sites/svelte.dev sites/svelte.dev/static/svelte-app.json diff --git a/benchmarking/compare/index.js b/benchmarking/compare/index.js index a5fc6d10a9a1..8f38686a29d8 100644 --- a/benchmarking/compare/index.js +++ b/benchmarking/compare/index.js @@ -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'); @@ -68,19 +67,29 @@ for (let i = 0; i < results[0].length; i += 1) { for (const metric of ['time', 'gc_time']) { const times = results.map((result) => +result[i][metric]); let min = Infinity; + let max = -Infinity; let min_index = -1; for (let b = 0; b < times.length; b += 1) { - if (times[b] < min) { - min = times[b]; + const time = times[b]; + + if (time < min) { + min = time; min_index = b; } + + if (time > max) { + max = time; + } } if (min !== 0) { - console.group(`${metric}: fastest is ${branches[min_index]}`); + console.group(`${metric}: fastest is ${char(min_index)} (${branches[min_index]})`); times.forEach((time, b) => { - console.log(`${branches[b]}: ${time.toFixed(2)}ms (${((time / min) * 100).toFixed(2)}%)`); + const SIZE = 20; + const n = Math.round(SIZE * (time / max)); + + console.log(`${char(b)}: ${'◼'.repeat(n)}${' '.repeat(SIZE - n)} ${time.toFixed(2)}ms`); }); console.groupEnd(); } @@ -88,3 +97,7 @@ for (let i = 0; i < results[0].length; i += 1) { console.groupEnd(); } + +function char(i) { + return String.fromCharCode(97 + i); +} diff --git a/benchmarking/compare/runner.js b/benchmarking/compare/runner.js index 6fa58e2bacf3..a2e864637969 100644 --- a/benchmarking/compare/runner.js +++ b/benchmarking/compare/runner.js @@ -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); diff --git a/documentation/docs/01-introduction/02-getting-started.md b/documentation/docs/01-introduction/02-getting-started.md index e035e6d6df99..c7351729ff17 100644 --- a/documentation/docs/01-introduction/02-getting-started.md +++ b/documentation/docs/01-introduction/02-getting-started.md @@ -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. diff --git a/documentation/docs/01-introduction/04-svelte-js-files.md b/documentation/docs/01-introduction/04-svelte-js-files.md index 0e05484299db..1d3e3dd61a8a 100644 --- a/documentation/docs/01-introduction/04-svelte-js-files.md +++ b/documentation/docs/01-introduction/04-svelte-js-files.md @@ -4,7 +4,7 @@ title: .svelte.js and .svelte.ts files Besides `.svelte` files, Svelte also operates on `.svelte.js` and `.svelte.ts` files. -These behave like any other `.js` or `.ts` module, except that you can use runes. This is useful for creating reusable reactive logic, or sharing reactive state across your app. +These behave like any other `.js` or `.ts` module, except that you can use runes. This is useful for creating reusable reactive logic, or sharing reactive state across your app (though note that you [cannot export reassigned state]($state#Passing-state-across-modules)). > [!LEGACY] > This is a concept that didn't exist prior to Svelte 5 diff --git a/documentation/docs/01-introduction/xx-props.md b/documentation/docs/01-introduction/xx-props.md deleted file mode 100644 index cad854d8785d..000000000000 --- a/documentation/docs/01-introduction/xx-props.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -title: Public API of a component ---- - -### Public API of a component - -Svelte uses the `$props` rune to declare _properties_ or _props_, which means describing the public interface of the component which becomes accessible to consumers of the component. - -> [!NOTE] `$props` is one of several runes, which are special hints for Svelte's compiler to make things reactive. - -```svelte - -``` - -You can specify a fallback value for a prop. It will be used if the component's consumer doesn't specify the prop on the component when instantiating the component, or if the passed value is `undefined` at some point. - -```svelte - -``` - -To get all properties, use rest syntax: - -```svelte - -``` - -You can use reserved words as prop names. - -```svelte - -``` - -If you're using TypeScript, you can declare the prop types: - -```svelte - -``` - -If you're using JavaScript, you can declare the prop types using JSDoc: - -```svelte - -``` - -If you export a `const`, `class` or `function`, it is readonly from outside the component. - -```svelte - -``` - -Readonly props can be accessed as properties on the element, tied to the component using [`bind:this` syntax](bindings#bind:this). - -### Reactive variables - -To change component state and trigger a re-render, just assign to a locally declared variable that was declared using the `$state` rune. - -Update expressions (`count += 1`) and property assignments (`obj.x = y`) have the same effect. - -```svelte - -``` - -Svelte's ` -``` - -If you'd like to react to changes to a prop, use the `$derived` or `$effect` runes instead. - -```svelte - -``` - -For more information on reactivity, read the documentation around runes. diff --git a/documentation/docs/01-introduction/xx-reactivity-fundamentals.md b/documentation/docs/01-introduction/xx-reactivity-fundamentals.md deleted file mode 100644 index d5e67ada71c8..000000000000 --- a/documentation/docs/01-introduction/xx-reactivity-fundamentals.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -title: Reactivity fundamentals ---- - -Reactivity is at the heart of interactive UIs. When you click a button, you expect some kind of response. It's your job as a developer to make this happen. It's Svelte's job to make your job as intuitive as possible, by providing a good API to express reactive systems. - -## Runes - -Svelte 5 uses _runes_, a powerful set of primitives for controlling reactivity inside your Svelte components and inside `.svelte.js` and `.svelte.ts` modules. - -Runes are function-like symbols that provide instructions to the Svelte compiler. You don't need to import them from anywhere — when you use Svelte, they're part of the language. - -The following sections introduce the most important runes for declare state, derived state and side effects at a high level. For more details refer to the later sections on [state](state) and [side effects](side-effects). - -## `$state` - -Reactive state is declared with the `$state` rune: - -```svelte - - - -``` - -You can also use `$state` in class fields (whether public or private): - -```js -// @errors: 7006 2554 -class Todo { - done = $state(false); - text = $state(); - - constructor(text) { - this.text = text; - } -} -``` - -> [!LEGACY] -> In Svelte 4, state was implicitly reactive if the variable was declared at the top level -> -> ```svelte -> -> -> -> ``` - -## `$derived` - -Derived state is declared with the `$derived` rune: - -```svelte - - - - -

{count} doubled is {doubled}

-``` - -The expression inside `$derived(...)` should be free of side-effects. Svelte will disallow state changes (e.g. `count++`) inside derived expressions. - -As with `$state`, you can mark class fields as `$derived`. - -> [!LEGACY] -> In Svelte 4, you could use reactive statements for this. -> -> ```svelte -> -> -> -> ->

{count} doubled is {doubled}

-> ``` -> -> This only worked at the top level of a component. - -## `$effect` - -To run _side-effects_ when the component is mounted to the DOM, and when values change, we can use the `$effect` rune ([demo](/playground/untitled#H4sIAAAAAAAAE31T24rbMBD9lUG7kAQ2sbdlX7xOYNk_aB_rQhRpbAsU2UiTW0P-vbrYubSlYGzmzMzROTPymdVKo2PFjzMzfIusYB99z14YnfoQuD1qQh-7bmdFQEonrOppVZmKNBI49QthCc-OOOH0LZ-9jxnR6c7eUpOnuv6KeT5JFdcqbvbcBcgDz1jXKGg6ncFyBedYR6IzLrAZwiN5vtSxaJA-EzadfJEjKw11C6GR22-BLH8B_wxdByWpvUYtqqal2XB6RVkG1CoHB6U1WJzbnYFDiwb3aGEdDa3Bm1oH12sQLTcNPp7r56m_00mHocSG97_zd7ICUXonA5fwKbPbkE2ZtMJGGVkEdctzQi4QzSwr9prnFYNk5hpmqVuqPQjNnfOJoMF22lUsrq_UfIN6lfSVyvQ7grB3X2mjMZYO3XO9w-U5iLx42qg29md3BP_ni5P4gy9ikTBlHxjLzAtPDlyYZmRdjAbGq7HprEQ7p64v4LU_guu0kvAkhBim3nMplWl8FreQD-CW20aZR0wq12t-KqDWeBywhvexKC3memmDwlHAv9q4Vo2ZK8KtK0CgX7u9J8wXbzdKv-nRnfF_2baTqlYoWUF2h5efl9-n0O6koAMAAA==)): - -```svelte - - - -``` - -The function passed to `$effect` will run when the component mounts, and will re-run after any changes to the values it reads that were declared with `$state` or `$derived` (including those passed in with `$props`). Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied. - -> [!LEGACY] -> In Svelte 4, you could use reactive statements for this. -> -> ```svelte -> -> -> -> ``` -> -> This only worked at the top level of a component. diff --git a/documentation/docs/02-runes/02-$state.md b/documentation/docs/02-runes/02-$state.md index 49e17cd08ff3..8e6c91fad769 100644 --- a/documentation/docs/02-runes/02-$state.md +++ b/documentation/docs/02-runes/02-$state.md @@ -20,9 +20,7 @@ Unlike other frameworks you may have encountered, there is no API for interactin If `$state` is used with an array or a simple object, the result is a deeply reactive _state proxy_. [Proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) allow Svelte to run code when you read or write properties, including via methods like `array.push(...)`, triggering granular updates. -> [!NOTE] Classes like `Set` and `Map` will not be proxied, but Svelte provides reactive implementations for various built-ins like these that can be imported from [`svelte/reactivity`](./svelte-reactivity). - -State is proxified recursively until Svelte finds something other than an array or simple object. In a case like this... +State is proxified recursively until Svelte finds something other than an array or simple object (like a class or an object created with `Object.create`). In a case like this... ```js let todos = $state([ @@ -67,16 +65,15 @@ todos[0].done = !todos[0].done; ### Classes -You can also use `$state` in class fields (whether public or private): +Class instances are not proxied. Instead, you can use `$state` in class fields (whether public or private), or as the first assignment to a property immediately inside the `constructor`: ```js // @errors: 7006 2554 class Todo { done = $state(false); - text = $state(); constructor(text) { - this.text = text; + this.text = $state(text); } reset() { @@ -110,10 +107,9 @@ You can either use an inline function... // @errors: 7006 2554 class Todo { done = $state(false); - text = $state(); constructor(text) { - this.text = text; + this.text = $state(text); } +++reset = () => {+++ @@ -123,6 +119,8 @@ class Todo { } ``` +> Svelte provides reactive implementations of built-in classes like `Set` and `Map` that can be imported from [`svelte/reactivity`](svelte-reactivity). + ## `$state.raw` In cases where you don't want objects and arrays to be deeply reactive you can use `$state.raw`. @@ -147,6 +145,8 @@ person = { This can improve performance with large arrays and objects that you weren't planning to mutate anyway, since it avoids the cost of making them reactive. Note that raw state can _contain_ reactive state (for example, a raw array of reactive objects). +As with `$state`, you can declare class fields using `$state.raw`. + ## `$state.snapshot` To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`: @@ -250,3 +250,83 @@ console.log(total.value); // 7 ``` ...though if you find yourself writing code like that, consider using [classes](#Classes) instead. + +## Passing state across modules + +You can declare state in `.svelte.js` and `.svelte.ts` files, but you can only _export_ that state if it's not directly reassigned. In other words you can't do this: + +```js +/// file: state.svelte.js +export let count = $state(0); + +export function increment() { + count += 1; +} +``` + +That's because every reference to `count` is transformed by the Svelte compiler — the code above is roughly equivalent to this: + +```js +/// file: state.svelte.js (compiler output) +// @filename: index.ts +interface Signal { + value: T; +} + +interface Svelte { + state(value?: T): Signal; + get(source: Signal): T; + set(source: Signal, value: T): void; +} +declare const $: Svelte; +// ---cut--- +export let count = $.state(0); + +export function increment() { + $.set(count, $.get(count) + 1); +} +``` + +> [!NOTE] You can see the code Svelte generates by clicking the 'JS Output' tab in the [playground](/playground). + +Since the compiler only operates on one file at a time, if another file imports `count` Svelte doesn't know that it needs to wrap each reference in `$.get` and `$.set`: + +```js +// @filename: state.svelte.js +export let count = 0; + +// @filename: index.js +// ---cut--- +import { count } from './state.svelte.js'; + +console.log(typeof count); // 'object', not 'number' +``` + +This leaves you with two options for sharing state between modules — either don't reassign it... + +```js +// This is allowed — since we're updating +// `counter.count` rather than `counter`, +// Svelte doesn't wrap it in `$.state` +export const counter = $state({ + count: 0 +}); + +export function increment() { + counter.count += 1; +} +``` + +...or don't directly export it: + +```js +let count = $state(0); + +export function getCount() { + return count; +} + +export function increment() { + count += 1; +} +``` diff --git a/documentation/docs/02-runes/03-$derived.md b/documentation/docs/02-runes/03-$derived.md index 24ab643b68f6..2464aa929550 100644 --- a/documentation/docs/02-runes/03-$derived.md +++ b/documentation/docs/02-runes/03-$derived.md @@ -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 + + + +``` + +> [!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'). diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md index da24084d4dd0..0e129973d58e 100644 --- a/documentation/docs/02-runes/04-$effect.md +++ b/documentation/docs/02-runes/04-$effect.md @@ -2,15 +2,11 @@ title: $effect --- -Effects are what make your application _do things_. When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes. +Effects are functions that run when state updates, and can be used for things like calling third-party libraries, drawing on `` elements, or making network requests. They only run in the browser, not during server-side rendering. -Most of the effects in a Svelte app are created by Svelte itself — they're the bits that update the text in `

hello {name}!

` when `name` changes, for example. +Generally speaking, you should _not_ update state inside effects, as it will make code more convoluted and will often lead to never-ending update cycles. If you find yourself doing so, see [when not to use `$effect`](#When-not-to-use-$effect) to learn about alternative approaches. -But you can also create your own effects with the `$effect` rune, which is useful when you need to synchronize an external system (whether that's a library, or a `` element, or something across a network) with state inside your Svelte app. - -> [!NOTE] Avoid overusing `$effect`! When you do too much work in effects, code often becomes difficult to understand and maintain. See [when not to use `$effect`](#When-not-to-use-$effect) to learn about alternative approaches. - -Your effects run after the component has been mounted to the DOM, and in a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) after state changes ([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)): +You can create an effect with the `$effect` rune ([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)): ```svelte - + ``` -Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied. +When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes. + +> [!NOTE] If you're having difficulty understanding why your `$effect` is rerunning or is not running see [understanding dependencies](#Understanding-dependencies). Effects are triggered differently than the `$:` blocks you may be used to if coming from Svelte 4. + +### Understanding lifecycle -You can place `$effect` anywhere, not just at the top level of a component, as long as it is called during component initialization (or while a parent effect is active). It is then tied to the lifecycle of the component (or parent effect) and will therefore destroy itself when the component unmounts (or the parent effect is destroyed). +Your effects run after the component has been mounted to the DOM, and in a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) after state changes. Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied. -You can return a function from `$effect`, which will run immediately before the effect re-runs, and before it is destroyed ([demo](/playground/untitled#H4sIAAAAAAAAE42RQY-bMBCF_8rI2kPopiXpMQtIPfbeW6m0xjyKtWaM7CFphPjvFVB2k2oPe7LmzXzyezOjaqxDVKefo5JrD3VaBLVXrLu5-tb3X-IZTmat0hHv6cazgCWqk8qiCbaXouRSHISMH1gop4coWrA7JE9bp7PO2QjjuY5vA8fDYZ3hUh7QNDCy2yWUFzTOUilpSj9aG-linaMKFGACtKCmSwvGGYGeLQvCWbtnMq3m34grajxHoa1JOUXI93_V_Sfz7Oz7Mafj0ypN-zvHm8dSAmQITP_xaUq2IU1GO1dp80I2Uh_82dao92Rl9R8GvgF0QrbrUFstcFeq0PgAkha0LoICPoeB4w1SJUvsZcj4rvcMlvmvGlGCv6J-DeSgw2vabQnJlm55p7nM0rcTctYei3HZxZSl7XHVqkHEM3k2zpqXfFyj393zU05fpyI6f0HI0hUoPoamC9roKDeo2ivBH1EnCQOmX9NfYw2GHrgCAAA=)). +You can use `$effect` anywhere, not just at the top level of a component, as long as it is called while a parent effect is running. + +> [!NOTE] Svelte uses effects internally to represent logic and expressions in your template — this is how `

hello {name}!

` updates when `name` changes. + +An effect can return a _teardown function_ which will run immediately before the effect re-runs ([demo](/playground/untitled#H4sIAAAAAAAAE42SQVODMBCF_8pOxkPRKq3HCsx49K4n64xpskjGkDDJ0tph-O8uINo6HjxB3u7HvrehE07WKDbiyZEhi1osRWksRrF57gQdm6E2CKx_dd43zU3co6VB28mIf-nKO0JH_BmRRRVMQ8XWbXkAgfKtI8jhIpIkXKySu7lSG2tNRGZ1_GlYr1ZTD3ddYFmiosUigbyAbpC2lKbwWJkIB8ZhhxBQBWRSw6FCh3sM8GrYTthL-wqqku4N44TyqEgwF3lmRHr4Op0PGXoH31c5rO8mqV-eOZ49bikgtcHBL55tmhIkEMqg_cFB2TpFxjtg703we6NRL8HQFCS07oSUCZi6Rm04lz1yytIHBKoQpo1w6Gsm4gmyS8b8Y5PydeMdX8gwS2Ok4I-ov5NZtvQde95GMsccn_1wzNKfu3RZtS66cSl9lvL7qO1aIk7knbJGvefdtIOzi73M4bYvovUHDFk6AcX_0HRESxnpBOW_jfCDxIZCi_1L_wm4xGQ60wIAAA==)). ```svelte +// later... +destroy(); ``` ## When not to use `$effect` @@ -246,11 +267,13 @@ 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`. -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)): +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#H4sIAAAAAAAAE5WRTWrDMBCFryKGLBJoY3fRjWIHeoiu6i6UZBwEY0VE49TB-O6VxrFTSih0qe_Ne_OjHpxpEDS8O7ZMeIAnqC1hAP3RA1990hKI_Fb55v06XJA4sZ0J-IjvT47RcYyBIuzP1vO2chVHHFjxiQ2pUr3k-SZRQlbBx_LIFoEN4zJfzQph_UMQr4hRXmBd456Xy5Uqt6pPKHmkfmzyPAZL2PCnbRpg8qWYu63I7lu4gswOSRYqrPNt3CgeqqzgbNwRK1A76w76YqjFspfcQTWmK3vJHlQm1puSTVSeqdOc_r9GaeCHfUSY26TXry6Br4RSK3C6yMEGT-aqVU3YbUZ2NF6rfP2KzXgbuYzY46czdgyazy0On_FlLH3F-UDXhgIO35UGlA1rAgAA)): ```svelte - - - - -``` - -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 - ``` diff --git a/documentation/docs/02-runes/05-$props.md b/documentation/docs/02-runes/05-$props.md index f300fb239d77..222b4831b65a 100644 --- a/documentation/docs/02-runes/05-$props.md +++ b/documentation/docs/02-runes/05-$props.md @@ -37,7 +37,7 @@ On the other side, inside `MyComponent.svelte`, we can receive props with the `$ ## Fallback values -Destructuring allows us to declare fallback values, which are used if the parent component does not set a given prop: +Destructuring allows us to declare fallback values, which are used if the parent component does not set a given prop (or the value is `undefined`): ```js let { adjective = 'happy' } = $props(); @@ -219,4 +219,4 @@ This is useful for linking elements via attributes like `for` and `aria-labelled -``` \ No newline at end of file +``` diff --git a/documentation/docs/02-runes/07-$inspect.md b/documentation/docs/02-runes/07-$inspect.md index ff3d64757b6b..13ac8b79a33a 100644 --- a/documentation/docs/02-runes/07-$inspect.md +++ b/documentation/docs/02-runes/07-$inspect.md @@ -52,6 +52,7 @@ This rune, added in 5.14, causes the surrounding function to be _traced_ in deve import { doSomeWork } from './elsewhere'; $effect(() => { + +++// $inspect.trace must be the first statement of a function body+++ +++$inspect.trace();+++ doSomeWork(); }); diff --git a/documentation/docs/02-runes/08-$host.md b/documentation/docs/02-runes/08-$host.md index 7b5e041e5ed9..ba6f0a5b5b40 100644 --- a/documentation/docs/02-runes/08-$host.md +++ b/documentation/docs/02-runes/08-$host.md @@ -2,7 +2,7 @@ title: $host --- -When compiling a component as a custom element, the `$host` rune provides access to the host element, allowing you to (for example) dispatch custom events ([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)): +When compiling a component as a [custom element](custom-elements), the `$host` rune provides access to the host element, allowing you to (for example) dispatch custom events ([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)): ```svelte diff --git a/documentation/docs/03-template-syntax/01-basic-markup.md b/documentation/docs/03-template-syntax/01-basic-markup.md index b41dc187c337..feecfe033e63 100644 --- a/documentation/docs/03-template-syntax/01-basic-markup.md +++ b/documentation/docs/03-template-syntax/01-basic-markup.md @@ -82,12 +82,14 @@ As with elements, `name={name}` can be replaced with the `{name}` shorthand. ``` +## Spread attributes + _Spread attributes_ allow many attributes or properties to be passed to an element or component at once. -An element or component can have multiple spread attributes, interspersed with regular ones. +An element or component can have multiple spread attributes, interspersed with regular ones. Order matters — if `things.a` exists it will take precedence over `a="b"`, while `c="d"` would take precedence over `things.c`: ```svelte - + ``` ## Events @@ -154,6 +156,8 @@ A JavaScript expression can be included as text by surrounding it with curly bra {expression} ``` +Expressions that are `null` or `undefined` will be omitted; all others are [coerced to strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#string_coercion). + Curly braces can be included in a Svelte template by using their [HTML entity](https://developer.mozilla.org/docs/Glossary/Entity) strings: `{`, `{`, or `{` for `{` and `}`, `}`, or `}` for `}`. If you're using a regular expression (`RegExp`) [literal notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#literal_notation_and_constructor), you'll need to wrap it in parentheses. @@ -185,7 +189,7 @@ You can use HTML comments inside components. Comments beginning with `svelte-ignore` disable warnings for the next block of markup. Usually, these are accessibility warnings; make sure that you're disabling them for a good reason. ```svelte - + ``` diff --git a/documentation/docs/03-template-syntax/03-each.md b/documentation/docs/03-template-syntax/03-each.md index 70666f6a5798..006cadd15257 100644 --- a/documentation/docs/03-template-syntax/03-each.md +++ b/documentation/docs/03-template-syntax/03-each.md @@ -43,7 +43,9 @@ An each block can also specify an _index_, equivalent to the second argument in {#each expression as name, index (key)}...{/each} ``` -If a _key_ expression is provided — which must uniquely identify each list item — Svelte will use it to diff the list when data changes, rather than adding or removing items at the end. The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change. +If a _key_ expression is provided — which must uniquely identify each list item — Svelte will use it to intelligently update the list when data changes by inserting, moving and deleting items, rather than adding or removing items at the end and updating the state in the middle. + +The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change. ```svelte {#each items as item (item.id)} diff --git a/documentation/docs/03-template-syntax/06-snippet.md b/documentation/docs/03-template-syntax/06-snippet.md index c9951d3f3414..ab536c6e5ced 100644 --- a/documentation/docs/03-template-syntax/06-snippet.md +++ b/documentation/docs/03-template-syntax/06-snippet.md @@ -112,6 +112,8 @@ Snippets can reference themselves and each other ([demo](/playground/untitled#H4 ## Passing snippets to components +### Explicit props + Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/playground/untitled#H4sIAAAAAAAAE3VS247aMBD9lZGpBGwDASRegonaPvQL2qdlH5zYEKvBNvbQLbL875VzAcKyj3PmzJnLGU8UOwqSkd8KJdaCk4TsZS0cyV49wYuJuQiQpGd-N2bu_ooaI1YwJ57hpVYoFDqSEepKKw3mO7VDeTTaIvxiRS1gb_URxvO0ibrS8WanIrHUyiHs7Vmigy28RmyHHmKvDMbMmFq4cQInvGSwTsBYWYoMVhCSB2rBFFPsyl0uruTlR3JZCWvlTXl1Yy_mawiR_rbZKZrellJ-5JQ0RiBUgnFhJ9OGR7HKmwVoilXeIye8DOJGfYCgRlZ3iE876TBsZPX7hPdteO75PC4QaIo8vwNPePmANQ2fMeEFHrLD7rR1jTNkW986E8C3KwfwVr8HSHOSEBT_kGRozyIkn_zQveXDL3rIfPJHtUDwzShJd_Qk3gQCbOGLsdq4yfTRJopRuin3I7nv6kL7ARRjmLdBDG3uv1mhuLA3V2mKtqNEf_oCn8p9aN-WYqH5peP4kWBl1UwJzAEPT9U7K--0fRrrWnPTXpCm1_EVdXjpNmlA8G1hPPyM1fKgMqjFHjctXGjLhZ05w0qpDhksGrybuNEHtJnCalZWsuaTlfq6nPaaBSv_HKw-K57BjzOiVj9ZKQYKzQjZodYFqydYTRN4gPhVzTDO2xnma3HsVWjaLjT8nbfwHy7Q5f2dBAAA)): ```svelte @@ -144,6 +146,8 @@ Within the template, snippets are values just like any other. As such, they can Think about it like passing content instead of data to a component. The concept is similar to slots in web components. +### Implicit props + As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component ([demo](/playground/untitled#H4sIAAAAAAAAE3VSTa_aMBD8Kyu_SkAbCA-JSzBR20N_QXt6vIMTO8SqsY29tI2s_PcqTiB8vaPHs7MzuxuIZgdBMvJLo0QlOElIJZXwJHsLBBvb_XUASc7Mb9Yu_B-hsMMK5sUzvDQahUZPMkJ96aTFfKd3KA_WOISfrFACKmcOMFmk8TWUTjY73RFLoz1C5U4SPWzhrcN2GKDrlcGEWauEnyRwxCaDdQLWyVJksII2uaMWTDPNLtzX5YX8-kgua-GcHJVXI3u5WEPb0d83O03TMZSmfRzOkG1Db7mNacOL19JagVALxoWbztq-H8U6j0SaYp2P2BGbOyQ2v8PQIFMXLKRDk177pq0zf6d8bMrzwBdd0pamyPMb-IjNEzS2f86Gz_Dwf-2F9nvNSUJQ_EOSoTuJNvngqK5v4Pas7n4-OCwlEEJcQTIMO-nSQwtb-GSdsX46e9gbRoP9yGQ11I0rEuycunu6PHx1QnPhxm3SFN15MOlYEFJZtf0dUywMbwZOeBGsrKNLYB54-1R9WNqVdki7usim6VmQphf7mnpshiQRhNAXdoOfMyX3OgMlKtz0cGEcF27uLSul3mewjPjgOOoDukxjPS9rqfh0pb-8zs6aBSt_7505aZ7B9xOi0T9YKW4UooVsr0zB1BTrWQJ3EL-oWcZ572GxFoezCk37QLe3897-B2i2U62uBAAA)): ```svelte @@ -165,6 +169,8 @@ As an authoring convenience, snippets declared directly _inside_ a component imp ``` +### Implicit `children` snippet + Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet ([demo](/playground/untitled#H4sIAAAAAAAAE3WOQQrCMBBFrzIMggql3ddY1Du4si5sOmIwnYRkFKX07lKqglqX8_7_w2uRDw1hjlsWI5ZqTPBoLEXMdy3K3fdZDzB5Ndfep_FKVnpWHSKNce1YiCVijirqYLwUJQOYxrsgsLmIOIZjcA1M02w4n-PpomSVvTclqyEutDX6DA2pZ7_ABIVugrmEC3XJH92P55_G39GodCmWBFrQJ2PrQAwdLGHig_NxNv9xrQa1dhWIawrv1Wzeqawa8953D-8QOmaEAQAA)): ```svelte @@ -184,6 +190,8 @@ Any content inside the component tags that is _not_ a snippet declaration implic > [!NOTE] Note that you cannot have a prop called `children` if you also have content inside the component — for this reason, you should avoid having props with that name +### Optional snippet props + You can declare snippet props as being optional. You can either use optional chaining to not render anything if the snippet isn't set... ```svelte diff --git a/documentation/docs/03-template-syntax/09-@attach.md b/documentation/docs/03-template-syntax/09-@attach.md new file mode 100644 index 000000000000..b25fbb32a678 --- /dev/null +++ b/documentation/docs/03-template-syntax/09-@attach.md @@ -0,0 +1,166 @@ +--- +title: {@attach ...} +--- + +Attachments are functions that run in an [effect]($effect) when an element is mounted to the DOM or when [state]($state) read inside the function updates. + +Optionally, they can return a function that is called before the attachment re-runs, or after the element is later removed from the DOM. + +> [!NOTE] +> Attachments are available in Svelte 5.29 and newer. + +```svelte + + + +
...
+``` + +An element can have any number of attachments. + +## Attachment factories + +A useful pattern is for a function, such as `tooltip` in this example, to _return_ an attachment ([demo](/playground/untitled#H4sIAAAAAAAAE3VT0XLaMBD8lavbDiaNCUlbHhTItG_5h5AH2T5ArdBppDOEMv73SkbGJGnH47F9t3un3TsfMyO3mInsh2SW1Sa7zlZKo8_E0zHjg42pGAjxBPxp7cTvUHOMldLjv-IVGUbDoUw295VTlh-WZslqa8kxsLL2ACtHWxh175NffnQfAAGikSGxYQGfPEvGfPSIWtOH0TiBVo2pWJEBJtKhQp4YYzjG9JIdcuMM5IZqHMPioY8vOSA997zQoevf4a7heO7cdp34olRiTGr07OhwH1IdoO2A7dLMbwahZq6MbRhKZWqxk7rBxTGVbuHmhCgb5qDgmIx_J6XtHHukHTrYYqx_YpzYng8aO4RYayql7hU-1ZJl0akqHBE_D9KLolwL-Dibzc7iSln9XjtqTF1UpMkJ2EmXR-BgQErsN4pxIJKr0RVO1qrxAqaTO4fbc9bKulZm3cfDY3aZDgvFGErWjmzhN7KmfX5rXyDeX8Pt1mU-hXjdBOrtuB97vK4GPUtmJ41XcRMEGDLD8do0nJ73zhUhSlyRw0t3vPqD8cjfLs-axiFgNBrkUd9Ulp50c-GLxlXAVlJX-ffpZyiSn7H0eLCUySZQcQdXlxj4El0Yv_FZvIKElqqGTruVLhzu7VRKCh22_5toOyxsWqLwwzK-cCbYNdg-hy-p9D7sbiZWUnts_wLUOF3CJgQAAA==)): + +```svelte + + + + + + +``` + +Since the `tooltip(content)` expression runs inside an [effect]($effect), the attachment will be destroyed and recreated whenever `content` changes. The same thing would happen for any state read _inside_ the attachment function when it first runs. (If this isn't what you want, see [Controlling when attachments re-run](#Controlling-when-attachments-re-run).) + +## Inline attachments + +Attachments can also be created inline ([demo](/playground/untitled#H4sIAAAAAAAAE71Wf3OaWBT9KoyTTnW3MS-I3dYmnWXVtnRAazRJzbozRSQEApiRhwKO333vuY8m225m_9yZGOT9OPfcc84D943UTfxGr_G7K6Xr3TVeNW7D2M8avT_3DVk-YAoDNF4vNB8e2tnWjyXGlm7mPzfurVPpp5JgGmeZtwkf5PtFupCxLzVvHa832rl2lElX-s2Xm2DZFNqp_hs-rZetd4v07ORpT3qmQHu7MF2td0BZp8k6z_xkvfXP902_pZ2_1_aYWEiqm0kN8I4r79qbdZ6umnq3q_2iNf22F4dE6qt2oimwdpim_uY6XMm7Fuo-IQT_iTD_CeGTHwZ38ieIJUFQRxirR1Xf39Dw0X5z0I72Af4tD61vvPNwWKQnqmfPTbduhsEd2J3vO_oBd3dc6fF2X7umNdWGf0vBRhSS6qoV7cCXfTXWfKmvWG61_si_vfU92Wz-E4RhsLhNIYinsox9QKGVd8-tuACCeKXRX12P-T_eKf7fhTq0Hvt-f3ailtSeoxJHRo1-58NoPe1UiBc1hkL8Yeh45y_vQ3mcuNl9T8s3cXPRWLnS7YWJG_gn2Tb4tUjid8jua-PVl08j_ab8I14mH8Llx0s5Tz5Err4ql52r_GYg0mVy1bEGZuD0ze64b5TWYFiM-16wSuJ4JT5vfVpDcztrcG_YkRU4s6HxufzDWF4XuVeJ1P10IbzBemt3Vp1V2e04ZXfrJd7Wicyd039brRIv_RIVu_nXi7X1cfL2sy66ztToUp1TO7qJ7NlwZ0f30pld5qNSVE5o6PbMojFHjgZB7oSicPpGteyLclQap7SvY0dXtM_LR1NT2JFHey3aaxa0VxCeYJ7RMHemoiCcgPZV9pR7o7kgcOjeGliYk9hjDZx8FAq6enwlTPSZj_vYPw9Il64dXdIY8ZmapzwfEd8-1ZyaxWhqkIZOibXUd-6Upqi1pD4uMicCV1GA_7zi73UN8BaF4sC8peJtMjfmjbHZBFwq5ov50qRaE0l96NZggnW4KqypYRAW-uhSz9ADvklwJF2J-5W0Z5fQPBhDX92R6I_0IFxRgDftge4l4dP-gH1hjD7uqU6fsOEZ9UNrCdPB-nys6uXgY6O3ZMd9sy5T9PghqrWHdjo4jB51CgLiKJaDYYA-7WgYONf1FbjkI-mE3EAfUY_rijfuJ_CVPaR50oe9JF7Q0pI8Dw3osxxYHdYPGbp2CnwHF8KvwJv2wEv0Z3ilQI6U9uwbZxbYJXvEmjjQjjCHkvNLvNg3yhzXQd1olamsT4IRrZmX0MUDpwL7R8zzHj7pSh9hPHFSHjLezKqAST51uC5zmtQ87skDUaneLokT5RbXkPWSYz53Abgjc8_o4KFGUZ-Hgv2Z1l5OTYM9D-HfUD0L-EwxH5wRnIG61gS-khfgY1bq7IAP_DA4l5xRuh9xlm8yGjutc8t-wHtkhWv3hc7aqGwiK5KzgvM5xRkZYn193uEln-su55j1GaIv7oM4iPrsVHiG0Dx7TR9-1lBfqFdwfvSd5LNL5xyZVp5NoHFZ57FkfiF6vKs4k5zvIfrX5xX6MXmt0gM5MTu8DjnhukrHHzTRd3jm0dma0_f_x5cxP9f4jBdqHvmbq2fUjzqcKh2Cp-yWj9ntcHanXmBXxhu7Q--eyjhfNFpaV7zgz4nWEUb7zUOhpevjjf_gu_KZ99pxFlZ-T3sttkmYqrco_26q35v0Ewzv5EZPbnL_8BfduWGMnyyN3q0bZ_7hb_7KG_L4CQAA)): + +```svelte + + { + const context = canvas.getContext('2d'); + + $effect(() => { + context.fillStyle = color; + context.fillRect(0, 0, canvas.width, canvas.height); + }); + }} +> +``` + +> [!NOTE] +> The nested effect runs whenever `color` changes, while the outer effect (where `canvas.getContext(...)` is called) only runs once, since it doesn't read any reactive state. + +## Passing attachments to components + +When used on a component, `{@attach ...}` will create a prop whose key is a [`Symbol`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol). If the component then [spreads](/tutorial/svelte/spread-props) props onto an element, the element will receive those attachments. + +This allows you to create _wrapper components_ that augment elements ([demo](/playground/untitled#H4sIAAAAAAAAE3VUS3ObMBD-KxvajnFqsJM2PhA7TXrKob31FjITAbKtRkiMtDhJPfz3LiAMdpxhGJvdb1_fPnaeYjn3Iu-WIbJ04028lZDcetHDzsO3olbVApI74F1RhHbLJdayhFl-Sp5qhVwhufEWNjWiwJtYxSjyQhsEFEXxBiujcxg1_8O_dnQ9APwsEbVyiHDafjrvDZCgkiO4MLCEzxYZcn90z6XUZ6OxA61KlaIgV6i1pFC-sxjDrlbHaDiWRoGvdMbHsLzp5DES0mJnRxGaRBvcBHb7yFUTCQeunEWYcYtGv12TqgFUDbCK1WLaM6IWQhUlQiJUFm2ZLPly51xXMG0Rjoyd69C7UqqG2nu95QZyXvtvLVpri2-SN4hoLXXCZFfhQ8aQBU1VgdEaH_vSgyBZR_BpPp_vi0tY-rw2ulRZkGqpTQRbZvwa2BPgFC8bgbw31CbjJjAsE6WNYBZeGp7vtQXLMqHWnZx-5kM1TR5ycpkZXQR2wzL94l8Ur1C_3-g168SfQf1MyfRi3LW9fs77emJEw5QV9SREoLTq06tcczq7d6xEUcJX2vAhO1b843XK34e5unZEMBr15ekuKEusluWAF8lXhE2ZTP2r2RcIHJ-163FPKerCgYJLOB9i4GvNwviI5-gAQiFFBk3tBTOU3HFXEk0R8o86WvUD64aINhv5K3oRmpJXkw8uxMG6Hh6JY9X7OwGSqfUy9tDG3sHNoEi0d_d_fv9qndxRU0VClFqo3KVo3U655Hnt1PXB3Qra2Y2QGdEwgTAMCxopsoxOe6SD0gD8movDhT0LAnhqlE8gVCpLWnRoV7OJCkFAwEXitrYL1W7p7pbiE_P7XH6E_rihODm5s52XtiH9Ekaw0VgI9exadWL1uoEYjPtg2672k5szsxbKyWB2fdT0w5Y_0hcT8oXOlRetmLS8-g-6TLXXQgYAAA==)): + +```svelte + + + + + +``` + +```svelte + + + + + + +``` + +## Controlling when attachments re-run + +Attachments, unlike [actions](use), are fully reactive: `{@attach foo(bar)}` will re-run on changes to `foo` _or_ `bar` (or any state read inside `foo`): + +```js +// @errors: 7006 2304 2552 +function foo(bar) { + return (node) => { + veryExpensiveSetupWork(node); + update(node, bar); + }; +} +``` + +In the rare case that this is a problem (for example, if `foo` does expensive and unavoidable setup work) consider passing the data inside a function and reading it in a child effect: + +```js +// @errors: 7006 2304 2552 +function foo(+++getBar+++) { + return (node) => { + veryExpensiveSetupWork(node); + ++++ $effect(() => { + update(node, getBar()); + });+++ + } +} +``` + +## Creating attachments programmatically + +To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](svelte-attachments#createAttachmentKey). + +## Converting actions to attachments + +If you're using a library that only provides actions, you can convert them to attachments with [`fromAction`](svelte-attachments#fromAction), allowing you to (for example) use them with components. diff --git a/documentation/docs/03-template-syntax/09-@const.md b/documentation/docs/03-template-syntax/10-@const.md similarity index 100% rename from documentation/docs/03-template-syntax/09-@const.md rename to documentation/docs/03-template-syntax/10-@const.md diff --git a/documentation/docs/03-template-syntax/10-@debug.md b/documentation/docs/03-template-syntax/11-@debug.md similarity index 100% rename from documentation/docs/03-template-syntax/10-@debug.md rename to documentation/docs/03-template-syntax/11-@debug.md diff --git a/documentation/docs/03-template-syntax/11-bind.md b/documentation/docs/03-template-syntax/12-bind.md similarity index 80% rename from documentation/docs/03-template-syntax/11-bind.md rename to documentation/docs/03-template-syntax/12-bind.md index c23f3b52327c..de57815687dc 100644 --- a/documentation/docs/03-template-syntax/11-bind.md +++ b/documentation/docs/03-template-syntax/12-bind.md @@ -4,7 +4,7 @@ title: bind: Data ordinarily flows down, from parent to child. The `bind:` directive allows data to flow the other way, from child to parent. -The general syntax is `bind:property={expression}`, where `expression` is an _lvalue_ (i.e. a variable or an object property). When the expression is an identifier with the same name as the property, we can omit the expression — in other words these are equivalent: +The general syntax is `bind:property={expression}`, where `expression` is an [_lvalue_](https://press.rebus.community/programmingfundamentals/chapter/lvalue-and-rvalue/) (i.e. a variable or an object property). When the expression is an identifier with the same name as the property, we can omit the expression — in other words these are equivalent: ```svelte @@ -117,28 +117,52 @@ Since 5.6.0, if an `` has a `defaultChecked` attribute and is part of a f ``` +## `` + +Checkboxes can be in an [indeterminate](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/indeterminate) state, independently of whether they are checked or unchecked: + +```svelte + + +
+ + + {#if indeterminate} + waiting... + {:else if checked} + checked + {:else} + unchecked + {/if} +
+``` + ## `` -Inputs that work together can use `bind:group`. +Inputs that work together can use `bind:group` ([demo](/playground/untitled#H4sIAAAAAAAAE62T32_TMBDH_5XDQkpbrct7SCMGEvCEECDxsO7BSW6L2c227EvbKOv_jp0f6jYhQKJv5_P3PvdL1wstH1Bk4hMSGdgbRzUssFaM9VJciFtF6EV23QvubNRFR_BPUVfWXvodEkdfKT3-zl8Zzag5YETuK6csF1u9ZUIGNo4VkYQNvPYsGRfJF5JKJ8s3QRJE6WoFb2Nq6K-ck13u2Sl9Vxxhlc6QUBIFnz9Brm9ifJ6esun81XoNd860FmtwslYGlLYte5AO4aHlVhJ1gIeKWq92COt1iMtJlkhFPkgh1rHZiiF6K6BUus4G5KafGznCTlIbVUMfQZUWMJh5OrL-C_qjMYSwb1DyiH7iOEuCb1ZpWTUjfHqcwC_GWDVY3ZfmME_SGttSmD9IHaYatvWHIc6xLyqad3mq6KuqcCwnWn9p8p-p71BqP2IH81zc9w2in-od7XORP7ayCpd5YCeXI_-p59mObPF9WmwGpx3nqS2Gzw8TO3zOaS5_GqUXyQUkS3h8hOSz0ZhMESHGc0c4Hm3MAn00t1wrb0l2GZRkqvt4sXwczm6Qh8vnUJzI2LV4vAkvqWgfehTZrSSPx19WiVfFfAQAAA==)): ```svelte + - - - + + + - - - - + + + + ``` > [!NOTE] `bind:group` only works if the inputs are in the same Svelte component. @@ -227,6 +251,7 @@ You can give the `` a default value by adding a `selected` attribute to ``` +## `window` and `document` + +To bind to properties of `window` and `document`, see [``](svelte-window) and [``](svelte-document). + ## Contenteditable bindings Elements with the `contenteditable` attribute support the following bindings: @@ -278,6 +307,10 @@ All visible elements have the following readonly bindings, measured with a `Resi - [`clientHeight`](https://developer.mozilla.org/en-US/docs/Web/API/Element/clientHeight) - [`offsetWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetWidth) - [`offsetHeight`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetHeight) +- [`contentRect`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentRect) +- [`contentBoxSize`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentBoxSize) +- [`borderBoxSize`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/borderBoxSize) +- [`devicePixelContentBoxSize`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/devicePixelContentBoxSize) ```svelte
@@ -285,7 +318,7 @@ All visible elements have the following readonly bindings, measured with a `Resi
``` -> [!NOTE] `display: inline` elements do not have a width or height (except for elements with 'intrinsic' dimensions, like `` and ``), and cannot be observed with a `ResizeObserver`. You will need to change the `display` style of these elements to something else, such as `inline-block`. +> [!NOTE] `display: inline` elements do not have a width or height (except for elements with 'intrinsic' dimensions, like `` and ``), and cannot be observed with a `ResizeObserver`. You will need to change the `display` style of these elements to something else, such as `inline-block`. Note that CSS transformations do not trigger `ResizeObserver` callbacks. ## bind:this diff --git a/documentation/docs/03-template-syntax/12-use.md b/documentation/docs/03-template-syntax/13-use.md similarity index 93% rename from documentation/docs/03-template-syntax/12-use.md rename to documentation/docs/03-template-syntax/13-use.md index 45de0235782b..5f5321a1c09f 100644 --- a/documentation/docs/03-template-syntax/12-use.md +++ b/documentation/docs/03-template-syntax/13-use.md @@ -2,6 +2,9 @@ title: use: --- +> [!NOTE] +> In Svelte 5.29 and newer, consider using [attachments](@attach) instead, as they are more flexible and composable. + Actions are functions that are called when an element is mounted. They are added with the `use:` directive, and will typically use an `$effect` so that they can reset any state when the element is unmounted: ```svelte diff --git a/documentation/docs/03-template-syntax/13-transition.md b/documentation/docs/03-template-syntax/14-transition.md similarity index 100% rename from documentation/docs/03-template-syntax/13-transition.md rename to documentation/docs/03-template-syntax/14-transition.md index 51c11e8b34e9..c51175c272bc 100644 --- a/documentation/docs/03-template-syntax/13-transition.md +++ b/documentation/docs/03-template-syntax/14-transition.md @@ -22,10 +22,6 @@ The `transition:` directive indicates a _bidirectional_ transition, which means {/if} ``` -## Built-in transitions - -A selection of built-in transitions can be imported from the [`svelte/transition`](svelte-transition) module. - ## Local vs global Transitions are local by default. Local transitions only play when the block they belong to is created or destroyed, _not_ when parent blocks are created or destroyed. @@ -40,6 +36,10 @@ Transitions are local by default. Local transitions only play when the block the {/if} ``` +## Built-in transitions + +A selection of built-in transitions can be imported from the [`svelte/transition`](svelte-transition) module. + ## Transition parameters Transitions can have parameters. diff --git a/documentation/docs/03-template-syntax/14-in-and-out.md b/documentation/docs/03-template-syntax/15-in-and-out.md similarity index 100% rename from documentation/docs/03-template-syntax/14-in-and-out.md rename to documentation/docs/03-template-syntax/15-in-and-out.md diff --git a/documentation/docs/03-template-syntax/15-animate.md b/documentation/docs/03-template-syntax/16-animate.md similarity index 100% rename from documentation/docs/03-template-syntax/15-animate.md rename to documentation/docs/03-template-syntax/16-animate.md diff --git a/documentation/docs/03-template-syntax/17-style.md b/documentation/docs/03-template-syntax/17-style.md index 749376c6e2a6..aa61cdcde35e 100644 --- a/documentation/docs/03-template-syntax/17-style.md +++ b/documentation/docs/03-template-syntax/17-style.md @@ -34,8 +34,10 @@ To mark a style as important, use the `|important` modifier:
...
``` -When `style:` directives are combined with `style` attributes, the directives will take precedence: +When `style:` directives are combined with `style` attributes, the directives will take precedence, +even over `!important` properties: ```svelte -
This will be red
+
This will be red
+
This will still be red
``` diff --git a/documentation/docs/03-template-syntax/xx-control-flow.md b/documentation/docs/03-template-syntax/xx-control-flow.md deleted file mode 100644 index b73917997bc5..000000000000 --- a/documentation/docs/03-template-syntax/xx-control-flow.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: Control flow ---- - -- if -- each -- await (or move that into some kind of data loading section?) -- NOT: key (move into transition section, because that's the common use case) - -Svelte augments HTML with control flow blocks to be able to express conditionally rendered content or lists. - -The syntax between these blocks is the same: - -- `{#` denotes the start of a block -- `{:` denotes a different branch part of the block. Depending on the block, there can be multiple of these -- `{/` denotes the end of a block - -## {#if ...} - -## {#each ...} - -```svelte - -{#each expression as name}...{/each} -``` - -```svelte - -{#each expression as name, index}...{/each} -``` - -```svelte - -{#each expression as name (key)}...{/each} -``` - -```svelte - -{#each expression as name, index (key)}...{/each} -``` - -```svelte - -{#each expression as name}...{:else}...{/each} -``` - -Iterating over lists of values can be done with an each block. - -```svelte -

Shopping list

-
    - {#each items as item} -
  • {item.name} x {item.qty}
  • - {/each} -
-``` - -You can use each blocks to iterate over any array or array-like value — that is, any object with a `length` property. - -An each block can also specify an _index_, equivalent to the second argument in an `array.map(...)` callback: - -```svelte -{#each items as item, i} -
  • {i + 1}: {item.name} x {item.qty}
  • -{/each} -``` - -If a _key_ expression is provided — which must uniquely identify each list item — Svelte will use it to diff the list when data changes, rather than adding or removing items at the end. The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change. - -```svelte -{#each items as item (item.id)} -
  • {item.name} x {item.qty}
  • -{/each} - - -{#each items as item, i (item.id)} -
  • {i + 1}: {item.name} x {item.qty}
  • -{/each} -``` - -You can freely use destructuring and rest patterns in each blocks. - -```svelte -{#each items as { id, name, qty }, i (id)} -
  • {i + 1}: {name} x {qty}
  • -{/each} - -{#each objects as { id, ...rest }} -
  • {id}
  • -{/each} - -{#each items as [id, ...rest]} -
  • {id}
  • -{/each} -``` - -An each block can also have an `{:else}` clause, which is rendered if the list is empty. - -```svelte -{#each todos as todo} -

    {todo.text}

    -{:else} -

    No tasks today!

    -{/each} -``` - -It is possible to iterate over iterables like `Map` or `Set`. Iterables need to be finite and static (they shouldn't change while being iterated over). Under the hood, they are transformed to an array using `Array.from` before being passed off to rendering. If you're writing performance-sensitive code, try to avoid iterables and use regular arrays as they are more performant. - -## Other block types - -Svelte also provides [`#snippet`](snippets), [`#key`](transitions-and-animations) and [`#await`](data-fetching) blocks. You can find out more about them in their respective sections. diff --git a/documentation/docs/03-template-syntax/xx-data-fetching.md b/documentation/docs/03-template-syntax/xx-data-fetching.md deleted file mode 100644 index 4526d5133561..000000000000 --- a/documentation/docs/03-template-syntax/xx-data-fetching.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Data fetching ---- - -Fetching data is a fundamental part of apps interacting with the outside world. Svelte is unopinionated with how you fetch your data. The simplest way would be using the built-in `fetch` method: - -```svelte - -``` - -While this works, it makes working with promises somewhat unergonomic. Svelte alleviates this problem using the `#await` block. - -## {#await ...} - -## SvelteKit loaders - -Fetching inside your components is great for simple use cases, but it's prone to data loading waterfalls and makes code harder to work with because of the promise handling. SvelteKit solves this problem by providing a opinionated data loading story that is coupled to its router. Learn more about it [in the docs](../kit). diff --git a/documentation/docs/05-special-elements/04-svelte-body.md b/documentation/docs/05-special-elements/04-svelte-body.md index d6536b0b7424..c6828b98f7d2 100644 --- a/documentation/docs/05-special-elements/04-svelte-body.md +++ b/documentation/docs/05-special-elements/04-svelte-body.md @@ -8,7 +8,7 @@ title: Similarly to ``, this element allows you to add listeners to events on `document.body`, such as `mouseenter` and `mouseleave`, which don't fire on `window`. It also lets you use [actions](use) on the `` element. -As with `` and ``, this element may only appear the top level of your component and must never be inside a block or element. +As with `` and ``, this element may only appear at the top level of your component and must never be inside a block or element. ```svelte diff --git a/documentation/docs/06-runtime/02-context.md b/documentation/docs/06-runtime/02-context.md index 30799215b6eb..f395de421cff 100644 --- a/documentation/docs/06-runtime/02-context.md +++ b/documentation/docs/06-runtime/02-context.md @@ -2,129 +2,137 @@ title: Context --- - +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 + + ``` +...and the child retrieves it with `getContext`: + ```svelte - + + +

    {message}, inside Child.svelte

    ``` -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 + + + +``` -- 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 + + + + + + ``` -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 - + ``` -`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 + +``` -> [!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 - - +```js +/// file: context.js +// @filename: ambient.d.ts +interface User {} - -``` +// @filename: index.js +// ---cut--- +import { getContext, setContext } from 'svelte'; -```svelte - - +/** @param {User} user */ +export function setUserContext(user) { + setContext(key, user); +} -

    Count is {value.count}

    +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 - + // ... +}); ``` -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 + ``` -## 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. diff --git a/documentation/docs/06-runtime/03-lifecycle-hooks.md b/documentation/docs/06-runtime/03-lifecycle-hooks.md index 2b97ca796fed..f051c46d73ba 100644 --- a/documentation/docs/06-runtime/03-lifecycle-hooks.md +++ b/documentation/docs/06-runtime/03-lifecycle-hooks.md @@ -147,7 +147,7 @@ With runes, we can use `$effect.pre`, which behaves the same as `$effect` but ru } function toggle() { - toggleValue = !toggleValue; + theme = theme === 'dark' ? 'light' : 'dark'; } diff --git a/documentation/docs/07-misc/02-testing.md b/documentation/docs/07-misc/02-testing.md index 08420190392b..db99b7077022 100644 --- a/documentation/docs/07-misc/02-testing.md +++ b/documentation/docs/07-misc/02-testing.md @@ -6,9 +6,9 @@ Testing helps you write and maintain your code and guard against regressions. Te ## Unit and integration testing using Vitest -Unit tests allow you to test small isolated parts of your code. Integration tests allow you to test parts of your application to see if they work together. If you're using Vite (including via SvelteKit), we recommend using [Vitest](https://vitest.dev/). +Unit tests allow you to test small isolated parts of your code. Integration tests allow you to test parts of your application to see if they work together. If you're using Vite (including via SvelteKit), we recommend using [Vitest](https://vitest.dev/). You can use the Svelte CLI to [setup Vitest](/docs/cli/vitest) either during project creation or later on. -To get started, install Vitest: +To setup Vitest manually, first install it: ```bash npm install -D vitest @@ -129,12 +129,12 @@ test('Effect', () => { // effects normally run after a microtask, // use flushSync to execute all pending effects synchronously flushSync(); - expect(log.value).toEqual([0]); + expect(log).toEqual([0]); count = 1; flushSync(); - expect(log.value).toEqual([0, 1]); + expect(log).toEqual([0, 1]); }); cleanup(); @@ -148,17 +148,13 @@ test('Effect', () => { */ export function logger(getValue) { /** @type {any[]} */ - let log = $state([]); + let log = []; $effect(() => { log.push(getValue()); }); - return { - get value() { - return log; - } - }; + return log; } ``` @@ -254,9 +250,9 @@ When writing component tests that involve two-way bindings, context or snippet p E2E (short for 'end to end') tests allow you to test your full application through the eyes of the user. This section uses [Playwright](https://playwright.dev/) as an example, but you can also use other solutions like [Cypress](https://www.cypress.io/) or [NightwatchJS](https://nightwatchjs.org/). -To get started with Playwright, either install it via [the VS Code extension](https://playwright.dev/docs/getting-started-vscode), or install it from the command line using `npm init playwright`. It is also part of the setup CLI when you run `npx sv create`. +You can use the Svelte CLI to [setup Playwright](/docs/cli/playwright) either during project creation or later on. You can also [set it up with `npm init playwright`](https://playwright.dev/docs/intro). Additionally, you may also want to install an IDE plugin such as [the VS Code extension](https://playwright.dev/docs/getting-started-vscode) to be able to execute tests from inside your IDE. -After you've done that, you should have a `tests` folder and a Playwright config. You may need to adjust that config to tell Playwright what to do before running the tests - mainly starting your application at a certain port: +If you've run `npm init playwright` or are not using Vite, you may need to adjust the Playwright config to tell Playwright what to do before running the tests - mainly starting your application at a certain port. For example: ```js /// file: playwright.config.js diff --git a/documentation/docs/07-misc/03-typescript.md b/documentation/docs/07-misc/03-typescript.md index fbf8817069e8..ff33885fb8d9 100644 --- a/documentation/docs/07-misc/03-typescript.md +++ b/documentation/docs/07-misc/03-typescript.md @@ -83,7 +83,7 @@ If you're using tools like Rollup or Webpack instead, install their respective S When using TypeScript, make sure your `tsconfig.json` is setup correctly. -- Use a [`target`](https://www.typescriptlang.org/tsconfig/#target) of at least `ES2022`, or a `target` of at least `ES2015` alongside [`useDefineForClassFields`](https://www.typescriptlang.org/tsconfig/#useDefineForClassFields). This ensures that rune declarations on class fields are not messed with, which would break the Svelte compiler +- Use a [`target`](https://www.typescriptlang.org/tsconfig/#target) of at least `ES2015` so classes are not compiled to functions - Set [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax) to `true` so that imports are left as-is - Set [`isolatedModules`](https://www.typescriptlang.org/tsconfig/#isolatedModules) to `true` so that each file is looked at in isolation. TypeScript has a few features which require cross-file analysis and compilation, which the Svelte compiler and tooling like Vite don't do. diff --git a/documentation/docs/07-misc/04-custom-elements.md b/documentation/docs/07-misc/04-custom-elements.md index a8e0c8176316..7e6a17b947c9 100644 --- a/documentation/docs/07-misc/04-custom-elements.md +++ b/documentation/docs/07-misc/04-custom-elements.md @@ -114,6 +114,8 @@ When constructing a custom element, you can tailor several aspects by defining ` ... ``` +> [!NOTE] While Typescript is supported in the `extend` function, it is subject to limitations: you need to set `lang="ts"` on one of the scripts AND you can only use [erasable syntax](https://www.typescriptlang.org/tsconfig/#erasableSyntaxOnly) in it. They are not processed by script preprocessors. + ## Caveats and limitations Custom elements can be a useful way to package components for consumption in a non-Svelte app, as they will work with vanilla HTML and JavaScript as well as [most frameworks](https://custom-elements-everywhere.com/). There are, however, some important differences to be aware of: diff --git a/documentation/docs/07-misc/07-v5-migration-guide.md b/documentation/docs/07-misc/07-v5-migration-guide.md index 94ade6e88796..c24c1febeeda 100644 --- a/documentation/docs/07-misc/07-v5-migration-guide.md +++ b/documentation/docs/07-misc/07-v5-migration-guide.md @@ -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 ``` @@ -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 ``` @@ -42,7 +42,8 @@ A `$:` statement could also be used to create side effects. In Svelte 5, this is ```svelte ``` +Note that [when `$effect` runs is different]($effect#Understanding-dependencies) than when `$:` runs. + > [!DETAILS] Why we did this > `$:` was a great shorthand and easy to get started with: you could slap a `$:` in front of most code and it would somehow work. This intuitiveness was also its drawback the more complicated your code became, because it wasn't as easy to reason about. Was the intent of the code to create a derivation, or a side effect? With `$derived` and `$effect`, you have a bit more up-front decision making to do (spoiler alert: 90% of the time you want `$derived`), but future-you and other developers on your team will have an easier time. > @@ -71,14 +74,14 @@ A `$:` statement could also be used to create side effects. In Svelte 5, this is > - 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 ``` @@ -103,8 +106,8 @@ In Svelte 5, the `$props` rune makes this straightforward without any additional ```svelte @@ -190,9 +193,9 @@ This function is deprecated in Svelte 5. Instead, components should accept _call ```svelte @@ -464,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 (`` -> `{@render children()}`) -- migrate slot usages to snippets (`
    ...
    ` -> `{#snippet x()}
    ...
    {/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 (`` → `{@render children()}`) +- migrate slot usages to snippets (`
    ...
    ` → `{#snippet x()}
    ...
    {/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. @@ -830,9 +833,9 @@ Svelte 5 is more strict about the HTML structure and will throw a compiler error Assignments to destructured parts of a `@const` declaration are no longer allowed. It was an oversight that this was ever allowed. -### :is(...) and :where(...) are scoped +### :is(...), :has(...), and :where(...) are scoped -Previously, Svelte did not analyse selectors inside `:is(...)` and `:where(...)`, effectively treating them as global. Svelte 5 analyses them in the context of the current component. As such, some selectors may now be treated as unused if they were relying on this treatment. To fix this, use `:global(...)` inside the `:is(...)/:where(...)` selectors. +Previously, Svelte did not analyse selectors inside `:is(...)`, `:has(...)`, and `:where(...)`, effectively treating them as global. Svelte 5 analyses them in the context of the current component. As such, some selectors may now be treated as unused if they were relying on this treatment. To fix this, use `:global(...)` inside the `:is(...)/:has(...)/:where(...)` selectors. When using Tailwind's `@apply` directive, add a `:global` selector to preserve rules that use Tailwind-generated `:is(...)` selectors: diff --git a/documentation/docs/07-misc/99-faq.md b/documentation/docs/07-misc/99-faq.md index b56c27af86b6..cf98cdd3c3f1 100644 --- a/documentation/docs/07-misc/99-faq.md +++ b/documentation/docs/07-misc/99-faq.md @@ -46,7 +46,7 @@ It will show up on hover. - You can use markdown here. - You can also use code blocks here. - Usage: - ```tsx + ```svelte
    ``` --> @@ -81,9 +81,10 @@ _End-to-End Tests_: To ensure your users are able to interact with your applicat Some resources for getting started with testing: +- [Svelte docs on testing](/docs/svelte/testing) +- [Setup Vitest using the Svelte CLI](/docs/cli/vitest) - [Svelte Testing Library](https://testing-library.com/docs/svelte-testing-library/example/) - [Svelte Component Testing in Cypress](https://docs.cypress.io/guides/component-testing/svelte/overview) -- [Example using vitest](https://github.com/vitest-dev/vitest/tree/main/examples/sveltekit) - [Example using uvu test runner with JSDOM](https://github.com/lukeed/uvu/tree/master/examples/svelte) - [Test Svelte components using Vitest & Playwright](https://davipon.hashnode.dev/test-svelte-component-using-vitest-playwright) - [Component testing with WebdriverIO](https://webdriver.io/docs/component-testing/svelte) @@ -96,7 +97,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. diff --git a/documentation/docs/07-misc/xx-reactivity-indepth.md b/documentation/docs/07-misc/xx-reactivity-indepth.md deleted file mode 100644 index b40072552f9a..000000000000 --- a/documentation/docs/07-misc/xx-reactivity-indepth.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Reactivity in depth ---- - -- how to think about Runes ("just JavaScript" with added reactivity, what this means for keeping reactivity alive across boundaries) -- signals diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 2c2e0707ea12..3f33e37d2e3d 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -21,7 +21,7 @@ A component is attempting to bind to a non-bindable property `%key%` belonging t ### component_api_changed ``` -%parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5 +Calling `%method%` on a component instance (of %component%) is no longer valid in Svelte 5 ``` See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information. @@ -74,6 +74,12 @@ Effect cannot be created inside a `$derived` value that was not itself created i Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops ``` +### get_abort_signal_outside_reaction + +``` +`getAbortSignal()` can only be called inside an effect or derived +``` + ### hydration_failed ``` @@ -122,14 +128,39 @@ Property descriptors defined on `$state` objects must contain `value` and always Cannot set prototype of `$state` object ``` -### state_unsafe_local_read +### state_unsafe_mutation ``` -Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state +Updating state inside `$derived(...)`, `$inspect(...)` or a template expression is forbidden. If the value should not be reactive, declare it without `$state` ``` -### state_unsafe_mutation +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 + + + + +

    {count} is even: {even}

    +

    {count} is odd: {odd}

    ``` -Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state` + +This is forbidden because it introduces instability: if `

    {count} is even: {even}

    ` 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); ``` + +If side-effects are unavoidable, use [`$effect`]($effect) instead. diff --git a/documentation/docs/98-reference/.generated/client-warnings.md b/documentation/docs/98-reference/.generated/client-warnings.md index 284e9a7c3e57..fe90b0db3815 100644 --- a/documentation/docs/98-reference/.generated/client-warnings.md +++ b/documentation/docs/98-reference/.generated/client-warnings.md @@ -161,7 +161,7 @@ Tried to unmount a component that was not mounted ### ownership_invalid_binding ``` -%parent% passed a value to %child% with `bind:`, but the value is owned by %owner%. Consider creating a binding between %owner% and %parent% +%parent% passed property `%prop%` to %child% with `bind:`, but its parent component %owner% did not declare `%prop%` as a binding. Consider creating a binding between %owner% and %parent% (e.g. `bind:%prop%={...}` instead of `%prop%={...}`) ``` Consider three components `GrandParent`, `Parent` and `Child`. If you do ``, inside `GrandParent` pass on the variable via `` (note the missing `bind:`) and then do `` inside `Parent`, this warning is thrown. @@ -171,11 +171,7 @@ To fix it, `bind:` to the value instead of just passing a property (i.e. in this ### ownership_invalid_mutation ``` -Mutating a value outside the component that created it is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead -``` - -``` -%component% mutated a value owned by %owner%. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead +Mutating unbound props (`%name%`, at %location%) is strongly discouraged. Consider using `bind:%prop%={...}` in %parent% (or using a callback) instead ``` Consider the following code: @@ -204,6 +200,19 @@ Consider the following code: To fix it, either create callback props to communicate changes, or mark `person` as [`$bindable`]($bindable). +### select_multiple_invalid_value + +``` +The `value` property of a ``, Svelte will mark all selected `
    `, which can cause an unexpected DOM structure. Add an explicit `` to avoid surprises.", + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 25 + } + }, + { + "code": "element_implicitly_closed", + "message": "This element is implicitly closed by the following ``, which can cause an unexpected DOM structure. Add an explicit `` to avoid surprises.", + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 20 + } + } +] diff --git a/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/input.svelte b/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/input.svelte new file mode 100644 index 000000000000..7721f2f3802a --- /dev/null +++ b/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/input.svelte @@ -0,0 +1,9 @@ +
    +

    + +

    +
    + +
    +

    +
    diff --git a/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/warnings.json b/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/warnings.json new file mode 100644 index 000000000000..6ea36c5a50f1 --- /dev/null +++ b/packages/svelte/tests/validator/samples/implicitly-closed-by-sibling/warnings.json @@ -0,0 +1,26 @@ +[ + { + "code": "element_implicitly_closed", + "message": "This element is implicitly closed by the following `

    `, which can cause an unexpected DOM structure. Add an explicit `

    ` to avoid surprises.", + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 18 + } + }, + { + "code": "element_implicitly_closed", + "message": "This element is implicitly closed by the following `

    `, which can cause an unexpected DOM structure. Add an explicit `

    ` to avoid surprises.", + "start": { + "line": 8, + "column": 1 + }, + "end": { + "line": 8, + "column": 18 + } + } +] diff --git a/packages/svelte/tests/validator/samples/invalid-self-closing-tag/input.svelte b/packages/svelte/tests/validator/samples/invalid-self-closing-tag/input.svelte index 376c9f79bd84..07582e834341 100644 --- a/packages/svelte/tests/validator/samples/invalid-self-closing-tag/input.svelte +++ b/packages/svelte/tests/validator/samples/invalid-self-closing-tag/input.svelte @@ -1,6 +1,7 @@ + diff --git a/packages/svelte/tests/validator/samples/invalid-self-closing-tag/warnings.json b/packages/svelte/tests/validator/samples/invalid-self-closing-tag/warnings.json index 40b87ec7c8f4..3e45bca5ad15 100644 --- a/packages/svelte/tests/validator/samples/invalid-self-closing-tag/warnings.json +++ b/packages/svelte/tests/validator/samples/invalid-self-closing-tag/warnings.json @@ -3,11 +3,11 @@ "code": "element_invalid_self_closing_tag", "message": "Self-closing HTML tags for non-void elements are ambiguous — use `
    ` rather than `
    `", "start": { - "line": 8, + "line": 9, "column": 0 }, "end": { - "line": 8, + "line": 9, "column": 7 } }, @@ -15,11 +15,11 @@ "code": "element_invalid_self_closing_tag", "message": "Self-closing HTML tags for non-void elements are ambiguous — use `` rather than ``", "start": { - "line": 9, + "line": 10, "column": 0 }, "end": { - "line": 9, + "line": 10, "column": 12 } } diff --git a/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json b/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json deleted file mode 100644 index 8681d84ab227..000000000000 --- a/packages/svelte/tests/validator/samples/reassign-derived-literal/errors.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "code": "constant_assignment", - "message": "Cannot assign to derived state", - "start": { - "column": 3, - "line": 6 - }, - "end": { - "column": 29, - "line": 6 - } - } -] diff --git a/packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte b/packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte deleted file mode 100644 index 8f109c9e1fe8..000000000000 --- a/packages/svelte/tests/validator/samples/reassign-derived-literal/input.svelte +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json b/packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json deleted file mode 100644 index c211aa460895..000000000000 --- a/packages/svelte/tests/validator/samples/reassign-derived-private-field/errors.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "code": "constant_assignment", - "message": "Cannot assign to derived state", - "start": { - "column": 3, - "line": 6 - }, - "end": { - "column": 27, - "line": 6 - } - } -] diff --git a/packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte b/packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte deleted file mode 100644 index 62e2317e033f..000000000000 --- a/packages/svelte/tests/validator/samples/reassign-derived-private-field/input.svelte +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json b/packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json deleted file mode 100644 index 98837589ac80..000000000000 --- a/packages/svelte/tests/validator/samples/reassign-derived-public-field/errors.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "code": "constant_assignment", - "message": "Cannot assign to derived state", - "start": { - "column": 3, - "line": 6 - }, - "end": { - "column": 26, - "line": 6 - } - } -] diff --git a/packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte b/packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte deleted file mode 100644 index e2c4693e86b7..000000000000 --- a/packages/svelte/tests/validator/samples/reassign-derived-public-field/input.svelte +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/errors.json b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/errors.json new file mode 100644 index 000000000000..be59da95fa6a --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "rune_invalid_spread", + "end": { + "column": 35, + "line": 3 + }, + "message": "`$derived.by` cannot be called with a spread argument", + "start": { + "column": 15, + "line": 3 + } + } +] diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/input.svelte b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/input.svelte new file mode 100644 index 000000000000..49e8057aa508 --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived-by/input.svelte @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/errors.json b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/errors.json new file mode 100644 index 000000000000..6a333bc36233 --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "rune_invalid_spread", + "end": { + "column": 32, + "line": 3 + }, + "message": "`$derived` cannot be called with a spread argument", + "start": { + "column": 15, + "line": 3 + } + } +] diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/input.svelte b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/input.svelte new file mode 100644 index 000000000000..9155493e1705 --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-derived/input.svelte @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/errors.json b/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/errors.json new file mode 100644 index 000000000000..e08b498fcbee --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "rune_invalid_spread", + "end": { + "column": 34, + "line": 3 + }, + "message": "`$state.raw` cannot be called with a spread argument", + "start": { + "column": 15, + "line": 3 + } + } +] diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/input.svelte b/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/input.svelte new file mode 100644 index 000000000000..d06fb053b3d9 --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-state-raw/input.svelte @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-state/errors.json b/packages/svelte/tests/validator/samples/rune-invalid-spread-state/errors.json new file mode 100644 index 000000000000..11ae2abce538 --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-state/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "rune_invalid_spread", + "end": { + "column": 30, + "line": 3 + }, + "message": "`$state` cannot be called with a spread argument", + "start": { + "column": 15, + "line": 3 + } + } +] diff --git a/packages/svelte/tests/validator/samples/rune-invalid-spread-state/input.svelte b/packages/svelte/tests/validator/samples/rune-invalid-spread-state/input.svelte new file mode 100644 index 000000000000..02feac893f6d --- /dev/null +++ b/packages/svelte/tests/validator/samples/rune-invalid-spread-state/input.svelte @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/static-state-reference/input.svelte b/packages/svelte/tests/validator/samples/static-state-reference/input.svelte index cd0c7b734925..577527ee60c7 100644 --- a/packages/svelte/tests/validator/samples/static-state-reference/input.svelte +++ b/packages/svelte/tests/validator/samples/static-state-reference/input.svelte @@ -2,6 +2,7 @@ let obj = $state({ a: 0 }); let count = $state(0); let doubled = $derived(count * 2); + let tripled = $state(count * 3); console.log(obj); console.log(count); diff --git a/packages/svelte/tests/validator/samples/static-state-reference/warnings.json b/packages/svelte/tests/validator/samples/static-state-reference/warnings.json index 9ba09415190b..a118d5e4a0d7 100644 --- a/packages/svelte/tests/validator/samples/static-state-reference/warnings.json +++ b/packages/svelte/tests/validator/samples/static-state-reference/warnings.json @@ -1,26 +1,38 @@ [ { "code": "state_referenced_locally", - "message": "State referenced in its own scope will never update. Did you mean to reference it inside a closure?", + "message": "This reference only captures the initial value of `count`. Did you mean to reference it inside a derived instead?", + "start": { + "column": 22, + "line": 5 + }, + "end": { + "column": 27, + "line": 5 + } + }, + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `count`. Did you mean to reference it inside a closure instead?", "start": { "column": 13, - "line": 7 + "line": 8 }, "end": { "column": 18, - "line": 7 + "line": 8 } }, { "code": "state_referenced_locally", - "message": "State referenced in its own scope will never update. Did you mean to reference it inside a closure?", + "message": "This reference only captures the initial value of `doubled`. Did you mean to reference it inside a closure instead?", "start": { "column": 13, - "line": 8 + "line": 9 }, "end": { "column": 20, - "line": 8 + "line": 9 } } ] diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json index 76005add13be..bab587ace3b1 100644 --- a/packages/svelte/tsconfig.json +++ b/packages/svelte/tsconfig.json @@ -24,11 +24,7 @@ "svelte/motion": ["./src/motion/public.d.ts"], "svelte/server": ["./src/server/index.d.ts"], "svelte/store": ["./src/store/public.d.ts"], - "svelte/reactivity": ["./src/reactivity/index-client.js"], - "#compiler": ["./src/compiler/types/index.d.ts"], - "#client": ["./src/internal/client/types.d.ts"], - "#server": ["./src/internal/server/types.d.ts"], - "#shared": ["./src/internal/shared/types.d.ts"] + "svelte/reactivity": ["./src/reactivity/index-client.js"] } }, "include": [ diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index c3dbdcac791e..432171ae0dd1 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -349,13 +349,38 @@ declare module 'svelte' { props: Props; }); /** - * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. - * It must be called during the component's initialisation (but doesn't need to live *inside* the component; - * it can be called from an external module). + * Returns an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that aborts when the current [derived](https://svelte.dev/docs/svelte/$derived) or [effect](https://svelte.dev/docs/svelte/$effect) re-runs or is destroyed. * - * If a function is returned _synchronously_ from `onMount`, it will be called when the component is unmounted. + * Must be called while a derived or effect is running. * - * `onMount` does not run inside [server-side components](https://svelte.dev/docs/svelte/svelte-server#render). + * ```svelte + * + * ``` + */ + export function getAbortSignal(): AbortSignal; + /** + * `onMount`, like [`$effect`](https://svelte.dev/docs/svelte/$effect), schedules a function to run as soon as the component has been mounted to the DOM. + * Unlike `$effect`, the provided function only runs once. + * + * It must be called during the component's initialisation (but doesn't need to live _inside_ the component; + * it can be called from an external module). If a function is returned _synchronously_ from `onMount`, + * it will be called when the component is unmounted. + * + * `onMount` functions do not run during [server-side rendering](https://svelte.dev/docs/svelte/svelte-server#render). * * */ export function onMount(fn: () => NotFunction | Promise> | (() => any)): void; @@ -380,7 +405,7 @@ declare module 'svelte' { * The event dispatcher can be typed to narrow the allowed event names and the type of the `detail` argument: * ```ts * const dispatch = createEventDispatcher<{ - * loaded: never; // does not take a detail argument + * loaded: null; // does not take a detail argument * change: string; // takes a detail argument of type string, which is required * optional: number | null; // takes an optional detail argument of type number * }>(); @@ -623,6 +648,145 @@ declare module 'svelte/animate' { export {}; } +declare module 'svelte/attachments' { + /** + * An [attachment](https://svelte.dev/docs/svelte/@attach) is a function that runs when an element is mounted + * to the DOM, and optionally returns a function that is called when the element is later removed. + * + * It can be attached to an element with an `{@attach ...}` tag, or by spreading an object containing + * a property created with [`createAttachmentKey`](https://svelte.dev/docs/svelte/svelte-attachments#createAttachmentKey). + */ + export interface Attachment { + (element: T): void | (() => void); + } + /** + * Creates an object key that will be recognised as an attachment when the object is spread onto an element, + * as a programmatic alternative to using `{@attach ...}`. This can be useful for library authors, though + * is generally not needed when building an app. + * + * ```svelte + * + * + * + * ``` + * @since 5.29 + */ + export function createAttachmentKey(): symbol; + /** + * Converts an [action](https://svelte.dev/docs/svelte/use) into an [attachment](https://svelte.dev/docs/svelte/@attach) keeping the same behavior. + * It's useful if you want to start using attachments on components but you have actions provided by a library. + * + * Note that the second argument, if provided, must be a function that _returns_ the argument to the + * action function, not the argument itself. + * + * ```svelte + * + *
    ...
    + * + * + *
    bar)}>...
    + * ``` + * */ + export function fromAction(action: Action | ((element: E, arg: T) => void | ActionReturn), fn: () => T): Attachment; + /** + * Converts an [action](https://svelte.dev/docs/svelte/use) into an [attachment](https://svelte.dev/docs/svelte/@attach) keeping the same behavior. + * It's useful if you want to start using attachments on components but you have actions provided by a library. + * + * Note that the second argument, if provided, must be a function that _returns_ the argument to the + * action function, not the argument itself. + * + * ```svelte + * + *
    ...
    + * + * + *
    bar)}>...
    + * ``` + * */ + export function fromAction(action: Action | ((element: E) => void | ActionReturn)): Attachment; + /** + * Actions can return an object containing the two properties defined in this interface. Both are optional. + * - update: An action can have a parameter. This method will be called whenever that parameter changes, + * immediately after Svelte has applied updates to the markup. `ActionReturn` and `ActionReturn` both + * mean that the action accepts no parameters. + * - destroy: Method that is called after the element is unmounted + * + * Additionally, you can specify which additional attributes and events the action enables on the applied element. + * This applies to TypeScript typings only and has no effect at runtime. + * + * Example usage: + * ```ts + * interface Attributes { + * newprop?: string; + * 'on:event': (e: CustomEvent) => void; + * } + * + * export function myAction(node: HTMLElement, parameter: Parameter): ActionReturn { + * // ... + * return { + * update: (updatedParameter) => {...}, + * destroy: () => {...} + * }; + * } + * ``` + */ + interface ActionReturn< + Parameter = undefined, + Attributes extends Record = Record + > { + update?: (parameter: Parameter) => void; + destroy?: () => void; + /** + * ### DO NOT USE THIS + * This exists solely for type-checking and has no effect at runtime. + * Set this through the `Attributes` generic instead. + */ + $$_attributes?: Attributes; + } + + /** + * Actions are functions that are called when an element is created. + * You can use this interface to type such actions. + * The following example defines an action that only works on `
    ` elements + * and optionally accepts a parameter which it has a default value for: + * ```ts + * export const myAction: Action = (node, param = { someProperty: true }) => { + * // ... + * } + * ``` + * `Action` and `Action` both signal that the action accepts no parameters. + * + * You can return an object with methods `update` and `destroy` from the function and type which additional attributes and events it has. + * See interface `ActionReturn` for more details. + */ + interface Action< + Element = HTMLElement, + Parameter = undefined, + Attributes extends Record = Record + > { + ( + ...args: undefined extends Parameter + ? [node: Node, parameter?: Parameter] + : [node: Node, parameter: Parameter] + ): void | ActionReturn; + } + + // Implementation notes: + // - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode + + export {}; +} + declare module 'svelte/compiler' { import type { SourceMap } from 'magic-string'; import type { ArrayExpression, ArrowFunctionExpression, VariableDeclaration, VariableDeclarator, Expression, Identifier, MemberExpression, Node, ObjectExpression, Pattern, Program, ChainExpression, SimpleCallExpression, SequenceExpression } from 'estree'; @@ -752,6 +916,8 @@ declare module 'svelte/compiler' { code: string; /** A source map */ map: SourceMap; + /** Whether or not the CSS includes global rules */ + hasGlobal: boolean; }; /** * An array of warning objects that were generated during compilation. Each warning has several properties: @@ -843,6 +1009,16 @@ declare module 'svelte/compiler' { * @default false */ preserveWhitespace?: boolean; + /** + * Which strategy to use when cloning DOM fragments: + * + * - `html` populates a `