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 の使い方は簡単で, 先ほどと同等の出力を得るのに必要なのは 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
のみを使っていたといったケースでも, バンドラの導入は十分に選択肢に入ってくるかなと思います.
- エントリポイントから間違ってテストコードなどを参照してしまうと出力に含まれてしまうのでは? と思われたかもしれません. 安心してください. ビルド用の tsconfig で除外していた場合でも含まれてしまいます.↩