プログラミング/svelte/svelte5への移行 の履歴(No.1)

更新


プログラミング/svelte

新しいバージョンではかなりいろいろ変わるらしい

Svelte 5

  1. export let で定義していたプロパティは $props() で定義するようになった
  2. 省略可能なものはタイプ名を ? 付きにする
  3. bind で使うものはさらに $bindable() とする
  4. <slot /> ではなく Snippet を {@render } するようになった
  5. on:click の形で Event Forwarding することはできなくなったので、正攻法で oninput?: FormEventHandler<HTMLInputElement> のようなプロパティを定義することになる

src/lib/components/Dialog.svelte

<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

<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

<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;
    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

<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;
    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

https://superforms.rocks/migration-v2

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

  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: 304 (from 2010/06/03), today: 2, yesterday: 3