@ryoppippi

TypeScriptの型システムに命を吹き込む: Typia と unplugin-typia

12 Jun 2024 ・ 29 min read


Note

2025年6月14日追記: unplugin-typiaは​アーカイブされました。 詳細に​関してはREADME.mdを​参照してください。

TL;DR

  • この​度、unplugin-typia と​いう​ Library を​作りました
  • unplugin-typia を​使うと​今まで​めんどく​さかった​ Typia の​導入が​簡単に​なります
  • Viteesbuildwebpackなどフロントエンドで​主流の​様々な​bundlerに​対応しています
  • Next.jsでも​簡単に​使えます
  • Bunにも​対応しています

https://github.com/ryoppippi/unplugin-typia/ https://jsr.io/@ryoppippi/unplugin-typia https://typia.io/docs/setup/#unplugin-typia

はじめに

皆さんは​TypeScriptでの​Validationには​どのような​ Library を​使っていますか?

zodは​エコシステムが​硬いし、​最近だとvalibotが​流行りつつありますね。 またarktypeも​注目に​値する​Libraryです。 typeboxも​耳に​する​機会が​増えてきました。 また​個人的には​(厳密には​Validatorではないですが​)、unknownutilも​手に​馴染んで​よく​使っています。

既存の​Validation Library/TypeScriptに​足りない​もの

TypeScriptの​型システムは​非常に​強力です。 アップデートを​重ねた​結果​とても​豊かな​表現力を​持ち、​型システムと​して​チューリング完全である​ことが​知られています^https://github.com/Microsoft/TypeScript/issues/14833。 型パズルを​駆使すれば​brainf**k interpreter^https://github.com/susisu/typefuckで​さえ​書けてしまいます。

しかし、​型システムは​実行時には​消えてしまいます。​また、​TypeScriptでは​原則と​して​「値から​型を​作る」ことは​できますが、​「型から​値を​作る」ことは​できません。

型から​値への​変換の​限界

TypeScriptの​型情報は​開発時の​安全性を​提供しますが、​実行時に​動作する​ことは​ありません。 例えば、​以下のような​コードを​考えます:

function someFunction(): any {}
const value: string = someFunction();

この​コードでは​someFunctionの​戻り値が​string型である​ことを​保証する​ために​type assertionや​明示的な​Validationが​必要ですが、​型自体から​そのような​Validation logicを​生成する​ことは​できません。

zodvalibotなどの​既存の​Validation Libraryは、​TSの​ Library と​して​用意された​DSL​(ドメイン固有言語)で​ assertion を​定義し、​そこから​TypeScriptの​型を​生成(推論)する​ことで​型安全を​担保しています。 これでは​既存の​型に​対する​ Validation 関数を​素朴な​方法で​用意で​きず、​DSLを​覚えて、​一から​ Validation 関数を​作りな​おす必要が​あります。 この​ため型と​Validation Logicが​分離してしまうことがあります。 また、​TSと​いう​型システムが​あるのに、​さらに​DSLで​型システムに​準じる​ものを​作り直すのは​直感的ではないですよね。

Typia とは

https://typia.io/

Typiaは、​このような​課題を​解決する​ための​ツールです。

  • 高速: Typiaは​既存の​Validation Library に​比べて​非常に​高速です。​「zodの​1500倍速い」とも​言われています^https://zenn.dev/hr20k_/articles/3ecde4239668b2#速度の​比較
  • 型情報から​ Validation を​生成: TypeScriptの​型情報を​元に​ Validation を​生成します。​コンパイルが​必要ですが、​これに​より​型チェックの​正確性と​パフォーマンスが​向上します。
  • シンプルな​記法: 特定の​ Library 特有の​記法を​覚える​必要は​なく、​TypeScriptの​標準的な​型記述から​ Validation を​生成します。
  • 多機能: Validation だけでなく、​高速な​JSON変換、​JSON Schema生成、​ProtoBuf生成、​ランダムデータ生成などの​機能も​提供します。

Typiaの​高速さは​特筆すべき点であり、​実際に​使用した​プロジェクトでは、​APIドキュメントから​自動生成された​大量の​TypeScript型ファイルを​使って​ Validation を​行う​場面で​非常に​有効でした。

しかし、Typiaの​真の​強みは、「独自の​記法を​覚える​必要が​ない​こと」 に​あります。 この​点は​既存の​ Validation Library との​大きな​差別化要因であり、Typiaの​導入障壁を​大幅に​下げています。 Typiaを​使えば、​TypeScriptの​標準的な​記述に​従うだけで、​自然に​Validationや​他の​機能を​実現できる​ため、​新しい​Libraryに​合わせて​ルールや​シンタックスを​覚える​必要が​ありません。 開発者は​既存の​TypeScriptの​知識だけでTypiaを​使いこな​すことができるのです。

Typiaは​実行時に​消え去る​運命に​合った​型情報に​息を​吹き込む Library と​言えるでしょう。

Typia の​コードを​見てみよう

シンプルな​例

手始めに​簡単な​例から​:

import typia from 'typia';

const b = typia.is<string>('hello world');
console.log(b);

この​コードは、'hello world'string型であるか​どうかを​チェックする​コードです。 これをTypiaで​コンパイルすると​以下のような​コードが​生成されます。

// 生成されたコード
import typia from 'typia';

const b = ((input: any): input is string => {
	return typeof input === 'string';
})('hello world');
console.log(b); // true

このように、typia.isは​ 型情報から Validation関数を​生成します。 生成された​コードを​見てみると、​importされているtypiaは​どこからも​参照されていないので、​この​あと​bundlerを​挟むと​tree-shakingされる​ことが​わかります。

Object型の​例

次は、​一般的な​型に​対して​Validationを​行う​コードを​見てみましょう。 例えば、​以下のようなMember型が​あり、​それを​チェックする​コードが​あるとします。 ここでは​Validationを​行う​関数を​生成する​ためにtypia.isを​使っています。

// 元のコード
import typia from 'typia';

type Member = {
	/**
  * @format uuid
  */
	id: string;

	/**
  * @type uint32
  * @minimum 20
  * @exclusiveMaximum 100
  */
	age: number;

	name: string;

	time?: Date;
};

const member = { id: '', name: 'taro', age: 20 } as const satisfies Member;
console.log(typia.is<Member>(member)); // false

これをTypiaを​使って​コンパイルすると​ランタイムでの​型チェックを​行う​コードが​生成されます。 少し​長いので​折りたたんでいます。

生成されたコード
// Typiaによって生成されたコード
import typia from 'typia';
// ←もはや`typia`は使用されてないのでtree-shakingの対象になる
type Member = {
	/**
	 * @format uuid
	 */
	id: string;
	/**
	 * @type uint32
	 * @minimum 20
	 * @exclusiveMaximum 100
	 */
	age: number;
	name: string;
	time?: Date;
};
const member = { id: '', name: 'taro', age: 20 } as const satisfies Member;
console.log(
	((input: any): input is Member => {
		const $io0 = (input: any): boolean =>
			typeof input.id === 'string'
			&& /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test(
				input.id,
			)
			&& typeof input.age === 'number'
			&& Math.floor(input.age) === input.age
			&& input.age >= 0
			&& input.age
			/**
			 * @format uuid
			*/ <= 4294967295
			 && input.age >= 20
			&& input.age < 100
			 && typeof input.name === 'string'
			&& (undefined === input.time || input.time instanceof Date);
		return typeof input === 'object' && input !== null && $io0(input);
	})(member),
); // false

またtypiaには​JSDocの​代わりにtagを​用いる​別の​書き方も​あります。 生成される​コードは​同じですが、​Editor上で​補完が​効くので​便利です。

import typia, { tags } from 'typia';

type Member = {
	id: string & tags.Format<'uuid'>;
	name: string;
	time?: Date;
	age: number
		& tags.Type<'uint32'>
		& tags.Minimum<20>
		& tags.ExclusiveMaximum<100>;
};

const member = { id: '', name: 'taro', age: 20 } as const satisfies Member;
console.log(typia.is<Member>(member)); // false

型関数を​用いたより​複雑な​型に​対しても​Validation関数を​生成できます。

比較的複雑な型の例
// 元のコード
import typia from 'typia';

type D = {
	/**
  * @format uuid
  */
	id: string;

	age?: number | null;
};

type Member = {
	name: string;
	id: string;
	details: D;
};

type ValidateType = Pick<Member, 'details'> & Omit<Member, 'id'>;

console.log(typia.is<ValidateType>({} as unknown)); // false
// 生成されたコード
type D = {
	/**
	 * @format uuid
	 */
	id: string;
	age?: number | null;
};
type Member = {
	name: string;
	id: string;
	details: D;
};
type ValidateType = Pick<Member, 'details'> & Omit<Member, 'id'>;
console.log(
	((input: any): input is ValidateType => {
		const $io0 = (input: any): boolean =>
			typeof input.details === 'object'
			&& input.details !== null
			&& $io1(input.details)
			&& typeof input.name === 'string';
		const $io1 = (input: any): boolean =>
			typeof input.id === 'string'
			&& /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test(
				input.id,
			)
			&& (input.age === null
				|| undefined === input.age
				|| typeof input.age === 'number');
		return typeof input === 'object' && input !== null && $io0(input);
	})({} as unknown),
); // false

Typiaの​Documentには​Playgroundが​用意されているので、​実際に​試してみると​いいでしょう。 https://typia.io/playground/

詳細な​ベンチマーク結果は​こちらの​記事を​参照してください。 https://zenn.dev/hr20k_/articles/3ecde4239668b2

Typiaの​導入の​課題

これまでTypiaを​使おうと​すると、​いく​つかの​ハードルが​必要でした。

Typiaには​2つの​モードが​あります。​Transformationモードと​Generationモードです。

  • Transformationモード: tscの​Transform APIを​使って​型情報から​Validationを​生成する​モード。tsc実行時に​Validationの​コードが​生成される。
  • Generationモード: Typiaの​CLIを​使って​型情報から​Validationを​生成する​モード。​Bundlerがtscを​使わない​場合に​使う。

癖が​なく​ハマりずらいのは​Generationモードです。 Typiaの​CLIを​使って​コードを​生成し、​それを​他の​ファイルからimportするだけで​使うことができます。 しかし、​ビルドステップが​一つ​増えますし、​管理する​コードも​増えるので​できれば​Transformationモードを​使いたいですよね。

ところが​Transformationモードは​導入の​ハードルが​高いです。

直接tscコマンドを​叩いて​コンパイルする​場合は​導入が​簡単ですが、​他の​Bundlerを​使う​場合には​あらかじめtscを​経由して​Bundleするように​設定を​する​必要が​あります。

viteの例
import react from '@vitejs/plugin-react';
import typescript from 'rollup-plugin-typescript2';
import { defineConfig } from 'vite';

// https://vitejs.dev/config/
export default defineConfig({
	esbuild: false,
	plugins: [
		react(),
		typescript(),
	],
});

ただ​手元だと​うまく​動かないことも​多かったです。​特にts/tsx以外のhtml-ishな​言語^https://biomejs.dev/blog/biome-v1-6/#partial-support-for-astro-svelte-and-vue-files、​例えばsveltevueなどを​使用している​プロジェクトでは​特に​問題が​多かったです。

https://github.com/samchon/typia/issues/812

その​ため Webpack などの​Bundlerを​用いた​プロジェクト( Next.js など​ )では、tscを​使って​コンパイルを​していないため、​Generationモードを​使う​必要が​ありました。

Generationモードを​使っても​いいのですが、​まあビルドステップが​一つ​増えますし、​管理する​コードも​増えるので、​できれば​Transformationモードを​使いたいですよね。

unplugin-typia

そこで、​私はunplugin-typia を​作りました。

https://github.com/ryoppippi/unplugin-typia https://jsr.io/@ryoppippi/unplugin-typia

unplugin-typia は​ unplugintscを​組み合わせて​作っています。 unplugin とは、Vite や​ esbuildwebpack などの​複数の​bundlerに​対応した​プラグインを​共通の​APIで​作る​ための​Libraryです。

unplugin-typiaを​使うと、​複雑な​設定を​する​ことなく、​bundlerで​typiaの​Transform モード相当の​体験を​得る​ことができます。

Note

それぞれの​bundlerの​詳しい​説明はJSR上の​docを​参照してください。 またexamplesには​各bundlerごとの​サンプルが​あります。 さらに​いく​つかの​サンプルに​ついては​実際に​デプロイされているので、​試してみてください。

bundlerdeploy siteGitHub
ViteCloudflare pagesexamples/vite-react
Vite + SveltekitCloudflare pagesexamples/sveltekit
Vite + HonoCloudflare pagesexamples/vite-hono
Next.jsVercelexamples/nextjs

(Vite + Honoは​APIのみの​デプロイです。README.mdを​読んでcurlしてね!​)

実際にunplugin-typiaを​使ってみよう

では​簡単にunplugin-typiaTypiaを​使って​遊んで​みましょう。

Bun と​一緒に​使ってみる

一番​手っ取り早く​使う​方法はBun.buildを​使う​ことです。

Github上に​テンプレートを​作成したので、​それを​使って​プロジェクトを​作成します。

https://github.com/ryoppippi/bun-typia-template

git clone https://github.com/ryoppippi/bun-typia-template
cd bun-typia-template
bun i

# 以下のコマンドで実行
bun run index.ts

# もしビルドして実行したい場合
bun run build # build.tsを実行してビルドを行う。`./out`にビルドされたファイルが出力される
bun run ./out/index.js
# もしくは
node ./out/index.js

これだけで、Typiaを​使った​コードを​実行する​ことができます。

とっても​簡単ですね。

Vite + Hono + unplugin-typiaで​使ってみる

次に、ViteHonounplugin-typiaを​使って​遊んで​みましょう。

プロジェクトの​作成

まずは、​プロジェクトを​作成します。

npm create hono@latest ./my-app -- --template cloudflare-pages
cd my-app
npm install

次に、unplugin-typiaを​インストールします。 unplugin-typiaJSRに​公開されているので、jsr コマンドを​使って​インストールします。

npx jsr add -D @ryoppippi/unplugin-typia

そしてtypiaを​導入します。Typiaの​ドキュメントを​参考に​してください。

npm i typia # typiaをインストール
npx typia setup # typiaのsetup wizardを実行
npm i @hono/typia-validator --force # honoのtypia-validatorをインストール

これで​準備が​できましたね!

unplugin-typiaの​設定を​する

unplugin-typiaviteの​プラグインと​して​使います。 vite.config.tsに​以下のように​設定します。

 import build from '@hono/vite-cloudflare-pages'
 import devServer from '@hono/vite-dev-server'
 import adapter from '@hono/vite-dev-server/cloudflare'
 import { defineConfig } from 'vite'
+import UnpluginTypia from '@ryoppippi/unplugin-typia/vite';

 export default defineConfig({
   plugins: [
     build(),
+    UnpluginTypia(), // unplugin-typiaを追加
     devServer({
       adapter,
       entry: 'src/index.tsx'
     })
   ]
 })

Typiaを​使って​実装してみる

では、src/index.tsxに​以下の​コードを​書いてみましょう。

 import { Hono } from 'hono'
 import { renderer } from './renderer'
+import typia from 'typia'
+import { typiaValidator } from '@hono/typia-validator'

+interface Props {
+  name: string
+}

 const app = new Hono()

 app.use(renderer)

 app.get('/', (c) => {
   return c.render(<h1>Hello!</h1>)
 })

+app.post('/',
+  typiaValidator('json', typia.createValidate<Props>()),
+  (c) => {
+    const data = c.req.valid('json');
+
+    return c.json({
+      success: true,
+      message: `Hello ${data.name}!`
+    })
+  }
+)

 export default app

これで​準備が​できました。

実行してみる

それでは、​実行してみましょう。

$ npm run dev

> dev
> vite

 ╭──────────────────────────────────╮

  [unplugin-typia] Cache enabled  │

 ╰──────────────────────────────────╯

(!) Could not auto-determine entry point from rollupOptions or html files and there are no explicit optimizeDeps.include patterns. Skipping dependency pre-bundling.

  VITE v5.2.13  ready in 588 ms

  Local:   http://localhost:5173/
  Network: use --host to expose
  press h + enter to show help

無事、​起動できましたね!

それでは、​APIを​叩いてみましょう。

curlを​使っても​いいのですが​記述が​長くなるので、​ここではxhを​使ってみましょう。

$ xh :5173 name=ryoppippi
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 45
Content-Type: application/json; charset=UTF-8
Date: Wed, 12 Jun 2024 11:14:19 GMT
Keep-Alive: timeout=5

{
    "success": true,
    "message": "Hello ryoppippi!"
}

無事、​Validationが​通り、​APIが​叩けましたね!

試しにnameを​文字列ではなく​数値で​送ってみましょう。

$ xh :5173 name:=5
HTTP/1.1 400 Bad Request
Connection: keep-alive
Content-Length: 80
Content-Type: application/json; charset=UTF-8
Date: Wed, 12 Jun 2024 11:15:58 GMT
Keep-Alive: timeout=5

{
    "success": false,
    "error": [
        {
            "path": "$input.name",
            "expected": "string",
            "value": 5
        }
    ]
}

Validation Error が​返ってきましたね!

Cloudflare Pagesに​デプロイしてみる

最後に、​Cloudflare Pagesに​デプロイしてみましょう。

$ npm run deploy
$ $npm_execpath run build && wrangler pages deploy
$ vite build

 ╭──────────────────────────────────╮

  [unplugin-typia] Cache enabled  │

 ╰──────────────────────────────────╯

vite v5.2.13 building SSR bundle for production...
 50 modules transformed.
dist/_worker.js  67.14 kB
 built in 167ms                                                                                                                                                                                                          The project you specified does not exist: "hono-vite". Would you like to create it?"
❯ Create a new project
✔ Enter the production branch name: … main
✨ Successfully created the 'hono-vite' project.
🌏  Uploading... (1/1)

✨ Success! Uploaded 1 files (1.61 sec)

✨ Compiled Worker successfully
✨ Uploading Worker bundle
✨ Uploading _routes.json
🌎 Deploying...
✨ Deployment complete! Take a peek over at https://xxx.pages.dev

無事デプロイできましたね! あとは​この​URLを​実際に​呼んで​みましょう。

$ xh https://xxx.pages.dev name=ryoppippi

# 省略

{
    "success": true,
    "message": "Hello ryoppippi!"
}

無事デプロイされた​APIが​叩けましたね!

まとめ

  • unplugin-typiaを​使うとTypiaの​導入が​簡単に​なります
  • Viteesbuildwebpackなどの​bundlerに​対応しています
  • Typiaは​楽しい
  • Typiaは​面白い!

ぜひTypiaを​試してみてください!

Appendix

jsonup + typia

jsonupは​TypeScriptで​書かれた​JSON Parserです。 JSON形式の​Literal Stringを​与えると​型推論を​行うと​いう、​謎技術 Library です​(本人曰く​ネタで​作った​そうですが、​TypeScriptの​Compilerの​限界に​挑戦してる​感が​好きです)。

https://github.com/tani/jsonup

そして、jsonuptypiaを​組み合わせると​なんと、​文字列から​validation関数が​生成されると​いう、​なんとも​不思議な​ことができます。

https://x.com/ryoppippi/status/1800183030235255019

typiaには​random generatorが​ついているので、​JSONの​文字列を​例と​して​与えると​それに​合致する​ランダムな​値を​生成する、​なんて​ことも​できます。

import type { ObjectLike } from 'jsonup';
import typia from 'typia';

const jsonSample = `{ "name": "jsonup", "age": 34}`;

/**
 * type Obj = {
 *    name: string;
 *    age: number;
 * }
 */
type Obj = ObjectLike<typeof jsonSample>;

console.log(typia.random<Obj>());

$ bun run ./index.ts
{ name: 'dvdnp', age: 49.25475568792122 }
$ bun run ./index.ts
{ name: 'htgywkaq', age: 13.818270173692525 }
$ bun run ./index.ts
{ name: 'pyurujgkvd', age: 47.19975642889989 }

https://github.com/ryoppippi/bun-typia-jsonup-experiments

GitHubから​コードを​落とせるのでぜひ​遊んで​みてください。

自分はjsonuptype-festのような​型パズルの​ Library が​大好きなので、​これらに​息を​吹き込めるようなtypiaは​とても​楽しいです。

typefuck + typia

typefuck とは、​型レベルで​実装された​Brainfuck interpreterです。

https://github.com/susisu/typefuck

この​実装に​より、​TypeScriptの​型システムは​チューリング完全である​ことがわかるのですが、​こちらもTypiaと​組み合わせる​ことができます。

import type { Brainfuck } from '@susisu/typefuck';
import typia from 'typia';

type Program = '>, [>, I<[.<]';
type Input = 'Hello, world!';
type Output = Brainfuck<Program, Input>;

console.log(typia.is<Output>('!drow ,olleH')); // true

type-fest + typia

type-festとは、​TypeScriptで​型を​操作する​時の​便利関数を​集めた​Libraryです。

https://github.com/sindresorhus/type-fest

た​とえば、​xor^https://ja.wikipedia.org/wiki/排他的論理和を​実現する​ための​型と​してMergeExclusiveが​用意されています。 これをTypiaと​組み合わせると、​それぞれの​keyに​対して​排他的な​Object型が​できあがります。

import type { MergeExclusive, SimplifyDeep } from 'type-fest';
import typia from 'typia';

type ExclusiveVariation1 = {
	exclusive1: boolean;
};

type ExclusiveVariation2 = {
	exclusive2: string;
};

type ExclusiveOptions = SimplifyDeep<
	MergeExclusive<
		ExclusiveVariation1,
		ExclusiveVariation2
	>
>;

const is = typia.createIs<ExclusiveOptions>();

console.log(is({ exclusive1: true })); // true
console.log(is({ exclusive2: 'string' })); // true
console.log(is({ exclusive1: true, exclusive2: 'string' })); // false
ExclusiveOptionsの型
type ExclusiveOptions = {
	exclusive1?: undefined;
	exclusive2: string;
} | {
	exclusive2?: undefined;
	exclusive1: boolean;
};

また、IntRangeと​いう​型も​あります。​この​型は​指定された​範囲の​整数を​表します。 これを​使って、​1ケタの​数字を​表す型を​作り、​Validation関数を​生成してみましょう。 さらに、random関数を​使って​ランダムな​値を​生成してみます。

import type { IntRange } from 'type-fest';
import typia from 'typia';

type Digit = IntRange<0, 10>; // 0 <= Digit < 10

const is = typia.createIs<Digit>();

console.log(is(5)); // true
console.log(is(11)); // false
console.log(is(-1)); // false

console.log(typia.random<Digit>()); // 5 (or any other number between 0 and 9)
random関数のコンパイル結果
console.log(((generator) => {
	const $pick = typia.random.pick;
	return $pick([
		() => 0,
		() => 1,
		() => 2,
		() => 3,
		() => 4,
		() => 5,
		() => 6,
		() => 7,
		() => 8,
		() => 9
	])();
})());

自分の​知る​限り、​従来型の​Validation Libraryで​このような​ロジックを​組んだと​しても、​それが​型​(z.infer<foo>など)に​反映される​ことはないと​思います。 た​とえばzodで​あれば

import { IntRange } from 'type-fest';
import { z } from 'zod';

const digit = z.number().int().min(0).max(10).transform(x => (x as IntRange<0, 10>));

と​する​必要が​ありますが、​これは​型と​Validation Logicが​分離してしまっていますよね。

Typiaは​型情報から​Validation Logicを​生成する​ため、​型と​Validation Logicが​一体と​なっているのが​特徴です。

Note

Digitの​例は、​一応Typiaの​記法を​使うと

import {'\{'} tags {'\}'} from 'typia';
type Digit = number
 & tags.Type<'uint32'>
 & tags.Minimum<0>
 & tags.ExclusiveMaximum<10>

と​書く​ことも​できます。

Typia記法を​使う​メリット

  • 範囲で​指定が​できる​(type-festは​union型なので​)
  • 範囲指定の​方が​JSON Schema等の​生成に​有利(type-festだと​union型なのでとりうる​数字が​全て​列挙される…​)

type-fest記法を​使う​メリット

  • Editor 上で​補完が​効く​(取りうる​値が​個別の​ Literal 型と​して​型情報に​反映されているので)
  • 範囲外の​数値に​対して​Editor 上で​型エラーが​出る​(取りうる​値が​型情報に​反映されているので)

zod/valibotと​バンドルサイズの​比較

先ほどのTypiaでの​実装例をzodvalibotで​書いた​例と​比較してみましょう。

import { z } from 'zod';

const Member = z.object({
	id: z.string().uuid(),
	age: z.number().int().min(20).max(99),
	name: z.string(),
	time: z.date().optional(),
});

type Member = z.infer<typeof Member>;

const member = { id: '', name: 'taro', age: 20 } as const satisfies Member;

console.log(Member.parse(member));
import * as v from 'valibot';

const Member = v.object({
	id: v.pipe(v.string(), v.uuid()),
	age: v.pipe(
		v.number(),
		v.integer(),
		v.minValue(20),
		v.maxValue(99) // exclusiveの代わりに-1しておく
	),
	name: v.string(),
	time: v.optional(v.date()),
});

type Member = v.InferOutput<typeof Member>;

const member = { id: '', name: 'taro', age: 20 } as const satisfies Member;

console.log(v.is(Member, member));
Libraryバンドルサイズ
typia0.563 kb
zod117 kb
valibot8.50kb

とても​小さいですね!

bundle sizeに​ついて​補足

現在の​実装でもcreateIsと​いう​型を​チェックするだけの​関数を​生成する​場合は​バンドルサイズが​とても​小さいです。 しかし、​エラーメッセージを​生成する​ための​関数を​生成しようと​すると、​なぜか​そこに​Random Generatorを​含めてしまい​バンドルサイズが​大きくなってしまうようです。 おそらくzodと​同じように​ Library の​かなりの​部分を​巻き込んで​バンドルされているようです。 それでもzodよりは​全然​小さいですが​… (余談ですが、zodも​次の​バージ​ョンで​バンドルサイズを​削減する​予定です)

これに​ついては​ Typia は​現在内部の​リファクタリングを​進めています。

また、​先日リリースされたv6.1.0では、​本格的に​ Typia が​ ESM に​対応しました。 これに​より、​Validationの​関数の​バンドルサイズは​手元だと​2/3ほどに​なりました。

https://github.com/samchon/typia/releases/tag/v6.1.0

開発環境に​ついて

ひとりごと

今回の​ unplugin-typia は​ npm ではなく​ JSR で​公開されています。 JSR は​ npm と​同じように​パッケージを​公開できる​サービスですが、npm とは​異なり、​サーバー上で​ package.json の​生成や​ TypeScript の​コンパイルを​開発者の​代わりに​行ってくれるので、​ Libaray の​公開が​とても​簡単です。 欠点と​して​ESMに​しか​対応していないので、​例えば​ Webpack の​設定を​書く​ときは​直接 require すると​エラーに​なってしまいますが、jiti などを​経由すれば​問題ありません。 実際、Webpackの​導入方​法 では​ jiti を​使った​方​法を​紹介しています。

また​ローカルでは​ Bun を​使って​開発しています。 Bun は​ TypeScript を​そのまま​import、​実行できるので​とても​楽でした。

結果​的に​ bundler 向けの​ plugin を​作っているのに​自分は​一切 bundler を​使わない、と​いう​謎な​状況に​なっていますが、​開発体​験は​めちゃくちゃ​良かったです。

まあunplugin-typia の​コードベースが​ Bun である​必要も​特に​ないので、node_modules を​管理しなくて​済む Deno に​移行するかもしれません。 Deno も​直接 TypeScript を​実行できますし、​何より​ JSR との​相性は​抜群ですからね。

リンクなど

comment on bluesky / twitter
CC BY-NC-SA 4.0 2022-PRESENT © ryoppippi