【テクニカル・上級編】 emitDeclarationOnlyの活用 – TypeScript実践ガイド

TypeScriptの「透明な」アーキテクチャ:`emitDeclarationOnly`がもたらすビルドパイプラインの静かな革命

やあ。フロントエンドの深淵を覗き込んでいる諸君。

大規模なモノレポを運用していると、ビルドパイプラインの最適化は単なる趣味ではなく、生存戦略になる。特に、TypeScriptのコンパイラ(`tsc`)が吐き出す「ゴミ」――つまり、コンパイル後のJavaScriptファイルが、実際には別のトランスパイラ(esbuildやSWC)によって生成されているという状況に、違和感を覚えたことはないか?

今日は、TypeScriptのコンパイラを「コード生成器」ではなく「型検証・定義生成器」として再定義する、`emitDeclarationOnly: true`という魔法について深掘りしよう。

なぜ「二重のトランスパイル」は悪なのか

現代のWeb開発において、`tsc`でJSを生成するのは、往々にして「時代遅れ」だ。`tsc`は型チェックにおいては至高の存在だが、実行速度の観点では`esbuild`や`SWC`の足元にも及ばない。

多くのプロジェクトで、以下のような構成を見かける。
1. `tsc` で JS を生成する。
2. `esbuild` でバンドルする。

これは二重のトランスパイルであり、メモリ効率も悪ければ、I/Oの無駄も大きい。特にCI環境では、この無駄なファイル出力がビルド時間を数秒~数十秒単位で食いつぶす。ここで登場するのが `emitDeclarationOnly` だ。

`emitDeclarationOnly` の本質:型の抽出という思想

`tsconfig.json` でこのオプションを有効にすると、TypeScriptコンパイラはJSコードの生成を完全に放棄する。その代わり、純粋に「型定義(`.d.ts`)」と「ソースマップ」の生成にリソースを集中させる。

// tsconfig.json
{
“compilerOptions”: {
“declaration”: true, // 型定義ファイルを生成する
“emitDeclarationOnly”: true, // JSコードの生成はスキップする
“outDir”: “./dist/types”, // 型定義のみを隔離して出力
“skipLibCheck”: true,
“strict”: true
},
“include”: [“src//”]
}

この設定の真価は、「ビルドパイプラインの分離」にある。

  • トランスパイル担当: `esbuild` や `SWC`。圧倒的な速度でESM/CJSを生成する。
  • 型チェック・定義生成担当: `tsc`。型安全を担保しつつ、公開ライブラリや疎結合なパッケージのための `.d.ts` を出力する。

これにより、コンパイラの競合を回避し、メモリ消費を最適化できる。特に、大規模なプロジェクトで `tsc –noEmit` を走らせるだけでは得られない「型定義の配布」というメリットを、高いパフォーマンスで両立できるのだ。

アーキテクトが知っておくべき副作用と回避策

ただし、この手法には特有の「罠」がある。特に、`isolatedModules: true` との併用だ。

`emitDeclarationOnly` を使う場合、コンパイラは各ファイルを独立して処理するため、`const enum` のインライン化のような「型に依存するJSコードの生成」が機能しなくなる。もし君たちのプロジェクトが古いTypeScriptの慣習に依存しているなら、ビルドで謎のランタイムエラーが頻発することになるだろう。

また、非同期処理の競合についてはこうだ。型定義生成は、実際には抽象構文木(AST)を走査する重い処理だ。並列ビルドを行う際は、`ts-loader` のようなオーバーヘッドの大きいプラグインを避け、`tsc –emitDeclarationOnly –pretty` を独立したプロセスとして走らせるのが、最も堅牢な解決策となる。

実践的なビルドスクリプトの断片

現場で私がよく使う、ビルドの分離戦略を示す。

package.json の scripts セクション
{
“scripts”: {
“build:js”: “esbuild src/index.ts –bundle –outfile=dist/bundle.js”,
“build:types”: “tsc –emitDeclarationOnly”,
“build”: “npm run build:js && npm run build:types”
}
}

この構成の最大の強みは、「型エラーがあってもJSは動く(あるいはその逆)」という柔軟な開発体験の構築にある。大規模アプリケーションにおいて、型定義の生成が遅いという理由だけでJSのデバッグが止まるのは、エンジニアの生産性に対する冒涜だ。

最後に:境界線を引く美学

`emitDeclarationOnly` は、単なる設定値ではない。それは、「TypeScriptを単なるコンパイラとして扱うのではなく、プロジェクトの『言語仕様の守護者』として敬意を払い、実務的なビルドプロセスから切り離す」という、アーキテクチャ上の意思表示だ。

型安全という強力な武器を、ビルドの足枷にしないこと。それが、上級エンジニアに求められる「疎結合な思考」だ。

次に `tsconfig.json` を開くとき、君が何を生成し、何を生成させるべきではないのか。その境界線を、もう一度見つめ直してみてほしい。システムは、無駄を削ぎ落とした先でこそ、真の性能を発揮するのだから。

コメント

タイトルとURLをコピーしました