🎄

valibotのご紹介 / TypeScript一人カレンダー

2024/12/16に公開

こんにちは、クレスウェア株式会社の奥野賢太郎 (@okunokentaro) です。本記事はTypeScript一人アドベントカレンダー 2024の1日目です。

アドベントカレンダーはとっくに始まっている12月半ばですが、songmuさんの記事を読んでアドベントカレンダー本来の姿を思い返しました。もともとアドベントカレンダーは、クリスマスを待ち望みながら毎日ちょっとしたお菓子やおもちゃを手に取る、そういう小さな楽しみだったんですよね。そう考えると、無理に長い記事を25本書く必要はないんじゃないかと思い直しました。1日目の記事でありながら12月16日に執筆していますが、なんとか25本書き切ろうと思いますので、よければ高評価、拡散などお願いいたします。

筆者は2022年に『TypeScript一人アドベントカレンダー』をやっていたことがあります。当時はTypeScriptがまだ4.9の頃で、今振り返ると「もう実践していない内容があるからもう少し改稿したい」と思う点も増えてきました。そこで2年ぶりに一人アドベントカレンダーをやってみようと思い至りました。ただ、今回は前回よりも短めな記事になることをご承知ください。 代わりに、実務の現場で得た小さなトピックを数多くお届けできればと思っています。

valibot

本日は『valibot』というライブラリを紹介します。TypeScript界隈のランタイムバリデーションライブラリだと、zodが有名かと思いますが、zodの競合として位置するライブラリだと解釈しています。zodは提供されるAPIがメソッドチェーン型なのに対して、valibotはpipeベースでスキーマを組み立てる点が特徴的です。

2022年当時はvalibotの存在自体がありませんでした。というのも、このライブラリが誕生したのは2023年7月頃なのです。 そのため『TypeScript一人アドベントカレンダー2022』ではこういったライブラリを使わずにいろいろと工夫して実装していましたが、今TypeScriptで型安全なバリデーションを組み込むのであれば、積極的にvalibotを使う選択肢があっても良いと思います。

valibot と zod のコード比較

たとえば次のようなコードがvalibotの典型的なコードです。

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

const LoginSchema = object({
  email: pipe(string(), email()),
  password: pipe(string(), minLength(8)),
});

公式のドキュメントではimport * as v from 'valibot';となっていますが、筆者は個人的には*で全て読み込むのは乱暴だと思っており、個別にインポートすることを好んでいます。zodの場合、pipe()ではなくメソッドチェーンを使う都合上、Tree shakingのアプローチが活用できず容量が膨らみやすいというデメリットがあります。このようなissueでもこの話題が触れられていることがわかります。

なおzodの場合はこのようになります。メソッドチェーンの方がわかりやすい、書きやすい、補完しやすいという意見もあると思いますので、好みではありますが、バンドルサイズの観点でzodによる肥大化を懸念するようであればvalibotは十分選択肢に入ると思います。

import { z } from 'zod';

const LoginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

valibot を使ってバリデーション処理を実行する

このスキーマを使ってバリデーション処理を実施することは簡単です。parse()を使います。

import { parse } from 'valibot';
import { LoginSchema } from './LoginSchema';

const parsed = parse(LoginSchema, somethingObject);

これだけでsomethingObjectLoginSchemaに準拠しているかどうか検証できます。違反している場合はValiErrorthrowされます。例外ではなくbooleanで結果を扱いたい場合はsafeParse()があります。

TypeScriptの型定義として得ることも簡単です。

import { type InferOutput } from 'valibot';
import { LoginSchema } from './LoginSchema';

type Login = InferOutput<typeof LoginSchema>;

InferOutput<T>を用いることで、emailpasswordプロパティを併せ持つオブジェクトの型が得られます。スキーマ定義では、よく型定義とランタイムバリデーション用スキーマの二重定義の扱いで苦労しがちですが、valibotを用いることで一元化が可能です。2年前の記事にて、assertMatchedTypes()関数をなんとかして生み出していたのも今は昔。単にvalibotを使うだけでよいというのは隔世の感があります。

明日は『branded types』

本日はvalibotを紹介しました。明日も引き続き、valibotのもう少し実践的な使い方としてbranded typesを紹介していきます。ではまた明日。

Discussion