Svelte - v5 Migration
Big fan of SvelteJS as framework and in particular the Svelte monolith that is SvelteKit.
I maintain a scaffolding repo which act as a base to most Svelte projects I create. You can see that scaffold here.
As of October 2024 Svelte released Svelte 5, their biggest release yet as covered here, but it brings some significant changes to day to day Svelte writing and patterns.
This article aims to show:
- How to easily upgrade a SvelteKit 2.0 project using Svelte 4
- What the changes are and why they've come about
Upgrading
Upgrading in Svelte couldn't be easier, because of the sv migrate tooling.
Usage
Within the repo run npx sv migrate TARGET
where target here is svelte-5, so npx sv migrate svelte-5
. Check the sv migrate docs for other available targets.
sv migrate will then ask:
- Ask about current dir, since some of the change will impact a mono-repo
- Ask which folders to refactor
- Ask if you want to tooling to convert components to new syntax
My repo is fairly simple so I went ahead and said yes and selected all the option on the above.
Changes - Packages
One thing of note here about sv migrate
vs a manual package upgrade is that you don't need to trial a bunch of module versions before you reach the right mix.
Having tried it before I will tell you now that: upgrading all packages to latest will break your build.
sv migrate already contains the right module combinations and here are the changes.
Updated svelte to ^5.0.0
Updated @sveltejs/kit to ^2.5.27
Updated @sveltejs/vite-plugin-svelte to ^4.0.0
Updated prettier-plugin-svelte to ^3.2.6
Updated eslint-plugin-svelte to ^2.45.1
Updated typescript to ^5.5.0
Updated vite to ^5.4.4
Latest versions for reference
svelte 5.20.0
@sveltejs/kit 2.17.1
@sveltejs/vite-plugin-svelte 5.0.3
prettier-plugin-svelte 3.3.3
eslint-plugin-svelte 2.46.1
typescript 5.7.3
vite 6.1.0
As you can see things move fast, and in the 3 months since release we've already had a fair bit of drift. This is mostly fine since Svelte is relatively stable.
Of most concerns is the vite major release of v6, and the large number of minor versions on the svelte packages.
Vite v5 vs v6
In my context I'm ok with a lagging codebase and will upgrade whenever crutial.
For anyone else who might be impacted I wanted to summarise what changed in vite v6, Read further in the migration guide, https://vite.dev/guide/migration.html.
v6 updates:
- Environement API refactors; experimental api included which changes to interface; opt-in only
- Runtime API: experimental Module Runner API, requires using the new API after updating
- resolve.conditions: impacts those with .conditions config; from v6 conditions need to be included manually as oposed to being defaulted, allows ssr and regular configs to be different (whilst before ssr would inherit from regular config), if you have previous config in v6 you need to add the defaults per resolution
- JSON.stringify: in v6 json.strigify: true no longer changes json.namedExports: disabled; json.namedExports is respected; strigify also has a new value of auto, which only stringifies large objects.
- Larger support for asset references in HTML element: v5 only and could reference assets. v6 has an extended list https://vite.dev/guide/features#html
- CSS: postcss-load-config updated in v6, sass uses modern api by default, custom CSS output file in library mode
Svelte minor releases difference
Because of the volume of releases there's a possibility here that we might be missing key bug fixes.
Installing packages
Now lets install the migrated packages and see what happens.
- Remove previous installed packages
rm -rf node_modules/ && rm package-lock.json
- Install with package of choice (npm)
npm install
Issues:
-
@sveltejs/vite-plugin-svelte@^4.0.0
not found (see logs below)
-
-
- {@render children?.()} throwing error about unrecognised @ symbol
npm error code ERESOLVE
npm error ERESOLVE could not resolve
npm error
npm error While resolving: streak-tracker--svelte@0.0.1
npm error Found: @sveltejs/vite-plugin-svelte@3.1.2
npm error node_modules/@sveltejs/vite-plugin-svelte
npm error dev @sveltejs/vite-plugin-svelte@"^4.0.0" from the root project
npm error peer @sveltejs/vite-plugin-svelte@"^3.0.0 || ^4.0.0-next.1" from @sveltejs/kit@2.6.4
npm error node_modules/@sveltejs/kit
npm error dev @sveltejs/kit@"^2.5.27" from the root project
npm error peer @sveltejs/kit@"^2.0.0" from @sveltejs/adapter-auto@3.2.5
npm error node_modules/@sveltejs/adapter-auto
npm error dev @sveltejs/adapter-auto@"^3.0.0" from the root project
npm error 1 more (@sveltejs/vite-plugin-svelte-inspector)
npm error
npm error Could not resolve dependency:
npm error dev @sveltejs/vite-plugin-svelte@"^4.0.0" from the root project
npm error
npm error Conflicting peer dependency: svelte@5.20.0
npm error node_modules/svelte
npm error peer svelte@"^5.0.0-next.96 || ^5.0.0" from @sveltejs/vite-plugin-svelte@4.0.4
npm error node_modules/@sveltejs/vite-plugin-svelte
npm error dev @sveltejs/vite-plugin-svelte@"^4.0.0" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error
npm error For a full report see:
Fixing errors
-
- @sveltejs/vite-plugin-svelte: not a real error, but I suspect an issue with timeout during the npm i process; making sure I have a clean install by removing node_modules and package-lock.json allowed me to get a successful install without the error
-
- script tag issue with "@sveltejs/adapter-auto": fixed it by updating the version to ^4.0.0
-
- noticed an issue with tsconfig.json, whereby "moduleResolution" required "module" to be present and set to preserve; after adding that value the IDE refreshed and the @render issue went away
Changes - Components
Runes
Multiple changes stem from the new Runes API: compiler instructions that tell Svelte about reactivity. Runes start with $
let -> $props
In v4 props were achieve using export let xyz = "xyz"
declarations. These declarations made content available from into the main body of the component.
In v5 all props come from the $props rune as seen below:
<script>
let { optional = 'unset', required } = $props();
</script>
In my case, the app is also using Typescript so there are further changes on the type definitions, which will be covered later.
v5
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/shadcn.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("bg-card text-card-foreground rounded-lg border shadow-sm", className)}
{...$$restProps}
>
<slot />
</div>
v6
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/shadcn.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
interface Props {
class?: $$Props["class"];
children?: import('svelte').Snippet;
[key: string]: any
}
let { class: className = undefined, children, ...rest }: Props = $props();
</script>
<div
class={cn("bg-card text-card-foreground rounded-lg border shadow-sm", className)}
{...rest}
>
{@render children?.()}
</div>
render method no longer compiled for SSR
This is specifically related to SSR application, but worth mentioning.
children() and snippets
In v4 the main paradigm for creating higher order components was to use .
In v5 they are deprecated in favour of constructs which increase the flexibility. Also added in v6 a clearer construct for children.
In my case I've only used for HOCs and those instances have been refactored to use children instead
v4
<script>
import '../app.css';
</script>
<slot></slot>
v5
<script lang="ts">
import '../app.css';
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
</script>
{@render children?.()}
Component typing changes
Most of the codebase saw change related to the new children component which is typed as Snippet from 'svelte' package.
No other significant changes present, but be advised that there are further changes detailed in the release note https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes-Component-typing-changes
let -> $state
Covered here last because I haven't implemented any states within this template.
I can see this being an important change to keep in mind in production apps since the let definition is common.
This essentially brings the biggest headaches, since prior to v5 developers built up this pattern of separation between prop definitions and state. This separation is still present in v5 but the change of let -> $props will confuse readers who will see more let -> $state.
I actually agree with the pattern separation, but getting used to it will take some time, mostly because the old definitions were worse.
v4
<script>
let count = 0;
</script>
v5
<script>
let count = $state(0);
</script>
Summary
Big update, one of the biggest yet! Largely positive, but with significant gotchas and implementation details.
Overall a move in the right direction, but shifting into the new paradigms will cause some headscratching.