はじめに
npmパッケージを作ろうとしたとき、tscだけではCJSとESMの両方を出力できず、Rollupの設定ファイルを書き始めたら思いのほか複雑になってしまった、という経験はありませんか。型定義ファイルの生成、複数フォーマットへの出力、Tree Shaking……ライブラリを1本公開するだけなのに、ビルド設定に半日溶かしてしまうことも珍しくありません。
そんなビルド周りの悩みを、ほぼ設定ゼロで解決してくれるのが「tsup」です。esbuildを内部で利用することで、高速かつシンプルにTypeScriptプロジェクトをバンドルできます。この記事では、tsupの特徴から実際の使い方までを順を追って紹介します。
tsupとは
tsupは、esbuildをベースにしたゼロコンフィグのバンドラーです。TypeScript/JavaScriptのファイルを指定するだけで、CJS・ESM・IIFEといった複数フォーマットへのビルドや型定義ファイル(.d.ts)の生成をまとめて行ってくれます。
GitHub上で1万を超えるスターを集めており、Prismaやdrizzle-ormなど多くの著名なOSSライブラリのビルドツールとして採用されています。npmパッケージ開発における「バンドル設定」を極力意識せずに済む点が、幅広く支持されている理由です。
主な特徴
- esbuildによる高速ビルド - Rollupやwebpackに比べ、ネイティブ実装のesbuildを使うことで圧倒的な速度でビルドできます
- ゼロコンフィグ - エントリーポイントを指定するだけで、設定ファイルなしにビルドが完了します
- 複数フォーマット同時出力 - CJS・ESM・IIFEなど、必要なフォーマットを同時に出力できます
- 型定義ファイルの自動生成 -
--dtsオプション一つで.d.tsファイルを生成できます - Watchモード対応 - ファイル変更を検知して自動で再ビルドできるため、開発中も快適です
インストール
npm、yarn、pnpmのいずれでもインストールできます。
# npm
npm install tsup --save-dev
# yarn
yarn add tsup --dev
# pnpm
pnpm add tsup --save-dev
TypeScriptを使う場合は、typescriptパッケージも合わせてインストールしておきましょう。
npm install typescript --save-dev
基本的な使い方
もっともシンプルな使い方は、エントリーポイントを指定してCLIから直接実行する方法です。
npx tsup src/index.ts
これだけで、dist/index.jsにCJS形式のバンドルが出力されます。ESM形式や型定義ファイルも合わせて出力したい場合は、オプションを追加します。
npx tsup src/index.ts --format cjs,esm --dts
package.jsonにビルドスクリプトとして登録しておくと便利です。
{
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
}
}
より細かい設定をしたい場合は、tsup.config.tsを用意します。
// tsup.config.ts
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
});
clean: trueを指定しておくと、ビルドのたびに古い出力ファイルを削除してくれるので、不要なファイルがdistに残る心配がありません。
実践的なユースケース
複数エントリーポイントを持つライブラリ
ユーティリティ関数とReactコンポーネントのように、公開したいモジュールが複数ある場合は、エントリーポイントを配列やオブジェクトで指定できます。
// tsup.config.ts
import { defineConfig } from "tsup";
export default defineConfig({
entry: {
index: "src/index.ts",
utils: "src/utils/index.ts",
},
format: ["cjs", "esm"],
dts: true,
});
こうすることで、import { helper } from "your-lib/utils"のようにサブパスとしてインポートできる出力を作成できます。
package.jsonのexportsフィールドとの連携
tsupでビルドしたファイルは、package.jsonのexportsフィールドと組み合わせることで、CJS/ESM両対応のパッケージとして公開できます。
{
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}
このように設定しておけば、利用者側の環境(Node.jsのrequireやESMのimport)に応じて適切なファイルが自動的に読み込まれます。
外部依存のバンドル除外
ライブラリ開発では、reactのようなpeerDependenciesをバンドルに含めたくないケースがよくあります。tsupはデフォルトでdependenciesとpeerDependenciesを自動的に外部化してくれますが、明示的に指定することもできます。
// tsup.config.ts
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true,
external: ["react", "react-dom"],
});
まとめ
tsupは、esbuildの速度を活かしながら、複雑なビルド設定を書かずにTypeScriptライブラリを公開できるツールです。CJS・ESMの同時出力や型定義ファイルの生成といった、ライブラリ開発でつまずきがちなポイントを、最小限の設定でカバーしてくれます。
これからnpmパッケージを作る予定がある方は、まずnpx tsup src/index.ts --format cjs,esm --dtsから試してみて、その手軽さを体感してみてください。設定に悩む時間が減った分、ライブラリ本体の実装に集中できるはずです。