@sveltejs/opencode 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,7 +40,7 @@ The default configuration:
40
40
 
41
41
  ```json
42
42
  {
43
- "$schema": "https://raw.githubusercontent.com/sveltejs/mcp/refs/heads/main/packages/opencode/schema.json",
43
+ "$schema": "https://raw.githubusercontent.com/sveltejs/ai-tools/refs/heads/main/packages/opencode/schema.json",
44
44
  "mcp": {
45
45
  "type": "remote",
46
46
  "enabled": true
package/config.ts CHANGED
@@ -4,6 +4,28 @@ import { homedir } from 'os';
4
4
  import { join } from 'path';
5
5
  import * as v from 'valibot';
6
6
 
7
+ // Schema for individual agent configuration
8
+ const agent_config_schema = v.object({
9
+ model: v.pipe(
10
+ v.optional(v.string()),
11
+ v.description('Model identifier for the agent (e.g., "anthropic/claude-sonnet-4-20250514")'),
12
+ ),
13
+ temperature: v.pipe(
14
+ v.optional(v.number()),
15
+ v.description('Temperature setting for the agent (e.g., 0.7)'),
16
+ ),
17
+ top_p: v.pipe(
18
+ v.optional(v.number()),
19
+ v.description(
20
+ 'Control response diversity with the top_p option. Alternative to temperature for controlling randomness.',
21
+ ),
22
+ ),
23
+ maxSteps: v.pipe(
24
+ v.optional(v.number()),
25
+ v.description('Maximum number of steps the agent can take (e.g., 10)'),
26
+ ),
27
+ });
28
+
7
29
  const default_config = {
8
30
  mcp: {
9
31
  type: 'remote' as 'remote' | 'local',
@@ -11,36 +33,61 @@ const default_config = {
11
33
  },
12
34
  subagent: {
13
35
  enabled: true,
36
+ agents: {} as Record<string, v.InferInput<typeof agent_config_schema>>,
14
37
  },
15
38
  instructions: {
16
39
  enabled: true,
17
40
  },
18
41
  skills: {
19
- enabled: true,
42
+ enabled: true as boolean | string[],
20
43
  },
21
44
  };
22
45
 
23
46
  export const config_schema = v.object({
24
- mcp: v.optional(
25
- v.object({
26
- type: v.optional(v.picklist(['remote', 'local'])),
27
- enabled: v.optional(v.boolean()),
28
- }),
47
+ mcp: v.pipe(
48
+ v.optional(
49
+ v.object({
50
+ type: v.optional(v.picklist(['remote', 'local'])),
51
+ enabled: v.optional(v.boolean()),
52
+ }),
53
+ ),
54
+ v.description(
55
+ "Configuration for the MCP. You can chose if it should be enabled or not and the transport to use 'remote' (default) and 'local'.",
56
+ ),
29
57
  ),
30
- subagent: v.optional(
31
- v.object({
32
- enabled: v.optional(v.boolean()),
33
- }),
58
+ subagent: v.pipe(
59
+ v.optional(
60
+ v.object({
61
+ enabled: v.optional(v.boolean()),
62
+ agents: v.optional(v.record(v.string(), agent_config_schema)),
63
+ }),
64
+ ),
65
+ v.description('Configuration for the subagent. You can choose if it should be enabled or not.'),
34
66
  ),
35
- instructions: v.optional(
36
- v.object({
37
- enabled: v.optional(v.boolean()),
38
- }),
67
+ instructions: v.pipe(
68
+ v.optional(
69
+ v.object({
70
+ enabled: v.optional(v.boolean()),
71
+ }),
72
+ ),
73
+ v.description(
74
+ 'Configuration for the automatic AGENTS.md injection. You can choose if it should be enabled or not.',
75
+ ),
39
76
  ),
40
- skills: v.optional(
41
- v.object({
42
- enabled: v.optional(v.boolean()),
43
- }),
77
+ skills: v.pipe(
78
+ v.optional(
79
+ v.object({
80
+ enabled: v.pipe(
81
+ v.optional(v.union([v.boolean(), v.array(v.string())])),
82
+ v.description(
83
+ 'It can be either a boolean or an array containing the skills that you want to enable',
84
+ ),
85
+ ),
86
+ }),
87
+ ),
88
+ v.description(
89
+ 'Configuration for the skills. You can choose if it they should be enabled or not, or specify an array of skill names to enable only specific skills.',
90
+ ),
44
91
  ),
45
92
  });
46
93
 
@@ -112,8 +159,12 @@ function merge_with_defaults(user_config: Partial<McpConfig>): McpConfig {
112
159
  ...user_config.mcp,
113
160
  },
114
161
  subagent: {
115
- ...default_config.subagent,
162
+ enabled: default_config.subagent.enabled,
116
163
  ...user_config.subagent,
164
+ agents: {
165
+ ...default_config.subagent.agents,
166
+ ...user_config.subagent?.agents,
167
+ },
117
168
  },
118
169
  instructions: {
119
170
  ...default_config.instructions,
@@ -151,7 +202,11 @@ export function get_mcp_config(ctx: PluginInput) {
151
202
  if (parsed.success) {
152
203
  merged = {
153
204
  mcp: { ...merged.mcp, ...parsed.output.mcp },
154
- subagent: { ...merged.subagent, ...parsed.output.subagent },
205
+ subagent: {
206
+ ...merged.subagent,
207
+ ...parsed.output.subagent,
208
+ agents: { ...merged.subagent?.agents, ...parsed.output.subagent?.agents },
209
+ },
155
210
  instructions: { ...merged.instructions, ...parsed.output.instructions },
156
211
  skills: { ...merged.skills, ...parsed.output.skills },
157
212
  };
package/index.ts CHANGED
@@ -39,29 +39,41 @@ export const svelte_plugin: Plugin = async (ctx) => {
39
39
  input.instructions.push(...instructions_paths.map((file) => join(instructions_dir, file)));
40
40
  }
41
41
 
42
- if (mcp_config.skills?.enabled !== false) {
42
+ const skills_enabled = mcp_config.skills?.enabled;
43
+ if (skills_enabled !== false) {
43
44
  const skills_dir = join(current_dir, 'skills');
44
- // @ts-expect-error -- skills is a new opencode feature
45
- input.skills.paths.push(skills_dir);
45
+ if (Array.isArray(skills_enabled)) {
46
+ // only add specific skill directories by name
47
+ for (const skill_name of skills_enabled) {
48
+ const skill_path = join(skills_dir, skill_name);
49
+ // @ts-expect-error -- skills is a new opencode feature
50
+ input.skills.paths.push(skill_path);
51
+ }
52
+ } else {
53
+ // @ts-expect-error -- skills is a new opencode feature
54
+ input.skills.paths.push(skills_dir);
55
+ }
46
56
  }
47
57
 
48
58
  // if the user doesn't have the MCP server already we add one based on config
49
- if (!input.mcp[svelte_mcp_name] && mcp_config.mcp?.enabled !== false) {
59
+ if (!input.mcp[svelte_mcp_name]) {
50
60
  if (mcp_config.mcp?.type === 'remote') {
51
61
  input.mcp[svelte_mcp_name] = {
52
62
  type: 'remote',
53
63
  url: 'https://mcp.svelte.dev/mcp',
64
+ enabled: mcp_config.mcp?.enabled ?? true,
54
65
  };
55
66
  } else {
56
67
  input.mcp[svelte_mcp_name] = {
57
68
  type: 'local',
58
69
  command: ['npx', '-y', '@sveltejs/mcp'],
70
+ enabled: mcp_config.mcp?.enabled ?? true,
59
71
  };
60
72
  }
61
73
  }
62
74
  if (mcp_config.subagent?.enabled !== false) {
63
75
  // we add the editor subagent that will be used when editing Svelte files to prevent wasting context on the main agent
64
- input.agent['svelte-file-editor'] = {
76
+ const default_config: (typeof input.agent)[string] = {
65
77
  color: '#ff3e00',
66
78
  mode: 'subagent',
67
79
  prompt: `You are a Svelte 5 expert responsible for writing, editing, and validating Svelte components and modules. You have access to the Svelte MCP server which provides documentation and code analysis tools. Always use the tools from the svelte MCP server to fetch documentation with \`get_documentation\` and validating the code with \`svelte_autofixer\`. If the autofixer returns any issue or suggestions try to solve them.
@@ -138,6 +150,27 @@ After completing your work, provide:
138
150
  [`${svelte_mcp_name}_*`]: true,
139
151
  },
140
152
  };
153
+
154
+ // Get per-agent config from svelte.json (if any)
155
+ const svelte_file_editor_config = mcp_config.subagent?.agents?.['svelte-file-editor'];
156
+
157
+ // Configure agent from svelte.json only
158
+ // Priority: svelte.json agent config > defaults
159
+ input.agent['svelte-file-editor'] = {
160
+ ...default_config,
161
+ ...(svelte_file_editor_config?.model !== undefined && {
162
+ model: svelte_file_editor_config.model,
163
+ }),
164
+ ...(svelte_file_editor_config?.temperature !== undefined && {
165
+ temperature: svelte_file_editor_config.temperature,
166
+ }),
167
+ ...(svelte_file_editor_config?.maxSteps !== undefined && {
168
+ maxSteps: svelte_file_editor_config.maxSteps,
169
+ }),
170
+ ...(svelte_file_editor_config?.top_p !== undefined && {
171
+ top_p: svelte_file_editor_config.top_p,
172
+ }),
173
+ };
141
174
  }
142
175
  },
143
176
  };
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@sveltejs/opencode",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
- "homepage": "https://github.com/sveltejs/mcp#readme",
6
+ "homepage": "https://github.com/sveltejs/ai-tools#readme",
7
7
  "bugs": {
8
- "url": "https://github.com/sveltejs/mcp/issues"
8
+ "url": "https://github.com/sveltejs/ai-tools/issues"
9
9
  },
10
10
  "files": [
11
11
  "index.ts",
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "git+https://github.com/sveltejs/mcp.git",
18
+ "url": "git+https://github.com/sveltejs/ai-tools.git",
19
19
  "path": "packages/opencode"
20
20
  },
21
21
  "publishConfig": {
@@ -0,0 +1,176 @@
1
+ ---
2
+ name: svelte-core-bestpractices
3
+ description: Guidance on writing fast, robust, modern Svelte code. Load this skill whenever in a Svelte project and asked to write/edit or analyze a Svelte component or module. Covers reactivity, event handling, styling, integration with libraries and more.
4
+ ---
5
+
6
+ ## `$state`
7
+
8
+ Only use the `$state` rune for variables that should be _reactive_ — in other words, variables that cause an `$effect`, `$derived` or template expression to update. Everything else can be a normal variable.
9
+
10
+ Objects and arrays (`$state({...})` or `$state([...])`) are made deeply reactive, meaning mutation will trigger updates. This has a trade-off: in exchange for fine-grained reactivity, the objects must be proxied, which has performance overhead. In cases where you're dealing with large objects that are only ever reassigned (rather than mutated), use `$state.raw` instead. This is often the case with API responses, for example.
11
+
12
+ ## `$derived`
13
+
14
+ To compute something from state, use `$derived` rather than `$effect`:
15
+
16
+ ```js
17
+ // do this
18
+ let square = $derived(num * num);
19
+
20
+ // don't do this
21
+ let square;
22
+
23
+ $effect(() => {
24
+ square = num * num;
25
+ });
26
+ ```
27
+
28
+ > [!NOTE] `$derived` is given an expression, _not_ a function. If you need to use a function (because the expression is complex, for example) use `$derived.by`.
29
+
30
+ Deriveds are writable — you can assign to them, just like `$state`, except that they will re-evaluate when their expression changes.
31
+
32
+ If the derived expression is an object or array, it will be returned as-is — it is _not_ made deeply reactive. You can, however, use `$state` inside `$derived.by` in the rare cases that you need this.
33
+
34
+ ## `$effect`
35
+
36
+ Effects are an escape hatch and should mostly be avoided. In particular, avoid updating state inside effects.
37
+
38
+ - If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/@attach.md)
39
+ - If you need to run some code in response to user interaction, put the code directly in an event handler or use a [function binding](references/bind.md) as appropriate
40
+ - If you need to log values for debugging purposes, use [`$inspect`](references/$inspect.md)
41
+ - If you need to observe something external to Svelte, use [`createSubscriber`](references/svelte-reactivity.md)
42
+
43
+ Never wrap the contents of an effect in `if (browser) {...}` or similar — effects do not run on the server.
44
+
45
+ ## `$props`
46
+
47
+ Treat props as though they will change. For example, values that depend on props should usually use `$derived`:
48
+
49
+ ```js
50
+ // @errors: 2451
51
+ let { type } = $props();
52
+
53
+ // do this
54
+ let color = $derived(type === 'danger' ? 'red' : 'green');
55
+
56
+ // don't do this — `color` will not update if `type` changes
57
+ let color = type === 'danger' ? 'red' : 'green';
58
+ ```
59
+
60
+ ## `$inspect.trace`
61
+
62
+ `$inspect.trace` is a debugging tool for reactivity. If something is not updating properly or running more than it should you can add `$inspect.trace(label)` as the first line of an `$effect` or `$derived.by` (or any function they call) to trace their dependencies and discover which one triggered an update.
63
+
64
+ ## Events
65
+
66
+ Any element attribute starting with `on` is treated as an event listener:
67
+
68
+ ```svelte
69
+ <button onclick={() => {...}}>click me</button>
70
+
71
+ <!-- attribute shorthand also works -->
72
+ <button {onclick}>...</button>
73
+
74
+ <!-- so do spread attributes -->
75
+ <button {...props}>...</button>
76
+ ```
77
+
78
+ If you need to attach listeners to `window` or `document` you can use `<svelte:window>` and `<svelte:document>`:
79
+
80
+ ```svelte
81
+ <svelte:window onkeydown={...} />
82
+ <svelte:document onvisibilitychange={...} />
83
+ ```
84
+
85
+ Avoid using `onMount` or `$effect` for this.
86
+
87
+ ## Snippets
88
+
89
+ [Snippets](references/snippet.md) are a way to define reusable chunks of markup that can be instantiated with the [`{@render ...}`](references/@render.md) tag, or passed to components as props. They must be declared within the template.
90
+
91
+ ```svelte
92
+ {#snippet greeting(name)}
93
+ <p>hello {name}!</p>
94
+ {/snippet}
95
+
96
+ {@render greeting('world')}
97
+ ```
98
+
99
+ > [!NOTE] Snippets declared at the top level of a component (i.e. not inside elements or blocks) can be referenced inside `<script>`. A snippet that doesn't reference component state is also available in a `<script module>`, in which case it can be exported for use by other components.
100
+
101
+ ## Each blocks
102
+
103
+ Prefer to use [keyed each blocks](references/each.md) — this improves performance by allowing Svelte to surgically insert or remove items rather than updating the DOM belonging to existing items.
104
+
105
+ > [!NOTE] The key _must_ uniquely identify the object. Do not use the index as a key.
106
+
107
+ Avoid destructuring if you need to mutate the item (with something like `bind:value={item.count}`, for example).
108
+
109
+ ## Using JavaScript variables in CSS
110
+
111
+ If you have a JS variable that you want to use inside CSS you can set a CSS custom property with the `style:` directive.
112
+
113
+ ```svelte
114
+ <div style:--columns={columns}>...</div>
115
+ ```
116
+
117
+ You can then reference `var(--columns)` inside the component's `<style>`.
118
+
119
+ ## Styling child components
120
+
121
+ The CSS in a component's `<style>` is scoped to that component. If a parent component needs to control the child's styles, the preferred way is to use CSS custom properties:
122
+
123
+ ```svelte
124
+ <!-- Parent.svelte -->
125
+ <Child --color="red" />
126
+
127
+ <!-- Child.svelte -->
128
+ <h1>Hello</h1>
129
+
130
+ <style>
131
+ h1 {
132
+ color: var(--color);
133
+ }
134
+ </style>
135
+ ```
136
+
137
+ If this impossible (for example, the child component comes from a library) you can use `:global` to override styles:
138
+
139
+ ```svelte
140
+ <div>
141
+ <Child />
142
+ </div>
143
+
144
+ <style>
145
+ div :global {
146
+ h1 {
147
+ color: red;
148
+ }
149
+ }
150
+ </style>
151
+ ```
152
+
153
+ ## Context
154
+
155
+ Consider using context instead of declaring state in a shared module. This will scope the state to the part of the app that needs it, and eliminate the possibility of it leaking between users when server-side rendering.
156
+
157
+ Use `createContext` rather than `setContext` and `getContext`, as it provides type safety.
158
+
159
+ ## Async Svelte
160
+
161
+ If using version 5.36 or higher, you can use [await expressions](references/await-expressions.md) and [hydratable](references/hydratable.md) to use promises directly inside components. Note that these require the `experimental.async` option to be enabled in `svelte.config.js` as they are not yet considered fully stable.
162
+
163
+ ## Avoid legacy features
164
+
165
+ Always use runes mode for new code, and avoid features that have more modern replacements:
166
+
167
+ - use `$state` instead of implicit reactivity (e.g. `let count = 0; count += 1`)
168
+ - use `$derived` and `$effect` instead of `$:` assignments and statements (but only use effects when there is no better solution)
169
+ - use `$props` instead of `export let`, `$$props` and `$$restProps`
170
+ - use `onclick={...}` instead of `on:click={...}`
171
+ - use `{#snippet ...}` and `{@render ...}` instead of `<slot>` and `$$slots` and `<svelte:fragment>`
172
+ - use `<DynamicComponent>` instead of `<svelte:component this={DynamicComponent}>`
173
+ - use `import Self from './ThisComponent.svelte'` and `<Self>` instead of `<svelte:self>`
174
+ - use classes with `$state` fields to share reactivity between components, instead of using stores
175
+ - use `{@attach ...}` instead of `use:action`
176
+ - use clsx-style arrays and objects in `class` attributes, instead of the `class:` directive
@@ -0,0 +1,53 @@
1
+ > [!NOTE] `$inspect` only works during development. In a production build it becomes a noop.
2
+
3
+ The `$inspect` rune is roughly equivalent to `console.log`, with the exception that it will re-run whenever its argument changes. `$inspect` tracks reactive state deeply, meaning that updating something inside an object or array using fine-grained reactivity will cause it to re-fire (demo:
4
+
5
+ ```svelte
6
+ <script>
7
+ let count = $state(0);
8
+ let message = $state('hello');
9
+
10
+ $inspect(count, message); // will console.log when `count` or `message` change
11
+ </script>
12
+
13
+ <button onclick={() => count++}>Increment</button>
14
+ <input bind:value={message} />
15
+ ```
16
+
17
+ On updates, a stack trace will be printed, making it easy to find the origin of a state change (unless you're in the playground, due to technical limitations).
18
+
19
+ ## $inspect(...).with
20
+
21
+ `$inspect` returns a property `with`, which you can invoke with a callback, which will then be invoked instead of `console.log`. The first argument to the callback is either `"init"` or `"update"`; subsequent arguments are the values passed to `$inspect` (demo:
22
+
23
+ ```svelte
24
+ <script>
25
+ let count = $state(0);
26
+
27
+ $inspect(count).with((type, count) => {
28
+ if (type === 'update') {
29
+ debugger; // or `console.trace`, or whatever you want
30
+ }
31
+ });
32
+ </script>
33
+
34
+ <button onclick={() => count++}>Increment</button>
35
+ ```
36
+
37
+ ## $inspect.trace(...)
38
+
39
+ This rune, added in 5.14, causes the surrounding function to be _traced_ in development. Any time the function re-runs as part of an [effect]($effect) or a [derived]($derived), information will be printed to the console about which pieces of reactive state caused the effect to fire.
40
+
41
+ ```svelte
42
+ <script>
43
+ import { doSomeWork } from './elsewhere';
44
+
45
+ $effect(() => {
46
+ +++// $inspect.trace must be the first statement of a function body+++
47
+ +++$inspect.trace();+++
48
+ doSomeWork();
49
+ });
50
+ </script>
51
+ ```
52
+
53
+ `$inspect.trace` takes an optional first argument which will be used as the label.
@@ -0,0 +1,166 @@
1
+ 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.
2
+
3
+ Optionally, they can return a function that is called before the attachment re-runs, or after the element is later removed from the DOM.
4
+
5
+ > [!NOTE]
6
+ > Attachments are available in Svelte 5.29 and newer.
7
+
8
+ ```svelte
9
+ <!--- file: App.svelte --->
10
+ <script>
11
+ /** @type {import('svelte/attachments').Attachment} */
12
+ function myAttachment(element) {
13
+ console.log(element.nodeName); // 'DIV'
14
+
15
+ return () => {
16
+ console.log('cleaning up');
17
+ };
18
+ }
19
+ </script>
20
+
21
+ <div {@attach myAttachment}>...</div>
22
+ ```
23
+
24
+ An element can have any number of attachments.
25
+
26
+ ## Attachment factories
27
+
28
+ A useful pattern is for a function, such as `tooltip` in this example, to _return_ an attachment (demo:
29
+
30
+ ```svelte
31
+ <!--- file: App.svelte --->
32
+ <script>
33
+ import tippy from 'tippy.js';
34
+
35
+ let content = $state('Hello!');
36
+
37
+ /**
38
+ * @param {string} content
39
+ * @returns {import('svelte/attachments').Attachment}
40
+ */
41
+ function tooltip(content) {
42
+ return (element) => {
43
+ const tooltip = tippy(element, { content });
44
+ return tooltip.destroy;
45
+ };
46
+ }
47
+ </script>
48
+
49
+ <input bind:value={content} />
50
+
51
+ <button {@attach tooltip(content)}> Hover me </button>
52
+ ```
53
+
54
+ 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).)
55
+
56
+ ## Inline attachments
57
+
58
+ Attachments can also be created inline (demo:
59
+
60
+ ```svelte
61
+ <!--- file: App.svelte --->
62
+ <canvas
63
+ width={32}
64
+ height={32}
65
+ {@attach (canvas) => {
66
+ const context = canvas.getContext('2d');
67
+
68
+ $effect(() => {
69
+ context.fillStyle = color;
70
+ context.fillRect(0, 0, canvas.width, canvas.height);
71
+ });
72
+ }}
73
+ ></canvas>
74
+ ```
75
+
76
+ > [!NOTE]
77
+ > 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.
78
+
79
+ ## Conditional attachments
80
+
81
+ Falsy values like `false` or `undefined` are treated as no attachment, enabling conditional usage:
82
+
83
+ ```svelte
84
+ <div {@attach enabled && myAttachment}>...</div>
85
+ ```
86
+
87
+ ## Passing attachments to components
88
+
89
+ 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.
90
+
91
+ This allows you to create _wrapper components_ that augment elements (demo:
92
+
93
+ ```svelte
94
+ <!--- file: Button.svelte --->
95
+ <script>
96
+ /** @type {import('svelte/elements').HTMLButtonAttributes} */
97
+ let { children, ...props } = $props();
98
+ </script>
99
+
100
+ <!-- `props` includes attachments -->
101
+ <button {...props}>
102
+ {@render children?.()}
103
+ </button>
104
+ ```
105
+
106
+ ```svelte
107
+ <!--- file: App.svelte --->
108
+ <script>
109
+ import tippy from 'tippy.js';
110
+ import Button from './Button.svelte';
111
+
112
+ let content = $state('Hello!');
113
+
114
+ /**
115
+ * @param {string} content
116
+ * @returns {import('svelte/attachments').Attachment}
117
+ */
118
+ function tooltip(content) {
119
+ return (element) => {
120
+ const tooltip = tippy(element, { content });
121
+ return tooltip.destroy;
122
+ };
123
+ }
124
+ </script>
125
+
126
+ <input bind:value={content} />
127
+
128
+ <Button {@attach tooltip(content)}>Hover me</Button>
129
+ ```
130
+
131
+ ## Controlling when attachments re-run
132
+
133
+ 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`):
134
+
135
+ ```js
136
+ // @errors: 7006 2304 2552
137
+ function foo(bar) {
138
+ return (node) => {
139
+ veryExpensiveSetupWork(node);
140
+ update(node, bar);
141
+ };
142
+ }
143
+ ```
144
+
145
+ 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:
146
+
147
+ ```js
148
+ // @errors: 7006 2304 2552
149
+ function foo(+++getBar+++) {
150
+ return (node) => {
151
+ veryExpensiveSetupWork(node);
152
+
153
+ +++ $effect(() => {
154
+ update(node, getBar());
155
+ });+++
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## Creating attachments programmatically
161
+
162
+ To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](svelte-attachments#createAttachmentKey).
163
+
164
+ ## Converting actions to attachments
165
+
166
+ 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.
@@ -0,0 +1,35 @@
1
+ To render a [snippet](snippet), use a `{@render ...}` tag.
2
+
3
+ ```svelte
4
+ {#snippet sum(a, b)}
5
+ <p>{a} + {b} = {a + b}</p>
6
+ {/snippet}
7
+
8
+ {@render sum(1, 2)}
9
+ {@render sum(3, 4)}
10
+ {@render sum(5, 6)}
11
+ ```
12
+
13
+ The expression can be an identifier like `sum`, or an arbitrary JavaScript expression:
14
+
15
+ ```svelte
16
+ {@render (cool ? coolSnippet : lameSnippet)()}
17
+ ```
18
+
19
+ ## Optional snippets
20
+
21
+ If the snippet is potentially undefined — for example, because it's an incoming prop — then you can use optional chaining to only render it when it _is_ defined:
22
+
23
+ ```svelte
24
+ {@render children?.()}
25
+ ```
26
+
27
+ Alternatively, use an [`{#if ...}`](if) block with an `:else` clause to render fallback content:
28
+
29
+ ```svelte
30
+ {#if children}
31
+ {@render children()}
32
+ {:else}
33
+ <p>fallback content</p>
34
+ {/if}
35
+ ```
@@ -0,0 +1,180 @@
1
+ As of Svelte 5.36, you can use the `await` keyword inside your components in three places where it was previously unavailable:
2
+
3
+ - at the top level of your component's `<script>`
4
+ - inside `$derived(...)` declarations
5
+ - inside your markup
6
+
7
+ This feature is currently experimental, and you must opt in by adding the `experimental.async` option wherever you [configure](/docs/kit/configuration) Svelte, usually `svelte.config.js`:
8
+
9
+ ```js
10
+ /// file: svelte.config.js
11
+ export default {
12
+ compilerOptions: {
13
+ experimental: {
14
+ async: true,
15
+ },
16
+ },
17
+ };
18
+ ```
19
+
20
+ The experimental flag will be removed in Svelte 6.
21
+
22
+ ## Synchronized updates
23
+
24
+ When an `await` expression depends on a particular piece of state, changes to that state will not be reflected in the UI until the asynchronous work has completed, so that the UI is not left in an inconsistent state. In other words, in an example like this...
25
+
26
+ ```svelte
27
+ <script>
28
+ let a = $state(1);
29
+ let b = $state(2);
30
+
31
+ async function add(a, b) {
32
+ await new Promise((f) => setTimeout(f, 500)); // artificial delay
33
+ return a + b;
34
+ }
35
+ </script>
36
+
37
+ <input type="number" bind:value={a} />
38
+ <input type="number" bind:value={b} />
39
+
40
+ <p>{a} + {b} = {await add(a, b)}</p>
41
+ ```
42
+
43
+ ...if you increment `a`, the contents of the `<p>` will _not_ immediately update to read this —
44
+
45
+ ```html
46
+ <p>2 + 2 = 3</p>
47
+ ```
48
+
49
+ — instead, the text will update to `2 + 2 = 4` when `add(a, b)` resolves.
50
+
51
+ Updates can overlap — a fast update will be reflected in the UI while an earlier slow update is still ongoing.
52
+
53
+ ## Concurrency
54
+
55
+ Svelte will do as much asynchronous work as it can in parallel. For example if you have two `await` expressions in your markup...
56
+
57
+ ```svelte
58
+ <p>{await one()}</p><p>{await two()}</p>
59
+ ```
60
+
61
+ ...both functions will run at the same time, as they are independent expressions, even though they are _visually_ sequential.
62
+
63
+ This does not apply to sequential `await` expressions inside your `<script>` or inside async functions — these run like any other asynchronous JavaScript. An exception is that independent `$derived` expressions will update independently, even though they will run sequentially when they are first created:
64
+
65
+ ```js
66
+ // these will run sequentially the first time,
67
+ // but will update independently
68
+ let a = $derived(await one());
69
+ let b = $derived(await two());
70
+ ```
71
+
72
+ > [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](runtime-warnings#Client-warnings-await_waterfall) warning
73
+
74
+ ## Indicating loading states
75
+
76
+ To render placeholder UI, you can wrap content in a `<svelte:boundary>` with a [`pending`](svelte-boundary#Properties-pending) snippet. This will be shown when the boundary is first created, but not for subsequent updates, which are globally coordinated.
77
+
78
+ After the contents of a boundary have resolved for the first time and have replaced the `pending` snippet, you can detect subsequent async work with [`$effect.pending()`]($effect#$effect.pending). This is what you would use to display a "we're asynchronously validating your input" spinner next to a form field, for example.
79
+
80
+ You can also use [`settled()`](svelte#settled) to get a promise that resolves when the current update is complete:
81
+
82
+ ```js
83
+ import { tick, settled } from 'svelte';
84
+
85
+ async function onclick() {
86
+ updating = true;
87
+
88
+ // without this, the change to `updating` will be
89
+ // grouped with the other changes, meaning it
90
+ // won't be reflected in the UI
91
+ await tick();
92
+
93
+ color = 'octarine';
94
+ answer = 42;
95
+
96
+ await settled();
97
+
98
+ // any updates affected by `color` or `answer`
99
+ // have now been applied
100
+ updating = false;
101
+ }
102
+ ```
103
+
104
+ ## Error handling
105
+
106
+ Errors in `await` expressions will bubble to the nearest [error boundary](svelte-boundary).
107
+
108
+ ## Server-side rendering
109
+
110
+ Svelte supports asynchronous server-side rendering (SSR) with the `render(...)` API. To use it, simply await the return value:
111
+
112
+ ```js
113
+ /// file: server.js
114
+ import { render } from 'svelte/server';
115
+ import App from './App.svelte';
116
+
117
+ const { head, body } = +++await+++ render(App);
118
+ ```
119
+
120
+ > [!NOTE] If you're using a framework like SvelteKit, this is done on your behalf.
121
+
122
+ If a `<svelte:boundary>` with a `pending` snippet is encountered during SSR, that snippet will be rendered while the rest of the content is ignored. All `await` expressions encountered outside boundaries with `pending` snippets will resolve and render their contents prior to `await render(...)` returning.
123
+
124
+ > [!NOTE] In the future, we plan to add a streaming implementation that renders the content in the background.
125
+
126
+ ## Forking
127
+
128
+ The [`fork(...)`](svelte#fork) API, added in 5.42, makes it possible to run `await` expressions that you _expect_ to happen in the near future. This is mainly intended for frameworks like SvelteKit to implement preloading when (for example) users signal an intent to navigate.
129
+
130
+ ```svelte
131
+ <script>
132
+ import { fork } from 'svelte';
133
+ import Menu from './Menu.svelte';
134
+
135
+ let open = $state(false);
136
+
137
+ /** @type {import('svelte').Fork | null} */
138
+ let pending = null;
139
+
140
+ function preload() {
141
+ pending ??= fork(() => {
142
+ open = true;
143
+ });
144
+ }
145
+
146
+ function discard() {
147
+ pending?.discard();
148
+ pending = null;
149
+ }
150
+ </script>
151
+
152
+ <button
153
+ onfocusin={preload}
154
+ onfocusout={discard}
155
+ onpointerenter={preload}
156
+ onpointerleave={discard}
157
+ onclick={() => {
158
+ pending?.commit();
159
+ pending = null;
160
+
161
+ // in case `pending` didn't exist
162
+ // (if it did, this is a no-op)
163
+ open = true;
164
+ }}>open menu</button
165
+ >
166
+
167
+ {#if open}
168
+ <!-- any async work inside this component will start
169
+ as soon as the fork is created -->
170
+ <Menu onclose={() => (open = false)} />
171
+ {/if}
172
+ ```
173
+
174
+ ## Caveats
175
+
176
+ As an experimental feature, the details of how `await` is handled (and related APIs like `$effect.pending()`) are subject to breaking changes outside of a semver major release, though we intend to keep such changes to a bare minimum.
177
+
178
+ ## Breaking changes
179
+
180
+ Effects run in a slightly different order when the `experimental.async` option is `true`. Specifically, _block_ effects like `{#if ...}` and `{#each ...}` now run before an `$effect.pre` or `beforeUpdate` in the same component, which means that in very rare situations.
@@ -0,0 +1,16 @@
1
+ ## Function bindings
2
+
3
+ You can also use `bind:property={get, set}`, where `get` and `set` are functions, allowing you to perform validation and transformation:
4
+
5
+ ```svelte
6
+ <input bind:value={() => value, (v) => (value = v.toLowerCase())} />
7
+ ```
8
+
9
+ In the case of readonly bindings like [dimension bindings](#Dimensions), the `get` value should be `null`:
10
+
11
+ ```svelte
12
+ <div bind:clientWidth={null, redraw} bind:clientHeight={null, redraw}>...</div>
13
+ ```
14
+
15
+ > [!NOTE]
16
+ > Function bindings are available in Svelte 5.9.0 and newer.
@@ -0,0 +1,42 @@
1
+ ## Keyed each blocks
2
+
3
+ ```svelte
4
+ <!--- copy: false --->
5
+ {#each expression as name (key)}...{/each}
6
+ ```
7
+
8
+ ```svelte
9
+ <!--- copy: false --->
10
+ {#each expression as name, index (key)}...{/each}
11
+ ```
12
+
13
+ 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.
14
+
15
+ The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change.
16
+
17
+ ```svelte
18
+ {#each items as item (item.id)}
19
+ <li>{item.name} x {item.qty}</li>
20
+ {/each}
21
+
22
+ <!-- or with additional index value -->
23
+ {#each items as item, i (item.id)}
24
+ <li>{i + 1}: {item.name} x {item.qty}</li>
25
+ {/each}
26
+ ```
27
+
28
+ You can freely use destructuring and rest patterns in each blocks.
29
+
30
+ ```svelte
31
+ {#each items as { id, name, qty }, i (id)}
32
+ <li>{i + 1}: {name} x {qty}</li>
33
+ {/each}
34
+
35
+ {#each objects as { id, ...rest }}
36
+ <li><span>{id}</span><MyComponent {...rest} /></li>
37
+ {/each}
38
+
39
+ {#each items as [id, ...rest]}
40
+ <li><span>{id}</span><MyComponent values={rest} /></li>
41
+ {/each}
42
+ ```
@@ -0,0 +1,100 @@
1
+ In Svelte, when you want to render asynchronous content data on the server, you can simply `await` it. This is great! However, it comes with a pitfall: when hydrating that content on the client, Svelte has to redo the asynchronous work, which blocks hydration for however long it takes:
2
+
3
+ ```svelte
4
+ <script>
5
+ import { getUser } from 'my-database-library';
6
+
7
+ // This will get the user on the server, render the user's name into the h1,
8
+ // and then, during hydration on the client, it will get the user _again_,
9
+ // blocking hydration until it's done.
10
+ const user = await getUser();
11
+ </script>
12
+
13
+ <h1>{user.name}</h1>
14
+ ```
15
+
16
+ That's silly, though. If we've already done the hard work of getting the data on the server, we don't want to get it again during hydration on the client. `hydratable` is a low-level API built to solve this problem. You probably won't need this very often — it will be used behind the scenes by whatever datafetching library you use. For example, it powers [remote functions in SvelteKit](/docs/kit/remote-functions).
17
+
18
+ To fix the example above:
19
+
20
+ ```svelte
21
+ <script>
22
+ import { hydratable } from 'svelte';
23
+ import { getUser } from 'my-database-library';
24
+
25
+ // During server rendering, this will serialize and stash the result of `getUser`, associating
26
+ // it with the provided key and baking it into the `head` content. During hydration, it will
27
+ // look for the serialized version, returning it instead of running `getUser`. After hydration
28
+ // is done, if it's called again, it'll simply invoke `getUser`.
29
+ const user = await hydratable('user', () => getUser());
30
+ </script>
31
+
32
+ <h1>{user.name}</h1>
33
+ ```
34
+
35
+ This API can also be used to provide access to random or time-based values that are stable between server rendering and hydration. For example, to get a random number that doesn't update on hydration:
36
+
37
+ ```ts
38
+ import { hydratable } from 'svelte';
39
+ const rand = hydratable('random', () => Math.random());
40
+ ```
41
+
42
+ If you're a library author, be sure to prefix the keys of your `hydratable` values with the name of your library so that your keys don't conflict with other libraries.
43
+
44
+ ## Serialization
45
+
46
+ All data returned from a `hydratable` function must be serializable. But this doesn't mean you're limited to JSON — Svelte uses [`devalue`](https://npmjs.com/package/devalue), which can serialize all sorts of things including `Map`, `Set`, `URL`, and `BigInt`. Check the documentation page for a full list. In addition to these, thanks to some Svelte magic, you can also fearlessly use promises:
47
+
48
+ ```svelte
49
+ <script>
50
+ import { hydratable } from 'svelte';
51
+ const promises = hydratable('random', () => {
52
+ return {
53
+ one: Promise.resolve(1),
54
+ two: Promise.resolve(2),
55
+ };
56
+ });
57
+ </script>
58
+
59
+ {await promises.one}
60
+ {await promises.two}
61
+ ```
62
+
63
+ ## CSP
64
+
65
+ `hydratable` adds an inline `<script>` block to the `head` returned from `render`. If you're using [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) (CSP), this script will likely fail to run. You can provide a `nonce` to `render`:
66
+
67
+ ```js
68
+ const nonce = crypto.randomUUID();
69
+
70
+ const { head, body } = await render(App, {
71
+ csp: { nonce },
72
+ });
73
+ ```
74
+
75
+ This will add the `nonce` to the script block, on the assumption that you will later add the same nonce to the CSP header of the document that contains it:
76
+
77
+ ```js
78
+ response.headers.set('Content-Security-Policy', `script-src 'nonce-${nonce}'`);
79
+ ```
80
+
81
+ It's essential that a `nonce` — which, British slang definition aside, means 'number used once' — is only used when dynamically server rendering an individual response.
82
+
83
+ If instead you are generating static HTML ahead of time, you must use hashes instead:
84
+
85
+ ```js
86
+ const { head, body, hashes } = await render(App, {
87
+ csp: { hash: true },
88
+ });
89
+ ```
90
+
91
+ `hashes.script` will be an array of strings like `["sha256-abcd123"]`. As with `nonce`, the hashes should be used in your CSP header:
92
+
93
+ ```js
94
+ response.headers.set(
95
+ 'Content-Security-Policy',
96
+ `script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`,
97
+ );
98
+ ```
99
+
100
+ We recommend using `nonce` over hash if you can, as `hash` will interfere with streaming SSR in the future.
@@ -0,0 +1,276 @@
1
+ ```svelte
2
+ <!--- copy: false --->
3
+ {#snippet name()}...{/snippet}
4
+ ```
5
+
6
+ ```svelte
7
+ <!--- copy: false --->
8
+ {#snippet name(param1, param2, paramN)}...{/snippet}
9
+ ```
10
+
11
+ Snippets, and [render tags](@render), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...
12
+
13
+ ```svelte
14
+ {#each images as image}
15
+ {#if image.href}
16
+ <a href={image.href}>
17
+ <figure>
18
+ <img src={image.src} alt={image.caption} width={image.width} height={image.height} />
19
+ <figcaption>{image.caption}</figcaption>
20
+ </figure>
21
+ </a>
22
+ {:else}
23
+ <figure>
24
+ <img src={image.src} alt={image.caption} width={image.width} height={image.height} />
25
+ <figcaption>{image.caption}</figcaption>
26
+ </figure>
27
+ {/if}
28
+ {/each}
29
+ ```
30
+
31
+ ...you can write this:
32
+
33
+ ```svelte
34
+ {#snippet figure(image)}
35
+ <figure>
36
+ <img src={image.src} alt={image.caption} width={image.width} height={image.height} />
37
+ <figcaption>{image.caption}</figcaption>
38
+ </figure>
39
+ {/snippet}
40
+
41
+ {#each images as image}
42
+ {#if image.href}
43
+ <a href={image.href}>
44
+ {@render figure(image)}
45
+ </a>
46
+ {:else}
47
+ {@render figure(image)}
48
+ {/if}
49
+ {/each}
50
+ ```
51
+
52
+ Like function declarations, snippets can have an arbitrary number of parameters, which can have default values, and you can destructure each parameter. You cannot use rest parameters, however.
53
+
54
+ ## Snippet scope
55
+
56
+ Snippets can be declared anywhere inside your component. They can reference values declared outside themselves, for example in the `<script>` tag or in `{#each ...}` blocks (demo...
57
+
58
+ ```svelte
59
+ <script>
60
+ let { message = `it's great to see you!` } = $props();
61
+ </script>
62
+
63
+ {#snippet hello(name)}
64
+ <p>hello {name}! {message}!</p>
65
+ {/snippet}
66
+
67
+ {@render hello('alice')}
68
+ {@render hello('bob')}
69
+ ```
70
+
71
+ ...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings):
72
+
73
+ ```svelte
74
+ <div>
75
+ {#snippet x()}
76
+ {#snippet y()}...{/snippet}
77
+
78
+ <!-- this is fine -->
79
+ {@render y()}
80
+ {/snippet}
81
+
82
+ <!-- this will error, as `y` is not in scope -->
83
+ {@render y()}
84
+ </div>
85
+
86
+ <!-- this will also error, as `x` is not in scope -->
87
+ {@render x()}
88
+ ```
89
+
90
+ Snippets can reference themselves and each other (demo:
91
+
92
+ ```svelte
93
+ {#snippet blastoff()}
94
+ <span>🚀</span>
95
+ {/snippet}
96
+
97
+ {#snippet countdown(n)}
98
+ {#if n > 0}
99
+ <span>{n}...</span>
100
+ {@render countdown(n - 1)}
101
+ {:else}
102
+ {@render blastoff()}
103
+ {/if}
104
+ {/snippet}
105
+
106
+ {@render countdown(10)}
107
+ ```
108
+
109
+ ## Passing snippets to components
110
+
111
+ ### Explicit props
112
+
113
+ Within the template, snippets are values just like any other. As such, they can be passed to components as props (demo:
114
+
115
+ ```svelte
116
+ <script>
117
+ import Table from './Table.svelte';
118
+
119
+ const fruits = [
120
+ { name: 'apples', qty: 5, price: 2 },
121
+ { name: 'bananas', qty: 10, price: 1 },
122
+ { name: 'cherries', qty: 20, price: 0.5 },
123
+ ];
124
+ </script>
125
+
126
+ {#snippet header()}
127
+ <th>fruit</th>
128
+ <th>qty</th>
129
+ <th>price</th>
130
+ <th>total</th>
131
+ {/snippet}
132
+
133
+ {#snippet row(d)}
134
+ <td>{d.name}</td>
135
+ <td>{d.qty}</td>
136
+ <td>{d.price}</td>
137
+ <td>{d.qty * d.price}</td>
138
+ {/snippet}
139
+
140
+ <Table data={fruits} {header} {row} />
141
+ ```
142
+
143
+ Think about it like passing content instead of data to a component. The concept is similar to slots in web components.
144
+
145
+ ### Implicit props
146
+
147
+ As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component (demo:
148
+
149
+ ```svelte
150
+ <!-- this is semantically the same as the above -->
151
+ <Table data={fruits}>
152
+ {#snippet header()}
153
+ <th>fruit</th>
154
+ <th>qty</th>
155
+ <th>price</th>
156
+ <th>total</th>
157
+ {/snippet}
158
+
159
+ {#snippet row(d)}
160
+ <td>{d.name}</td>
161
+ <td>{d.qty}</td>
162
+ <td>{d.price}</td>
163
+ <td>{d.qty * d.price}</td>
164
+ {/snippet}
165
+ </Table>
166
+ ```
167
+
168
+ ### Implicit `children` snippet
169
+
170
+ Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet (demo:
171
+
172
+ ```svelte
173
+ <!--- file: App.svelte --->
174
+ <Button>click me</Button>
175
+ ```
176
+
177
+ ```svelte
178
+ <!--- file: Button.svelte --->
179
+ <script>
180
+ let { children } = $props();
181
+ </script>
182
+
183
+ <!-- result will be <button>click me</button> -->
184
+ <button>{@render children()}</button>
185
+ ```
186
+
187
+ > [!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
188
+
189
+ ### Optional snippet props
190
+
191
+ You can declare snippet props as being optional. You can either use optional chaining to not render anything if the snippet isn't set...
192
+
193
+ ```svelte
194
+ <script>
195
+ let { children } = $props();
196
+ </script>
197
+
198
+ {@render children?.()}
199
+ ```
200
+
201
+ ...or use an `#if` block to render fallback content:
202
+
203
+ ```svelte
204
+ <script>
205
+ let { children } = $props();
206
+ </script>
207
+
208
+ {#if children}
209
+ {@render children()}
210
+ {:else}
211
+ fallback content
212
+ {/if}
213
+ ```
214
+
215
+ ## Typing snippets
216
+
217
+ Snippets implement the `Snippet` interface imported from `'svelte'`:
218
+
219
+ ```svelte
220
+ <script lang="ts">
221
+ import type { Snippet } from 'svelte';
222
+
223
+ interface Props {
224
+ data: any[];
225
+ children: Snippet;
226
+ row: Snippet<[any]>;
227
+ }
228
+
229
+ let { data, children, row }: Props = $props();
230
+ </script>
231
+ ```
232
+
233
+ With this change, red squigglies will appear if you try and use the component without providing a `data` prop and a `row` snippet. Notice that the type argument provided to `Snippet` is a tuple, since snippets can have multiple parameters.
234
+
235
+ We can tighten things up further by declaring a generic, so that `data` and `row` refer to the same type:
236
+
237
+ ```svelte
238
+ <script lang="ts" generics="T">
239
+ import type { Snippet } from 'svelte';
240
+
241
+ let {
242
+ data,
243
+ children,
244
+ row,
245
+ }: {
246
+ data: T[];
247
+ children: Snippet;
248
+ row: Snippet<[T]>;
249
+ } = $props();
250
+ </script>
251
+ ```
252
+
253
+ ## Exporting snippets
254
+
255
+ Snippets declared at the top level of a `.svelte` file can be exported from a `<script module>` for use in other components, provided they don't reference any declarations in a non-module `<script>` (whether directly or indirectly, via other snippets) (demo:
256
+
257
+ ```svelte
258
+ <script module>
259
+ export { add };
260
+ </script>
261
+
262
+ {#snippet add(a, b)}
263
+ {a} + {b} = {a + b}
264
+ {/snippet}
265
+ ```
266
+
267
+ > [!NOTE]
268
+ > This requires Svelte 5.5.0 or newer
269
+
270
+ ## Programmatic snippets
271
+
272
+ Snippets can be created programmatically with the [`createRawSnippet`](svelte#createRawSnippet) API. This is intended for advanced use cases.
273
+
274
+ ## Snippets and slots
275
+
276
+ In Svelte 4, content can be passed to components using [slots](legacy-slots). Snippets are more powerful and flexible, and so slots have been deprecated in Svelte 5.
@@ -0,0 +1,61 @@
1
+ ## createSubscriber
2
+
3
+ <blockquote class="since note">
4
+
5
+ Available since 5.7.0
6
+
7
+ </blockquote>
8
+
9
+ Returns a `subscribe` function that integrates external event-based systems with Svelte's reactivity.
10
+ It's particularly useful for integrating with web APIs like `MediaQuery`, `IntersectionObserver`, or `WebSocket`.
11
+
12
+ If `subscribe` is called inside an effect (including indirectly, for example inside a getter),
13
+ the `start` callback will be called with an `update` function. Whenever `update` is called, the effect re-runs.
14
+
15
+ If `start` returns a cleanup function, it will be called when the effect is destroyed.
16
+
17
+ If `subscribe` is called in multiple effects, `start` will only be called once as long as the effects
18
+ are active, and the returned teardown function will only be called when all effects are destroyed.
19
+
20
+ It's best understood with an example. Here's an implementation of [`MediaQuery`](/docs/svelte/svelte-reactivity#MediaQuery):
21
+
22
+ ```js
23
+ // @errors: 7031
24
+ import { createSubscriber } from 'svelte/reactivity';
25
+ import { on } from 'svelte/events';
26
+
27
+ export class MediaQuery {
28
+ #query;
29
+ #subscribe;
30
+
31
+ constructor(query) {
32
+ this.#query = window.matchMedia(`(${query})`);
33
+
34
+ this.#subscribe = createSubscriber((update) => {
35
+ // when the `change` event occurs, re-run any effects that read `this.current`
36
+ const off = on(this.#query, 'change', update);
37
+
38
+ // stop listening when all the effects are destroyed
39
+ return () => off();
40
+ });
41
+ }
42
+
43
+ get current() {
44
+ // This makes the getter reactive, if read in an effect
45
+ this.#subscribe();
46
+
47
+ // Return the current state of the query, whether or not we're in an effect
48
+ return this.#query.matches;
49
+ }
50
+ }
51
+ ```
52
+
53
+ <div class="ts-block">
54
+
55
+ ```dts
56
+ function createSubscriber(
57
+ start: (update: () => void) => (() => void) | void
58
+ ): () => void;
59
+ ```
60
+
61
+ </div>