The migration tool (sv migrate svelte-5) automates conversion from Svelte 4 to Svelte 5 syntax. It parses .svelte files, builds an AST with component analysis, and applies transformations using the zimmerframe walker with MagicString for code modifications.
The tool performs the following conversions:
| Svelte 4 Pattern | Svelte 5 Pattern | Handler |
|---|---|---|
export let prop | let { prop } = $props() | index.js565-915 |
$: derived = x * 2 | let derived = $derived(x * 2) | index.js925-1038 |
$: { sideEffect() } | run(() => { sideEffect() }) | index.js1012-1037 |
<slot /> | {@render children?.()} | Template walker |
on:click={handler} | onclick={handler} | Template walker |
<svelte:component this={C} /> | <C /> | Template walker |
Not Migrated: createEventDispatcher, beforeUpdate, afterUpdate require manual changes.
CLI:
IDE/Playground:
Entry Point: migrate(source, { filename?, use_ts? }) exported from packages/svelte/src/compiler/migrate/index.js124
Sources:
Migration Flow:
Core Functions:
Sources:
The State object passed to walker visitors tracks transformation context:
| Property | Type | Purpose |
|---|---|---|
str | MagicString | Source code transformation buffer |
scope | Scope | Current lexical scope for identifier resolution |
analysis | ComponentAnalysis | Metadata from analyzer phase |
props | Array<PropData> | Collected prop declarations for $props() |
legacy_imports | Set<string> | Helper functions needed from svelte/legacy |
derived_components | Map<string, string> | Components requiring $derived() wrapper |
names | Record<string, string> | Generated unique identifiers (e.g., props_1, run_1) |
PropData Structure:
Sources:
The migration tool converts export let declarations to the $props() rune:
| Svelte 4 Pattern | Svelte 5 Pattern |
|---|---|
export let name; | let { name } = $props(); |
export let name = 'default'; | let { name = 'default' } = $props(); |
export let name: string; | let { name }: { name: string } = $props(); |
export { renamed as name } | let { name: renamed } = $props(); |
(binding) <input bind:value={prop}> | let { prop = $bindable() } = $props(); |
The transformation process:
VariableDeclaration visitor identifies export let packages/svelte/src/compiler/migrate/index.js565-915state.props array packages/svelte/src/compiler/migrate/index.js656-671Example transformation:
Sources:
The LabeledStatement visitor analyzes $: statements and applies transformations based on assignment patterns:
Decision Tree:
Reordering: If reactive statements reference variables declared later, all reactive statements are moved to the end of the script block in dependency order packages/svelte/src/compiler/migrate/index.js369-406
Examples:
Sources:
Event directives (on:event) are converted to event attributes (onevent) by the template walker's element handlers.
Transformation Table:
| Svelte 4 Pattern | Svelte 5 Output | Legacy Helper |
|---|---|---|
on:click={handler} | onclick={handler} | None |
on:click (forwarding) | onclick={bubble('click')} | createBubbler |
on:click|preventDefault | onclick={preventDefault(() => ...)} | preventDefault |
on:click|stopPropagation | onclick={stopPropagation(() => ...)} | stopPropagation |
on:click|once | onclick={once(() => ...)} | once |
on:click|capture | onclickcapture={...} | None (syntax) |
on:click|self | onclick={self(() => ...)} | self |
Multiple Handlers: Combined into handlers() call:
Event Bubbling Setup:
When event forwarding is detected, the tool injects:
Then uses: onclick={bubble('click')}
Sources:
The tool converts <slot> elements to snippet-based rendering:
| Pattern | Before | After |
|---|---|---|
| Default slot | <slot /> | {@render children?.()} |
| Named slot | <slot name="header" /> | {@render header?.()} |
| Slot with props | <slot user={data} /> | {@render children?.({ user: data })} |
| Slot checking | {#if $$slots.foo} | {#if foo} |
Props Generation: Slots become snippet props in the component signature:
Conflict Resolution: When $$props is used alongside slots, the tool wraps props:
Then accesses snippets via props.children, props.header, etc. packages/svelte/tests/migrate/samples/slots-with-$$props/output.svelte
Sources:
<svelte:component> elements are transformed to direct component references or $derived expressions:
Simple Cases:
Dynamic Cases:
Let Directive Context:
The tool generates unique names (e.g., SvelteComponent_10) to avoid conflicts packages/svelte/tests/migrate/samples/svelte-component/output.svelte7
Sources:
<svelte:self> MigrationThe tool replaces <svelte:self> with an import of the component itself:
This transformation requires the filename option to be provided packages/svelte/src/compiler/migrate/index.js1076-1087 If no filename is available, a migration task comment is inserted instead packages/svelte/src/compiler/migrate/index.js1078-1086
Sources:
The CSS transformation focuses on :global() pseudo-class adjustments:
Target Pattern: :has, :is, :where, :not selectors that contain scoped selectors need :global() wrapping to maintain specificity:
The transformation:
:global content unless it's a :not selector packages/svelte/src/compiler/migrate/index.js83-89CSS Blanking: During parsing, CSS content is replaced with a placeholder to prevent preprocessor syntax (SCSS, Less, etc.) from interfering with the parser packages/svelte/src/compiler/migrate/index.js128-135
Sources:
The tool adds imports from svelte/legacy when patterns cannot be cleanly migrated to runes:
Helper Functions:
| Import | Trigger | Purpose |
|---|---|---|
run | $: side effects | Executes callback like $: (runs once on server, as $effect.pre on client) |
createBubbler | on:event forwarding | Returns bubble(eventName) function for event propagation |
handlers | Multiple on:event | Combines multiple event handlers |
preventDefault | on:event|preventDefault | Wraps handler with e.preventDefault() |
stopPropagation | on:event|stopPropagation | Wraps handler with e.stopPropagation() |
once | on:event|once | Ensures handler runs only once |
self | on:event|self | Only runs if e.target === e.currentTarget |
Name Collision Handling:
Helpers are renamed if names conflict with existing identifiers:
Generated via analysis.root.unique(name).name packages/svelte/src/compiler/migrate/index.js177-192
Sources:
When migration cannot be completed automatically, the tool generates @migration-task comments:
Error Cases:
beforeUpdate/afterUpdate usage: Not auto-migrated documentation/docs/07-misc/07-v5-migration-guide.md517createEventDispatcher usage: Not auto-migrated documentation/docs/07-misc/07-v5-migration-guide.md515state, derived, props, bindable) collide with existing variables packages/svelte/src/compiler/migrate/index.js275-282Example Output:
The tool logs migration tasks to the console upon completion packages/svelte/src/compiler/migrate/index.js452-457
Sources:
Not Migrated Automatically:
createEventDispatcher → callback props (component API change)beforeUpdate → $effect.pre (semantic differences)afterUpdate → $effect + tick() (semantic differences)run())export let patterns packages/svelte/src/compiler/migrate/index.js601-628Manual Intervention Required:
Compatibility Options:
For gradual migration, Svelte 5 supports backward compatibility:
<svelte:options runes={false}> to disable runes in a componentcompatibility.componentApi: 4 compiler option to preserve new Component() API documentation/docs/07-misc/07-v5-migration-guide.md586-596svelte/legacy module for bridging patternsSources:
Refresh this wiki