ビルド用の tsconfig を用意するよりもバンドラに任せた方が楽かもしれない

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.jsonexclude を追加してテストコードを除いてしまうと, 今度は 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 のみを使っていたといったケースでも, バンドラの導入は十分に選択肢に入ってくるかなと思います.


  1. エントリポイントから間違ってテストコードなどを参照してしまうと出力に含まれてしまうのでは? と思われたかもしれません. 安心してください. ビルド用の tsconfig で除外していた場合でも含まれてしまいます.