|
2 | 2 | title: Svelte 4 migration guide
|
3 | 3 | ---
|
4 | 4 |
|
5 |
| -TODO copy over existing https://svelte.dev/docs/v4-migration-guide |
| 5 | +This migration guide provides an overview of how to migrate from Svelte version 3 to 4. See the linked PRs for more details about each change. Use the migration script to migrate some of these automatically: `npx svelte-migrate@latest svelte-4` |
| 6 | + |
| 7 | +If you're a library author, consider whether to only support Svelte 4 or if it's possible to support Svelte 3 too. Since most of the breaking changes don't affect many people, this may be easily possible. Also remember to update the version range in your `peerDependencies`. |
| 8 | + |
| 9 | +## Minimum version requirements |
| 10 | + |
| 11 | +- Upgrade to Node 16 or higher. Earlier versions are no longer supported. ([#8566](https://github.com/sveltejs/svelte/issues/8566)) |
| 12 | +- If you are using SvelteKit, upgrade to 1.20.4 or newer ([sveltejs/kit#10172](https://github.com/sveltejs/kit/pull/10172)) |
| 13 | +- If you are using Vite without SvelteKit, upgrade to `vite-plugin-svelte` 2.4.1 or newer ([#8516](https://github.com/sveltejs/svelte/issues/8516)) |
| 14 | +- If you are using webpack, upgrade to webpack 5 or higher and `svelte-loader` 3.1.8 or higher. Earlier versions are no longer supported. ([#8515](https://github.com/sveltejs/svelte/issues/8515), [198dbcf](https://github.com/sveltejs/svelte/commit/198dbcf)) |
| 15 | +- If you are using Rollup, upgrade to `rollup-plugin-svelte` 7.1.5 or higher ([198dbcf](https://github.com/sveltejs/svelte/commit/198dbcf)) |
| 16 | +- If you are using TypeScript, upgrade to TypeScript 5 or higher. Lower versions might still work, but no guarantees are made about that. ([#8488](https://github.com/sveltejs/svelte/issues/8488)) |
| 17 | + |
| 18 | +## Browser conditions for bundlers |
| 19 | + |
| 20 | +Bundlers must now specify the `browser` condition when building a frontend bundle for the browser. SvelteKit and Vite will handle this automatically for you. If you're using any others, you may observe lifecycle callbacks such as `onMount` not get called and you'll need to update the module resolution configuration. |
| 21 | +- For Rollup this is done within the `@rollup/plugin-node-resolve` plugin by setting `browser: true` in its options. See the [`rollup-plugin-svelte`](https://github.com/sveltejs/rollup-plugin-svelte/#usage) documentation for more details |
| 22 | +- For webpack this is done by adding `"browser"` to the `conditionNames` array. You may also have to update your `alias` config, if you have set it. See the [`svelte-loader`](https://github.com/sveltejs/svelte-loader#usage) documentation for more details |
| 23 | + |
| 24 | +([#8516](https://github.com/sveltejs/svelte/issues/8516)) |
| 25 | + |
| 26 | +## Removal of CJS related output |
| 27 | + |
| 28 | +Svelte no longer supports the CommonJS (CJS) format for compiler output and has also removed the `svelte/register` hook and the CJS runtime version. If you need to stay on the CJS output format, consider using a bundler to convert Svelte's ESM output to CJS in a post-build step. ([#8613](https://github.com/sveltejs/svelte/issues/8613)) |
| 29 | + |
| 30 | +## Stricter types for Svelte functions |
| 31 | + |
| 32 | +There are now stricter types for `createEventDispatcher`, `Action`, `ActionReturn`, and `onMount`: |
| 33 | + |
| 34 | +- `createEventDispatcher` now supports specifying that a payload is optional, required, or non-existent, and the call sites are checked accordingly ([#7224](https://github.com/sveltejs/svelte/issues/7224)) |
| 35 | + |
| 36 | +```ts |
| 37 | +// @errors: 2554 2345 |
| 38 | +import { createEventDispatcher } from 'svelte'; |
| 39 | + |
| 40 | +const dispatch = createEventDispatcher<{ |
| 41 | + optional: number | null; |
| 42 | + required: string; |
| 43 | + noArgument: null; |
| 44 | +}>(); |
| 45 | + |
| 46 | +// Svelte version 3: |
| 47 | +dispatch('optional'); |
| 48 | +dispatch('required'); // I can still omit the detail argument |
| 49 | +dispatch('noArgument', 'surprise'); // I can still add a detail argument |
| 50 | + |
| 51 | +// Svelte version 4 using TypeScript strict mode: |
| 52 | +dispatch('optional'); |
| 53 | +dispatch('required'); // error, missing argument |
| 54 | +dispatch('noArgument', 'surprise'); // error, cannot pass an argument |
| 55 | +``` |
| 56 | + |
| 57 | +- `Action` and `ActionReturn` have a default parameter type of `undefined` now, which means you need to type the generic if you want to specify that this action receives a parameter. The migration script will migrate this automatically ([#7442](https://github.com/sveltejs/svelte/pull/7442)) |
| 58 | + |
| 59 | +```diff |
| 60 | +-const action: Action = (node, params) => { .. } // this is now an error if you use params in any way |
| 61 | ++const action: Action<HTMLElement, string> = (node, params) => { .. } // params is of type string |
| 62 | +``` |
| 63 | + |
| 64 | +- `onMount` now shows a type error if you return a function asynchronously from it, because this is likely a bug in your code where you expect the callback to be called on destroy, which it will only do for synchronously returned functions ([#8136](https://github.com/sveltejs/svelte/issues/8136)) |
| 65 | + |
| 66 | +```diff |
| 67 | +// Example where this change reveals an actual bug |
| 68 | +onMount( |
| 69 | +- // someCleanup() not called because function handed to onMount is async |
| 70 | +- async () => { |
| 71 | +- const something = await foo(); |
| 72 | ++ // someCleanup() is called because function handed to onMount is sync |
| 73 | ++ () => { |
| 74 | ++ foo().then(something => .. |
| 75 | + // .. |
| 76 | + return () => someCleanup(); |
| 77 | +} |
| 78 | +); |
| 79 | +``` |
| 80 | + |
| 81 | +## Custom Elements with Svelte |
| 82 | + |
| 83 | +The creation of custom elements with Svelte has been overhauled and significantly improved. The `tag` option is deprecated in favor of the new `customElement` option: |
| 84 | + |
| 85 | +```diff |
| 86 | +-<svelte:options tag="my-component" /> |
| 87 | ++<svelte:options customElement="my-component" /> |
| 88 | +``` |
| 89 | + |
| 90 | +This change was made to allow [more configurability](custom-elements-api#component-options) for advanced use cases. The migration script will adjust your code automatically. The update timing of properties has changed slightly as well. ([#8457](https://github.com/sveltejs/svelte/issues/8457)) |
| 91 | + |
| 92 | +## SvelteComponentTyped is deprecated |
| 93 | + |
| 94 | +`SvelteComponentTyped` is deprecated, as `SvelteComponent` now has all its typing capabilities. Replace all instances of `SvelteComponentTyped` with `SvelteComponent`. |
| 95 | + |
| 96 | +```diff |
| 97 | +- import { SvelteComponentTyped } from 'svelte'; |
| 98 | ++ import { SvelteComponent } from 'svelte'; |
| 99 | + |
| 100 | +- export class Foo extends SvelteComponentTyped<{ aProp: string }> {} |
| 101 | ++ export class Foo extends SvelteComponent<{ aProp: string }> {} |
| 102 | +``` |
| 103 | + |
| 104 | +If you have used `SvelteComponent` as the component instance type previously, you may see a somewhat opaque type error now, which is solved by changing `: typeof SvelteComponent` to `: typeof SvelteComponent<any>`. |
| 105 | + |
| 106 | +```diff |
| 107 | +<script> |
| 108 | + import ComponentA from './ComponentA.svelte'; |
| 109 | + import ComponentB from './ComponentB.svelte'; |
| 110 | + import { SvelteComponent } from 'svelte'; |
| 111 | + |
| 112 | +- let component: typeof SvelteComponent; |
| 113 | ++ let component: typeof SvelteComponent<any>; |
| 114 | + |
| 115 | + function choseRandomly() { |
| 116 | + component = Math.random() > 0.5 ? ComponentA : ComponentB; |
| 117 | + } |
| 118 | +</script> |
| 119 | + |
| 120 | +<button on:click={choseRandomly}>random</button> |
| 121 | +<svelte:element this={component} /> |
| 122 | +``` |
| 123 | + |
| 124 | +The migration script will do both automatically for you. ([#8512](https://github.com/sveltejs/svelte/issues/8512)) |
| 125 | + |
| 126 | +## Transitions are local by default |
| 127 | + |
| 128 | +Transitions are now local by default to prevent confusion around page navigations. "local" means that a transition will not play if it's within a nested control flow block (`each/if/await/key`) and not the direct parent block but a block above it is created/destroyed. In the following example, the `slide` intro animation will only play when `success` goes from `false` to `true`, but it will _not_ play when `show` goes from `false` to `true`: |
| 129 | + |
| 130 | +```svelte |
| 131 | +{#if show} |
| 132 | + ... |
| 133 | + {#if success} |
| 134 | + <p in:slide>Success</p> |
| 135 | + {/each} |
| 136 | +{/if} |
| 137 | +``` |
| 138 | + |
| 139 | +To make transitions global, add the `|global` modifier - then they will play when _any_ control flow block above is created/destroyed. The migration script will do this automatically for you. ([#6686](https://github.com/sveltejs/svelte/issues/6686)) |
| 140 | + |
| 141 | +## Default slot bindings |
| 142 | + |
| 143 | +Default slot bindings are no longer exposed to named slots and vice versa: |
| 144 | + |
| 145 | +```svelte |
| 146 | +<script> |
| 147 | + import Nested from './Nested.svelte'; |
| 148 | +</script> |
| 149 | +
|
| 150 | +<Nested let:count> |
| 151 | + <p> |
| 152 | + count in default slot - is available: {count} |
| 153 | + </p> |
| 154 | + <p slot="bar"> |
| 155 | + count in bar slot - is not available: {count} |
| 156 | + </p> |
| 157 | +</Nested> |
| 158 | +``` |
| 159 | + |
| 160 | +This makes slot bindings more consistent as the behavior is undefined when for example the default slot is from a list and the named slot is not. ([#6049](https://github.com/sveltejs/svelte/issues/6049)) |
| 161 | + |
| 162 | +## Preprocessors |
| 163 | + |
| 164 | +The order in which preprocessors are applied has changed. Now, preprocessors are executed in order, and within one group, the order is markup, script, style. |
| 165 | + |
| 166 | +```js |
| 167 | +// @errors: 2304 |
| 168 | +import { preprocess } from 'svelte/compiler'; |
| 169 | + |
| 170 | +const { code } = await preprocess( |
| 171 | + source, |
| 172 | + [ |
| 173 | + { |
| 174 | + markup: () => { |
| 175 | + console.log('markup-1'); |
| 176 | + }, |
| 177 | + script: () => { |
| 178 | + console.log('script-1'); |
| 179 | + }, |
| 180 | + style: () => { |
| 181 | + console.log('style-1'); |
| 182 | + } |
| 183 | + }, |
| 184 | + { |
| 185 | + markup: () => { |
| 186 | + console.log('markup-2'); |
| 187 | + }, |
| 188 | + script: () => { |
| 189 | + console.log('script-2'); |
| 190 | + }, |
| 191 | + style: () => { |
| 192 | + console.log('style-2'); |
| 193 | + } |
| 194 | + } |
| 195 | + ], |
| 196 | + { |
| 197 | + filename: 'App.svelte' |
| 198 | + } |
| 199 | +); |
| 200 | + |
| 201 | +// Svelte 3 logs: |
| 202 | +// markup-1 |
| 203 | +// markup-2 |
| 204 | +// script-1 |
| 205 | +// script-2 |
| 206 | +// style-1 |
| 207 | +// style-2 |
| 208 | + |
| 209 | +// Svelte 4 logs: |
| 210 | +// markup-1 |
| 211 | +// script-1 |
| 212 | +// style-1 |
| 213 | +// markup-2 |
| 214 | +// script-2 |
| 215 | +// style-2 |
| 216 | +``` |
| 217 | + |
| 218 | +This could affect you for example if you are using `MDsveX` - in which case you should make sure it comes before any script or style preprocessor. |
| 219 | + |
| 220 | +```diff |
| 221 | +preprocess: [ |
| 222 | +- vitePreprocess(), |
| 223 | +- mdsvex(mdsvexConfig) |
| 224 | ++ mdsvex(mdsvexConfig), |
| 225 | ++ vitePreprocess() |
| 226 | +] |
| 227 | +``` |
| 228 | + |
| 229 | +Each preprocessor must also have a name. ([#8618](https://github.com/sveltejs/svelte/issues/8618)) |
| 230 | + |
| 231 | +## New eslint package |
| 232 | + |
| 233 | +`eslint-plugin-svelte3` is deprecated. It may still work with Svelte 4 but we make no guarantees about that. We recommend switching to our new package [eslint-plugin-svelte](https://github.com/sveltejs/eslint-plugin-svelte). See [this Github post](https://github.com/sveltejs/kit/issues/10242#issuecomment-1610798405) for an instruction how to migrate. Alternatively, you can create a new project using `npm create svelte@latest`, select the eslint (and possibly TypeScript) option and then copy over the related files into your existing project. |
| 234 | + |
| 235 | +## Other breaking changes |
| 236 | + |
| 237 | +- the `inert` attribute is now applied to outroing elements to make them invisible to assistive technology and prevent interaction. ([#8628](https://github.com/sveltejs/svelte/pull/8628)) |
| 238 | +- the runtime now uses `classList.toggle(name, boolean)` which may not work in very old browsers. Consider using a [polyfill](https://github.com/eligrey/classList.js) if you need to support these browsers. ([#8629](https://github.com/sveltejs/svelte/issues/8629)) |
| 239 | +- the runtime now uses the `CustomEvent` constructor which may not work in very old browsers. Consider using a [polyfill](https://github.com/theftprevention/event-constructor-polyfill/tree/master) if you need to support these browsers. ([#8775](https://github.com/sveltejs/svelte/pull/8775)) |
| 240 | +- people implementing their own stores from scratch using the `StartStopNotifier` interface (which is passed to the create function of `writable` etc) from `svelte/store` now need to pass an update function in addition to the set function. This has no effect on people using stores or creating stores using the existing Svelte stores. ([#6750](https://github.com/sveltejs/svelte/issues/6750)) |
| 241 | +- `derived` will now throw an error on falsy values instead of stores passed to it. ([#7947](https://github.com/sveltejs/svelte/issues/7947)) |
| 242 | +- type definitions for `svelte/internal` were removed to further discourage usage of those internal methods which are not public API. Most of these will likely change for Svelte 5 |
| 243 | +- Removal of DOM nodes is now batched which slightly changes its order, which might affect the order of events fired if you're using a `MutationObserver` on these elements ([#8763](https://github.com/sveltejs/svelte/pull/8763)) |
| 244 | +- if you enhanced the global typings through the `svelte.JSX` namespace before, you need to migrate this to use the `svelteHTML` namespace. Similarly if you used the `svelte.JSX` namespace to use type definitions from it, you need to migrate those to use the types from `svelte/elements` instead. You can find more information about what to do [here](https://github.com/sveltejs/language-tools/blob/master/docs/preprocessors/typescript.md#im-getting-deprecation-warnings-for-sveltejsx--i-want-to-migrate-to-the-new-typings) |
0 commit comments