I'm using svelte 5 instead of svelte 4 here is an overview of the changes.
Svelte 5 introduces runes, a set of advanced primitives for controlling reactivity. The runes replace certain non-runes features and provide more explicit control over state and effects.
- Purpose: Declare reactive state.
- Usage:
<script>let count = $state(0);</script>
- Replaces: Top-level
let
declarations in non-runes mode. - Class Fields:
class Todo {
done = $state(false);
text = $state();
constructor(text) {
this.text = text;
}
}
- Deep Reactivity: Only plain objects and arrays become deeply reactive.
- Purpose: Declare state that cannot be mutated, only reassigned.
- Usage:
<script>let numbers = $state.raw([1, 2, 3]);</script>
- Performance: Improves with large arrays and objects.
- Purpose: Take a static snapshot of $state.
- Usage:
<script>
let counter = $state({ count: 0 });
function onClick() {
console.log($state.snapshot(counter));
}
</script>
- Purpose: Declare derived state.
- Usage:
<script>let count = $state(0); let doubled = $derived(count * 2);</script>
- Replaces: Reactive variables computed using
$:
in non-runes mode.
- Purpose: Create complex derivations with a function.
- Usage:
<script>
let numbers = $state([1, 2, 3]); let total = $derived.by(() => numbers.reduce((a, b) => a + b,
0));
</script>
- Purpose: Run side-effects when values change.
- Usage:
<script>
let size = $state(50);
let color = $state('#ff3e00');
$effect(() => {
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = color;
context.fillRect(0, 0, size, size);
});
</script>
- Replacements: $effect replaces a substantial part of
$: {}
blocks triggering side-effects.
- Purpose: Run code before the DOM updates.
- Usage:
<script>
$effect.pre(() =>{' '}
{
// logic here
}
);
</script>
- Replaces: beforeUpdate.
- Purpose: Check if code is running inside a tracking context.
- Usage:
<script>console.log('tracking:', $effect.tracking());</script>
- Purpose: Declare component props.
- Usage:
<script>let {(prop1, prop2)} = $props();</script>
- Replaces: export let syntax for declaring props.
- Purpose: Declare bindable props.
- Usage:
<script>let {(bindableProp = $bindable('fallback'))} = $props();</script>
- Purpose: Equivalent to
console.log
but re-runs when its argument changes. - Usage:
<script>let count = $state(0); $inspect(count);</script>
- Purpose: Retrieve the this reference of the custom element.
- Usage:
<script>
function greet(greeting) {
$host().dispatchEvent(new CustomEvent('greeting', { detail: greeting }));
}
</script>
- Note: Only available inside custom element components on the client-side.
Snippets, along with render tags, help create reusable chunks of markup inside your components, reducing duplication and enhancing maintainability.
- Definition: Use the
#snippet
syntax to define reusable markup sections. - Basic Example:
{#snippet figure(image)}
<figure>
<img src={image.src} alt={image.caption} width={image.width} height={image.height} />
<figcaption>{image.caption}</figcaption>
</figure>
{/snippet}
- Invocation: Render predefined snippets with
@render
:
{@render figure(image)}
- Destructuring Parameters: Parameters can be destructured for concise usage:
{#snippet figure({ src, caption, width, height })}
<figure>
<img alt={caption} {src} {width} {height} />
<figcaption>{caption}</figcaption>
</figure>
{/snippet}
- Scope Rules: Snippets have lexical scoping rules; they are visible to everything in the same lexical scope:
<div>
{#snippet x()}
{#snippet y()}...{/snippet}
<!-- valid usage -->
{@render y()}
{/snippet}
<!-- invalid usage -->
{@render y()}
</div>
<!-- invalid usage -->
{@render x()}
- Recursive References: Snippets can self-reference or reference other snippets:
{#snippet blastoff()}
<span>🚀</span>
{/snippet}
{#snippet countdown(n)}
{#if n > 0}
<span>{n}...</span>
{@render countdown(n - 1)}
{:else}
{@render blastoff()}
{/if}
{/snippet}
{@render countdown(10)}
- Direct Passing as Props:
<script>
import Table from './Table.svelte';
const fruits = [{ name: 'apples', qty: 5, price: 2 }, ...];
</script>
{#snippet header()}
<th>fruit</th>
<th>qty</th>
<th>price</th>
<th>total</th>
{/snippet}
{#snippet row(fruit)}
<td>{fruit.name}</td>
<td>{fruit.qty}</td>
<td>{fruit.price}</td>
<td>{fruit.qty * fruit.price}</td>
{/snippet}
<Table data={fruits} {header} {row} />
- Implicit Binding:
<table data="{fruits}">
{#snippet header()}
<th>fruit</th>
<th>qty</th>
<th>price</th>
<th>total</th>
{/snippet} {#snippet row(fruit)}
<td>{fruit.name}</td>
<td>{fruit.qty}</td>
<td>{fruit.price}</td>
<td>{fruit.qty * fruit.price}</td>
{/snippet}
</table>
- Children Snippet: Non-snippet content defaults to the
children
snippet:
<table data="{fruits}">
<th>fruit</th>
<th>qty</th>
<th>price</th>
<th>total</th>
<!-- additional content -->
</table>
<script>
let { data, children, row } = $props();
</script>
<table>
<thead>
<tr>
{@render children()}
</tr>
</thead>
<!-- table body -->
</table>
- Avoid Conflicts: Do not use a prop named
children
if also providing content inside the component.
- TypeScript Integration:
<script lang="ts">
import type { Snippet } from 'svelte';
let { data, children, row }: {
data: any[];
children: Snippet;
row: Snippet<[any]>;
} = $props();
</script>
- Generics for Improved Typing:
<script lang="ts" generics="T">
import type { Snippet } from 'svelte';
let { data, children, row }: {
data: T[];
children: Snippet;
row: Snippet<[T]>;
} = $props();
</script>
- Advanced Use: Create snippets programmatically using
createRawSnippet
where necessary.
- Mixing with Slots: Slots are deprecated but still work. Snippets provide more flexibility and power.
- Custom Elements: Continue using
<slot />
for custom elements as usual.
Sure! Here are the succinct instructions for handling Event Handlers in Svelte 5, tailored for the AI-integrated code editor to help it understand and utilize these features effectively.
In Svelte 5, event handlers are treated as properties, simplifying their use and integrating them more closely with the rest of the properties in the component.
- Declaration: Use properties to attach event handlers.
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
clicks: {count}
</button>
- Shorthand Syntax:
<script>
let count = $state(0);
function handleClick() {
count++;
}
</script>
<button {handleClick}>
clicks: {count}
</button>
- Deprecation: The traditional
on:
directive is deprecated.
- Replacing createEventDispatcher: Components should accept callback props instead of using
createEventDispatcher
.
<script>
import Pump from './Pump.svelte';
let size = $state(15);
let burst = $state(false);
function reset() {
size = 15;
burst = false;
}
</script>
<Pump
inflate={(power) => { size += power; if (size > 75) burst = true; }}
deflate={(power) => { if (size > 0) size -= power; }}
/>
{#if burst}
<button onclick={reset}>new balloon</button>
<span class="boom">💥</span>
{:else}
<span class="balloon" style="scale: {0.01 * size}"> 🎈 </span>
{/if}
- Accept Callback Props:
<script>
let { onclick, children } = $props();
</script>
<button {onclick}>
{@render children()}
</button>
- Spreading Props:
<script>
let { children, ...props } = $props();
</script>
<button {...props}>
{@render children()}
</button>
- Avoiding Modifiers: Modifiers like
|once
,|preventDefault
, etc., are not supported. Use wrapper functions instead. - Example Wrapper Functions:
<script>
function once(fn) {
return function (event) {
if (fn) fn.call(this, event);
fn = null;
};
}
function preventDefault(fn) {
return function (event) {
event.preventDefault();
fn.call(this, event);
};
}
</script>
<button onclick={once(preventDefault(handler))}>...</button>
- Special Modifiers: For
capture
:
<button onclickcapture={...}>...</button>
- Combining Handlers: Instead of using multiple handlers, combine them into one.
<button
onclick={(e) => {
handlerOne(e);
handlerTwo(e);
}}
>
...
</button>
-
Svelte 4 vs. Svelte 5:
- Before:
<script> let count = 0; $: double = count * 2; $: { if (count > 10) alert('Too high!'); } </script> <button on:click="{()" ="">count++}> {count} / {double}</button>
- After:
<script> let count = $state(0); let double = $derived(count * 2); $effect(() => { if (count > 10) alert('Too high!'); }); </script> <button onclick="{()" ="">count++}> {count} / {double}</button>
-
Svelte 4 vs. Svelte 5:
- Before:
<script> let a = 0; let b = 0; $: sum = add(a, b); function add(x, y) { return x + y; } </script> <button on:click="{()" ="">a++}>a++</button> <button on:click="{()" ="">b++}>b++</button> <p>{a} + {b} = {sum}</p>
- After:
<script> let a = $state(0); let b = $state(0); let sum = $derived(add()); function add() { return a + b; } </script> <button onclick="{()" ="">a++}>a++</button> <button onclick="{()" ="">b++}>b++</button> <p>{a} + {b} = {sum}</p>
-
Svelte 4 vs. Svelte 5:
- Before:
<script> let a = 0; let b = 0; $: sum = a + noTrack(b); function noTrack(value) { return value; } </script> <button on:click="{()" ="">a++}>a++</button> <button on:click="{()" ="">b++}>b++</button> <p>{a} + {b} = {sum}</p>
- After:
<script> import { untrack } from 'svelte'; let a = $state(0); let b = $state(0); let sum = $derived(add()); function add() { return a + untrack(() => b); } </script> <button onclick="{()" ="">a++}>a++</button> <button onclick="{()" ="">b++}>b++</button> <p>{a} + {b} = {sum}</p>
-
Svelte 5:
<script> let { count = 0 } = $props(); </script> {count}
-
Svelte 5:
<script> let { class: classname, ...others } = $props(); </script> <pre class="{classname}"> {JSON.stringify(others)} </pre>
-
Svelte 4 vs. Svelte 5:
- Before:
<script> import { tick, beforeUpdate } from 'svelte'; let theme = 'dark'; let messages = []; let viewport; let updatingMessages = false; beforeUpdate(() => { if (updatingMessages) { const autoscroll = viewport && viewport.offsetHeight + viewport.scrollTop > viewport.scrollHeight - 50; if (autoscroll) { tick().then(() => viewport.scrollTo(0, viewport.scrollHeight)); } } }); function handleKeydown(event) { if (event.key === 'Enter') { const text = event.target.value; if (text) { messages = [...messages, text]; updatingMessages = true; event.target.value = ''; } } } function toggle() { theme = theme === 'dark' ? 'light' : 'dark'; } </script> <div class:dark="{theme" ="" ="" ="dark" }> <div bind:this="{viewport}"> {#each messages as message} <p>{message}</p> {/each} </div> <input on:keydown="{handleKeydown}" /> <button on:click="{toggle}">Toggle dark mode</button> </div>
- After:
<script> import { tick } from 'svelte'; let theme = $state('dark'); let messages = $state([]); let viewport; $effect.pre(() => { messages; const autoscroll = viewport && viewport.offsetHeight + viewport.scrollTop > viewport.scrollHeight - 50; if (autoscroll) { tick().then(() => viewport.scrollTo(0, viewport.scrollHeight)); } }); function handleKeydown(event) { if (event.key === 'Enter') { const text = event.target.value; if (text) { messages = [...messages, text]; event.target.value = ''; } } } function toggle() { theme = theme === 'dark' ? 'light' : 'dark'; } </script> <div class:dark="{theme" ="" ="" ="dark" }> <div bind:this="{viewport}"> {#each messages as message} <p>{message}</p> {/each} </div> <input onkeydown="{handleKeydown}" /> <button onclick="{toggle}">Toggle dark mode</button> </div>
-
Svelte 5:
<script> let { ...props } = $props(); </script> <button {...props}>a button</button>
-
Passing content using snippets:
<!-- consumer --> <script> import Button from './Button.svelte'; </script> <button>{#snippet children(prop)} click {prop} {/snippet}</button> <!-- provider (Button.svelte) --> <script> let { children } = $props(); </script> <button>{@render children("some value")}</button>
I'm also using sveltekit 2 which also has some changes I'd like you to keep in mind
In SvelteKit 2, it is no longer necessary to throw the results of error(...)
and redirect(...)
. Simply calling them is sufficient.
SvelteKit 1:
import { error } from '@sveltejs/kit';
function load() {
throw error(500, 'something went wrong');
}
SvelteKit 2:
import { error } from '@sveltejs/kit';
function load() {
error(500, 'something went wrong');
}
Distinguish Errors:
Use isHttpError
and isRedirect
to differentiate known errors from unexpected ones.
import { isHttpError, isRedirect } from '@sveltejs/kit';
try {
// some code
} catch (err) {
if (isHttpError(err) || isRedirect(err)) {
// handle error
}
}
Cookies now require a specified path when set, deleted, or serialized.
SvelteKit 1:
export function load({ cookies }) {
cookies.set(name, value);
return { response };
}
SvelteKit 2:
export function load({ cookies }) {
cookies.set(name, value, { path: '/' });
return { response };
}
Promises in load
functions are no longer awaited automatically.
Single Promise:
SvelteKit 1:
export function load({ fetch }) {
return {
response: fetch(...).then(r => r.json())
};
}
SvelteKit 2:
export async function load({ fetch }) {
const response = await fetch(...).then(r => r.json());
return { response };
}
Multiple Promises:
SvelteKit 1:
export function load({ fetch }) {
return {
a: fetch(...).then(r => r.json()),
b: fetch(...).then(r => r.json())
};
}
SvelteKit 2:
export async function load({ fetch }) {
const [a, b] = await Promise.all([
fetch(...).then(r => r.json()),
fetch(...).then(r => r.json())
]);
return { a, b };
}
goto(...)
no longer accepts external URLs. Use window.location.href = url
for external navigation.
Paths are now relative by default, ensuring portability across different environments. The paths.relative
config option manages this behavior.
- Server Fetches are no longer trackable.
preloadCode
Arguments: Must be prefixed with the base path.resolvePath
Replacement: UseresolveRoute
instead.
import { resolveRoute } from '$app/paths';
const path = resolveRoute('/blog/[slug]', { slug: 'hello' });
Errors trigger the handleError
hook with status
and message
properties for better discernment.
Dynamic environment variables cannot be used during prerendering. Use static modules instead.
The properties form
and data
have been removed from use:enhance
callbacks, replaced by formElement
and formData
.
Forms containing <input type="file">
must use enctype="multipart/form-data"
.
With these adjusted guidelines, your AI can now generate SvelteKit 2 code accurately while considering the migration changes.