TypeScript でライブラリ (npm パッケージ) を作るときに, ビルド用の tsconfig を用意することがあります.
例えば以下のような tsconfig.json
を作成したとしましょう.
{
"compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"sourceMap": true,
"declaration": true
},
"include": [
"src/**/*"
]
}
これを使って素朴に tsc -p tsconfig.json
のようにビルドすると, src/index.ts
のようなライブラリ本体のコードだけではなく, src/index.test.ts
といったテストコードなどもコンパイルされ, 出力に含まれてしまいます.
ライブラリのユーザーにとってはテストコードは普通は全く役に立たないので, 不要なファイルが含まれてしまっている状態です.
かといって以下のように tsconfig.json
に exclude
を追加してテストコードを除いてしまうと, 今度は ESLint や IDE など他の tsconfig.json
を参照する開発ツールがテストコードをうまく扱えなくなってしまいます.
{
"compilerOptions": {
// (省略)
},
"include": [
"src/**/*"
],
"exclude": [
"src/**/*.test.*"
]
}
これらを両立させるためには, 以下のようなビルド用の tsconfig (tsconfig.build.json
のような名前にすることが多そう) を用意します.
これを使って tsc -p tsconfig.build.json
のようにビルドすると, テストコードは出力に含まれませんし, 開発ツールは tsconfig.json
を参照することでテストコードもうまく扱うことができます.
{
"extends": "./tsconfig.json",
"exclude": [
"src/**/*.test.*"
]
}
ここまでが前提の話.
上記のようなビルドと開発ツールを両立させる方法にはもう一つ選択肢があって, それはバンドラを使うことです.
エントリポイントから参照されないテストコードなどはバンドラが勝手に除外してくれるはずなので, ビルド用の tsconfig みたいなものを用意する必要がなくなります1.
一昔前はバンドラを使うとなると Webpack や Rollup を直接使って, デフォルトでは TypeScript に対応していないのでプラグインを入れて, それでもなぜかうまく動かなかったり, プラグインが複数あってどれを使えば良いのか悩んだり...
といった感じだったかと思いますが, 近年では tsup のような TypeScript でのライブラリ作成というユースケースに特化したバンドラが登場しているので, これらに任せておけば悪いようにはならないでしょう.
tsup.egoist.dev
tsup の使い方は簡単で, 先ほどと同等の出力を得るのに必要なのは tsup
のインストールと, せいぜい以下のような tsup.config.js
を置くくらいでしょう.
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
outDir: "./lib",
format: ["esm"],
splitting: false,
sourcemap: true,
dts: true,
clean: true,
});
他にもバンドラを使うと ESM と CommonJS の両方に比較的簡単に対応させられたり, コンパイル時定数を使ってデバッグ用のコードを除去できたりといった利点もあります.
見ての通りバンドラを設定するのとビルド用の tsconfig を用意するのとでは大して手間は変わらないので, 設定の複雑さを嫌ってビルドには tsc
のみを使っていたといったケースでも, バンドラの導入は十分に選択肢に入ってくるかなと思います.