This skill covers Svelte 4's Legacy API, which is still usable in Svelte 5 (Legacy Mode), but migrating to Runes is recommended. This skill is only for understanding and migrating Legacy code,new code should use Runes。
When to use this skill
Use this skill when the user needs to maintain a Svelte 4 project, understand $: reactive statements,export let props、createEventDispatcher, Slots, or needs to migrate Legacy code to Svelte 5 Runes Mode.
Critical: Reactive let declarations
In Legacy Mode (components not using Runes), top-level let declarations automatically become reactive:
<script>
let count = 0; // 自动响应式
</script>
<button on:click={() => count++}> {count}
> Svelte 5 的 Runes Mode:`let count = $state(0)`。
### 数组/对象变更注意
Legacy 响应式基于**赋值**,数组方法(`.push()`、`.splice()`)不触发更新:
```svelte
<script>
let items = [1, 2, 3];
function addItem() {
items.push(4); // ❌ 不触发更新
items = items; // ✅ 触发
// 或
items = [...items, 4]; // ✅ 触发
}
</script>
Critical: Reactive $: statements
In Legacy Mode,$: labeled statements create reactive statements:
<script>
let a = 1;
let b = 2;
$: sum = a + b; // 依赖 a, b
$: console.log(`sum = ${sum}`); // 依赖 sum
</script>
Topological sorting
$: statements are topologically sorted by dependency (order-independent):
$: z = y; // z 依赖 y
$: y = x; // y 依赖 x
$: x = 5; // x 无依赖
Block-level statements
$: {
total = 0;
for (const item of items) total += item.value;
}
Critical: export let
In Legacy Mode, declare props with export let :
<script>
export let name; // 必需 prop
export let age = 18; // 带默认值
export let items = []; // 数组默认值
</script>
Svelte 5 Runes:
let { name, age = 18 } = $props()
Re-export (API)
<script>
export function greet(name) {
alert(`Hello ${name}!`);
}
export const VERSION = '1.0';
</script>
The parent component calls via bind:this :
<script>
let greeter;
</script>
<Greeter bind:this={greeter} />
<button on:click={() => greeter.greet('world')}>greet</button>
prop renaming
<script>
export { foo as bar }; // 对外叫 bar,内部叫 foo
</script>
Critical: $$props / $$restProps
$$props (get all passed-in props)
<script>
let { foo, ...rest } = $$props;
// foo 是显式 prop,rest 是其余所有 props
</script>
Svelte 5 Runes:
let { foo, ...rest } = $props()
$$restProps (only the remaining props)
<script>
export let required;
let { ...rest } = $$props;
</script>
<!-- required 不在 rest 中 -->
Critical: createEventDispatcher
Legacy's event dispatch mechanism (replaced by Svelte 5 callback props):
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function handleClick() {
dispatch('change', { value: 42 });
dispatch('complete'); // 无数据
}
</script>
<button on:click={handleClick}>发送</button>
Using events
<Component on:change={(e) => console.log(e.detail)} />
Typed (TypeScript)
<script lang="ts">
const dispatch = createEventDispatcher<{
change: { value: number };
complete: null;
}>();
</script>
Svelte 5 recommends: use callback props directly:
<Component onChange={(detail) => handle(detail)} />
Critical: Slots
<slot> is Legacy's content-passing mechanism (Svelte 5 replaces it with Snippets):
Default slot
<!-- Component.svelte -->
<div class="card">
<slot />
</div>
<!-- 使用 -->
<Component>
<p>内容</p>
</Component>
Named slot
<!-- Component.svelte -->
<slot name="footer" />
<slot name="header" />
<!-- 使用 -->
<Component>
<p slot="header">头部</p>
<p slot="footer">尾部</p>
</Component>
Slot props (passing data to slot)
<!-- Component.svelte -->
<slot value={count} onClick={handleClick} />
<!-- 使用 -->
<Component let:value let:onClick>
<p>{value}</p>
<button on:click={onClick}>click</button>
</Component>
Note: In Svelte 5, default slot props require the
let:prefix; named slots' slot props are not exposed to named slots.
$$slots (check whether a slot exists)
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
$: if (!$$slots.footer) {
// footer slot 未被提供
}
</script>
Critical: svelte:component / svelte:self
<svelte:component>(dynamic components)
<script>
let Current = FirstComponent;
$: Current = showSecond ? SecondComponent : FirstComponent;
</script>
<svelte:component this={Current} />
Svelte 5 recommends:
<svelte:element this={Current} />
<svelte:self>(recursive components)
<script>
import { svelte:self } from 'svelte';
</script>
<svelte:self />
Critical: svelte:fragment
<!-- Component.svelte -->
<slot name="title" />
<div>
<slot name="body" />
</div>
<!-- 使用 -->
<Component>
<h1 slot="title">Title</h1>
<svelte:fragment slot="body">
<p>多元素</p>
<p>不会被包裹</p>
</svelte:fragment>
</Component>
Critical: v5 Migration Quick Reference
| Legacy (Svelte 4) | Modern (Svelte 5) |
|---|---|
let count = 0(top-level reactive) |
let count = $state(0) |
$: x = expr |
let x = $derived(expr) |
$: { if (cond) x = v } |
$effect(() => { if (cond) x = v }) |
export let prop |
let { prop } = $props() |
$$props |
let { ...rest } = $props() |
<slot /> |
{@render children()} |
createEventDispatcher |
callback props |
on:click={fn} |
onclick={fn} |
<svelte:component this={x}> |
<svelte:element this={x}> |
<svelte:self> |
import Self from './This.svelte' + <Self> |
class:cool={isCool} |
class={{ cool: isCool }} |
let:slotProp={value} |
Snippets automatically passed |
Gotchas
- Legacy
$:is assignment-based — array.push()and other methods do not trigger updates - Slot props behave differently — default slot can access slot props, named slots cannot
createEventDispatcheris deprecated — not recommended in Svelte 5, new code should use callback props$$slotsis injected at compile time — can be used directly in script$:reference<svelte:self>requires import —import { svelte:self } from 'svelte'
Critical: Svelte 4 Migration (Svelte 3 → Svelte 4)
If you are maintaining a Svelte 3 project and upgrading to Svelte 4, the following breaking changes must be checked item by item. For detailed comparison and automated scripts, see examples/migration-v4.md and references/migration-v4-reference.md, source baseline at /tmp/svelte-llms.txt:6182。
Recommended starting point:
npx svelte-migrate@latest svelte-4— the script automatically handles most items; the rest require manual intervention.
Minimum version requirements
{
"engines": { "node": ">=16" },
"devDependencies": {
"svelte": "^4.0.0",
"@sveltejs/kit": ">=1.20.4",
"vite-plugin-svelte": ">=2.4.1",
"svelte-loader": ">=3.1.8",
"rollup-plugin-svelte": ">=7.1.5",
"typescript": ">=5"
}
}
Major breaking changes quick reference
| Category | Svelte 3 | Svelte 4 |
|---|---|---|
| Bundler browser condition | Optional | Rollup browser: true / Webpack conditionNames: ['browser'] |
| CJS output | Support | Removed (compiler,svelte/register、runtime) |
createEventDispatcher type |
Optional payload | Strict:T | undefined optional;T required;null disable detail |
Action / ActionReturn |
params implicit any |
must be explicit Action<HTMLElement, T> |
onMount async cleanup |
no error | A cleanup function that returns a Promise is ignored (TS error) |
| Custom Element | <svelte:options tag="x" /> |
<svelte:options customElement="x" />(string or object) |
SvelteComponentTyped |
with SvelteComponent coexist |
deprecated, unified as SvelteComponent |
| Transitions default scope | Any ancestor block triggers | Only the direct parent block triggers; add |global to restore the old behavior |
| Default slot bindings | Named slots are also visible | Only the default slot is visible |
| Preprocessor order | markup×N, script×N, style×N | markup, script, style ×N (three segments inside each preprocessor) |
eslint-plugin-svelte3 |
Recommended | Deprecated, change to eslint-plugin-svelte |
inert on outroing |
Not applied | Applied by default (accessibility improvement) |
classList.toggle |
Compatible with old browsers | Requires a polyfill |
CustomEvent |
Compatible with old browsers | Requires a polyfill |
derived falsy values |
Warning | Throw |
svelte/internal types |
Exposed | Removed |
svelte.JSX namespace |
Use | Change to svelteHTML / svelte/elements |
Library authors peerDependencies |
^3 |
^3 || ^4 |
What automation scripts can handle
tag=→customElement=(string form)SvelteComponentTyped→SvelteComponentAction/ActionReturngeneric parameter- Transitions default behavior (add the
|globalmodifier)
What needs to be handled manually
- Bundler
browsercondition configuration (Rollup / Webpack) - Fixes for strict type errors (
createEventDispatcherpayload) onMountasync cleanup changed to synchronous- Preprocessor
nameattributes + order adjustments - ESLint plugin replacement + config migration
derivedfalsy value edge cases
Custom Element upgrade
<!-- v3 --><svelte:options tag="my-counter" />
<!-- v4 --><svelte:options customElement={{ tag: 'my-counter', props: { initial: { type: Number } } }} />
onMount async cleanup anti-pattern
// ❌ v4 报错且永远不清理
onMount(async () => { const data = await fetch('/api'); setup(data); return () => teardown(); });
// ✅ 同步返回值
onMount(() => { fetch('/api').then(setup); return () => teardown(); });
Preprocessor order: MDsveX must come first
// svelte.config.js
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { mdsvex } from 'mdsvex';
export default {
preprocess: [
mdsvex({ name: 'mdsvex' }), // 先 markup 再 script
vitePreprocess({ name: 'vite' })
]
};
v3: all markup → all script → all style. v4: inside each preprocessor, markup → script → style, then the next one. Each preprocessor must declare
name。
Library authors' peerDependencies
{
"peerDependencies": { "svelte": "^3.0.0 || ^4.0.0" },
"devDependencies": { "svelte": "^4.0.0" }
}
Run-it-Yourself Checklist
npm install svelte@^4 # 1. 升级依赖
npx svelte-migrate@latest svelte-4 # 2. 跑迁移脚本
npx svelte-check # 3. 看 TypeScript
npm test # 4. 跑测试
If many Action generic errors appear, it means the migration script missed them; manually add <HTMLElement, YourType>。
Further v5 migration is in svelte-runes / svelte-misc; this skill only covers the Svelte 4 upgrade.
Quick Fixes
| Problem | Solution |
|---|---|
Array .push() not updating |
items = [...items, newItem] or items = items |
| slot styles not taking effect | Style isolation is scoped and does not affect slot content |
$$restProps type error |
Confirm it is export let and not let |
| EventDispatcher type issue | Upgrade TypeScript to 5+ |
FAQ
Q: When must the Legacy API be used? A: When maintaining an un-migrated Svelte 4 project. New projects should use Runes.
Q: Can Legacy and Runes be mixed?
A: Yes. Once a component uses $state , it enters Runes Mode, and the parts not using it use Legacy syntax.
Q: $: and $derived ?
A: The functionality is similar, but $derived is Svelte 5's Runes API, which is more explicit and controllable.$: is available in Legacy Mode.
Q: Which is better, Slot or Snippet? A: Snippets are more powerful and flexible. Slot is suitable for simple content passing; Snippet can accept parameters, can be used where it is defined, and has better type safety.
Cross-References
This skill is dedicated to Svelte Legacy maintenance, and works in coordination with the following skills:
- Svelte 5 entry and learning path → hand off to
svelte-awesomeskill. Install:npx skills add full-stack-skills/svelte-skills --skill svelte-awesome. - Svelte 4 → Svelte 5 migration comparison → hand off to
svelte-miscskill (migration section). Install:npx skills add full-stack-skills/svelte-skills --skill svelte-misc. - Runes reactivity (
$state/$derived/$effect)→ hand off tosvelte-runesskill. Install:npx skills add full-stack-skills/svelte-skills --skill svelte-runes. - Stores(writable / readable / derived)→ hand off to
svelte-lifecycleskill. Install:npx skills add full-stack-skills/svelte-skills --skill svelte-lifecycle. - Template syntax (snippet / event / bind) → hand off to
svelte-template-syntaxskill. Install:npx skills add full-stack-skills/svelte-skills --skill svelte-template-syntax. - Debugging reactivity not updating /
$inspect→ hand off tosvelte-lifecycleskill. Install:npx skills add full-stack-skills/svelte-skills --skill svelte-lifecycle. - Compile components as Web Components → hand off to
svelte-miscskill (Custom Elements section). Install:npx skills add full-stack-skills/svelte-skills --skill svelte-misc. - Initialization and migration scripts → hand off to
svelte-cliskill(sv migrate svelte-5). Install:npx skills add full-stack-skills/svelte-skills --skill svelte-cli.
Examples
Practical examples for working with Legacy APIs:
- examples/README.md - Examples entry point
- examples/migration-scripts.md - Migration workflows (svelte-migrate, let→$state, $:→$derived, etc.)
- examples/legacy-mode.md - Legacy mode usage, mixing legacy and runes
- examples/store-comparison.md - Store patterns in Svelte 4 and Svelte 5
- examples/migration-v4.md - Svelte 3 → Svelte 4 migration with 12 practical examples (bundler configuration, strict types, Custom Element, transition, slot, preprocessor, etc.)
References
Complete reference documentation:
- references/README.md - References entry point
- references/migration-reference.md - Complete migration reference with before/after code
- references/legacy-mode-reference.md - Legacy mode behavior and limitations
- references/store-reference.md - Store API reference (writable, readable, derived, custom stores)
- references/migration-v4-reference.md - Svelte 3 → Svelte 4 complete comparison reference (minimum version, bundler, strict types, Custom Element, slot, preprocessor, other breaking changes, library author guidance)