プログラミング/svelte/svelte5への移行 の変更点

更新


[[プログラミング/svelte]]

* 新しいバージョンではかなりいろいろ変わるらしい [#s4e47972]

** Svelte 5 [#vd443973]

+ export let で定義していたプロパティは $props() で定義するようになった
+ export let data: PageData も let { data } : { data: PageData } = $props() として使う
+ 省略可能なプロパティはタイプ定義で ? 付きにする
+ bind で使うものはさらに $bindable() とする
+ <slot /> の代わりに Snippet を {@render } するようになった
+ ハンドラを何も指定しない on:click の形で "Event Forwarding" することはできなくなったので、正攻法で oninput?: FormEventHandler<HTMLInputElement> のようなプロパティを定義することになる
+ store は使わず $state と $derived で済ませられるはず

src/lib/components/Dialog.svelte
 LANG: html
 <script lang="ts">
   import type { Snippet } from 'svelte';
   let { title, children }: { title: string; children: Snippet } = $props();
 </script>
 
 <div class="dialog relative flex min-h-screen flex-col items-center justify-center">
   <div class="m-auto w-full rounded-md p-6 shadow-2xl lg:max-w-lg">
     <h1 class="text-center text-3xl font-semibold text-primary">{title}</h1>
     {@render children()}
   </div>
 </div>

src/lib/components/Form.svelte
 LANG: html
 <script lang="ts">
   import type { Snippet } from 'svelte';
   let {
     message = '',
     enhance = () => {
       return {};
     },
     props = {},
     children,
   }: {
     message?: string;
     enhance?: (el: HTMLFormElement) => object;
     props?: svelteHTML.IntrinsicElements['form'];
     children: Snippet;
   } = $props();
 
   let form: HTMLFormElement;
   $effect(() => {
     if (message) {
       // エラーメッセージが付いていたらすべてのコントロールをエラー表示にする
       ['checkbox', 'file-input', 'radio', 'range', 'select', 'ihput', 'textarea', 'toggle'].forEach(
         (kind) => {
           for (const elem of form.querySelectorAll(`.${kind}`)) {
             elem.classList.add(`${kind}-error`);
           }
         }
       );
     }
   });
 </script>
 
 {#if message}<span class="text-sm text-error">{message}</span>{/if}
 <form bind:this={form} class="space-y-4" method="POST" use:enhance {...props}>
   {@render children()}
 </form>

src/lib/components/InputText.svelte
 LANG: html
 <script lang="ts">
   import type { FormEventHandler } from 'svelte/elements';
   import type { Writable } from 'svelte/store';
 
   let {
     name,
     label,
     labelAlt = '',
     value = $bindable(),
     disabled = false,
     errors = undefined,
     props = {},
     oninput = () => {},
   }: {
     name: string;
     label: string;
     labelAlt?: string;
     value: string;
     disabled?: boolean;
     errors?: Writable<{}> | undefined;
     errors?: {[key:string]: string} | undefined;
     props?: svelteHTML.IntrinsicElements['input'];
     oninput?: FormEventHandler<HTMLInputElement>;
   } = $props();
 
   let key = name as keyof typeof errors;
 </script>
 
 <div class="form-control w-full">
   <label for={name} class="label">
     <span class="label-text">{label}</span>
     {#if labelAlt}<span class="label-text-alt">{labelAlt}</span>{/if}
   </label>
   <input
     {...{ name, type: 'text', ...props }}
     bind:value
     {disabled}
     {oninput}
     class="input input-bordered input-primary w-full"
     class:input-error={errors && $errors && $errors[key]}
   />
   {#if errors && $errors && $errors[key]}
     <label class="label" for={name}>
       <span class="label-text-alt text-error">{$errors[key]}</span>
     </label>
   {/if}
 </div>

src/lib/components/InputPassword.svelte
 LANG: html
 <script lang="ts">
   import InputText from '$lib/components/InputText.svelte';
   import type { FormEventHandler } from 'svelte/elements';
   import type { Writable } from 'svelte/store';
 
   let {
     name,
     label,
     labelAlt = '',
     value = $bindable(),
     disabled = false,
     errors = undefined,
     props = {},
     oninput = () => {},
   }: {
     name: string;
     label: string;
     labelAlt?: string;
     value: string;
     disabled?: boolean;
     errors?: Writable<{}> | undefined;
     errors?: {[key:string]: string} | undefined;
     props?: svelteHTML.IntrinsicElements['input'] | undefined;
     oninput?: FormEventHandler<HTMLInputElement>;
   } = $props();
 </script>
 
 <InputText
   {...{ name, label, labelAlt, disabled, errors }}
   props={{ type: 'password', ...props }}
   bind:value
   {oninput}
 />


** SuperForms v2 [#s70e495c]

https://superforms.rocks/migration-v2

superValidate へ schema を渡す際に、zod のアダプタでラップしなければならなくなった。

 LANG: ts
   import type { Actions, PageServerLoad } from './$types';
   import { schema } from './zod-email';
 - import { superValidate } from 'sveltekit-superforms/server';
 + import { superValidate } from 'sveltekit-superforms';
 + import { zod } from 'sveltekit-superforms/adapters';
   import { fail, redirect } from '@sveltejs/kit';
   import { setFlash } from 'sveltekit-flash-message/server';
   import type { purposes } from '$params/emailVerificationPurpose';
   import { path } from '$lib';
   
   export const load = (async (event) => {
 -   const form = await superValidate(schema);
 +   const form = await superValidate(zod(schema));
     const purpose = event.params.purpose as keyof typeof purposes;
     return { form, purpose };
   }) satisfies PageServerLoad;
   
   export const actions: Actions = {
     default: async (event) => {
       // フォームデータのバリデーション
 -     const form = await superValidate(event, schema);
 +     const form = await superValidate(event, zod(schema));
       const purpose = event.params.purpose as keyof typeof purposes;
       if (!form.valid) {
         return fail(400, { form, purpose });
       }
   
       // TODO: emailVerification レコードを作成
   
       // TODO: メールを送信
   
       setFlash(
         {
           type: 'success',
           message: 'An email was sent to the  mail address for mail address validation.',
         },
         event
       );
       throw redirect(302, path('/'));
     },
   };

Counter: 306 (from 2010/06/03), today: 4, yesterday: 3