From 9998507a089aaff218884944d5608561c8502b4a Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 14 Nov 2025 12:03:53 +0100 Subject: [PATCH 1/3] feat: claude skill + marketplace --- .claude-plugin/marketplace.json | 13 + .../docs/20-setup/30-remote-setup.md | 2 + documentation/docs/40-claude-plugin/index.md | 3 + documentation/docs/40-claude-plugin/plugin.md | 21 + documentation/docs/40-claude-plugin/skill.md | 11 + plugins/svelte/.claude-plugin/plugin.json | 8 + plugins/svelte/.mcp.json | 8 + .../svelte/skills/svelte-code-writer/SKILL.md | 198 ++++++ .../references/MIGRATION.md | 550 +++++++++++++++ .../svelte-code-writer/references/PATTERNS.md | 542 ++++++++++++++ .../svelte-code-writer/references/PITFALLS.md | 354 ++++++++++ .../svelte-code-writer/references/RUNES.md | 358 ++++++++++ .../references/TYPESCRIPT.md | 660 ++++++++++++++++++ 13 files changed, 2728 insertions(+) create mode 100644 .claude-plugin/marketplace.json create mode 100644 documentation/docs/40-claude-plugin/index.md create mode 100644 documentation/docs/40-claude-plugin/plugin.md create mode 100644 documentation/docs/40-claude-plugin/skill.md create mode 100644 plugins/svelte/.claude-plugin/plugin.json create mode 100644 plugins/svelte/.mcp.json create mode 100644 plugins/svelte/skills/svelte-code-writer/SKILL.md create mode 100644 plugins/svelte/skills/svelte-code-writer/references/MIGRATION.md create mode 100644 plugins/svelte/skills/svelte-code-writer/references/PATTERNS.md create mode 100644 plugins/svelte/skills/svelte-code-writer/references/PITFALLS.md create mode 100644 plugins/svelte/skills/svelte-code-writer/references/RUNES.md create mode 100644 plugins/svelte/skills/svelte-code-writer/references/TYPESCRIPT.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..9ed99ed --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,13 @@ +{ + "name": "svelte", + "owner": { + "name": "Svelte" + }, + "plugins": [ + { + "name": "svelte", + "source": "./plugins/svelte", + "description": "A plugin for all things svelte development, MCP, skills, and more." + } + ] +} diff --git a/documentation/docs/20-setup/30-remote-setup.md b/documentation/docs/20-setup/30-remote-setup.md index 108c2bf..8f4d3ad 100644 --- a/documentation/docs/20-setup/30-remote-setup.md +++ b/documentation/docs/20-setup/30-remote-setup.md @@ -16,6 +16,8 @@ claude mcp add -t http -s [scope] svelte https://mcp.svelte.dev/mcp You can choose your preferred `scope` (it must be `user`, `project` or `local`) and `name`. +If you prefer you can also install the `svelte` plugin in [the svelte claude code marketplace](plugin) that will give you both the remote server and a useful [skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview). + ## Claude Desktop - Open Settings > Connectors diff --git a/documentation/docs/40-claude-plugin/index.md b/documentation/docs/40-claude-plugin/index.md new file mode 100644 index 0000000..43c987a --- /dev/null +++ b/documentation/docs/40-claude-plugin/index.md @@ -0,0 +1,3 @@ +--- +title: Claude Plugin +--- diff --git a/documentation/docs/40-claude-plugin/plugin.md b/documentation/docs/40-claude-plugin/plugin.md new file mode 100644 index 0000000..e6d24b1 --- /dev/null +++ b/documentation/docs/40-claude-plugin/plugin.md @@ -0,0 +1,21 @@ +--- +title: Overview +--- + +The open source [repository](https://github.com/sveltejs/mcp) containing the code for the MCP server is also a claude code marketplace. + +The marketplace allow you to install the `svelte` plugin which will give you both the remote MCP server and a [skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview) to instruct the LLM on how to properly write svelte 5 code. + +## Installation + +To add the repository as a marketplace launch claude code and type + +```bash +/plugin marketplace add sveltejs/mcp +``` + +once you do that you can install the svelte skill doing + +```bash +/plugin install svelte +``` diff --git a/documentation/docs/40-claude-plugin/skill.md b/documentation/docs/40-claude-plugin/skill.md new file mode 100644 index 0000000..1f2dc79 --- /dev/null +++ b/documentation/docs/40-claude-plugin/skill.md @@ -0,0 +1,11 @@ +--- +title: Skill +--- + +Claude Skills are a set of MD files that live in your `.claude` folder (or that you can upload in your claude web/desktop). They are automatically loaded by Claude when it thinks they are appropriate for the current task. + +With those markdown files you can steer the agent behaviours and, in our case, we teach him how to properly write Svelte 5 code. The advantage over the MCP server is that the relevant tokens are only loaded when they are needed (so if you ask the LLM to write a Typescript utility in a Svelte project it will not load the skill in the context). + +You can find the skill inside the [`sveltejs/mcp`](https://github.com/sveltejs/mcp/plugins/svelte/skills) repo (it's in the `/plugins/svelte/skills` folder) and you can download it as a zip file to load it in your Claude web/desktop or to extract it inside your `.claude` folder. + +If you are using claude code you can also install it through the [svelte marketplace](plugin). diff --git a/plugins/svelte/.claude-plugin/plugin.json b/plugins/svelte/.claude-plugin/plugin.json new file mode 100644 index 0000000..5bdb514 --- /dev/null +++ b/plugins/svelte/.claude-plugin/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "svelte", + "description": "A plugin for all things svelte development, MCP, skills, and more.", + "version": "1.0.0", + "author": { + "name": "Svelte" + } +} diff --git a/plugins/svelte/.mcp.json b/plugins/svelte/.mcp.json new file mode 100644 index 0000000..0b7b5c6 --- /dev/null +++ b/plugins/svelte/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "svelte": { + "type": "http", + "url": "https://mcp.svelte.dev/mcp" + } + } +} diff --git a/plugins/svelte/skills/svelte-code-writer/SKILL.md b/plugins/svelte/skills/svelte-code-writer/SKILL.md new file mode 100644 index 0000000..6f465a6 --- /dev/null +++ b/plugins/svelte/skills/svelte-code-writer/SKILL.md @@ -0,0 +1,198 @@ +--- +name: svelte5-code-writer +description: Expert guidance for writing proper Svelte 5 code with runes-based reactivity. Use when writing Svelte 5 components, migrating from Svelte 4, or troubleshooting Svelte 5 syntax. Covers $state, $derived, $effect, $props, $bindable, event handling, snippets, TypeScript integration, and common pitfalls. +--- + +# Svelte 5 Code Writer + +## Quick Reference + +Svelte 5 uses **runes** (functions starting with `$`) for explicit reactivity: + +```svelte + + + +``` + +## Core Workflow + +1. **Choose the right rune:** + - Computing from state? → Use `$derived` + - Managing reactive values? → Use `$state` + - Side effects (DOM, network, etc.)? → Use `$effect` + - Component props? → Use `$props` + +2. **Apply the pattern** (see references below for details) + +3. **Add TypeScript types** for props and state + +## Key Patterns + +### State: $state + +```svelte + +``` + +### Computed Values: $derived + +**Use `$derived` for values computed from other state** (90% of cases): + +```svelte + +``` + +### Side Effects: $effect + +**Use `$effect` only for side effects, not derivations, try to avoid reassign state in it:** + +```svelte + +``` + +### Props: $props + +```svelte + +``` + +### Two-Way Binding: $bindable + +```svelte + + + +``` + +## Event Handling + +**Events are properties** (not directives): + +```svelte + + + + + console.log(data)} /> +``` + +## Snippets (Replacing Slots) + +```svelte + + + + + + {#snippet header()} +

Title

+ {/snippet} + + Default content here +
+ +{@render header?.()} +{@render children?.()} +``` + +## Common Pitfalls + +**❌ Don't synchronize state with $effect:** + +```svelte +let doubled = $state(0); +$effect(() => { doubled = count * 2; }); // Wrong! +``` + +**✅ Use $derived instead:** + +```svelte +let doubled = $derived(count * 2); // Correct! +``` + +**❌ Don't mutate non-bindable props:** + +```svelte +let {count} = $props(); count++; // Warning! +``` + +**✅ Use callbacks or $bindable:** + +```svelte +let {(count, onIncrement)} = $props(); onIncrement(); // Correct! +``` + +## Migration from Svelte 4 + +| Svelte 4 | Svelte 5 | +| ------------------------ | ----------------------------------- | +| `let count = 0` | `let count = $state(0)` | +| `$: doubled = count * 2` | `let doubled = $derived(count * 2)` | +| `$: console.log(count)` | `$effect(() => console.log(count))` | +| `export let prop` | `let { prop } = $props()` | +| `on:click={handler}` | `onclick={handler}` | +| `` | `{@render children?.()}` | + +## Detailed References + +For comprehensive details, see: + +- **[RUNES.md](references/RUNES.md)** - Complete runes reference with examples +- **[PATTERNS.md](references/PATTERNS.md)** - Component patterns and best practices +- **[PITFALLS.md](references/PITFALLS.md)** - Common mistakes and how to fix them +- **[MIGRATION.md](references/MIGRATION.md)** - Detailed migration guide from Svelte 4 +- **[TYPESCRIPT.md](references/TYPESCRIPT.md)** - TypeScript patterns and typing + +Load these references only when you need detailed information beyond the quick reference above. diff --git a/plugins/svelte/skills/svelte-code-writer/references/MIGRATION.md b/plugins/svelte/skills/svelte-code-writer/references/MIGRATION.md new file mode 100644 index 0000000..ee2497c --- /dev/null +++ b/plugins/svelte/skills/svelte-code-writer/references/MIGRATION.md @@ -0,0 +1,550 @@ +# Migration Guide: Svelte 4 → Svelte 5 + +## Overview + +Svelte 5 is largely backward compatible. You can mix Svelte 4 and 5 syntax, and migrate incrementally. + +## Migration Strategy + +1. **Update dependencies** in package.json +2. **Run migration script**: `npx sv migrate svelte-5` +3. **Fix manual migrations** (see below) +4. **Test thoroughly** +5. **Iterate component by component** + +## Automatic Migrations + +The migration script handles: +- `let` → `$state()` +- `$:` derivations → `$derived()` +- `$:` effects → `$effect()` or `run()` +- `export let` → `$props()` +- `on:event` → `onevent` (for DOM elements) +- `` → `{@render children()}` +- Slot usage → snippets + +## Manual Migrations Required + +### 1. Component Events (createEventDispatcher) + +**Before:** +```svelte + + + +``` + +**After:** +```svelte + + + +``` + +**Parent usage before:** +```svelte + +``` + +**Parent usage after:** +```svelte + +``` + +### 2. beforeUpdate/afterUpdate + +**Before:** +```svelte + +``` + +**After:** +```svelte + +``` + +### 3. Component Instantiation + +**Before:** +```js +import App from './App.svelte'; + +const app = new App({ + target: document.getElementById('app'), + props: { name: 'world' } +}); + +app.$set({ name: 'everybody' }); +app.$on('event', callback); +app.$destroy(); +``` + +**After:** +```js +import { mount, unmount } from 'svelte'; +import App from './App.svelte'; + +const props = $state({ name: 'world' }); +const app = mount(App, { + target: document.getElementById('app'), + props +}); + +// Instead of $set: +props.name = 'everybody'; + +// Instead of $on: +const app = mount(App, { + target: document.getElementById('app'), + props, + events: { event: callback } +}); + +// Instead of $destroy: +unmount(app); +``` + +### 4. Server-Side Rendering + +**Before:** +```js +import App from './App.svelte'; + +const { html, css, head } = App.render({ name: 'world' }); +``` + +**After:** +```js +import { render } from 'svelte/server'; +import App from './App.svelte'; + +const { html, head } = render(App, { + props: { name: 'world' } +}); +``` + +Note: CSS is no longer returned by default. Set compiler option `css: 'injected'` if needed. + +### 5. Stores in Components + +If using stores, they still work but consider migrating to runes: + +**Before:** +```svelte + + + +``` + +**After (using runes):** +```svelte + + + +``` + +## Common Migration Patterns + +### Pattern 1: Reactive Statements + +**Before:** +```svelte + +``` + +**After:** +```svelte + +``` + +### Pattern 2: Props with Defaults + +**Before:** +```svelte + +``` + +**After:** +```svelte + +``` + +### Pattern 3: Slot Props + +**Before:** +```svelte + + + + + + {user.name} is {user.age} + +``` + +**After:** +```svelte + + + +{@render children({ name: 'Alice', age: 30 })} + + + + {#snippet children(user)} + {user.name} is {user.age} + {/snippet} + +``` + +### Pattern 4: Named Slots + +**Before:** +```svelte + +
+
+
+ + + +

Title

+

Content

+

Footer

+
+``` + +**After:** +```svelte + + + +
{@render header?.()}
+
{@render children?.()}
+
{@render footer?.()}
+ + + + {#snippet header()} +

Title

+ {/snippet} + +

Content

+ + {#snippet footer()} +

Footer

+ {/snippet} +
+``` + +### Pattern 5: Event Forwarding + +**Before:** +```svelte + +``` + +**After:** +```svelte + + + +``` + +### Pattern 6: Prop Spreading with Events + +**Before:** +```svelte + + + +``` + +**After:** +```svelte + + + +``` + +## The `run` Function + +The migration script may create `run()` calls for `$:` statements it can't determine are safe to convert to `$effect`: + +```svelte + +``` + +**Action required:** Review each `run()` and convert to: +- `$derived()` if it's computing a value +- `$effect()` if it's a side effect +- Remove if no longer needed + +## Compatibility Options + +### Keep Svelte 4 Syntax Working + +Use `compatibility.componentApi: 4` to keep `new Component()` working: + +```js +// svelte.config.js +export default { + compilerOptions: { + compatibility: { + componentApi: 4 + } + } +}; +``` + +This adds some overhead but helps during migration. + +## Breaking Changes in Runes Mode + +### 1. Bindings Need $bindable + +**Before (Svelte 4):** +```svelte + +``` + +All props were bindable in parent: +```svelte + +``` + +**After (Svelte 5):** +```svelte + +``` + +Only explicitly bindable props can use `bind:`. + +### 2. No Implicit Reactivity + +**Before:** +```svelte + +``` + +**After:** +```svelte + +``` + +### 3. Component Type Changes + +**Before:** +```ts +import type { SvelteComponent } from 'svelte'; +import MyComponent from './MyComponent.svelte'; + +let instance: SvelteComponent; +let ComponentType: typeof SvelteComponent; +``` + +**After:** +```ts +import type { Component } from 'svelte'; +import MyComponent from './MyComponent.svelte'; + +let instance: MyComponent; +let ComponentType: Component<{ prop: string }>; +``` + +## Testing Migration + +After migrating, test: + +1. **All user interactions** - clicks, forms, inputs +2. **Reactive updates** - ensure UI updates correctly +3. **Component communication** - props, events (callbacks) +4. **Lifecycle behavior** - mounting, updating, cleanup +5. **Conditional rendering** - `{#if}`, `{#each}`, `{#await}` + +## Common Migration Issues + +### Issue: Events not firing + +**Problem:** +```svelte + +``` + +**Solution:** +```svelte + +``` + +### Issue: Slot not rendering + +**Problem:** +```svelte +Content +``` + +**Solution:** Update Child to use snippets: +```svelte + +{@render children?.()} +``` + +### Issue: Derived value not updating + +**Problem:** +```svelte +let doubled = count * 2; +``` + +**Solution:** +```svelte +let doubled = $derived(count * 2); +``` + +### Issue: $set no longer exists + +**Problem:** +```js +componentInstance.$set({ prop: 'value' }); +``` + +**Solution:** Use reactive props: +```js +const props = $state({ prop: 'value' }); +mount(Component, { props }); +props.prop = 'new value'; // Updates component +``` + +## Incremental Migration Tips + +1. **Start with leaf components** (no children) +2. **Migrate one component at a time** +3. **Test after each component** +4. **Use compatibility mode** during transition +5. **Update TypeScript types** as you go +6. **Document custom patterns** your team uses + +## Resources + +- Run `npx sv migrate svelte-5` for automated migration +- Check compiler warnings - they guide migration +- Review Svelte 5 docs: https://svelte.dev/docs +- Use VS Code extension for real-time feedback diff --git a/plugins/svelte/skills/svelte-code-writer/references/PATTERNS.md b/plugins/svelte/skills/svelte-code-writer/references/PATTERNS.md new file mode 100644 index 0000000..5511edb --- /dev/null +++ b/plugins/svelte/skills/svelte-code-writer/references/PATTERNS.md @@ -0,0 +1,542 @@ +# Component Patterns and Best Practices + +## TypeScript Integration + +### Always Use TypeScript + +```svelte + +``` + +### Generic Components + +```svelte + + +{#each items as item (item.id)} + {@render renderItem(item)} +{/each} +``` + +## State Management Patterns + +### Minimize State, Maximize Derived + +**❌ Avoid:** +```svelte + +``` + +**✅ Better:** +```svelte + +``` + +### Extract Reusable Reactive Logic + +Use `.svelte.js` or `.svelte.ts` files for shared state: + +```ts +// counter.svelte.ts +export function createCounter(initial = 0) { + let count = $state(initial); + let doubled = $derived(count * 2); + + return { + get count() { return count; }, + get doubled() { return doubled; }, + increment: () => count++, + decrement: () => count--, + }; +} +``` + +```svelte + + + + +``` + +### Class-Based Reactive State + +```svelte + +``` + +## Component Composition + +### Snippet-Based Composition + +```svelte + + + + + {#if header} + {@render header()} + {/if} + + + {#each data as item} + {@render row(item)} + {/each} + + + {#if footer} + {@render footer()} + {/if} +
+``` + +Usage: + +```svelte + + {#snippet header()} + + + + + {/snippet} + + {#snippet row(person)} + + + {/snippet} + + {#snippet footer()} + + {/snippet} +
NameAge
{person.name}{person.age}
Total: {data.length}
+``` + +### Higher-Order Components Pattern + +```svelte + + + +{#if isAuthenticated} + +{:else} +

Please log in

+{/if} +``` + +## Event Handling Patterns + +### Named Event Handlers + +```svelte + + + + + +``` + +### Event Delegation Pattern + +For many similar elements: + +```svelte + + +
+ {#each items as item} + + {/each} +
+``` + +### Custom Event Handlers (Callbacks) + +```svelte + + +``` + +## Form Patterns + +### Two-Way Form Binding + +```svelte + + +
+ + + + +
+``` + +### Custom Form Components + +```svelte + + + + +``` + +## Async Patterns + +### Loading States + +```svelte + + +{#await promise} +

Loading...

+{:then data} + +{:catch error} +

Error: {error.message}

+{/await} +``` + +### Manual Loading State + +```svelte + + +{#if loading} +

Loading...

+{:else if error} +

Error: {error.message}

+{:else if data} + +{/if} +``` + +## Styling Patterns + +### Dynamic Classes + +```svelte + + + +
Content
+ + +
Content
+ + +
Content
+``` + +### Component Style Props + +```svelte + + + + + + +``` + +## Performance Patterns + +### Use Keys in Lists + +```svelte +{#each items as item (item.id)} + +{/each} +``` + +### Use $state.raw for Large Non-Reactive Data + +```svelte + +``` + +### Lazy Loading Components + +```svelte + + +{#if show && HeavyComponent} + +{/if} +``` + +## Testing Patterns + +```typescript +import { mount } from 'svelte'; +import { expect, test } from 'vitest'; +import Component from './Component.svelte'; + +test('increments counter', async () => { + const target = document.createElement('div'); + const instance = mount(Component, { target }); + + const button = target.querySelector('button'); + button?.click(); + + await new Promise(resolve => setTimeout(resolve, 0)); + + expect(target.textContent).toContain('1'); +}); +``` + +## Accessibility Patterns + +```svelte + + + + + +``` diff --git a/plugins/svelte/skills/svelte-code-writer/references/PITFALLS.md b/plugins/svelte/skills/svelte-code-writer/references/PITFALLS.md new file mode 100644 index 0000000..10fe73e --- /dev/null +++ b/plugins/svelte/skills/svelte-code-writer/references/PITFALLS.md @@ -0,0 +1,354 @@ +# Common Pitfalls + +## 1. Using $effect Instead of $derived + +**❌ Wrong:** +```svelte + +``` + +**✅ Correct:** +```svelte + +``` + +## 2. Mutating Non-Bindable Props + +**❌ Wrong:** +```svelte + +``` + +**✅ Correct Option 1 - Callback:** +```svelte + + + +``` + +**✅ Correct Option 2 - $bindable:** +```svelte + + + + + + +``` + +## 3. Accessing Undefined DOM References + +**❌ Wrong:** +```svelte + + + +``` + +**✅ Correct:** +```svelte + + + +``` + +## 4. Missing Keys in Lists + +**❌ Wrong:** +```svelte +{#each items as item} +
{item.name}
+{/each} +``` + +**✅ Correct:** +```svelte +{#each items as item (item.id)} +
{item.name}
+{/each} +``` + +## 5. Using on: Event Directive + +**❌ Wrong (Svelte 4 syntax):** +```svelte + + +``` + +**✅ Correct:** +```svelte + + +``` + +## 6. Using export let for Props + +**❌ Wrong (Svelte 4 syntax):** +```svelte + +``` + +**✅ Correct:** +```svelte + +``` + +## 7. Using Instead of Snippets + +**❌ Wrong (deprecated):** +```svelte + + + +``` + +**✅ Correct:** +```svelte + + + +{@render header?.()} +{@render children?.()} +``` + +## 8. Using createEventDispatcher + +**❌ Wrong (deprecated):** +```svelte + +``` + +**✅ Correct:** +```svelte + +``` + +## 9. Not Handling Undefined Snippets + +**❌ Wrong:** +```svelte + + +{@render header()} +``` + +**✅ Correct:** +```svelte + + +{@render header?.()} + + +{#if header} + {@render header()} +{:else} +

Default Header

+{/if} +``` + +## 10. Destructuring Props Without Types + +**❌ Wrong:** +```svelte + +``` + +**✅ Correct:** +```svelte + +``` + +## 11. Making Everything Bindable + +**❌ Wrong:** +```svelte + +``` + +Only use `$bindable()` when truly needed for two-way binding. + +**✅ Correct:** +```svelte + +``` + +## 12. Using Plain let for Reactive Values + +**❌ Wrong:** +```svelte + + + +``` + +**✅ Correct:** +```svelte + + + +``` + +## 13. Infinite Effect Loops + +**❌ Wrong:** +```svelte + +``` + +Use `untrack` if you must read without tracking: + +```svelte + +``` + +## 14. Forgetting Cleanup in Effects + +**❌ Wrong:** +```svelte + +``` + +**✅ Correct:** +```svelte + +``` + +## 15. Using + +**❌ Wrong (unnecessary):** +```svelte + + + +``` + +**✅ Correct:** +```svelte + + + +``` diff --git a/plugins/svelte/skills/svelte-code-writer/references/RUNES.md b/plugins/svelte/skills/svelte-code-writer/references/RUNES.md new file mode 100644 index 0000000..f337d84 --- /dev/null +++ b/plugins/svelte/skills/svelte-code-writer/references/RUNES.md @@ -0,0 +1,358 @@ +# Runes Reference + +## $state + +Create reactive state that updates the UI when changed. + +### Basic Usage + +```svelte + +``` + +### Deep Reactivity + +Objects and arrays become deeply reactive proxies: + +```svelte + +``` + +### In Classes + +```svelte + +``` + +### $state.raw + +Skip deep reactivity for performance: + +```svelte + +``` + +### $state.snapshot + +Get non-reactive snapshot: + +```svelte + +``` + +## $derived + +Compute values from other state. **Always prefer $derived over $effect for computations.** + +### Basic Usage + +```svelte + +``` + +### $derived.by + +For complex computations: + +```svelte + +``` + +### Dependency Tracking + +Dependencies are tracked at runtime: + +```svelte + +``` + +### Overriding Derived Values + +Since Svelte 5.25, deriveds can be reassigned temporarily: + +```svelte + +``` + +## $effect + +Run side effects when dependencies change. **Use sparingly - most "effects" should be $derived.** + +### Basic Pattern + +```svelte + +``` + +### Timing + +Effects run: +- After component mounts +- After state changes (in microtask) +- After DOM updates + +### Common Use Cases + +**Canvas drawing:** +```svelte + + + +``` + +**Intervals/timers:** +```svelte + +``` + +**Network requests:** +```svelte + +``` + +### $effect.pre + +Run before DOM updates: + +```svelte + +``` + +### Dependency Management + +Dependencies are tracked automatically: + +```svelte + +``` + +To exempt something from tracking, use `untrack`: + +```svelte + +``` + +## $props + +Receive data from parent components. + +### Basic Pattern + +```svelte + +``` + +### Rest Props + +```svelte + + +
{name}
+``` + +### Renaming + +```svelte + +``` + +### Generic Props + +```svelte + +``` + +## $bindable + +Make props two-way bindable. + +### Basic Pattern + +```svelte + + + +``` + +### With Fallback + +Fallback is used when prop is not bound: + +```svelte + + + + + + +``` + +### Function Bindings + +```svelte + + + + value, (v) => value = v.toUpperCase()} /> +``` diff --git a/plugins/svelte/skills/svelte-code-writer/references/TYPESCRIPT.md b/plugins/svelte/skills/svelte-code-writer/references/TYPESCRIPT.md new file mode 100644 index 0000000..97c44f9 --- /dev/null +++ b/plugins/svelte/skills/svelte-code-writer/references/TYPESCRIPT.md @@ -0,0 +1,660 @@ +# TypeScript Patterns + +## Basic Setup + +### Component with Types + +```svelte + +``` + +### Inline Types + +For simple components: + +```svelte + +``` + +## Typing $state + +### Basic State + +```svelte + +``` + +### State with Undefined + +```svelte + +``` + +### Arrays and Objects + +```svelte + +``` + +### Class State + +```svelte + +``` + +## Typing $derived + +```svelte + +``` + +## Typing $props + +### Basic Props Interface + +```svelte + +``` + +### Generic Props + +```svelte + + +{#each items as item (item.id)} + +{/each} +``` + +### Multiple Generics + +```svelte + + +

{data[key]}

+``` + +### Generic with Constraints + +```svelte + + +{#each keys as key} +
{key}: {data[key]}
+{/each} +``` + +## Typing Snippets + +### Basic Snippet + +```svelte + + +{@render children?.()} +``` + +### Snippet with Parameters + +```svelte + + +{#each users as user} + {@render row(user)} +{/each} +``` + +### Multiple Parameters + +```svelte + + +{@render cell(0, 0, 'A1')} +``` + +### Optional Snippets + +```svelte + + +{@render header?.()} +
Content
+{@render footer?.()} +``` + +## Typing $bindable + +```svelte + +``` + +## Component Types + +### Component Type + +```svelte + + + +``` + +### ComponentProps Type + +Extract props from a component: + +```svelte + + + +``` + +### Component Instance Type + +```svelte + + + +``` + +## Event Handler Types + +### DOM Events + +```svelte + + + + +
...
+``` + +### Custom Event Callbacks + +```svelte + +``` + +## Typing with HTML Elements + +### Element References + +```svelte + + +
Content
+ + +``` + +### HTML Attributes + +Extend HTML attributes for custom elements: + +```ts +// app.d.ts +import type { HTMLAttributes } from 'svelte/elements'; + +declare module 'svelte/elements' { + export interface SvelteHTMLElements { + 'custom-element': HTMLAttributes & { + 'custom-prop'?: string; + }; + } +} +``` + +## Typing Actions + +```svelte + + +
{} }}> + Content +
+``` + +## Wrapper Components + +### Extending HTML Element Props + +```svelte + + + +``` + +### Generic Wrapper + +```svelte + + + + {@render children?.()} + +``` + +## Store Types (Legacy) + +If still using stores: + +```svelte + + +{#if $user} +

{$user.name}

+{/if} +``` + +## Advanced Patterns + +### Discriminated Unions + +```svelte + + +{#if state.status === 'loading'} +

Loading...

+{:else if state.status === 'success'} + +{:else if state.status === 'error'} +

Error: {state.error.message}

+{/if} +``` + +### Conditional Props + +```svelte + + +{#if props.mode === 'edit'} + +{/if} +``` + +### Type Guards + +```svelte + +``` + +## tsconfig.json Settings + +Recommended settings: + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} +``` + +## Common Type Issues + +### Issue: Type not inferred in $derived + +```svelte + +``` + +### Issue: Snippet parameter types + +```svelte + +``` + +### Issue: Generic component props + +```svelte + +``` From ddfb0fe3aae76e5bf917029dbbcda5a538c2d93a Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Fri, 14 Nov 2025 12:08:45 +0100 Subject: [PATCH 2/3] fix: lint --- .../references/MIGRATION.md | 300 ++++---- .../svelte-code-writer/references/PATTERNS.md | 581 +++++++-------- .../svelte-code-writer/references/PITFALLS.md | 242 ++++--- .../svelte-code-writer/references/RUNES.md | 293 ++++---- .../references/TYPESCRIPT.md | 670 +++++++++--------- 5 files changed, 1090 insertions(+), 996 deletions(-) diff --git a/plugins/svelte/skills/svelte-code-writer/references/MIGRATION.md b/plugins/svelte/skills/svelte-code-writer/references/MIGRATION.md index ee2497c..961b0be 100644 --- a/plugins/svelte/skills/svelte-code-writer/references/MIGRATION.md +++ b/plugins/svelte/skills/svelte-code-writer/references/MIGRATION.md @@ -15,6 +15,7 @@ Svelte 5 is largely backward compatible. You can mix Svelte 4 and 5 syntax, and ## Automatic Migrations The migration script handles: + - `let` → `$state()` - `$:` derivations → `$derived()` - `$:` effects → `$effect()` or `run()` @@ -28,38 +29,42 @@ The migration script handles: ### 1. Component Events (createEventDispatcher) **Before:** + ```svelte ``` **After:** + ```svelte ``` **Parent usage before:** + ```svelte ``` **Parent usage after:** + ```svelte ``` @@ -67,48 +72,51 @@ The migration script handles: ### 2. beforeUpdate/afterUpdate **Before:** + ```svelte ``` **After:** + ```svelte ``` ### 3. Component Instantiation **Before:** + ```js import App from './App.svelte'; const app = new App({ - target: document.getElementById('app'), - props: { name: 'world' } + target: document.getElementById('app'), + props: { name: 'world' }, }); app.$set({ name: 'everybody' }); @@ -117,14 +125,15 @@ app.$destroy(); ``` **After:** + ```js import { mount, unmount } from 'svelte'; import App from './App.svelte'; const props = $state({ name: 'world' }); const app = mount(App, { - target: document.getElementById('app'), - props + target: document.getElementById('app'), + props, }); // Instead of $set: @@ -132,9 +141,9 @@ props.name = 'everybody'; // Instead of $on: const app = mount(App, { - target: document.getElementById('app'), - props, - events: { event: callback } + target: document.getElementById('app'), + props, + events: { event: callback }, }); // Instead of $destroy: @@ -144,6 +153,7 @@ unmount(app); ### 4. Server-Side Rendering **Before:** + ```js import App from './App.svelte'; @@ -151,12 +161,13 @@ const { html, css, head } = App.render({ name: 'world' }); ``` **After:** + ```js import { render } from 'svelte/server'; import App from './App.svelte'; const { html, head } = render(App, { - props: { name: 'world' } + props: { name: 'world' }, }); ``` @@ -167,34 +178,36 @@ Note: CSS is no longer returned by default. Set compiler option `css: 'injected' If using stores, they still work but consider migrating to runes: **Before:** + ```svelte ``` **After (using runes):** + ```svelte ``` @@ -203,88 +216,95 @@ If using stores, they still work but consider migrating to runes: ### Pattern 1: Reactive Statements **Before:** + ```svelte ``` **After:** + ```svelte ``` ### Pattern 2: Props with Defaults **Before:** + ```svelte ``` **After:** + ```svelte ``` ### Pattern 3: Slot Props **Before:** + ```svelte - {user.name} is {user.age} + {user.name} is {user.age} ``` **After:** + ```svelte {@render children({ name: 'Alice', age: 30 })} - {#snippet children(user)} - {user.name} is {user.age} - {/snippet} + {#snippet children(user)} + {user.name} is {user.age} + {/snippet} ``` ### Pattern 4: Named Slots **Before:** + ```svelte
@@ -293,22 +313,27 @@ If using stores, they still work but consider migrating to runes: -

Title

-

Content

-

Footer

+

Title

+

Content

+

Footer

``` **After:** + ```svelte
{@render header?.()}
@@ -317,29 +342,31 @@ If using stores, they still work but consider migrating to runes: - {#snippet header()} -

Title

- {/snippet} - -

Content

- - {#snippet footer()} -

Footer

- {/snippet} + {#snippet header()} +

Title

+ {/snippet} + +

Content

+ + {#snippet footer()} +

Footer

+ {/snippet}
``` ### Pattern 5: Event Forwarding **Before:** + ```svelte ``` **After:** + ```svelte @@ -348,24 +375,26 @@ If using stores, they still work but consider migrating to runes: ### Pattern 6: Prop Spreading with Events **Before:** + ```svelte ``` **After:** + ```svelte ``` @@ -375,15 +404,16 @@ The migration script may create `run()` calls for `$:` statements it can't deter ```svelte ``` **Action required:** Review each `run()` and convert to: + - `$derived()` if it's computing a value - `$effect()` if it's a side effect - Remove if no longer needed @@ -397,11 +427,11 @@ Use `compatibility.componentApi: 4` to keep `new Component()` working: ```js // svelte.config.js export default { - compilerOptions: { - compatibility: { - componentApi: 4 - } - } + compilerOptions: { + compatibility: { + componentApi: 4, + }, + }, }; ``` @@ -412,21 +442,24 @@ This adds some overhead but helps during migration. ### 1. Bindings Need $bindable **Before (Svelte 4):** + ```svelte ``` All props were bindable in parent: + ```svelte ``` **After (Svelte 5):** + ```svelte ``` @@ -435,22 +468,25 @@ Only explicitly bindable props can use `bind:`. ### 2. No Implicit Reactivity **Before:** + ```svelte ``` **After:** + ```svelte ``` ### 3. Component Type Changes **Before:** + ```ts import type { SvelteComponent } from 'svelte'; import MyComponent from './MyComponent.svelte'; @@ -460,6 +496,7 @@ let ComponentType: typeof SvelteComponent; ``` **After:** + ```ts import type { Component } from 'svelte'; import MyComponent from './MyComponent.svelte'; @@ -483,38 +520,45 @@ After migrating, test: ### Issue: Events not firing **Problem:** + ```svelte - + ``` **Solution:** + ```svelte - + ``` ### Issue: Slot not rendering **Problem:** + ```svelte -Content +Content ``` **Solution:** Update Child to use snippets: + ```svelte + {@render children?.()} ``` ### Issue: Derived value not updating **Problem:** + ```svelte -let doubled = count * 2; +let doubled = count * 2; ``` **Solution:** + ```svelte let doubled = $derived(count * 2); ``` @@ -522,15 +566,17 @@ let doubled = $derived(count * 2); ### Issue: $set no longer exists **Problem:** + ```js componentInstance.$set({ prop: 'value' }); ``` **Solution:** Use reactive props: + ```js const props = $state({ prop: 'value' }); mount(Component, { props }); -props.prop = 'new value'; // Updates component +props.prop = 'new value'; // Updates component ``` ## Incremental Migration Tips diff --git a/plugins/svelte/skills/svelte-code-writer/references/PATTERNS.md b/plugins/svelte/skills/svelte-code-writer/references/PATTERNS.md index 5511edb..836df4f 100644 --- a/plugins/svelte/skills/svelte-code-writer/references/PATTERNS.md +++ b/plugins/svelte/skills/svelte-code-writer/references/PATTERNS.md @@ -6,12 +6,12 @@ ```svelte ``` @@ -19,16 +19,16 @@ ```svelte {#each items as item (item.id)} - {@render renderItem(item)} + {@render renderItem(item)} {/each} ``` @@ -37,25 +37,27 @@ ### Minimize State, Maximize Derived **❌ Avoid:** + ```svelte ``` **✅ Better:** + ```svelte ``` @@ -66,28 +68,32 @@ Use `.svelte.js` or `.svelte.ts` files for shared state: ```ts // counter.svelte.ts export function createCounter(initial = 0) { - let count = $state(initial); - let doubled = $derived(count * 2); - - return { - get count() { return count; }, - get doubled() { return doubled; }, - increment: () => count++, - decrement: () => count--, - }; + let count = $state(initial); + let doubled = $derived(count * 2); + + return { + get count() { + return count; + }, + get doubled() { + return doubled; + }, + increment: () => count++, + decrement: () => count--, + }; } ``` ```svelte ``` @@ -95,28 +101,28 @@ export function createCounter(initial = 0) { ```svelte ``` @@ -127,32 +133,32 @@ export function createCounter(initial = 0) { ```svelte - {#if header} - {@render header()} - {/if} - - - {#each data as item} - {@render row(item)} - {/each} - - - {#if footer} - {@render footer()} - {/if} + {#if header} + {@render header()} + {/if} + + + {#each data as item} + {@render row(item)} + {/each} + + + {#if footer} + {@render footer()} + {/if}
``` @@ -160,21 +166,21 @@ Usage: ```svelte - {#snippet header()} - - - - - {/snippet} - - {#snippet row(person)} - - - {/snippet} - - {#snippet footer()} - - {/snippet} + {#snippet header()} + + + + + {/snippet} + + {#snippet row(person)} + + + {/snippet} + + {#snippet footer()} + + {/snippet}
NameAge
{person.name}{person.age}
Total: {data.length}
NameAge
{person.name}{person.age}
Total: {data.length}
``` @@ -183,27 +189,27 @@ Usage: ```svelte {#if isAuthenticated} - + {:else} -

Please log in

+

Please log in

{/if} ``` @@ -213,19 +219,19 @@ Usage: ```svelte @@ -239,21 +245,21 @@ For many similar elements: ```svelte
- {#each items as item} - - {/each} + {#each items as item} + + {/each}
``` @@ -262,21 +268,21 @@ For many similar elements: ```svelte ``` @@ -286,29 +292,29 @@ For many similar elements: ```svelte
- - - - + + + +
``` @@ -317,27 +323,27 @@ For many similar elements: ```svelte ``` @@ -347,15 +353,15 @@ For many similar elements: ```svelte {#await promise} -

Loading...

+

Loading...

{:then data} - + {:catch error} -

Error: {error.message}

+

Error: {error.message}

{/await} ``` @@ -363,33 +369,33 @@ For many similar elements: ```svelte {#if loading} -

Loading...

+

Loading...

{:else if error} -

Error: {error.message}

+

Error: {error.message}

{:else if data} - + {/if} ``` @@ -399,21 +405,18 @@ For many similar elements: ```svelte
Content
-
Content
+
+ Content +
Content
@@ -424,33 +427,45 @@ For many similar elements: ```svelte ``` @@ -460,7 +475,7 @@ For many similar elements: ```svelte {#each items as item (item.id)} - + {/each} ``` @@ -468,8 +483,8 @@ For many similar elements: ```svelte ``` @@ -477,20 +492,20 @@ For many similar elements: ```svelte {#if show && HeavyComponent} - + {/if} ``` @@ -502,15 +517,15 @@ import { expect, test } from 'vitest'; import Component from './Component.svelte'; test('increments counter', async () => { - const target = document.createElement('div'); - const instance = mount(Component, { target }); - - const button = target.querySelector('button'); - button?.click(); - - await new Promise(resolve => setTimeout(resolve, 0)); - - expect(target.textContent).toContain('1'); + const target = document.createElement('div'); + const instance = mount(Component, { target }); + + const button = target.querySelector('button'); + button?.click(); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(target.textContent).toContain('1'); }); ``` @@ -518,25 +533,21 @@ test('increments counter', async () => { ```svelte -