Valibot vs Zod: TypeScriptプロジェクトのバンドルサイズを劇的に削減する「軽量化」の極意

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Zodの影の部分:利便性と引き換えになる「重さ」

TypeScriptコミュニティにおいて、Zodはほぼ標準的なデータバリデーションの選択肢となっています。これは、Effect TSによるプロフェッショナルなエラー管理と同様に、型安全性を追求する開発者から強く支持されています。z.string().email()のようなメソッドチェーンの構文は、書きやすく読みやすいため非常に魅力的です。しかし、この利便性の代償は**バンドルサイズ**に現れます。多くの開発者は、プロジェクトが肥大化してからようやくその事実に気づくのです。

筆者が約5万行のコードを持つ管理ダッシュボードをリファクタリングした際の話です。NxでFull-stack TypeScript Monorepoを管理するプロジェクトにおいて、webpack-bundle-analyzerを実行して驚愕しました。Zodが50KB(gzip圧縮時)近くも占めていたのです。皮肉なことに、プロジェクトでは文字列や単純なオブジェクトのチェックなど、機能の10%程度しか使っていませんでした。Zodは効率的なツリーシェイキング(Tree-shaking)をサポートしていないため、たとえ1つの関数しか使っていなくても、ブラウザはライブラリのほぼ全体をダウンロードする必要があります。

なぜZodの最適化はこれほど難しいのか?

核心的な弱点は**メソッドチェーン(Method Chaining)**というアーキテクチャにあります。z.string().email()のような記述をサポートするために、Zodはすべてのメソッドを1つの巨大なクラスまたはオブジェクト内に定義しなければなりません。

WebpackやViteのようなツールは、実際に.email()が使われているかどうかを判別して切り離すことができません。その結果、ユーザーは不要なコードまで読み込むことになり、LCP(Largest Contentful Paint)の悪化やJavaScriptの実行時間の遅延を招きます。これは、ReactアプリをプロフェッショナルなPWAへ最適化しようとする開発者にとって無視できない問題です。低スペックのモバイル端末にとって、50KBのJavaScriptは単なる数字ではなく、体感できるほどの遅延となります。

Valibot:関数型(Functional)の思考がゲームを変える

Valibotは、**モジュール化(Modular)**というアプローチでこの問題を解決します。すべてを1つのオブジェクトに詰め込むのではなく、機能を個別の関数に分割しています。必要なものだけをインポートすればよいのです。TanStack Routerをマスターする過程で、URLパラメータの型安全な検証を行う際にも、Valibotはその真価を発揮します。

この仕組みにより、ツリーシェイキングが最大限に機能します。もしemail()url()を使わなければ、それらは最終的なビルドファイルから除外されます。実際、Valibotは一般的なケースでZodより最大90%小さくなる(gzip圧縮時で5KB未満)ことが示されています。これは非常に驚異的な数値です。

実プロジェクトでのValibotの導入

1. クイックインストール

以下のコマンド1つでValibotをプロジェクトに追加できます:

npm install valibot
# または pnpm/yarn を使用
pnpm add valibot

2. スキーマ定義:思考法の違い

ValibotがZodと比較して、どのようにスキーマを構造化してサイズを最適化しているかを見てみましょう。

Zodの場合(オールインワン):

import { z } from 'zod';

const UserSchema = z.object({
  username: z.string().min(3),
  email: z.string().email(),
});

Valibotの場合(必要な分だけ取り出す):

import * as v from 'valibot';

const UserSchema = v.object({
  username: v.pipe(v.string(), v.minLength(3)),
  email: v.pipe(v.string(), v.email()),
  age: v.optional(v.number()),
});

ここでは、v.pipe()が各条件を連結する役割を果たします。この書き方は一見少し冗長に見えるかもしれませんが、これこそがビルドツールが未使用のコードを削除するための鍵となります。

3. データのパースとエラーハンドリング

Valibotは、ロジックの処理方法に応じて2つの主要なデータチェック方法を提供しています。

const rawData = { username: "dev_viet", email: "[email protected]" };

// 方法1:即座にエラーをスローする必要があるロジックに使用
try {
  const data = v.parse(UserSchema, rawData);
} catch (err) {
  console.error(err);
}

// 方法2:Safe parse(よりクリーンなコードのために推奨)
const result = v.safeParse(UserSchema, rawData);
if (result.success) {
  console.log("検証済みデータ:", result.output);
} else {
  console.log("具体的なエラー:", result.issues);
}

実際にValibotに移行すべきタイミングは?

すでにプロジェクトが安定して動いているなら、すべてを書き直す必要はありません。ただし、以下の2つのシナリオではValibotを優先すべきです:

  • 純粋なクライアントサイドアプリ: 読み込み速度を最優先するランディングページやECサイト。また、ElysiaJSとBunで「超速」REST APIを構築するような、パフォーマンスと型安全性が両立する環境でも軽量なバリデーターは有利に働きます。
  • ライブラリ/SDK開発: 数個のパラメータをバリデートするためだけに、ライブラリのユーザーに50KB의 Zodを負担させないようにしましょう。

最近、ある登録モジュールでZodを置き換えたところ、バンドルサイズが即座に28KB減少しました。3G回線などの低速なネットワーク環境では、フォームの表示とインタラクションが約200〜300ms速くなりました。

終わりに

Valibotは単なるバリデーションツールではなく、現代のJavaScriptエコシステムにおけるモジュール化のトレンドを象徴しています。プラグインの数ではまだZodに及びませんが、その軽量さとパフォーマンスは否定できない強みです。

Zodに慣れているなら、Valibotへの移行はわずか15分程度で済みます。次のプロジェクトでぜひ最適化を試してみてください。ユーザーもきっと喜ぶはずです。

Share: