TypeScriptでValibotをマスターする:軽量でTree-shakableなスキーマバリデーションガイド

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

モジュール式スキーマバリデーションへの移行

長年、ZodはTypeScriptエコシステムにおいて圧倒的な存在でした。信頼性が高く、開発者に優しく、型システムともシームレスに統合されています。しかし、開発者がCore Web Vitalsのようなパフォーマンス指標を重視するようになるにつれ、バンドルサイズが大きな懸念事項となってきました。

Zodはクラスベースの「Fluent API(流れるようなAPI)」に依存しています。この設計では、すべてのメソッドが単一の大きなオブジェクトに紐付いているため、Tree-shakingが困難になります。たとえ機能の一部しか使用していなくても、最終的には13.4kB(gzip時)のライブラリ全体をユーザーに配信することになる場合がほとんどです。

Valibotは、よりスリムな道を提供します。z.string().email()のようにメソッドをチェーンする代わりに、Valibotは独立した構成可能な関数を使用します。この関数型アーキテクチャにより、Vite、Webpack、Rollupなどのモダンなバンドラーは、ピンポイントなTree-shakingを実行できます。プロジェクトで文字列のバリデーションしか行わない場合、プロダクションバンドルには文字列のロジックのみが含まれます。私は1KBの差が重要となる本番環境でこれを導入してきましたが、パフォーマンスの向上は即座に、かつ明確に現れました。

利便性の代償:Valibot vs. Zod

なぜValibotがこれほど注目を集めているのでしょうか?その答えは依存関係の処理方法にあります。従来のライブラリの多くは、メソッドがメインオブジェクトにリンクされている「Fluent API」を採用しています。これはIDE上では綺麗に見えますが、バンドラーが簡単に分解できない強固な依存関係グラフを作成してしまいます。

Zodのアプローチ(メソッドチェーン)

import { z } from 'zod';

const schema = z.string().min(5).email();
// 'z' オブジェクトはモノリスです。
// バンドラーは、呼び出していないメソッドを簡単に削除できません。

Valibotのアプローチ(関数型 & モジュール式)

import { string, minLength, email, pipe, parse } from 'valibot';

const schema = pipe(string(), minLength(5), email());
// これらの特定の関数のみが、最終的なJavaScriptファイルに含まれます。

独立した関数に移行することで、Valibotは驚異的なフットプリントの小ささを実現しています。一般的な実務のフォームにおいて、ZodからValibotに切り替えることで、バリデーションロジックの重さを約13kBから1kB未満に削減できることがあります。これは、全く同じ機能でありながら, コードサイズを90%削減できることを意味します。

メリットとデメリット:切り替える価値はあるか?

ライブラリの選択にはトレードオフのバランスが伴います。パフォーマンスを重視するプロジェクトにはValibotを推奨しますが、コードベース全体を移行する前に両側面を検討する必要があります。

メリット

  • 最小限のバンドルサイズ: 現在、市場で最も軽量なバリデーションライブラリの一つです。
  • きめ細かなTree-shaking: 実際にインポートした機能に対してのみコスト(サイズ)が発生します。
  • 完全な型安全性: 自動型推論を備えた、ファーストクラスのTypeScriptサポートを提供します。
  • 容易な拡張性: カスタムルールは単なるシンプルな関数であるため、Zodプラグインよりも簡単に記述できます。

デメリット

  • 発展途上のエコシステム: Zodが長年築き上げてきた膨大なサードパーティ統合ライブラリがまだ不足しています。
  • 冗長性の増加: 一部の開発者は、pipe()構文がZodのドットチェーンよりも繰り返しが多いと感じるかもしれません。
  • APIの安定性: 新しいライブラリであるため、成熟した代替ツールと比較してAPIの変更が頻繁に行われてきました。

はじめに

Valibotは、Node.jsやBunからReact、Vueまで、TypeScriptが動作するあらゆる環境で動作します。インストールはすべてのパッケージマネージャーで標準的な方法で行えます。

npm install valibot
# または
pnpm add valibot

構成をクリーンに保つために、/schemasフォルダを作成することをお勧めします。これにより、フロントエンドのフォームとバックエンドのAPIルートの間で、バリデーションロジックを重複させることなく共有できます。

実装ガイド:基本から応用まで

Valibotが実世界のデータをどのように処理するか、単純な文字列から複雑なオブジェクトまで見ていきましょう。

1. 基本的なバリデーション

単一の値を検証するには、スキーマを定義してparse関数を使用します。データが無効な場合、parseはValiErrorをスローすることに注意してください。

import { string, minLength, pipe, parse } from 'valibot';

const passwordSchema = pipe(
  string(), 
  minLength(8, "パスワードは8文字以上である必要があります")
);

try {
  const result = parse(passwordSchema, "my-secure-password");
  console.log("成功:", result);
} catch (error) {
  console.error(error.message);
}

2. プロダクション向けの安全なパース

try/catchによるエラーキャッチは煩雑になることがあるため、本番環境でparseを直接使うことは滅多にありません。代わりにsafeParseを使用します。これは、コードを停止させることなく、何が問題だったかを正確に伝える結果オブジェクトを返します。

import { string, email, pipe, safeParse } from 'valibot';

const emailSchema = pipe(string(), email());
const result = safeParse(emailSchema, "not-an-email");

if (result.success) {
  console.log("データは有効です:", result.output);
} else {
  // ここで詳細なエラー内容(issues)にアクセスします
  console.log("バリデーションに失敗しました:", result.issues);
}

3. 複雑なオブジェクトのバリデーション

Valibotは、ネストされたAPIレスポンスを処理する際に真価を発揮します。InferOutputを使用して、スキーマからTypeScriptの型を直接抽出することも可能です。

import { object, string, number, pipe, minLength, email, type InferOutput } from 'valibot';

const UserProfileSchema = object({
  username: pipe(string(), minLength(3)),
  age: number(),
  email: pipe(string(), email())
});

type UserProfile = InferOutput<typeof UserProfileSchema>;

const data = {
  username: "dev_user",
  age: 30,
  email: "[email protected]"
};

4. React Hook Formとの統合

ほとんどのフロントエンド開発者は、フォームライブラリ内でValibotを使用します。公式のリゾルバーパッケージを使用して、React Hook Formに接続できます。

import { useForm } from 'react-hook-form';
import { valibotResolver } from '@hookform/resolvers/valibot';

function RegistrationForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: valibotResolver(UserProfileSchema)
  });

  const onSubmit = (data) => console.log("フォームデータ:", data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("username")} />
      {errors.username && <p>{errors.username.message}</p>}
      <button type="submit">登録</button>
    </form>
  );
}

パフォーマンスに関する最終的な考察

Valibotの強みはそのシンプルさにあります。中規模のプロジェクトをZodから移行した際、Lighthouseの「First Load JS」メトリクスが数ポイント改善されました。Zodの13KBは小さく見えるかもしれませんが、他のライブラリと組み合わさると蓄積されます。1KBまでスケールダウンできるValibotの能力は、モダンでパフォーマンス優先のWeb開発において優れた選択肢となります。今日から新しいプロジェクトを始めるなら、ユーザーの帯域幅を尊重するライブラリを選びましょう。

Share: