Compare commits

..

10 Commits

Author SHA1 Message Date
renovate[bot]
8fb661afc5 chore(deps): update dependency @sveltejs/vite-plugin-svelte to v7 2026-07-30 21:08:58 +00:00
github-actions[bot]
6468954022 Version Packages (#246) 2026-07-27 17:51:10 +02:00
Paolo Ricciuti
1eb06fd05a fix: remove special characters from references (#247) 2026-07-27 17:22:19 +02:00
Roman
e0aa00a3a5 fix: use "directory" for "repository" field (#244) 2026-07-27 16:33:48 +02:00
Paolo Ricciuti
cbac8c2bb7 fix: links in skill references (for real this time) (#245) 2026-07-27 16:33:25 +02:00
github-actions[bot]
c240d44eda Version Packages (#241) 2026-07-26 16:33:25 +02:00
Paolo Ricciuti
c2bbc2a4e7 fix: replace links in references with respective llms.txt (#243) 2026-07-26 16:24:40 +02:00
github-actions[bot]
022324b0ed chore: sync plugins from tools/ (#242)
Co-authored-by: paoloricciuti <26281609+paoloricciuti@users.noreply.github.com>
2026-07-26 14:58:03 +02:00
jyc.dev
d2571d81ad chore: fix typos and heading casing in agent + skill docs (#240) 2026-07-26 14:56:11 +02:00
Paolo Ricciuti
ed4272d55f chore: show warning for old versions (#238)
Co-authored-by: Rich Harris <hello@rich-harris.dev>
2026-07-26 14:55:12 +02:00
63 changed files with 1283 additions and 458 deletions

View File

@@ -9,13 +9,11 @@ CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used when
<!-- prettier-ignore-start -->
````markdown
# Svelte 5 Code Writer
## CLI Tools
## CLI tools
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
### List Documentation Sections
### List documentation sections
```bash
npx @sveltejs/mcp list-sections
@@ -23,7 +21,7 @@ npx @sveltejs/mcp list-sections
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
### Get Documentation
### Get documentation
```bash
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
@@ -37,7 +35,7 @@ Retrieves full documentation for specified sections. Use after `list-sections` t
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
```
### Svelte Autofixer
### Svelte autofixer
```bash
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]
@@ -118,9 +116,9 @@ If the derived expression is an object or array, it will be returned as-is — i
Effects are an escape hatch and should mostly be avoided. In particular, avoid updating state inside effects.
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/@attach.md)
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/attach.md)
- 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
- If you need to log values for debugging purposes, use [`$inspect`](references/$inspect.md)
- If you need to log values for debugging purposes, use [`$inspect`](references/inspect.md)
- If you need to observe something external to Svelte, use [`createSubscriber`](references/svelte-reactivity.md)
Never wrap the contents of an effect in `if (browser) {...}` or similar — effects do not run on the server.
@@ -169,7 +167,7 @@ Avoid using `onMount` or `$effect` for this.
## Snippets
[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.
[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.
```svelte
{#snippet greeting(name)}

View File

@@ -3,13 +3,13 @@ name: svelte-file-editor
description: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server or the `svelte-file-editor` skill if they are available. Fetches relevant documentation and validates code using the Svelte MCP server tools.
---
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.
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 validate the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.
If the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.
If the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.
## Available MCP Tools
## Available MCP tools
### 1. list-sections
@@ -35,30 +35,30 @@ Analyzes Svelte code and returns suggestions to fix issues. Pass the component c
When invoked to work on a Svelte file:
### 1. Gather Context (if needed)
### 1. Gather context (if needed)
If you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:
1. Call `list-sections` to see available documentation
2. Call `get-documentation` with relevant section names
### 2. Read the Target File
### 2. Read the target file
Read the file to understand the current implementation.
### 3. Make Changes
### 3. Make changes
Apply edits following Svelte 5 best practices:
### 4. Validate Changes
### 4. Validate changes
After editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.
### 5. Fix Any Issues
### 5. Fix any issues
If the autofixer reports problems, fix them and re-validate until no issues remain.
## Output Format
## Output format
After completing your work, provide:

View File

@@ -38,7 +38,7 @@ Restart OpenCode, then run `/svelte-plugin` or select 'Configure Svelte plugin'
## Configuration
By default, everything is enabled. The TUI plugin writes the same configuration files that you can create or edit manually:
By default, the MCP server, subagent, skills, instructions, and automatic updates are enabled. The TUI plugin writes the same configuration files that you can create or edit manually:
- locally, in `.opencode/svelte.json`
- globally, in `~/.config/opencode/svelte.json` (or, if you have specified the environment variable, in `$OPENCODE_CONFIG_DIR/svelte.json`)
@@ -68,6 +68,13 @@ By default, everything is enabled. The TUI plugin writes the same configuration
},
"instructions": {
"enabled": true
}
},
"autoupdate": true
}
```
### Automatic updates
The plugin checks npm for newer versions and warns you when one is available. OpenCode caches plugins, so it continues using the cached version until that cache is removed.
Automatic updates are enabled by default. After detecting a newer version, the plugin removes itself from the cache when OpenCode shuts down. OpenCode installs the latest version the next time it starts. Automatic updates only apply when the plugin is unpinned or explicitly uses the `latest` tag. Exact versions, ranges, and other dist-tags are left untouched because reinstalling them may resolve to the same version again. Set `"autoupdate": false` to only receive the warning.

View File

@@ -14,7 +14,7 @@
"repository": {
"type": "git",
"url": "git+https://github.com/sveltejs/ai-tools.git",
"path": "packages/mcp-stdio"
"directory": "packages/mcp-stdio"
},
"files": [
"dist"

View File

@@ -1,5 +1,23 @@
# @sveltejs/opencode
## 0.1.13
### Patch Changes
- fix: links in skill references (for real this time) ([#245](https://github.com/sveltejs/ai-tools/pull/245))
- fix: remove special characters from references ([#247](https://github.com/sveltejs/ai-tools/pull/247))
## 0.1.12
### Patch Changes
- chore: show warning for old versions ([#238](https://github.com/sveltejs/ai-tools/pull/238))
- feat: add `autoupdate` option to reinstall the plugin when a new version is available ([#238](https://github.com/sveltejs/ai-tools/pull/238))
- fix: links within references in SKILLS ([#243](https://github.com/sveltejs/ai-tools/pull/243))
## 0.1.11
### Patch Changes

View File

@@ -72,10 +72,17 @@ Create `svelte.json` to customize how the plugin configures MCP, the Svelte suba
},
"skills": {
"enabled": ["svelte-code-writer", "svelte-core-bestpractices"]
}
},
"autoupdate": true
}
```
### Auto update
The plugin checks npm for newer versions and warns you when one is available. OpenCode caches plugins, so a new version is only picked up once that cache is wiped.
Automatic updates are enabled by default. When a newer version is detected, the plugin removes itself from the OpenCode cache as OpenCode shuts down, so the latest version is installed on the next start. This only applies when the plugin is unpinned or explicitly uses the `latest` tag. Exact versions, ranges, and other dist-tags are left untouched because reinstalling them may resolve to the same version again. Set `"autoupdate": false` to only receive the warning.
### Defaults
If omitted, the plugin uses these defaults:
@@ -86,6 +93,7 @@ If omitted, the plugin uses these defaults:
- `subagent.agents`: `{}`
- `instructions.enabled`: `true`
- `skills.enabled`: `true`
- `autoupdate`: `true`
### Configuration Options
@@ -100,6 +108,7 @@ If omitted, the plugin uses these defaults:
| `subagent.agents.svelte-file-editor.maxSteps` | `number` | unlimited | Limit the number of steps the subagent can execute. |
| `instructions.enabled` | `boolean` | `true` | Enable or disable automatic instruction-file injection. |
| `skills.enabled` | `boolean \| string[]` | `true` | Enable all skills (`true`), disable all skills (`false`), or enable only specific skill names. |
| `autoupdate` | `boolean` | `true` | Remove an unpinned/latest plugin from the cache on exit when a newer version is available. |
### Supported Skill Names

View File

@@ -6,6 +6,6 @@ export const agents = {
description:
'Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server or the `svelte-file-editor` skill if they are available. Fetches relevant documentation and validates code using the Svelte MCP server tools.',
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.\n\nIf the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.\n\nIf the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.\n\n## Available MCP Tools\n\n### 1. list-sections\n\nLists all available Svelte 5 and SvelteKit documentation sections with titles and paths. Use this first to discover what documentation is available.\n\n### 2. get-documentation\n\nRetrieves full documentation for specified sections. Accepts a single section name or an array of section names. Use after `list-sections` to fetch relevant docs for the task at hand.\n\n**Example sections:** `$state`, `$derived`, `$effect`, `$props`, `$bindable`, `snippets`, `routing`, `load functions`\n\n### 3. svelte-autofixer\n\nAnalyzes Svelte code and returns suggestions to fix issues. Pass the component code directly to this tool. It will detect common mistakes like:\n\n- Using `$effect` instead of `$derived` for computations\n- Missing cleanup in effects\n- Svelte 4 syntax (`on:click`, `export let`, `<slot>`)\n- Missing keys in `{#each}` blocks\n- And more\n\n## Workflow\n\nWhen invoked to work on a Svelte file:\n\n### 1. Gather Context (if needed)\n\nIf you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:\n\n1. Call `list-sections` to see available documentation\n2. Call `get-documentation` with relevant section names\n\n### 2. Read the Target File\n\nRead the file to understand the current implementation.\n\n### 3. Make Changes\n\nApply edits following Svelte 5 best practices:\n\n### 4. Validate Changes\n\nAfter editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.\n\n### 5. Fix Any Issues\n\nIf the autofixer reports problems, fix them and re-validate until no issues remain.\n\n## Output Format\n\nAfter completing your work, provide:\n\n1. Summary of changes made\n2. Any issues found and fixed by the autofixer\n3. Recommendations for further improvements (if any)",
"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 validate the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.\n\nIf the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.\n\nIf the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.\n\n## Available MCP tools\n\n### 1. list-sections\n\nLists all available Svelte 5 and SvelteKit documentation sections with titles and paths. Use this first to discover what documentation is available.\n\n### 2. get-documentation\n\nRetrieves full documentation for specified sections. Accepts a single section name or an array of section names. Use after `list-sections` to fetch relevant docs for the task at hand.\n\n**Example sections:** `$state`, `$derived`, `$effect`, `$props`, `$bindable`, `snippets`, `routing`, `load functions`\n\n### 3. svelte-autofixer\n\nAnalyzes Svelte code and returns suggestions to fix issues. Pass the component code directly to this tool. It will detect common mistakes like:\n\n- Using `$effect` instead of `$derived` for computations\n- Missing cleanup in effects\n- Svelte 4 syntax (`on:click`, `export let`, `<slot>`)\n- Missing keys in `{#each}` blocks\n- And more\n\n## Workflow\n\nWhen invoked to work on a Svelte file:\n\n### 1. Gather context (if needed)\n\nIf you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:\n\n1. Call `list-sections` to see available documentation\n2. Call `get-documentation` with relevant section names\n\n### 2. Read the target file\n\nRead the file to understand the current implementation.\n\n### 3. Make changes\n\nApply edits following Svelte 5 best practices:\n\n### 4. Validate changes\n\nAfter editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.\n\n### 5. Fix any issues\n\nIf the autofixer reports problems, fix them and re-validate until no issues remain.\n\n## Output format\n\nAfter completing your work, provide:\n\n1. Summary of changes made\n2. Any issues found and fixed by the autofixer\n3. Recommendations for further improvements (if any)",
},
};

View File

@@ -42,6 +42,7 @@ const default_config = {
skills: {
enabled: /** @type {boolean | string[]} */ (true),
},
autoupdate: true,
};
export const config_schema = v.object({
@@ -90,6 +91,12 @@ export const config_schema = v.object({
'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.',
),
),
autoupdate: v.pipe(
v.optional(v.boolean()),
v.description(
'When a new version of an unpinned or latest-tagged plugin is available, remove it from the opencode cache on exit so that the latest version is installed the next time opencode starts. Enabled by default; set it to false to only get a warning.',
),
),
});
/** @typedef {v.InferInput<typeof config_schema>} McpConfig */
@@ -184,6 +191,7 @@ function merge_with_defaults(user_config) {
...default_config.skills,
...user_config.skills,
},
autoupdate: user_config.autoupdate ?? default_config.autoupdate,
};
}
@@ -221,6 +229,7 @@ export function get_mcp_config(ctx) {
},
instructions: { ...merged.instructions, ...parsed.output.instructions },
skills: { ...merged.skills, ...parsed.output.skills },
autoupdate: parsed.output.autoupdate ?? merged.autoupdate,
};
} else {
setTimeout(() => {

View File

@@ -3,6 +3,7 @@ import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { agents } from './agents.js';
import { get_mcp_config } from './config.js';
import { setup_updates } from './update.js';
/** @typedef {import('@opencode-ai/plugin').Plugin} Plugin */
@@ -13,7 +14,11 @@ const current_dir = dirname(fileURLToPath(import.meta.url));
* @returns {ReturnType<Plugin>}
*/
export async function svelte_plugin(ctx) {
const mcp_config = get_mcp_config(ctx);
const dispose = setup_updates(ctx, mcp_config.autoupdate === true);
return {
dispose,
async config(input) {
input.agent ??= {};
input.mcp ??= {};
@@ -36,7 +41,6 @@ export async function svelte_plugin(ctx) {
break;
}
}
const mcp_config = get_mcp_config(ctx);
if (mcp_config.instructions?.enabled !== false) {
const instructions_dir = join(current_dir, 'instructions');

View File

@@ -1,6 +1,6 @@
{
"name": "@sveltejs/opencode",
"version": "0.1.11",
"version": "0.1.13",
"type": "module",
"license": "MIT",
"homepage": "https://github.com/sveltejs/ai-tools#readme",
@@ -14,6 +14,7 @@
"files": [
"index.js",
"config.js",
"update.js",
"tui.jsx",
"agents.js",
"instructions",
@@ -32,21 +33,22 @@
"repository": {
"type": "git",
"url": "git+https://github.com/sveltejs/ai-tools.git",
"path": "packages/opencode"
"directory": "packages/opencode"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"valibot": "catalog:tooling",
"@opentui/core": "catalog:opencode",
"@opentui/keymap": "catalog:opencode",
"@opentui/solid": "catalog:opencode",
"solid-js": "catalog:opencode"
"solid-js": "catalog:opencode",
"valibot": "catalog:tooling",
"verkit": "catalog:tooling"
},
"devDependencies": {
"@opencode-ai/plugin": "catalog:opencode",
"@valibot/to-json-schema": "catalog:tooling",
"@types/node": "catalog:tooling"
"@types/node": "catalog:tooling",
"@valibot/to-json-schema": "catalog:tooling"
}
}

View File

@@ -104,6 +104,10 @@
},
"required": [],
"description": "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."
},
"autoupdate": {
"type": "boolean",
"description": "When a new version of an unpinned or latest-tagged plugin is available, remove it from the opencode cache on exit so that the latest version is installed the next time opencode starts. Enabled by default; set it to false to only get a warning."
}
},
"required": [],

View File

@@ -3,13 +3,11 @@ name: svelte-code-writer
description: CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating, editing or analyzing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.
---
# Svelte 5 Code Writer
## CLI Tools
## CLI tools
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
### List Documentation Sections
### List documentation sections
```bash
npx @sveltejs/mcp list-sections
@@ -17,7 +15,7 @@ npx @sveltejs/mcp list-sections
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
### Get Documentation
### Get documentation
```bash
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
@@ -31,7 +29,7 @@ Retrieves full documentation for specified sections. Use after `list-sections` t
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
```
### Svelte Autofixer
### Svelte autofixer
```bash
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]

View File

@@ -35,9 +35,9 @@ If the derived expression is an object or array, it will be returned as-is — i
Effects are an escape hatch and should mostly be avoided. In particular, avoid updating state inside effects.
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/@attach.md)
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/attach.md)
- 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
- If you need to log values for debugging purposes, use [`$inspect`](references/$inspect.md)
- If you need to log values for debugging purposes, use [`$inspect`](references/inspect.md)
- If you need to observe something external to Svelte, use [`createSubscriber`](references/svelte-reactivity.md)
Never wrap the contents of an effect in `if (browser) {...}` or similar — effects do not run on the server.
@@ -86,7 +86,7 @@ Avoid using `onMount` or `$effect` for this.
## Snippets
[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.
[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.
```svelte
{#snippet greeting(name)}

View File

@@ -1,4 +1,4 @@
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.
Attachments are functions that run in an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt) when an element is mounted to the DOM or when [state](https://svelte.dev/docs/svelte/$state/llms.txt) 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.
@@ -48,10 +48,12 @@ A useful pattern is for a function, such as `tooltip` in this example, to _retur
<input bind:value={content} />
<button {@attach tooltip(content)}> Hover me </button>
<button {@attach tooltip(content)}>
Hover me
</button>
```
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).)
Since the `tooltip(content)` expression runs inside an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt), 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
@@ -86,7 +88,7 @@ Falsy values like `false` or `undefined` are treated as no attachment, enabling
## 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.
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](https://svelte.dev/tutorial/svelte/spread-props/llms.txt) props onto an element, the element will receive those attachments.
This allows you to create _wrapper components_ that augment elements (demo:
@@ -125,12 +127,14 @@ This allows you to create _wrapper components_ that augment elements (demo:
<input bind:value={content} />
<Button {@attach tooltip(content)}>Hover me</Button>
<Button {@attach tooltip(content)}>
Hover me
</Button>
```
## 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`):
Attachments, unlike [actions](https://svelte.dev/docs/svelte/use/llms.txt), 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
@@ -159,8 +163,8 @@ function foo(+++getBar+++) {
## Creating attachments programmatically
To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](svelte-attachments#createAttachmentKey).
To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#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.
If you're using a library that only provides actions, you can convert them to attachments with [`fromAction`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#fromAction), allowing you to (for example) use them with components.

View File

@@ -4,16 +4,16 @@ As of Svelte 5.36, you can use the `await` keyword inside your components in thr
- inside `$derived(...)` declarations
- inside your markup
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`:
This feature is currently experimental, and you must opt in by adding the `experimental.async` option wherever you [configure](https://svelte.dev/docs/kit/configuration/llms.txt) Svelte, usually `svelte.config.js`:
```js
/// file: svelte.config.js
export default {
compilerOptions: {
experimental: {
async: true,
},
},
async: true
}
}
};
```
@@ -23,7 +23,10 @@ The experimental flag will be removed in Svelte 6.
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...
<!-- codeblock:start {"title":"Synchronized updates"} -->
```svelte
<!--- file: App.svelte --->
<script>
let a = $state(1);
let b = $state(2);
@@ -34,12 +37,14 @@ When an `await` expression depends on a particular piece of state, changes to th
}
</script>
<input type="number" bind:value={a} />
<input type="number" bind:value={b} />
<input type="number" bind:value={a}>
<input type="number" bind:value={b}>
<p>{a} + {b} = {await add(a, b)}</p>
```
<!-- codeblock:end -->
...if you increment `a`, the contents of the `<p>` will _not_ immediately update to read this —
```html
@@ -55,7 +60,8 @@ Updates can overlap — a fast update will be reflected in the UI while an earli
Svelte will do as much asynchronous work as it can in parallel. For example if you have two `await` expressions in your markup...
```svelte
<p>{await one()}</p><p>{await two()}</p>
<p>{await one(x)}</p>
<p>{await two(y)}</p>
```
...both functions will run at the same time, as they are independent expressions, even though they are _visually_ sequential.
@@ -63,21 +69,22 @@ Svelte will do as much asynchronous work as it can in parallel. For example if y
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:
```js
// these will run sequentially the first time,
// but will update independently
let a = $derived(await one());
let b = $derived(await two());
// `b` will not be created until `a` has resolved,
// but once created they will update independently
// even if `x` and `y` update simultaneously
let a = $derived(await one(x));
let b = $derived(await two(y));
```
> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](runtime-warnings#Client-warnings-await_waterfall) warning
> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](https://svelte.dev/docs/svelte/runtime-warnings/llms.txt#Client-warnings-await_waterfall) warning
## Indicating loading states
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.
To render placeholder UI, you can wrap content in a `<svelte:boundary>` with a [`pending`](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt#Properties-pending) snippet. This will be shown when the boundary is first created, but not for subsequent updates, which are globally coordinated.
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.
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()`](https://svelte.dev/docs/svelte/$effect/llms.txt#$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.
You can also use [`settled()`](svelte#settled) to get a promise that resolves when the current update is complete:
You can also use [`settled()`](https://svelte.dev/docs/svelte/svelte/llms.txt#settled) to get a promise that resolves when the current update is complete:
```js
import { tick, settled } from 'svelte';
@@ -103,7 +110,7 @@ async function onclick() {
## Error handling
Errors in `await` expressions will bubble to the nearest [error boundary](svelte-boundary).
Errors in `await` expressions will bubble to the nearest [error boundary](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt).
## Server-side rendering
@@ -125,7 +132,7 @@ If a `<svelte:boundary>` with a `pending` snippet is encountered during SSR, tha
## Forking
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.
The [`fork(...)`](https://svelte.dev/docs/svelte/svelte/llms.txt#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.
```svelte
<script>
@@ -161,13 +168,13 @@ The [`fork(...)`](svelte#fork) API, added in 5.42, makes it possible to run `awa
// in case `pending` didn't exist
// (if it did, this is a no-op)
open = true;
}}>open menu</button
>
}}
>open menu</button>
{#if open}
<!-- any async work inside this component will start
as soon as the fork is created -->
<Menu onclose={() => (open = false)} />
<Menu onclose={() => open = false} />
{/if}
```

View File

@@ -3,13 +3,19 @@
You can also use `bind:property={get, set}`, where `get` and `set` are functions, allowing you to perform validation and transformation:
```svelte
<input bind:value={() => value, (v) => (value = v.toLowerCase())} />
<input bind:value={
() => value,
(v) => value = v.toLowerCase()}
/>
```
In the case of readonly bindings like [dimension bindings](#Dimensions), the `get` value should be `null`:
```svelte
<div bind:clientWidth={null, redraw} bind:clientHeight={null, redraw}>...</div>
<div
bind:clientWidth={null, redraw}
bind:clientHeight={null, redraw}
>...</div>
```
> [!NOTE]

View File

@@ -2,31 +2,31 @@ In Svelte, when you want to render asynchronous content data on the server, you
```svelte
<script>
import { getUser } from 'my-database-library';
import { getUser } from 'my-database-library';
// This will get the user on the server, render the user's name into the h1,
// and then, during hydration on the client, it will get the user _again_,
// blocking hydration until it's done.
const user = await getUser();
// This will get the user on the server, render the user's name into the h1,
// and then, during hydration on the client, it will get the user _again_,
// blocking hydration until it's done.
const user = await getUser();
</script>
<h1>{user.name}</h1>
```
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).
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](https://svelte.dev/docs/kit/remote-functions/llms.txt).
To fix the example above:
```svelte
<script>
import { hydratable } from 'svelte';
import { getUser } from 'my-database-library';
import { hydratable } from 'svelte';
import { getUser } from 'my-database-library';
// During server rendering, this will serialize and stash the result of `getUser`, associating
// it with the provided key and baking it into the `head` content. During hydration, it will
// look for the serialized version, returning it instead of running `getUser`. After hydration
// is done, if it's called again, it'll simply invoke `getUser`.
const user = await hydratable('user', () => getUser());
// During server rendering, this will serialize and stash the result of `getUser`, associating
// it with the provided key and baking it into the `head` content. During hydration, it will
// look for the serialized version, returning it instead of running `getUser`. After hydration
// is done, if it's called again, it'll simply invoke `getUser`.
const user = await hydratable('user', () => getUser());
</script>
<h1>{user.name}</h1>
@@ -47,13 +47,13 @@ All data returned from a `hydratable` function must be serializable. But this do
```svelte
<script>
import { hydratable } from 'svelte';
const promises = hydratable('random', () => {
return {
one: Promise.resolve(1),
two: Promise.resolve(2),
};
});
import { hydratable } from 'svelte';
const promises = hydratable('random', () => {
return {
one: Promise.resolve(1),
two: Promise.resolve(2)
}
});
</script>
{await promises.one}
@@ -68,14 +68,17 @@ All data returned from a `hydratable` function must be serializable. But this do
const nonce = crypto.randomUUID();
const { head, body } = await render(App, {
csp: { nonce },
csp: { nonce }
});
```
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:
```js
response.headers.set('Content-Security-Policy', `script-src 'nonce-${nonce}'`);
response.headers.set(
'Content-Security-Policy',
`script-src 'nonce-${nonce}'`
);
```
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.
@@ -84,7 +87,7 @@ If instead you are generating static HTML ahead of time, you must use hashes ins
```js
const { head, body, hashes } = await render(App, {
csp: { hash: true },
csp: { hash: true }
});
```
@@ -92,9 +95,9 @@ const { head, body, hashes } = await render(App, {
```js
response.headers.set(
'Content-Security-Policy',
`script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`,
);
'Content-Security-Policy',
`script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`
);
```
We recommend using `nonce` over hash if you can, as `hash` will interfere with streaming SSR in the future.

View File

@@ -1,8 +1,11 @@
> [!NOTE] `$inspect` only works during development. In a production build it becomes a noop.
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:
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:
<!-- codeblock:start {"title":"$inspect(...)"} -->
```svelte
<!--- file: App.svelte --->
<script>
let count = $state(0);
let message = $state('hello');
@@ -14,13 +17,18 @@ The `$inspect` rune is roughly equivalent to `console.log`, with the exception t
<input bind:value={message} />
```
<!-- codeblock:end -->
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).
## $inspect(...).with
`$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:
`$inspect(...)` returns an object with a `with` method, which you can invoke with a callback that 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`:
<!-- codeblock:start {"title":"$inspect(...).with(...)"} -->
```svelte
<!--- file: App.svelte --->
<script>
let count = $state(0);
@@ -34,9 +42,11 @@ On updates, a stack trace will be printed, making it easy to find the origin of
<button onclick={() => count++}>Increment</button>
```
<!-- codeblock:end -->
## $inspect.trace(...)
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.
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](https://svelte.dev/docs/svelte/$effect/llms.txt) or a [derived](https://svelte.dev/docs/svelte/$derived/llms.txt), information will be printed to the console about which pieces of reactive state caused the effect to fire.
```svelte
<script>

View File

@@ -1,4 +1,4 @@
To render a [snippet](snippet), use a `{@render ...}` tag.
To render a [snippet](https://svelte.dev/docs/svelte/snippet/llms.txt), use a `{@render ...}` tag.
```svelte
{#snippet sum(a, b)}
@@ -24,7 +24,7 @@ If the snippet is potentially undefined — for example, because it's an incomin
{@render children?.()}
```
Alternatively, use an [`{#if ...}`](if) block with an `:else` clause to render fallback content:
Alternatively, use an [`{#if ...}`](https://svelte.dev/docs/svelte/if/llms.txt) block with an `:else` clause to render fallback content:
```svelte
{#if children}

View File

@@ -8,7 +8,7 @@
{#snippet name(param1, param2, paramN)}...{/snippet}
```
Snippets, and [render tags](@render), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...
Snippets, and [render tags](https://svelte.dev/docs/svelte/@render/llms.txt), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...
```svelte
{#each images as image}
@@ -53,9 +53,12 @@ Like function declarations, snippets can have an arbitrary number of parameters,
## Snippet scope
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...
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...
<!-- codeblock:start {"title":"Snippets"} -->
```svelte
<!--- file: App.svelte --->
<script>
let { message = `it's great to see you!` } = $props();
</script>
@@ -68,6 +71,8 @@ Snippets can be declared anywhere inside your component. They can reference valu
{@render hello('bob')}
```
<!-- codeblock:end -->
...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings):
```svelte
@@ -87,9 +92,12 @@ Snippets can be declared anywhere inside your component. They can reference valu
{@render x()}
```
Snippets can reference themselves and each other (demo:
Snippets can reference themselves and each other:
<!-- codeblock:start {"title":"Self-referencing snippets"} -->
```svelte
<!--- file: App.svelte --->
{#snippet blastoff()}
<span>🚀</span>
{/snippet}
@@ -106,20 +114,25 @@ Snippets can reference themselves and each other (demo:
{@render countdown(10)}
```
<!-- codeblock:end -->
## 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:
Within the template, snippets are values just like any other. As such, they can be passed to components as props:
<!-- codeblock:start {"title":"Explicit snippet props"} -->
```svelte
<!--- file: App.svelte --->
<script>
import Table from './Table.svelte';
const fruits = [
{ name: 'apples', qty: 5, price: 2 },
{ name: 'bananas', qty: 10, price: 1 },
{ name: 'cherries', qty: 20, price: 0.5 },
{ name: 'cherries', qty: 20, price: 0.5 }
];
</script>
@@ -137,17 +150,67 @@ Within the template, snippets are values just like any other. As such, they can
<td>{d.qty * d.price}</td>
{/snippet}
<Table data={fruits} {header} {row} />
<Table data={fruits} +++{header} {row}+++ />
```
```svelte
<!--- file: Table.svelte --->
<script>
let { data, header, row } = $props();
</script>
<table>
{#if header}
<thead>
<tr>{@render header()}</tr>
</thead>
{/if}
<tbody>
{#each data as d}
<tr>{@render row(d)}</tr>
{/each}
</tbody>
</table>
<style>
table {
text-align: left;
border-spacing: 0;
}
tbody tr:nth-child(2n+1) {
background: ButtonFace;
}
table :global(th), table :global(td) {
padding: 0.5em;
}
</style>
```
<!-- codeblock:end -->
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:
As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component:
<!-- codeblock:start {"title":"Implicit snippet props"} -->
```svelte
<!-- this is semantically the same as the above -->
<!--- file: App.svelte --->
<script>
import Table from './Table.svelte';
const fruits = [
{ name: 'apples', qty: 5, price: 2 },
{ name: 'bananas', qty: 10, price: 1 },
{ name: 'cherries', qty: 20, price: 0.5 }
];
</script>
<Table data={fruits}>
{#snippet header()}
<th>fruit</th>
@@ -165,12 +228,56 @@ As an authoring convenience, snippets declared directly _inside_ a component imp
</Table>
```
```svelte
<!--- file: Table.svelte --->
<script>
let { data, header, row } = $props();
</script>
<table>
{#if header}
<thead>
<tr>{@render header()}</tr>
</thead>
{/if}
<tbody>
{#each data as d}
<tr>{@render row(d)}</tr>
{/each}
</tbody>
</table>
<style>
table {
text-align: left;
border-spacing: 0;
}
tbody tr:nth-child(2n+1) {
background: ButtonFace;
}
table :global(th), table :global(td) {
padding: 0.5em;
}
</style>
```
<!-- codeblock:end -->
### Implicit `children` snippet
Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet (demo:
Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet:
<!-- codeblock:start {"title":"Implicit children snippet","selected":"Button.svelte"} -->
```svelte
<!--- file: App.svelte --->
<script>
import Button from './Button.svelte';
</script>
<Button>click me</Button>
```
@@ -184,6 +291,8 @@ Any content inside the component tags that is _not_ a snippet declaration implic
<button>{@render children()}</button>
```
<!-- codeblock:end -->
> [!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
@@ -192,7 +301,7 @@ You can declare snippet props as being optional. You can either use optional cha
```svelte
<script>
let { children } = $props();
let { children } = $props();
</script>
{@render children?.()}
@@ -202,13 +311,13 @@ You can declare snippet props as being optional. You can either use optional cha
```svelte
<script>
let { children } = $props();
let { children } = $props();
</script>
{#if children}
{@render children()}
{@render children()}
{:else}
fallback content
fallback content
{/if}
```
@@ -241,7 +350,7 @@ We can tighten things up further by declaring a generic, so that `data` and `row
let {
data,
children,
row,
row
}: {
data: T[];
children: Snippet;
@@ -252,9 +361,22 @@ We can tighten things up further by declaring a generic, so that `data` and `row
## Exporting snippets
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:
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):
<!-- codeblock:start {"title":"Exported snippets","selected":"snippets.svelte"} -->
```svelte
<!--- file: App.svelte --->
<script>
import { add } from './snippets.svelte';
</script>
{@render add(1, 2)}
```
```svelte
<!--- file: snippets.svelte --->
<script module>
export { add };
</script>
@@ -264,13 +386,15 @@ Snippets declared at the top level of a `.svelte` file can be exported from a `<
{/snippet}
```
<!-- codeblock:end -->
> [!NOTE]
> This requires Svelte 5.5.0 or newer
## Programmatic snippets
Snippets can be created programmatically with the [`createRawSnippet`](svelte#createRawSnippet) API. This is intended for advanced use cases.
Snippets can be created programmatically with the [`createRawSnippet`](https://svelte.dev/docs/svelte/svelte/llms.txt#createRawSnippet) API. This is intended for advanced use cases.
## Snippets and slots
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.
In Svelte 4, content can be passed to components using [slots](https://svelte.dev/docs/svelte/legacy-slots/llms.txt). Snippets are more powerful and flexible, and so slots have been deprecated in Svelte 5.

View File

@@ -17,7 +17,7 @@ If `start` returns a cleanup function, it will be called when the effect is dest
If `subscribe` is called in multiple effects, `start` will only be called once as long as the effects
are active, and the returned teardown function will only be called when all effects are destroyed.
It's best understood with an example. Here's an implementation of [`MediaQuery`](/docs/svelte/svelte-reactivity#MediaQuery):
It's best understood with an example. Here's an implementation of [`MediaQuery`](https://svelte.dev/docs/svelte/svelte-reactivity/llms.txt#MediaQuery):
```js
// @errors: 7031

View File

@@ -5,6 +5,6 @@
"jsxImportSource": "@opentui/solid",
"types": ["@types/node"]
},
"include": ["index.js", "config.js", "agents.js", "tui.jsx", "scripts/*"],
"include": ["index.js", "config.js", "update.js", "agents.js", "tui.jsx", "scripts/*"],
"exclude": ["node_modules"]
}

View File

@@ -250,6 +250,12 @@ const tui = async (api) => {
value: `skill:${name}`,
category: 'Skills',
})),
{
title: `${config.autoupdate !== false ? '[x]' : '[ ]'} Auto update`,
value: 'autoupdate',
category: 'Updates',
description: 'Reinstall the plugin on the next start when a new version is out',
},
{
title: 'Revert changes',
value: 'revert',
@@ -284,6 +290,7 @@ const tui = async (api) => {
if (option.value === 'skills-all') {
config.skills = { enabled: all_skills_selected ? [] : [...skill_names] };
}
if (option.value === 'autoupdate') config.autoupdate = config.autoupdate === false;
if (option.value.startsWith('skill:')) {
const name = option.value.slice('skill:'.length);
if (selected_skills.has(name)) {

View File

@@ -0,0 +1,97 @@
import { exec } from 'node:child_process';
import { rmSync } from 'node:fs';
import { basename, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { compare } from 'verkit';
import package_json from './package.json' with { type: 'json' };
/** @typedef {import('@opencode-ai/plugin').PluginInput} PluginInput */
const current_dir = dirname(fileURLToPath(import.meta.url));
const name_segments = package_json.name.split('/');
/**
* @param {string} dir
* @param {number} levels
*/
function up(dir, levels) {
for (let i = 0; i < levels; i++) dir = dirname(dir);
return dir;
}
/**
* opencode installs every plugin in `<cache>/packages/<spec>/node_modules/<name>`, so removing
* `<spec>` is enough to make it reinstall the plugin from scratch on the next start.
*
* We return `null` whenever we don't recognize that layout (for example when the plugin is linked
* locally during development) so that we never delete a folder we don't own.
*/
export function get_install_dir(dir = current_dir) {
// from `<cache>/packages/<spec>/node_modules/<name>` up to `<cache>/packages/<spec>`
const install_dir = up(dir, name_segments.length + 1);
// ...and from there up to `<cache>/packages`
if (basename(up(install_dir, name_segments.length)) !== 'packages') return null;
// Only unconstrained installs can pick up npm's latest version. Ranges and alternate tags may
// resolve to the same installed version after every wipe.
const package_name = name_segments.at(-1);
if (!package_name || ![package_name, `${package_name}@latest`].includes(basename(install_dir))) {
return null;
}
return install_dir;
}
/**
* Checks npm for a newer version of the plugin and warns the user about it. If `autoupdate` is
* enabled we also delete the cached plugin once opencode shuts down, so the next start picks up the
* new version.
*
* @param {PluginInput} ctx
* @param {boolean} autoupdate
* @returns {() => Promise<void>} the `dispose` hook
*/
export function setup_updates(ctx, autoupdate) {
/** @type {string | null} */
let stale_dir = null;
let wiped = false;
function wipe() {
if (wiped || !stale_dir) return;
wiped = true;
try {
rmSync(stale_dir, { recursive: true, force: true });
} catch {
// if we can't delete it there's nothing useful we can do at this point, the user will
// just get the warning again on the next start
}
}
exec(`npm view ${package_json.name} version`, (_, version) => {
const latest = version?.trim();
if (!latest || compare(latest, package_json.version) !== 1) return;
stale_dir = autoupdate ? get_install_dir() : null;
// `dispose` covers a graceful shutdown, `exit` is the safety net for everything else. We only
// register it once we know we have something to delete to avoid piling up listeners.
if (stale_dir) process.once('exit', wipe);
setTimeout(() => {
ctx.client.tui.showToast({
body: {
title: 'Svelte: new plugin version available',
message: `${package_json.name}@${latest} is available (you are using ${package_json.version}).\n\n${
stale_dir
? 'It will be installed automatically the next time you start OpenCode.'
: 'Wipe the cache or update your OpenCode config to update.'
}`,
variant: 'warning',
duration: 7000,
},
});
}, 7000);
});
return async () => {
process.off('exit', wipe);
wipe();
};
}

View File

@@ -0,0 +1,35 @@
import { join } from 'node:path';
import { describe, expect, test } from 'vitest';
import { get_install_dir } from './update.js';
const cache_packages = join('/cache', 'packages');
/**
* @param {string} spec
*/
function plugin_dir(spec) {
return join(cache_packages, '@sveltejs', spec, 'node_modules', '@sveltejs', 'opencode');
}
describe('get_install_dir', () => {
test.each([
['an unpinned install', 'opencode'],
['the latest tag', 'opencode@latest'],
])('returns the cache directory for %s', (_, spec) => {
expect(get_install_dir(plugin_dir(spec))).toBe(join(cache_packages, '@sveltejs', spec));
});
test.each([
['an exact version', 'opencode@0.1.11'],
['an exact version with a v prefix', 'opencode@v0.1.11'],
['a range', 'opencode@^0.1.0'],
['an alternate dist-tag', 'opencode@beta'],
])('ignores %s', (_, spec) => {
expect(get_install_dir(plugin_dir(spec))).toBeNull();
});
test('ignores a matching layout outside the OpenCode package cache', () => {
const dir = join('/workspace', 'node_modules', '@sveltejs', 'opencode');
expect(get_install_dir(dir)).toBeNull();
});
});

View File

@@ -1,7 +1,7 @@
{
"name": "svelte",
"description": "A plugin for all things related to Svelte development, MCP, skills, and more.",
"version": "1.0.4",
"version": "1.0.8",
"author": {
"name": "Svelte"
},

View File

@@ -4,13 +4,13 @@ description: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when cre
permissionMode: acceptEdits
---
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.
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 validate the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.
If the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.
If the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.
## Available MCP Tools
## Available MCP tools
### 1. list-sections
@@ -36,30 +36,30 @@ Analyzes Svelte code and returns suggestions to fix issues. Pass the component c
When invoked to work on a Svelte file:
### 1. Gather Context (if needed)
### 1. Gather context (if needed)
If you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:
1. Call `list-sections` to see available documentation
2. Call `get-documentation` with relevant section names
### 2. Read the Target File
### 2. Read the target file
Read the file to understand the current implementation.
### 3. Make Changes
### 3. Make changes
Apply edits following Svelte 5 best practices:
### 4. Validate Changes
### 4. Validate changes
After editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.
### 5. Fix Any Issues
### 5. Fix any issues
If the autofixer reports problems, fix them and re-validate until no issues remain.
## Output Format
## Output format
After completing your work, provide:

View File

@@ -3,13 +3,11 @@ name: svelte-code-writer
description: CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating, editing or analyzing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.
---
# Svelte 5 Code Writer
## CLI Tools
## CLI tools
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
### List Documentation Sections
### List documentation sections
```bash
npx @sveltejs/mcp list-sections
@@ -17,7 +15,7 @@ npx @sveltejs/mcp list-sections
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
### Get Documentation
### Get documentation
```bash
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
@@ -31,7 +29,7 @@ Retrieves full documentation for specified sections. Use after `list-sections` t
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
```
### Svelte Autofixer
### Svelte autofixer
```bash
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]

View File

@@ -35,9 +35,9 @@ If the derived expression is an object or array, it will be returned as-is — i
Effects are an escape hatch and should mostly be avoided. In particular, avoid updating state inside effects.
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/@attach.md)
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/attach.md)
- 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
- If you need to log values for debugging purposes, use [`$inspect`](references/$inspect.md)
- If you need to log values for debugging purposes, use [`$inspect`](references/inspect.md)
- If you need to observe something external to Svelte, use [`createSubscriber`](references/svelte-reactivity.md)
Never wrap the contents of an effect in `if (browser) {...}` or similar — effects do not run on the server.
@@ -86,7 +86,7 @@ Avoid using `onMount` or `$effect` for this.
## Snippets
[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.
[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.
```svelte
{#snippet greeting(name)}

View File

@@ -1,4 +1,4 @@
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.
Attachments are functions that run in an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt) when an element is mounted to the DOM or when [state](https://svelte.dev/docs/svelte/$state/llms.txt) 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.
@@ -48,10 +48,12 @@ A useful pattern is for a function, such as `tooltip` in this example, to _retur
<input bind:value={content} />
<button {@attach tooltip(content)}> Hover me </button>
<button {@attach tooltip(content)}>
Hover me
</button>
```
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).)
Since the `tooltip(content)` expression runs inside an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt), 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
@@ -86,7 +88,7 @@ Falsy values like `false` or `undefined` are treated as no attachment, enabling
## 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.
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](https://svelte.dev/tutorial/svelte/spread-props/llms.txt) props onto an element, the element will receive those attachments.
This allows you to create _wrapper components_ that augment elements (demo:
@@ -125,12 +127,14 @@ This allows you to create _wrapper components_ that augment elements (demo:
<input bind:value={content} />
<Button {@attach tooltip(content)}>Hover me</Button>
<Button {@attach tooltip(content)}>
Hover me
</Button>
```
## 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`):
Attachments, unlike [actions](https://svelte.dev/docs/svelte/use/llms.txt), 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
@@ -159,8 +163,8 @@ function foo(+++getBar+++) {
## Creating attachments programmatically
To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](svelte-attachments#createAttachmentKey).
To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#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.
If you're using a library that only provides actions, you can convert them to attachments with [`fromAction`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#fromAction), allowing you to (for example) use them with components.

View File

@@ -4,16 +4,16 @@ As of Svelte 5.36, you can use the `await` keyword inside your components in thr
- inside `$derived(...)` declarations
- inside your markup
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`:
This feature is currently experimental, and you must opt in by adding the `experimental.async` option wherever you [configure](https://svelte.dev/docs/kit/configuration/llms.txt) Svelte, usually `svelte.config.js`:
```js
/// file: svelte.config.js
export default {
compilerOptions: {
experimental: {
async: true,
},
},
async: true
}
}
};
```
@@ -23,7 +23,10 @@ The experimental flag will be removed in Svelte 6.
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...
<!-- codeblock:start {"title":"Synchronized updates"} -->
```svelte
<!--- file: App.svelte --->
<script>
let a = $state(1);
let b = $state(2);
@@ -34,12 +37,14 @@ When an `await` expression depends on a particular piece of state, changes to th
}
</script>
<input type="number" bind:value={a} />
<input type="number" bind:value={b} />
<input type="number" bind:value={a}>
<input type="number" bind:value={b}>
<p>{a} + {b} = {await add(a, b)}</p>
```
<!-- codeblock:end -->
...if you increment `a`, the contents of the `<p>` will _not_ immediately update to read this —
```html
@@ -55,7 +60,8 @@ Updates can overlap — a fast update will be reflected in the UI while an earli
Svelte will do as much asynchronous work as it can in parallel. For example if you have two `await` expressions in your markup...
```svelte
<p>{await one()}</p><p>{await two()}</p>
<p>{await one(x)}</p>
<p>{await two(y)}</p>
```
...both functions will run at the same time, as they are independent expressions, even though they are _visually_ sequential.
@@ -63,21 +69,22 @@ Svelte will do as much asynchronous work as it can in parallel. For example if y
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:
```js
// these will run sequentially the first time,
// but will update independently
let a = $derived(await one());
let b = $derived(await two());
// `b` will not be created until `a` has resolved,
// but once created they will update independently
// even if `x` and `y` update simultaneously
let a = $derived(await one(x));
let b = $derived(await two(y));
```
> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](runtime-warnings#Client-warnings-await_waterfall) warning
> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](https://svelte.dev/docs/svelte/runtime-warnings/llms.txt#Client-warnings-await_waterfall) warning
## Indicating loading states
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.
To render placeholder UI, you can wrap content in a `<svelte:boundary>` with a [`pending`](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt#Properties-pending) snippet. This will be shown when the boundary is first created, but not for subsequent updates, which are globally coordinated.
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.
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()`](https://svelte.dev/docs/svelte/$effect/llms.txt#$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.
You can also use [`settled()`](svelte#settled) to get a promise that resolves when the current update is complete:
You can also use [`settled()`](https://svelte.dev/docs/svelte/svelte/llms.txt#settled) to get a promise that resolves when the current update is complete:
```js
import { tick, settled } from 'svelte';
@@ -103,7 +110,7 @@ async function onclick() {
## Error handling
Errors in `await` expressions will bubble to the nearest [error boundary](svelte-boundary).
Errors in `await` expressions will bubble to the nearest [error boundary](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt).
## Server-side rendering
@@ -125,7 +132,7 @@ If a `<svelte:boundary>` with a `pending` snippet is encountered during SSR, tha
## Forking
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.
The [`fork(...)`](https://svelte.dev/docs/svelte/svelte/llms.txt#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.
```svelte
<script>
@@ -161,13 +168,13 @@ The [`fork(...)`](svelte#fork) API, added in 5.42, makes it possible to run `awa
// in case `pending` didn't exist
// (if it did, this is a no-op)
open = true;
}}>open menu</button
>
}}
>open menu</button>
{#if open}
<!-- any async work inside this component will start
as soon as the fork is created -->
<Menu onclose={() => (open = false)} />
<Menu onclose={() => open = false} />
{/if}
```

View File

@@ -3,13 +3,19 @@
You can also use `bind:property={get, set}`, where `get` and `set` are functions, allowing you to perform validation and transformation:
```svelte
<input bind:value={() => value, (v) => (value = v.toLowerCase())} />
<input bind:value={
() => value,
(v) => value = v.toLowerCase()}
/>
```
In the case of readonly bindings like [dimension bindings](#Dimensions), the `get` value should be `null`:
```svelte
<div bind:clientWidth={null, redraw} bind:clientHeight={null, redraw}>...</div>
<div
bind:clientWidth={null, redraw}
bind:clientHeight={null, redraw}
>...</div>
```
> [!NOTE]

View File

@@ -2,31 +2,31 @@ In Svelte, when you want to render asynchronous content data on the server, you
```svelte
<script>
import { getUser } from 'my-database-library';
import { getUser } from 'my-database-library';
// This will get the user on the server, render the user's name into the h1,
// and then, during hydration on the client, it will get the user _again_,
// blocking hydration until it's done.
const user = await getUser();
// This will get the user on the server, render the user's name into the h1,
// and then, during hydration on the client, it will get the user _again_,
// blocking hydration until it's done.
const user = await getUser();
</script>
<h1>{user.name}</h1>
```
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).
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](https://svelte.dev/docs/kit/remote-functions/llms.txt).
To fix the example above:
```svelte
<script>
import { hydratable } from 'svelte';
import { getUser } from 'my-database-library';
import { hydratable } from 'svelte';
import { getUser } from 'my-database-library';
// During server rendering, this will serialize and stash the result of `getUser`, associating
// it with the provided key and baking it into the `head` content. During hydration, it will
// look for the serialized version, returning it instead of running `getUser`. After hydration
// is done, if it's called again, it'll simply invoke `getUser`.
const user = await hydratable('user', () => getUser());
// During server rendering, this will serialize and stash the result of `getUser`, associating
// it with the provided key and baking it into the `head` content. During hydration, it will
// look for the serialized version, returning it instead of running `getUser`. After hydration
// is done, if it's called again, it'll simply invoke `getUser`.
const user = await hydratable('user', () => getUser());
</script>
<h1>{user.name}</h1>
@@ -47,13 +47,13 @@ All data returned from a `hydratable` function must be serializable. But this do
```svelte
<script>
import { hydratable } from 'svelte';
const promises = hydratable('random', () => {
return {
one: Promise.resolve(1),
two: Promise.resolve(2),
};
});
import { hydratable } from 'svelte';
const promises = hydratable('random', () => {
return {
one: Promise.resolve(1),
two: Promise.resolve(2)
}
});
</script>
{await promises.one}
@@ -68,14 +68,17 @@ All data returned from a `hydratable` function must be serializable. But this do
const nonce = crypto.randomUUID();
const { head, body } = await render(App, {
csp: { nonce },
csp: { nonce }
});
```
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:
```js
response.headers.set('Content-Security-Policy', `script-src 'nonce-${nonce}'`);
response.headers.set(
'Content-Security-Policy',
`script-src 'nonce-${nonce}'`
);
```
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.
@@ -84,7 +87,7 @@ If instead you are generating static HTML ahead of time, you must use hashes ins
```js
const { head, body, hashes } = await render(App, {
csp: { hash: true },
csp: { hash: true }
});
```
@@ -92,9 +95,9 @@ const { head, body, hashes } = await render(App, {
```js
response.headers.set(
'Content-Security-Policy',
`script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`,
);
'Content-Security-Policy',
`script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`
);
```
We recommend using `nonce` over hash if you can, as `hash` will interfere with streaming SSR in the future.

View File

@@ -1,8 +1,11 @@
> [!NOTE] `$inspect` only works during development. In a production build it becomes a noop.
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:
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:
<!-- codeblock:start {"title":"$inspect(...)"} -->
```svelte
<!--- file: App.svelte --->
<script>
let count = $state(0);
let message = $state('hello');
@@ -14,13 +17,18 @@ The `$inspect` rune is roughly equivalent to `console.log`, with the exception t
<input bind:value={message} />
```
<!-- codeblock:end -->
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).
## $inspect(...).with
`$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:
`$inspect(...)` returns an object with a `with` method, which you can invoke with a callback that 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`:
<!-- codeblock:start {"title":"$inspect(...).with(...)"} -->
```svelte
<!--- file: App.svelte --->
<script>
let count = $state(0);
@@ -34,9 +42,11 @@ On updates, a stack trace will be printed, making it easy to find the origin of
<button onclick={() => count++}>Increment</button>
```
<!-- codeblock:end -->
## $inspect.trace(...)
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.
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](https://svelte.dev/docs/svelte/$effect/llms.txt) or a [derived](https://svelte.dev/docs/svelte/$derived/llms.txt), information will be printed to the console about which pieces of reactive state caused the effect to fire.
```svelte
<script>

View File

@@ -1,4 +1,4 @@
To render a [snippet](snippet), use a `{@render ...}` tag.
To render a [snippet](https://svelte.dev/docs/svelte/snippet/llms.txt), use a `{@render ...}` tag.
```svelte
{#snippet sum(a, b)}
@@ -24,7 +24,7 @@ If the snippet is potentially undefined — for example, because it's an incomin
{@render children?.()}
```
Alternatively, use an [`{#if ...}`](if) block with an `:else` clause to render fallback content:
Alternatively, use an [`{#if ...}`](https://svelte.dev/docs/svelte/if/llms.txt) block with an `:else` clause to render fallback content:
```svelte
{#if children}

View File

@@ -8,7 +8,7 @@
{#snippet name(param1, param2, paramN)}...{/snippet}
```
Snippets, and [render tags](@render), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...
Snippets, and [render tags](https://svelte.dev/docs/svelte/@render/llms.txt), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...
```svelte
{#each images as image}
@@ -53,9 +53,12 @@ Like function declarations, snippets can have an arbitrary number of parameters,
## Snippet scope
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...
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...
<!-- codeblock:start {"title":"Snippets"} -->
```svelte
<!--- file: App.svelte --->
<script>
let { message = `it's great to see you!` } = $props();
</script>
@@ -68,6 +71,8 @@ Snippets can be declared anywhere inside your component. They can reference valu
{@render hello('bob')}
```
<!-- codeblock:end -->
...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings):
```svelte
@@ -87,9 +92,12 @@ Snippets can be declared anywhere inside your component. They can reference valu
{@render x()}
```
Snippets can reference themselves and each other (demo:
Snippets can reference themselves and each other:
<!-- codeblock:start {"title":"Self-referencing snippets"} -->
```svelte
<!--- file: App.svelte --->
{#snippet blastoff()}
<span>🚀</span>
{/snippet}
@@ -106,20 +114,25 @@ Snippets can reference themselves and each other (demo:
{@render countdown(10)}
```
<!-- codeblock:end -->
## 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:
Within the template, snippets are values just like any other. As such, they can be passed to components as props:
<!-- codeblock:start {"title":"Explicit snippet props"} -->
```svelte
<!--- file: App.svelte --->
<script>
import Table from './Table.svelte';
const fruits = [
{ name: 'apples', qty: 5, price: 2 },
{ name: 'bananas', qty: 10, price: 1 },
{ name: 'cherries', qty: 20, price: 0.5 },
{ name: 'cherries', qty: 20, price: 0.5 }
];
</script>
@@ -137,17 +150,67 @@ Within the template, snippets are values just like any other. As such, they can
<td>{d.qty * d.price}</td>
{/snippet}
<Table data={fruits} {header} {row} />
<Table data={fruits} +++{header} {row}+++ />
```
```svelte
<!--- file: Table.svelte --->
<script>
let { data, header, row } = $props();
</script>
<table>
{#if header}
<thead>
<tr>{@render header()}</tr>
</thead>
{/if}
<tbody>
{#each data as d}
<tr>{@render row(d)}</tr>
{/each}
</tbody>
</table>
<style>
table {
text-align: left;
border-spacing: 0;
}
tbody tr:nth-child(2n+1) {
background: ButtonFace;
}
table :global(th), table :global(td) {
padding: 0.5em;
}
</style>
```
<!-- codeblock:end -->
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:
As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component:
<!-- codeblock:start {"title":"Implicit snippet props"} -->
```svelte
<!-- this is semantically the same as the above -->
<!--- file: App.svelte --->
<script>
import Table from './Table.svelte';
const fruits = [
{ name: 'apples', qty: 5, price: 2 },
{ name: 'bananas', qty: 10, price: 1 },
{ name: 'cherries', qty: 20, price: 0.5 }
];
</script>
<Table data={fruits}>
{#snippet header()}
<th>fruit</th>
@@ -165,12 +228,56 @@ As an authoring convenience, snippets declared directly _inside_ a component imp
</Table>
```
```svelte
<!--- file: Table.svelte --->
<script>
let { data, header, row } = $props();
</script>
<table>
{#if header}
<thead>
<tr>{@render header()}</tr>
</thead>
{/if}
<tbody>
{#each data as d}
<tr>{@render row(d)}</tr>
{/each}
</tbody>
</table>
<style>
table {
text-align: left;
border-spacing: 0;
}
tbody tr:nth-child(2n+1) {
background: ButtonFace;
}
table :global(th), table :global(td) {
padding: 0.5em;
}
</style>
```
<!-- codeblock:end -->
### Implicit `children` snippet
Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet (demo:
Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet:
<!-- codeblock:start {"title":"Implicit children snippet","selected":"Button.svelte"} -->
```svelte
<!--- file: App.svelte --->
<script>
import Button from './Button.svelte';
</script>
<Button>click me</Button>
```
@@ -184,6 +291,8 @@ Any content inside the component tags that is _not_ a snippet declaration implic
<button>{@render children()}</button>
```
<!-- codeblock:end -->
> [!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
@@ -192,7 +301,7 @@ You can declare snippet props as being optional. You can either use optional cha
```svelte
<script>
let { children } = $props();
let { children } = $props();
</script>
{@render children?.()}
@@ -202,13 +311,13 @@ You can declare snippet props as being optional. You can either use optional cha
```svelte
<script>
let { children } = $props();
let { children } = $props();
</script>
{#if children}
{@render children()}
{@render children()}
{:else}
fallback content
fallback content
{/if}
```
@@ -241,7 +350,7 @@ We can tighten things up further by declaring a generic, so that `data` and `row
let {
data,
children,
row,
row
}: {
data: T[];
children: Snippet;
@@ -252,9 +361,22 @@ We can tighten things up further by declaring a generic, so that `data` and `row
## Exporting snippets
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:
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):
<!-- codeblock:start {"title":"Exported snippets","selected":"snippets.svelte"} -->
```svelte
<!--- file: App.svelte --->
<script>
import { add } from './snippets.svelte';
</script>
{@render add(1, 2)}
```
```svelte
<!--- file: snippets.svelte --->
<script module>
export { add };
</script>
@@ -264,13 +386,15 @@ Snippets declared at the top level of a `.svelte` file can be exported from a `<
{/snippet}
```
<!-- codeblock:end -->
> [!NOTE]
> This requires Svelte 5.5.0 or newer
## Programmatic snippets
Snippets can be created programmatically with the [`createRawSnippet`](svelte#createRawSnippet) API. This is intended for advanced use cases.
Snippets can be created programmatically with the [`createRawSnippet`](https://svelte.dev/docs/svelte/svelte/llms.txt#createRawSnippet) API. This is intended for advanced use cases.
## Snippets and slots
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.
In Svelte 4, content can be passed to components using [slots](https://svelte.dev/docs/svelte/legacy-slots/llms.txt). Snippets are more powerful and flexible, and so slots have been deprecated in Svelte 5.

View File

@@ -17,7 +17,7 @@ If `start` returns a cleanup function, it will be called when the effect is dest
If `subscribe` is called in multiple effects, `start` will only be called once as long as the effects
are active, and the returned teardown function will only be called when all effects are destroyed.
It's best understood with an example. Here's an implementation of [`MediaQuery`](/docs/svelte/svelte-reactivity#MediaQuery):
It's best understood with an example. Here's an implementation of [`MediaQuery`](https://svelte.dev/docs/svelte/svelte-reactivity/llms.txt#MediaQuery):
```js
// @errors: 7031

View File

@@ -1,7 +1,7 @@
{
"name": "svelte",
"description": "A plugin for all things related to Svelte development, MCP, skills, and more.",
"version": "1.0.4",
"version": "1.0.8",
"author": {
"name": "Svelte"
},

View File

@@ -3,13 +3,13 @@ name: svelte-file-editor
description: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server or the `svelte-file-editor` skill if they are available. Fetches relevant documentation and validates code using the Svelte MCP server tools.
---
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.
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 validate the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.
If the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.
If the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.
## Available MCP Tools
## Available MCP tools
### 1. list-sections
@@ -35,30 +35,30 @@ Analyzes Svelte code and returns suggestions to fix issues. Pass the component c
When invoked to work on a Svelte file:
### 1. Gather Context (if needed)
### 1. Gather context (if needed)
If you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:
1. Call `list-sections` to see available documentation
2. Call `get-documentation` with relevant section names
### 2. Read the Target File
### 2. Read the target file
Read the file to understand the current implementation.
### 3. Make Changes
### 3. Make changes
Apply edits following Svelte 5 best practices:
### 4. Validate Changes
### 4. Validate changes
After editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.
### 5. Fix Any Issues
### 5. Fix any issues
If the autofixer reports problems, fix them and re-validate until no issues remain.
## Output Format
## Output format
After completing your work, provide:

View File

@@ -3,13 +3,11 @@ name: svelte-code-writer
description: CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating, editing or analyzing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.
---
# Svelte 5 Code Writer
## CLI Tools
## CLI tools
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
### List Documentation Sections
### List documentation sections
```bash
npx @sveltejs/mcp list-sections
@@ -17,7 +15,7 @@ npx @sveltejs/mcp list-sections
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
### Get Documentation
### Get documentation
```bash
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
@@ -31,7 +29,7 @@ Retrieves full documentation for specified sections. Use after `list-sections` t
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
```
### Svelte Autofixer
### Svelte autofixer
```bash
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]

View File

@@ -35,9 +35,9 @@ If the derived expression is an object or array, it will be returned as-is — i
Effects are an escape hatch and should mostly be avoided. In particular, avoid updating state inside effects.
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/@attach.md)
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/attach.md)
- 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
- If you need to log values for debugging purposes, use [`$inspect`](references/$inspect.md)
- If you need to log values for debugging purposes, use [`$inspect`](references/inspect.md)
- If you need to observe something external to Svelte, use [`createSubscriber`](references/svelte-reactivity.md)
Never wrap the contents of an effect in `if (browser) {...}` or similar — effects do not run on the server.
@@ -86,7 +86,7 @@ Avoid using `onMount` or `$effect` for this.
## Snippets
[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.
[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.
```svelte
{#snippet greeting(name)}

View File

@@ -1,4 +1,4 @@
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.
Attachments are functions that run in an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt) when an element is mounted to the DOM or when [state](https://svelte.dev/docs/svelte/$state/llms.txt) 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.
@@ -48,10 +48,12 @@ A useful pattern is for a function, such as `tooltip` in this example, to _retur
<input bind:value={content} />
<button {@attach tooltip(content)}> Hover me </button>
<button {@attach tooltip(content)}>
Hover me
</button>
```
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).)
Since the `tooltip(content)` expression runs inside an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt), 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
@@ -86,7 +88,7 @@ Falsy values like `false` or `undefined` are treated as no attachment, enabling
## 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.
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](https://svelte.dev/tutorial/svelte/spread-props/llms.txt) props onto an element, the element will receive those attachments.
This allows you to create _wrapper components_ that augment elements (demo:
@@ -125,12 +127,14 @@ This allows you to create _wrapper components_ that augment elements (demo:
<input bind:value={content} />
<Button {@attach tooltip(content)}>Hover me</Button>
<Button {@attach tooltip(content)}>
Hover me
</Button>
```
## 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`):
Attachments, unlike [actions](https://svelte.dev/docs/svelte/use/llms.txt), 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
@@ -159,8 +163,8 @@ function foo(+++getBar+++) {
## Creating attachments programmatically
To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](svelte-attachments#createAttachmentKey).
To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#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.
If you're using a library that only provides actions, you can convert them to attachments with [`fromAction`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#fromAction), allowing you to (for example) use them with components.

View File

@@ -4,16 +4,16 @@ As of Svelte 5.36, you can use the `await` keyword inside your components in thr
- inside `$derived(...)` declarations
- inside your markup
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`:
This feature is currently experimental, and you must opt in by adding the `experimental.async` option wherever you [configure](https://svelte.dev/docs/kit/configuration/llms.txt) Svelte, usually `svelte.config.js`:
```js
/// file: svelte.config.js
export default {
compilerOptions: {
experimental: {
async: true,
},
},
async: true
}
}
};
```
@@ -23,7 +23,10 @@ The experimental flag will be removed in Svelte 6.
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...
<!-- codeblock:start {"title":"Synchronized updates"} -->
```svelte
<!--- file: App.svelte --->
<script>
let a = $state(1);
let b = $state(2);
@@ -34,12 +37,14 @@ When an `await` expression depends on a particular piece of state, changes to th
}
</script>
<input type="number" bind:value={a} />
<input type="number" bind:value={b} />
<input type="number" bind:value={a}>
<input type="number" bind:value={b}>
<p>{a} + {b} = {await add(a, b)}</p>
```
<!-- codeblock:end -->
...if you increment `a`, the contents of the `<p>` will _not_ immediately update to read this —
```html
@@ -55,7 +60,8 @@ Updates can overlap — a fast update will be reflected in the UI while an earli
Svelte will do as much asynchronous work as it can in parallel. For example if you have two `await` expressions in your markup...
```svelte
<p>{await one()}</p><p>{await two()}</p>
<p>{await one(x)}</p>
<p>{await two(y)}</p>
```
...both functions will run at the same time, as they are independent expressions, even though they are _visually_ sequential.
@@ -63,21 +69,22 @@ Svelte will do as much asynchronous work as it can in parallel. For example if y
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:
```js
// these will run sequentially the first time,
// but will update independently
let a = $derived(await one());
let b = $derived(await two());
// `b` will not be created until `a` has resolved,
// but once created they will update independently
// even if `x` and `y` update simultaneously
let a = $derived(await one(x));
let b = $derived(await two(y));
```
> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](runtime-warnings#Client-warnings-await_waterfall) warning
> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](https://svelte.dev/docs/svelte/runtime-warnings/llms.txt#Client-warnings-await_waterfall) warning
## Indicating loading states
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.
To render placeholder UI, you can wrap content in a `<svelte:boundary>` with a [`pending`](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt#Properties-pending) snippet. This will be shown when the boundary is first created, but not for subsequent updates, which are globally coordinated.
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.
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()`](https://svelte.dev/docs/svelte/$effect/llms.txt#$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.
You can also use [`settled()`](svelte#settled) to get a promise that resolves when the current update is complete:
You can also use [`settled()`](https://svelte.dev/docs/svelte/svelte/llms.txt#settled) to get a promise that resolves when the current update is complete:
```js
import { tick, settled } from 'svelte';
@@ -103,7 +110,7 @@ async function onclick() {
## Error handling
Errors in `await` expressions will bubble to the nearest [error boundary](svelte-boundary).
Errors in `await` expressions will bubble to the nearest [error boundary](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt).
## Server-side rendering
@@ -125,7 +132,7 @@ If a `<svelte:boundary>` with a `pending` snippet is encountered during SSR, tha
## Forking
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.
The [`fork(...)`](https://svelte.dev/docs/svelte/svelte/llms.txt#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.
```svelte
<script>
@@ -161,13 +168,13 @@ The [`fork(...)`](svelte#fork) API, added in 5.42, makes it possible to run `awa
// in case `pending` didn't exist
// (if it did, this is a no-op)
open = true;
}}>open menu</button
>
}}
>open menu</button>
{#if open}
<!-- any async work inside this component will start
as soon as the fork is created -->
<Menu onclose={() => (open = false)} />
<Menu onclose={() => open = false} />
{/if}
```

View File

@@ -3,13 +3,19 @@
You can also use `bind:property={get, set}`, where `get` and `set` are functions, allowing you to perform validation and transformation:
```svelte
<input bind:value={() => value, (v) => (value = v.toLowerCase())} />
<input bind:value={
() => value,
(v) => value = v.toLowerCase()}
/>
```
In the case of readonly bindings like [dimension bindings](#Dimensions), the `get` value should be `null`:
```svelte
<div bind:clientWidth={null, redraw} bind:clientHeight={null, redraw}>...</div>
<div
bind:clientWidth={null, redraw}
bind:clientHeight={null, redraw}
>...</div>
```
> [!NOTE]

View File

@@ -2,31 +2,31 @@ In Svelte, when you want to render asynchronous content data on the server, you
```svelte
<script>
import { getUser } from 'my-database-library';
import { getUser } from 'my-database-library';
// This will get the user on the server, render the user's name into the h1,
// and then, during hydration on the client, it will get the user _again_,
// blocking hydration until it's done.
const user = await getUser();
// This will get the user on the server, render the user's name into the h1,
// and then, during hydration on the client, it will get the user _again_,
// blocking hydration until it's done.
const user = await getUser();
</script>
<h1>{user.name}</h1>
```
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).
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](https://svelte.dev/docs/kit/remote-functions/llms.txt).
To fix the example above:
```svelte
<script>
import { hydratable } from 'svelte';
import { getUser } from 'my-database-library';
import { hydratable } from 'svelte';
import { getUser } from 'my-database-library';
// During server rendering, this will serialize and stash the result of `getUser`, associating
// it with the provided key and baking it into the `head` content. During hydration, it will
// look for the serialized version, returning it instead of running `getUser`. After hydration
// is done, if it's called again, it'll simply invoke `getUser`.
const user = await hydratable('user', () => getUser());
// During server rendering, this will serialize and stash the result of `getUser`, associating
// it with the provided key and baking it into the `head` content. During hydration, it will
// look for the serialized version, returning it instead of running `getUser`. After hydration
// is done, if it's called again, it'll simply invoke `getUser`.
const user = await hydratable('user', () => getUser());
</script>
<h1>{user.name}</h1>
@@ -47,13 +47,13 @@ All data returned from a `hydratable` function must be serializable. But this do
```svelte
<script>
import { hydratable } from 'svelte';
const promises = hydratable('random', () => {
return {
one: Promise.resolve(1),
two: Promise.resolve(2),
};
});
import { hydratable } from 'svelte';
const promises = hydratable('random', () => {
return {
one: Promise.resolve(1),
two: Promise.resolve(2)
}
});
</script>
{await promises.one}
@@ -68,14 +68,17 @@ All data returned from a `hydratable` function must be serializable. But this do
const nonce = crypto.randomUUID();
const { head, body } = await render(App, {
csp: { nonce },
csp: { nonce }
});
```
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:
```js
response.headers.set('Content-Security-Policy', `script-src 'nonce-${nonce}'`);
response.headers.set(
'Content-Security-Policy',
`script-src 'nonce-${nonce}'`
);
```
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.
@@ -84,7 +87,7 @@ If instead you are generating static HTML ahead of time, you must use hashes ins
```js
const { head, body, hashes } = await render(App, {
csp: { hash: true },
csp: { hash: true }
});
```
@@ -92,9 +95,9 @@ const { head, body, hashes } = await render(App, {
```js
response.headers.set(
'Content-Security-Policy',
`script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`,
);
'Content-Security-Policy',
`script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`
);
```
We recommend using `nonce` over hash if you can, as `hash` will interfere with streaming SSR in the future.

View File

@@ -1,8 +1,11 @@
> [!NOTE] `$inspect` only works during development. In a production build it becomes a noop.
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:
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:
<!-- codeblock:start {"title":"$inspect(...)"} -->
```svelte
<!--- file: App.svelte --->
<script>
let count = $state(0);
let message = $state('hello');
@@ -14,13 +17,18 @@ The `$inspect` rune is roughly equivalent to `console.log`, with the exception t
<input bind:value={message} />
```
<!-- codeblock:end -->
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).
## $inspect(...).with
`$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:
`$inspect(...)` returns an object with a `with` method, which you can invoke with a callback that 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`:
<!-- codeblock:start {"title":"$inspect(...).with(...)"} -->
```svelte
<!--- file: App.svelte --->
<script>
let count = $state(0);
@@ -34,9 +42,11 @@ On updates, a stack trace will be printed, making it easy to find the origin of
<button onclick={() => count++}>Increment</button>
```
<!-- codeblock:end -->
## $inspect.trace(...)
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.
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](https://svelte.dev/docs/svelte/$effect/llms.txt) or a [derived](https://svelte.dev/docs/svelte/$derived/llms.txt), information will be printed to the console about which pieces of reactive state caused the effect to fire.
```svelte
<script>

View File

@@ -1,4 +1,4 @@
To render a [snippet](snippet), use a `{@render ...}` tag.
To render a [snippet](https://svelte.dev/docs/svelte/snippet/llms.txt), use a `{@render ...}` tag.
```svelte
{#snippet sum(a, b)}
@@ -24,7 +24,7 @@ If the snippet is potentially undefined — for example, because it's an incomin
{@render children?.()}
```
Alternatively, use an [`{#if ...}`](if) block with an `:else` clause to render fallback content:
Alternatively, use an [`{#if ...}`](https://svelte.dev/docs/svelte/if/llms.txt) block with an `:else` clause to render fallback content:
```svelte
{#if children}

View File

@@ -8,7 +8,7 @@
{#snippet name(param1, param2, paramN)}...{/snippet}
```
Snippets, and [render tags](@render), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...
Snippets, and [render tags](https://svelte.dev/docs/svelte/@render/llms.txt), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...
```svelte
{#each images as image}
@@ -53,9 +53,12 @@ Like function declarations, snippets can have an arbitrary number of parameters,
## Snippet scope
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...
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...
<!-- codeblock:start {"title":"Snippets"} -->
```svelte
<!--- file: App.svelte --->
<script>
let { message = `it's great to see you!` } = $props();
</script>
@@ -68,6 +71,8 @@ Snippets can be declared anywhere inside your component. They can reference valu
{@render hello('bob')}
```
<!-- codeblock:end -->
...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings):
```svelte
@@ -87,9 +92,12 @@ Snippets can be declared anywhere inside your component. They can reference valu
{@render x()}
```
Snippets can reference themselves and each other (demo:
Snippets can reference themselves and each other:
<!-- codeblock:start {"title":"Self-referencing snippets"} -->
```svelte
<!--- file: App.svelte --->
{#snippet blastoff()}
<span>🚀</span>
{/snippet}
@@ -106,20 +114,25 @@ Snippets can reference themselves and each other (demo:
{@render countdown(10)}
```
<!-- codeblock:end -->
## 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:
Within the template, snippets are values just like any other. As such, they can be passed to components as props:
<!-- codeblock:start {"title":"Explicit snippet props"} -->
```svelte
<!--- file: App.svelte --->
<script>
import Table from './Table.svelte';
const fruits = [
{ name: 'apples', qty: 5, price: 2 },
{ name: 'bananas', qty: 10, price: 1 },
{ name: 'cherries', qty: 20, price: 0.5 },
{ name: 'cherries', qty: 20, price: 0.5 }
];
</script>
@@ -137,17 +150,67 @@ Within the template, snippets are values just like any other. As such, they can
<td>{d.qty * d.price}</td>
{/snippet}
<Table data={fruits} {header} {row} />
<Table data={fruits} +++{header} {row}+++ />
```
```svelte
<!--- file: Table.svelte --->
<script>
let { data, header, row } = $props();
</script>
<table>
{#if header}
<thead>
<tr>{@render header()}</tr>
</thead>
{/if}
<tbody>
{#each data as d}
<tr>{@render row(d)}</tr>
{/each}
</tbody>
</table>
<style>
table {
text-align: left;
border-spacing: 0;
}
tbody tr:nth-child(2n+1) {
background: ButtonFace;
}
table :global(th), table :global(td) {
padding: 0.5em;
}
</style>
```
<!-- codeblock:end -->
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:
As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component:
<!-- codeblock:start {"title":"Implicit snippet props"} -->
```svelte
<!-- this is semantically the same as the above -->
<!--- file: App.svelte --->
<script>
import Table from './Table.svelte';
const fruits = [
{ name: 'apples', qty: 5, price: 2 },
{ name: 'bananas', qty: 10, price: 1 },
{ name: 'cherries', qty: 20, price: 0.5 }
];
</script>
<Table data={fruits}>
{#snippet header()}
<th>fruit</th>
@@ -165,12 +228,56 @@ As an authoring convenience, snippets declared directly _inside_ a component imp
</Table>
```
```svelte
<!--- file: Table.svelte --->
<script>
let { data, header, row } = $props();
</script>
<table>
{#if header}
<thead>
<tr>{@render header()}</tr>
</thead>
{/if}
<tbody>
{#each data as d}
<tr>{@render row(d)}</tr>
{/each}
</tbody>
</table>
<style>
table {
text-align: left;
border-spacing: 0;
}
tbody tr:nth-child(2n+1) {
background: ButtonFace;
}
table :global(th), table :global(td) {
padding: 0.5em;
}
</style>
```
<!-- codeblock:end -->
### Implicit `children` snippet
Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet (demo:
Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet:
<!-- codeblock:start {"title":"Implicit children snippet","selected":"Button.svelte"} -->
```svelte
<!--- file: App.svelte --->
<script>
import Button from './Button.svelte';
</script>
<Button>click me</Button>
```
@@ -184,6 +291,8 @@ Any content inside the component tags that is _not_ a snippet declaration implic
<button>{@render children()}</button>
```
<!-- codeblock:end -->
> [!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
@@ -192,7 +301,7 @@ You can declare snippet props as being optional. You can either use optional cha
```svelte
<script>
let { children } = $props();
let { children } = $props();
</script>
{@render children?.()}
@@ -202,13 +311,13 @@ You can declare snippet props as being optional. You can either use optional cha
```svelte
<script>
let { children } = $props();
let { children } = $props();
</script>
{#if children}
{@render children()}
{@render children()}
{:else}
fallback content
fallback content
{/if}
```
@@ -241,7 +350,7 @@ We can tighten things up further by declaring a generic, so that `data` and `row
let {
data,
children,
row,
row
}: {
data: T[];
children: Snippet;
@@ -252,9 +361,22 @@ We can tighten things up further by declaring a generic, so that `data` and `row
## Exporting snippets
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:
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):
<!-- codeblock:start {"title":"Exported snippets","selected":"snippets.svelte"} -->
```svelte
<!--- file: App.svelte --->
<script>
import { add } from './snippets.svelte';
</script>
{@render add(1, 2)}
```
```svelte
<!--- file: snippets.svelte --->
<script module>
export { add };
</script>
@@ -264,13 +386,15 @@ Snippets declared at the top level of a `.svelte` file can be exported from a `<
{/snippet}
```
<!-- codeblock:end -->
> [!NOTE]
> This requires Svelte 5.5.0 or newer
## Programmatic snippets
Snippets can be created programmatically with the [`createRawSnippet`](svelte#createRawSnippet) API. This is intended for advanced use cases.
Snippets can be created programmatically with the [`createRawSnippet`](https://svelte.dev/docs/svelte/svelte/llms.txt#createRawSnippet) API. This is intended for advanced use cases.
## Snippets and slots
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.
In Svelte 4, content can be passed to components using [slots](https://svelte.dev/docs/svelte/legacy-slots/llms.txt). Snippets are more powerful and flexible, and so slots have been deprecated in Svelte 5.

View File

@@ -17,7 +17,7 @@ If `start` returns a cleanup function, it will be called when the effect is dest
If `subscribe` is called in multiple effects, `start` will only be called once as long as the effects
are active, and the returned teardown function will only be called when all effects are destroyed.
It's best understood with an example. Here's an implementation of [`MediaQuery`](/docs/svelte/svelte-reactivity#MediaQuery):
It's best understood with an example. Here's an implementation of [`MediaQuery`](https://svelte.dev/docs/svelte/svelte-reactivity/llms.txt#MediaQuery):
```js
// @errors: 7031

77
pnpm-lock.yaml generated
View File

@@ -82,8 +82,8 @@ catalogs:
specifier: ^2.42.2
version: 2.50.1
'@sveltejs/vite-plugin-svelte':
specifier: ^6.0.0
version: 6.2.4
specifier: ^7.0.0
version: 7.2.0
svelte:
specifier: ^5.56.1
version: 5.56.1
@@ -149,6 +149,9 @@ catalogs:
valibot:
specifier: ^1.2.0
version: 1.2.0
verkit:
specifier: ^0.1.2
version: 0.1.2
vite:
specifier: ^7.0.4
version: 7.3.1
@@ -183,7 +186,7 @@ importers:
version: 0.19.0(@types/node@24.10.9)(hono@4.11.7)(typescript@5.9.3)
'@sveltejs/adapter-vercel':
specifier: catalog:svelte
version: 6.3.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(rollup@4.57.0)
version: 6.3.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(rollup@4.57.0)
eslint:
specifier: catalog:lint
version: 9.39.2
@@ -234,7 +237,7 @@ importers:
version: 0.8.5(tmcp@1.19.3(typescript@5.9.3))
'@vercel/analytics':
specifier: catalog:tooling
version: 2.0.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(react@18.3.1)(svelte@5.56.1(@typescript-eslint/types@8.54.0))
version: 2.0.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(react@18.3.1)(svelte@5.56.1(@typescript-eslint/types@8.54.0))
tmcp:
specifier: catalog:tmcp
version: 1.19.3(typescript@5.9.3)
@@ -250,13 +253,13 @@ importers:
version: 0.19.0(@types/node@24.10.9)(hono@4.11.7)(typescript@5.9.3)
'@sveltejs/adapter-vercel':
specifier: catalog:svelte
version: 6.3.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(rollup@4.57.0)
version: 6.3.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(rollup@4.57.0)
'@sveltejs/kit':
specifier: catalog:svelte
version: 2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
version: 2.50.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
'@sveltejs/vite-plugin-svelte':
specifier: catalog:svelte
version: 6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
version: 7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
'@types/node':
specifier: catalog:tooling
version: 24.10.9
@@ -350,7 +353,7 @@ importers:
version: 0.71.2(zod@4.1.8)
'@sveltejs/kit':
specifier: catalog:svelte
version: 2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
version: 2.50.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
'@types/estree':
specifier: catalog:tooling
version: 1.0.8
@@ -412,6 +415,9 @@ importers:
valibot:
specifier: catalog:tooling
version: 1.2.0(typescript@5.9.3)
verkit:
specifier: catalog:tooling
version: 0.1.2
devDependencies:
'@opencode-ai/plugin':
specifier: catalog:opencode
@@ -2047,20 +2053,12 @@ packages:
typescript:
optional: true
'@sveltejs/vite-plugin-svelte-inspector@5.0.2':
resolution: {integrity: sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==}
'@sveltejs/vite-plugin-svelte@7.2.0':
resolution: {integrity: sha512-1SpkuMSRLfugrVX+IrKfE1RUegzo8AQzKQ6qQPfVzbcWi5IhuTPaKb5ZrLpucleFznkc4/RTeSPoRnGWFxX+EQ==}
engines: {node: ^20.19 || ^22.12 || >=24}
peerDependencies:
'@sveltejs/vite-plugin-svelte': ^6.0.0-next.0
svelte: ^5.0.0
vite: ^6.3.0 || ^7.0.0
'@sveltejs/vite-plugin-svelte@6.2.4':
resolution: {integrity: sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==}
engines: {node: ^20.19 || ^22.12 || >=24}
peerDependencies:
svelte: ^5.0.0
vite: ^6.3.0 || ^7.0.0
svelte: ^5.46.4
vite: ^8.0.0-beta.7 || ^8.0.0
'@tmcp/adapter-valibot@0.1.5':
resolution: {integrity: sha512-9P2wrVYPngemNK0UvPb/opC722/jfd09QxXmme1TRp/wPsl98vpSk/MXt24BCMqBRv4Dvs0xxJH4KHDcjXW52Q==}
@@ -3141,7 +3139,6 @@ packages:
glob@9.3.5:
resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
engines: {node: '>=16 || 14 >=14.17'}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
globals@14.0.0:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
@@ -4518,6 +4515,10 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
verkit@0.1.2:
resolution: {integrity: sha512-WqkT8n3hqizuCu71W3bUzf5fjBmkbXcudsehe/NbxA8PgqoKnSOY5K0Ba2ckg1qaRaSpSz7as/n9K1R9JXjQKg==}
engines: {node: '>=18.12.0'}
vite-plugin-devtools-json@1.0.0:
resolution: {integrity: sha512-MobvwqX76Vqt/O4AbnNMNWoXWGrKUqZbphCUle/J2KXH82yKQiunOeKnz/nqEPosPsoWWPP9FtNuPBSYpiiwkw==}
peerDependencies:
@@ -4563,10 +4564,10 @@ packages:
yaml:
optional: true
vitefu@1.1.1:
resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==}
vitefu@1.1.3:
resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==}
peerDependencies:
vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0
vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
vite:
optional: true
@@ -6238,9 +6239,9 @@ snapshots:
dependencies:
acorn: 8.15.0
'@sveltejs/adapter-vercel@6.3.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(rollup@4.57.0)':
'@sveltejs/adapter-vercel@6.3.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(rollup@4.57.0)':
dependencies:
'@sveltejs/kit': 2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
'@sveltejs/kit': 2.50.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
'@vercel/nft': 1.3.0(rollup@4.57.0)
esbuild: 0.25.12
transitivePeerDependencies:
@@ -6248,11 +6249,11 @@ snapshots:
- rollup
- supports-color
'@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))':
'@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))':
dependencies:
'@standard-schema/spec': 1.1.0
'@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0)
'@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
'@sveltejs/vite-plugin-svelte': 7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
'@types/cookie': 0.6.0
acorn: 8.15.0
cookie: 0.6.0
@@ -6269,22 +6270,14 @@ snapshots:
optionalDependencies:
typescript: 5.9.3
'@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))':
'@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))':
dependencies:
'@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
obug: 2.1.1
svelte: 5.56.1(@typescript-eslint/types@8.54.0)
vite: 7.3.1(@types/node@24.10.9)(yaml@2.9.0)
'@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))':
dependencies:
'@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
deepmerge: 4.3.1
magic-string: 0.30.21
obug: 2.1.1
svelte: 5.56.1(@typescript-eslint/types@8.54.0)
vite: 7.3.1(@types/node@24.10.9)(yaml@2.9.0)
vitefu: 1.1.1(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
vitefu: 1.1.3(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
'@tmcp/adapter-valibot@0.1.5(tmcp@1.19.3(typescript@5.9.3))(valibot@1.2.0(typescript@5.9.3))':
dependencies:
@@ -6445,9 +6438,9 @@ snapshots:
dependencies:
valibot: 1.2.0(typescript@5.9.3)
'@vercel/analytics@2.0.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(react@18.3.1)(svelte@5.56.1(@typescript-eslint/types@8.54.0))':
'@vercel/analytics@2.0.1(@sveltejs/kit@2.50.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(react@18.3.1)(svelte@5.56.1(@typescript-eslint/types@8.54.0))':
optionalDependencies:
'@sveltejs/kit': 2.50.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
'@sveltejs/kit': 2.50.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.1(@typescript-eslint/types@8.54.0))(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)))(svelte@5.56.1(@typescript-eslint/types@8.54.0))(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0))
react: 18.3.1
svelte: 5.56.1(@typescript-eslint/types@8.54.0)
@@ -8915,6 +8908,8 @@ snapshots:
vary@1.1.2: {}
verkit@0.1.2: {}
vite-plugin-devtools-json@1.0.0(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)):
dependencies:
uuid: 11.1.0
@@ -8933,7 +8928,7 @@ snapshots:
fsevents: 2.3.3
yaml: 2.9.0
vitefu@1.1.1(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)):
vitefu@1.1.3(vite@7.3.1(@types/node@24.10.9)(yaml@2.9.0)):
optionalDependencies:
vite: 7.3.1(@types/node@24.10.9)(yaml@2.9.0)

View File

@@ -8,12 +8,6 @@ catalogs:
'@anthropic-ai/sdk': ^0.71.0
'@mcp-ui/server': ^6.0.0
'@modelcontextprotocol/inspector': ^0.19.0
opencode:
'@opencode-ai/plugin': 1.17.9
'@opentui/core': ^0.4.3
'@opentui/keymap': ^0.4.3
'@opentui/solid': ^0.4.3
solid-js: 1.9.12
lint:
'@eslint/compat': ^2.0.0
'@eslint/js': ^9.36.0
@@ -29,10 +23,16 @@ catalogs:
prettier-plugin-svelte: ^3.3.3
svelte-eslint-parser: ^1.7.1
typescript-eslint: ^8.44.0
opencode:
'@opencode-ai/plugin': 1.17.9
'@opentui/core': ^0.4.3
'@opentui/keymap': ^0.4.3
'@opentui/solid': ^0.4.3
solid-js: 1.9.12
svelte:
'@sveltejs/adapter-vercel': ^6.0.0
'@sveltejs/kit': ^2.42.2
'@sveltejs/vite-plugin-svelte': ^6.0.0
'@sveltejs/vite-plugin-svelte': ^7.0.0
svelte: ^5.56.1
svelte-check: ^4.0.0
tmcp:
@@ -56,6 +56,7 @@ catalogs:
tsdown: ^0.20.0
typescript: ^5.0.0
valibot: ^1.2.0
verkit: ^0.1.2
vite: ^7.0.4
vite-plugin-devtools-json: ^1.0.0
vitest: ^4.0.0

View File

@@ -103,7 +103,33 @@ function remove_frontmatter_unneeded_fields(content: string) {
function derive_name(link: string) {
const without_hash = link.split('#')[0]!;
const segments = without_hash.split('/').filter(Boolean);
return segments[segments.length - 1] ?? 'reference';
const name = segments[segments.length - 1] ?? 'reference';
const safe_name = name
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[^a-zA-Z0-9_-]+/g, '-')
.replace(/^-+|-+$/g, '');
return safe_name || 'reference';
}
/**
* Makes links copied into reference files point back to their pages on svelte.dev.
*/
export function resolve_reference_links(content: string, repo: string) {
return content.replace(
/\[([^\]]*)\]\((?![a-z][a-z\d+.-]*:|#|\/\/)([^)]+)\)/gi,
(full_match, text: string, href: string) => {
const url = new URL(
href.startsWith('/')
? `https://svelte.dev${href}`
: `https://svelte.dev/docs/${repo}/${href}`,
);
url.pathname += '/llms.txt';
return `[${text}](${url})`;
},
);
}
const content = remove_llm_ignore_blocks(
@@ -203,8 +229,13 @@ for (const link of links) {
const ref_filename = `${name}.md`;
const ref_path = path.join(references_dir, ref_filename);
const reference_repo = link.is_absolute_docs ? link.clean_path.split('/')[2]! : repo;
const reference_content = resolve_reference_links(
remove_llm_ignore_blocks(remove_cut_preambles(fetched_content)),
reference_repo,
);
await fs.writeFile(ref_path, remove_llm_ignore_blocks(remove_cut_preambles(fetched_content)));
await fs.writeFile(ref_path, reference_content);
console.log(` Saved: references/${ref_filename}`);
// Replace the link in the markdown

View File

@@ -3,13 +3,13 @@ name: svelte-file-editor
description: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server or the `svelte-file-editor` skill if they are available. Fetches relevant documentation and validates code using the Svelte MCP server tools.
---
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.
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 validate the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.
If the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.
If the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.
## Available MCP Tools
## Available MCP tools
### 1. list-sections
@@ -35,30 +35,30 @@ Analyzes Svelte code and returns suggestions to fix issues. Pass the component c
When invoked to work on a Svelte file:
### 1. Gather Context (if needed)
### 1. Gather context (if needed)
If you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:
1. Call `list-sections` to see available documentation
2. Call `get-documentation` with relevant section names
### 2. Read the Target File
### 2. Read the target file
Read the file to understand the current implementation.
### 3. Make Changes
### 3. Make changes
Apply edits following Svelte 5 best practices:
### 4. Validate Changes
### 4. Validate changes
After editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.
### 5. Fix Any Issues
### 5. Fix any issues
If the autofixer reports problems, fix them and re-validate until no issues remain.
## Output Format
## Output format
After completing your work, provide:

View File

@@ -3,13 +3,11 @@ name: svelte-code-writer
description: CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating, editing or analyzing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.
---
# Svelte 5 Code Writer
## CLI Tools
## CLI tools
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
### List Documentation Sections
### List documentation sections
```bash
npx @sveltejs/mcp list-sections
@@ -17,7 +15,7 @@ npx @sveltejs/mcp list-sections
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
### Get Documentation
### Get documentation
```bash
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
@@ -31,7 +29,7 @@ Retrieves full documentation for specified sections. Use after `list-sections` t
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
```
### Svelte Autofixer
### Svelte autofixer
```bash
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]

View File

@@ -35,9 +35,9 @@ If the derived expression is an object or array, it will be returned as-is — i
Effects are an escape hatch and should mostly be avoided. In particular, avoid updating state inside effects.
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/@attach.md)
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/attach.md)
- 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
- If you need to log values for debugging purposes, use [`$inspect`](references/$inspect.md)
- If you need to log values for debugging purposes, use [`$inspect`](references/inspect.md)
- If you need to observe something external to Svelte, use [`createSubscriber`](references/svelte-reactivity.md)
Never wrap the contents of an effect in `if (browser) {...}` or similar — effects do not run on the server.
@@ -86,7 +86,7 @@ Avoid using `onMount` or `$effect` for this.
## Snippets
[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.
[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.
```svelte
{#snippet greeting(name)}

View File

@@ -1,4 +1,4 @@
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.
Attachments are functions that run in an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt) when an element is mounted to the DOM or when [state](https://svelte.dev/docs/svelte/$state/llms.txt) 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.
@@ -48,10 +48,12 @@ A useful pattern is for a function, such as `tooltip` in this example, to _retur
<input bind:value={content} />
<button {@attach tooltip(content)}> Hover me </button>
<button {@attach tooltip(content)}>
Hover me
</button>
```
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).)
Since the `tooltip(content)` expression runs inside an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt), 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
@@ -86,7 +88,7 @@ Falsy values like `false` or `undefined` are treated as no attachment, enabling
## 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.
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](https://svelte.dev/tutorial/svelte/spread-props/llms.txt) props onto an element, the element will receive those attachments.
This allows you to create _wrapper components_ that augment elements (demo:
@@ -125,12 +127,14 @@ This allows you to create _wrapper components_ that augment elements (demo:
<input bind:value={content} />
<Button {@attach tooltip(content)}>Hover me</Button>
<Button {@attach tooltip(content)}>
Hover me
</Button>
```
## 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`):
Attachments, unlike [actions](https://svelte.dev/docs/svelte/use/llms.txt), 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
@@ -159,8 +163,8 @@ function foo(+++getBar+++) {
## Creating attachments programmatically
To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](svelte-attachments#createAttachmentKey).
To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#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.
If you're using a library that only provides actions, you can convert them to attachments with [`fromAction`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#fromAction), allowing you to (for example) use them with components.

View File

@@ -4,16 +4,16 @@ As of Svelte 5.36, you can use the `await` keyword inside your components in thr
- inside `$derived(...)` declarations
- inside your markup
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`:
This feature is currently experimental, and you must opt in by adding the `experimental.async` option wherever you [configure](https://svelte.dev/docs/kit/configuration/llms.txt) Svelte, usually `svelte.config.js`:
```js
/// file: svelte.config.js
export default {
compilerOptions: {
experimental: {
async: true,
},
},
async: true
}
}
};
```
@@ -23,7 +23,10 @@ The experimental flag will be removed in Svelte 6.
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...
<!-- codeblock:start {"title":"Synchronized updates"} -->
```svelte
<!--- file: App.svelte --->
<script>
let a = $state(1);
let b = $state(2);
@@ -34,12 +37,14 @@ When an `await` expression depends on a particular piece of state, changes to th
}
</script>
<input type="number" bind:value={a} />
<input type="number" bind:value={b} />
<input type="number" bind:value={a}>
<input type="number" bind:value={b}>
<p>{a} + {b} = {await add(a, b)}</p>
```
<!-- codeblock:end -->
...if you increment `a`, the contents of the `<p>` will _not_ immediately update to read this —
```html
@@ -55,7 +60,8 @@ Updates can overlap — a fast update will be reflected in the UI while an earli
Svelte will do as much asynchronous work as it can in parallel. For example if you have two `await` expressions in your markup...
```svelte
<p>{await one()}</p><p>{await two()}</p>
<p>{await one(x)}</p>
<p>{await two(y)}</p>
```
...both functions will run at the same time, as they are independent expressions, even though they are _visually_ sequential.
@@ -63,21 +69,22 @@ Svelte will do as much asynchronous work as it can in parallel. For example if y
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:
```js
// these will run sequentially the first time,
// but will update independently
let a = $derived(await one());
let b = $derived(await two());
// `b` will not be created until `a` has resolved,
// but once created they will update independently
// even if `x` and `y` update simultaneously
let a = $derived(await one(x));
let b = $derived(await two(y));
```
> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](runtime-warnings#Client-warnings-await_waterfall) warning
> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](https://svelte.dev/docs/svelte/runtime-warnings/llms.txt#Client-warnings-await_waterfall) warning
## Indicating loading states
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.
To render placeholder UI, you can wrap content in a `<svelte:boundary>` with a [`pending`](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt#Properties-pending) snippet. This will be shown when the boundary is first created, but not for subsequent updates, which are globally coordinated.
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.
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()`](https://svelte.dev/docs/svelte/$effect/llms.txt#$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.
You can also use [`settled()`](svelte#settled) to get a promise that resolves when the current update is complete:
You can also use [`settled()`](https://svelte.dev/docs/svelte/svelte/llms.txt#settled) to get a promise that resolves when the current update is complete:
```js
import { tick, settled } from 'svelte';
@@ -103,7 +110,7 @@ async function onclick() {
## Error handling
Errors in `await` expressions will bubble to the nearest [error boundary](svelte-boundary).
Errors in `await` expressions will bubble to the nearest [error boundary](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt).
## Server-side rendering
@@ -125,7 +132,7 @@ If a `<svelte:boundary>` with a `pending` snippet is encountered during SSR, tha
## Forking
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.
The [`fork(...)`](https://svelte.dev/docs/svelte/svelte/llms.txt#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.
```svelte
<script>
@@ -161,13 +168,13 @@ The [`fork(...)`](svelte#fork) API, added in 5.42, makes it possible to run `awa
// in case `pending` didn't exist
// (if it did, this is a no-op)
open = true;
}}>open menu</button
>
}}
>open menu</button>
{#if open}
<!-- any async work inside this component will start
as soon as the fork is created -->
<Menu onclose={() => (open = false)} />
<Menu onclose={() => open = false} />
{/if}
```

View File

@@ -3,13 +3,19 @@
You can also use `bind:property={get, set}`, where `get` and `set` are functions, allowing you to perform validation and transformation:
```svelte
<input bind:value={() => value, (v) => (value = v.toLowerCase())} />
<input bind:value={
() => value,
(v) => value = v.toLowerCase()}
/>
```
In the case of readonly bindings like [dimension bindings](#Dimensions), the `get` value should be `null`:
```svelte
<div bind:clientWidth={null, redraw} bind:clientHeight={null, redraw}>...</div>
<div
bind:clientWidth={null, redraw}
bind:clientHeight={null, redraw}
>...</div>
```
> [!NOTE]

View File

@@ -2,31 +2,31 @@ In Svelte, when you want to render asynchronous content data on the server, you
```svelte
<script>
import { getUser } from 'my-database-library';
import { getUser } from 'my-database-library';
// This will get the user on the server, render the user's name into the h1,
// and then, during hydration on the client, it will get the user _again_,
// blocking hydration until it's done.
const user = await getUser();
// This will get the user on the server, render the user's name into the h1,
// and then, during hydration on the client, it will get the user _again_,
// blocking hydration until it's done.
const user = await getUser();
</script>
<h1>{user.name}</h1>
```
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).
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](https://svelte.dev/docs/kit/remote-functions/llms.txt).
To fix the example above:
```svelte
<script>
import { hydratable } from 'svelte';
import { getUser } from 'my-database-library';
import { hydratable } from 'svelte';
import { getUser } from 'my-database-library';
// During server rendering, this will serialize and stash the result of `getUser`, associating
// it with the provided key and baking it into the `head` content. During hydration, it will
// look for the serialized version, returning it instead of running `getUser`. After hydration
// is done, if it's called again, it'll simply invoke `getUser`.
const user = await hydratable('user', () => getUser());
// During server rendering, this will serialize and stash the result of `getUser`, associating
// it with the provided key and baking it into the `head` content. During hydration, it will
// look for the serialized version, returning it instead of running `getUser`. After hydration
// is done, if it's called again, it'll simply invoke `getUser`.
const user = await hydratable('user', () => getUser());
</script>
<h1>{user.name}</h1>
@@ -47,13 +47,13 @@ All data returned from a `hydratable` function must be serializable. But this do
```svelte
<script>
import { hydratable } from 'svelte';
const promises = hydratable('random', () => {
return {
one: Promise.resolve(1),
two: Promise.resolve(2),
};
});
import { hydratable } from 'svelte';
const promises = hydratable('random', () => {
return {
one: Promise.resolve(1),
two: Promise.resolve(2)
}
});
</script>
{await promises.one}
@@ -68,14 +68,17 @@ All data returned from a `hydratable` function must be serializable. But this do
const nonce = crypto.randomUUID();
const { head, body } = await render(App, {
csp: { nonce },
csp: { nonce }
});
```
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:
```js
response.headers.set('Content-Security-Policy', `script-src 'nonce-${nonce}'`);
response.headers.set(
'Content-Security-Policy',
`script-src 'nonce-${nonce}'`
);
```
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.
@@ -84,7 +87,7 @@ If instead you are generating static HTML ahead of time, you must use hashes ins
```js
const { head, body, hashes } = await render(App, {
csp: { hash: true },
csp: { hash: true }
});
```
@@ -92,9 +95,9 @@ const { head, body, hashes } = await render(App, {
```js
response.headers.set(
'Content-Security-Policy',
`script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`,
);
'Content-Security-Policy',
`script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`
);
```
We recommend using `nonce` over hash if you can, as `hash` will interfere with streaming SSR in the future.

View File

@@ -1,8 +1,11 @@
> [!NOTE] `$inspect` only works during development. In a production build it becomes a noop.
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:
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:
<!-- codeblock:start {"title":"$inspect(...)"} -->
```svelte
<!--- file: App.svelte --->
<script>
let count = $state(0);
let message = $state('hello');
@@ -14,13 +17,18 @@ The `$inspect` rune is roughly equivalent to `console.log`, with the exception t
<input bind:value={message} />
```
<!-- codeblock:end -->
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).
## $inspect(...).with
`$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:
`$inspect(...)` returns an object with a `with` method, which you can invoke with a callback that 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`:
<!-- codeblock:start {"title":"$inspect(...).with(...)"} -->
```svelte
<!--- file: App.svelte --->
<script>
let count = $state(0);
@@ -34,9 +42,11 @@ On updates, a stack trace will be printed, making it easy to find the origin of
<button onclick={() => count++}>Increment</button>
```
<!-- codeblock:end -->
## $inspect.trace(...)
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.
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](https://svelte.dev/docs/svelte/$effect/llms.txt) or a [derived](https://svelte.dev/docs/svelte/$derived/llms.txt), information will be printed to the console about which pieces of reactive state caused the effect to fire.
```svelte
<script>

View File

@@ -1,4 +1,4 @@
To render a [snippet](snippet), use a `{@render ...}` tag.
To render a [snippet](https://svelte.dev/docs/svelte/snippet/llms.txt), use a `{@render ...}` tag.
```svelte
{#snippet sum(a, b)}
@@ -24,7 +24,7 @@ If the snippet is potentially undefined — for example, because it's an incomin
{@render children?.()}
```
Alternatively, use an [`{#if ...}`](if) block with an `:else` clause to render fallback content:
Alternatively, use an [`{#if ...}`](https://svelte.dev/docs/svelte/if/llms.txt) block with an `:else` clause to render fallback content:
```svelte
{#if children}

View File

@@ -8,7 +8,7 @@
{#snippet name(param1, param2, paramN)}...{/snippet}
```
Snippets, and [render tags](@render), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...
Snippets, and [render tags](https://svelte.dev/docs/svelte/@render/llms.txt), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...
```svelte
{#each images as image}
@@ -53,9 +53,12 @@ Like function declarations, snippets can have an arbitrary number of parameters,
## Snippet scope
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...
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...
<!-- codeblock:start {"title":"Snippets"} -->
```svelte
<!--- file: App.svelte --->
<script>
let { message = `it's great to see you!` } = $props();
</script>
@@ -68,6 +71,8 @@ Snippets can be declared anywhere inside your component. They can reference valu
{@render hello('bob')}
```
<!-- codeblock:end -->
...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings):
```svelte
@@ -87,9 +92,12 @@ Snippets can be declared anywhere inside your component. They can reference valu
{@render x()}
```
Snippets can reference themselves and each other (demo:
Snippets can reference themselves and each other:
<!-- codeblock:start {"title":"Self-referencing snippets"} -->
```svelte
<!--- file: App.svelte --->
{#snippet blastoff()}
<span>🚀</span>
{/snippet}
@@ -106,20 +114,25 @@ Snippets can reference themselves and each other (demo:
{@render countdown(10)}
```
<!-- codeblock:end -->
## 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:
Within the template, snippets are values just like any other. As such, they can be passed to components as props:
<!-- codeblock:start {"title":"Explicit snippet props"} -->
```svelte
<!--- file: App.svelte --->
<script>
import Table from './Table.svelte';
const fruits = [
{ name: 'apples', qty: 5, price: 2 },
{ name: 'bananas', qty: 10, price: 1 },
{ name: 'cherries', qty: 20, price: 0.5 },
{ name: 'cherries', qty: 20, price: 0.5 }
];
</script>
@@ -137,17 +150,67 @@ Within the template, snippets are values just like any other. As such, they can
<td>{d.qty * d.price}</td>
{/snippet}
<Table data={fruits} {header} {row} />
<Table data={fruits} +++{header} {row}+++ />
```
```svelte
<!--- file: Table.svelte --->
<script>
let { data, header, row } = $props();
</script>
<table>
{#if header}
<thead>
<tr>{@render header()}</tr>
</thead>
{/if}
<tbody>
{#each data as d}
<tr>{@render row(d)}</tr>
{/each}
</tbody>
</table>
<style>
table {
text-align: left;
border-spacing: 0;
}
tbody tr:nth-child(2n+1) {
background: ButtonFace;
}
table :global(th), table :global(td) {
padding: 0.5em;
}
</style>
```
<!-- codeblock:end -->
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:
As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component:
<!-- codeblock:start {"title":"Implicit snippet props"} -->
```svelte
<!-- this is semantically the same as the above -->
<!--- file: App.svelte --->
<script>
import Table from './Table.svelte';
const fruits = [
{ name: 'apples', qty: 5, price: 2 },
{ name: 'bananas', qty: 10, price: 1 },
{ name: 'cherries', qty: 20, price: 0.5 }
];
</script>
<Table data={fruits}>
{#snippet header()}
<th>fruit</th>
@@ -165,12 +228,56 @@ As an authoring convenience, snippets declared directly _inside_ a component imp
</Table>
```
```svelte
<!--- file: Table.svelte --->
<script>
let { data, header, row } = $props();
</script>
<table>
{#if header}
<thead>
<tr>{@render header()}</tr>
</thead>
{/if}
<tbody>
{#each data as d}
<tr>{@render row(d)}</tr>
{/each}
</tbody>
</table>
<style>
table {
text-align: left;
border-spacing: 0;
}
tbody tr:nth-child(2n+1) {
background: ButtonFace;
}
table :global(th), table :global(td) {
padding: 0.5em;
}
</style>
```
<!-- codeblock:end -->
### Implicit `children` snippet
Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet (demo:
Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet:
<!-- codeblock:start {"title":"Implicit children snippet","selected":"Button.svelte"} -->
```svelte
<!--- file: App.svelte --->
<script>
import Button from './Button.svelte';
</script>
<Button>click me</Button>
```
@@ -184,6 +291,8 @@ Any content inside the component tags that is _not_ a snippet declaration implic
<button>{@render children()}</button>
```
<!-- codeblock:end -->
> [!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
@@ -192,7 +301,7 @@ You can declare snippet props as being optional. You can either use optional cha
```svelte
<script>
let { children } = $props();
let { children } = $props();
</script>
{@render children?.()}
@@ -202,13 +311,13 @@ You can declare snippet props as being optional. You can either use optional cha
```svelte
<script>
let { children } = $props();
let { children } = $props();
</script>
{#if children}
{@render children()}
{@render children()}
{:else}
fallback content
fallback content
{/if}
```
@@ -241,7 +350,7 @@ We can tighten things up further by declaring a generic, so that `data` and `row
let {
data,
children,
row,
row
}: {
data: T[];
children: Snippet;
@@ -252,9 +361,22 @@ We can tighten things up further by declaring a generic, so that `data` and `row
## Exporting snippets
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:
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):
<!-- codeblock:start {"title":"Exported snippets","selected":"snippets.svelte"} -->
```svelte
<!--- file: App.svelte --->
<script>
import { add } from './snippets.svelte';
</script>
{@render add(1, 2)}
```
```svelte
<!--- file: snippets.svelte --->
<script module>
export { add };
</script>
@@ -264,13 +386,15 @@ Snippets declared at the top level of a `.svelte` file can be exported from a `<
{/snippet}
```
<!-- codeblock:end -->
> [!NOTE]
> This requires Svelte 5.5.0 or newer
## Programmatic snippets
Snippets can be created programmatically with the [`createRawSnippet`](svelte#createRawSnippet) API. This is intended for advanced use cases.
Snippets can be created programmatically with the [`createRawSnippet`](https://svelte.dev/docs/svelte/svelte/llms.txt#createRawSnippet) API. This is intended for advanced use cases.
## Snippets and slots
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.
In Svelte 4, content can be passed to components using [slots](https://svelte.dev/docs/svelte/legacy-slots/llms.txt). Snippets are more powerful and flexible, and so slots have been deprecated in Svelte 5.

View File

@@ -17,7 +17,7 @@ If `start` returns a cleanup function, it will be called when the effect is dest
If `subscribe` is called in multiple effects, `start` will only be called once as long as the effects
are active, and the returned teardown function will only be called when all effects are destroyed.
It's best understood with an example. Here's an implementation of [`MediaQuery`](/docs/svelte/svelte-reactivity#MediaQuery):
It's best understood with an example. Here's an implementation of [`MediaQuery`](https://svelte.dev/docs/svelte/svelte-reactivity/llms.txt#MediaQuery):
```js
// @errors: 7031