Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TypeScriptのmoduleオプションを改めて整理する

 TypeScriptのmoduleオプションを改めて整理する

本発表は、TypeScriptにおけるmoduleオプションについて、その基本的な役割と設定時に注意すべきポイントを体系的に整理します。

moduleオプションは、TypeScriptが出力するモジュール形式を指定するための重要な設定項目であり、ESModuleやCommonJSの違い、Node.jsにおける実行時の挙動、さらにはビルド結果の違いを正確に理解しておかねば、意図しないトラブルに見舞われるリスクが高まります。

そこで、発表では実際に私が遭遇したトラブル事例をもとに、moduleオプションがもたらす挙動の微妙な違いとその影響を解説し、さらにTypeScript 5.8で導入された "NodeNext" の仕様変更がどのような影響を及ぼすのかについても触れます。

主な対象は、日常的にTypeScriptを活用している開発者で、特に tsconfig.json の設定に携わる機会のある方々です。基本的なTypeScriptの知識があれば参加可能な内容となっています。

発表を通じて複雑なmoduleオプションに対する苦手意識を払拭し、基礎知識と最新の挙動を理解する方法を知ることで、自信をもって設定に臨めることを目指します。

【この資料で発表したイベント】

- TSKaigi 2025
- TSKaigi 2025 本編で話せなかったこと、話し足りなかったこと

【著者について】

- Portfolio: bicstone.me
- X: @bicstone_me

© 2025 Oishi Takanori

Avatar for おおいし

おおいし

May 24, 2025
Tweet

More Decks by おおいし

Other Decks in Programming

Transcript

  1. © Findy Inc. 2 @bicstone_me ⼤⽯ 貴則 OISHI Takanori 登壇者紹介

    • ⾼専機械⼯学科卒、元機械設計エンジニア • 現在はSaaS業界に4年携わるWebエンジニア • TypeScript / Ruby / PHP / Python / Dart • Certified ScrumMaster ® @oishi.takanori @bicstone Certified ScrumMaster® is a certification mark of Scrum Alliance, Inc. Any unauthorized use is strictly prohibited.
  2. © Findy Inc. 3 【背景】module オプションとは • 出⼒するJavaScriptのモジュール形式 を指定する設定 •

    正しい設定にはESM, CJSの特性や Node.jsの挙動など背景知識が必要 • 設定を誤ると予期しないビルドエラー やランタイムエラーの原因に ➔ プロジェクトごとに適した 設定を⾏うことが重要 👀 tsconfig.json 👉
  3. © Findy Inc. 4 【結論】module オプションの役割 ① モジュール形式の指定 ◦ “CommonJS”:

    すべてCJSに変換 ◦ “ES*”: すべてESMに変換 ② モジュール解決戦略の指定 ◦ “Node*”: Node.jsランタイムの挙動 にもとづきESM/CJSやimport構⽂ を出し分け ◦ “Preserve”: ⼊⼒されたTSに書かれた モジュール構⽂を変換せず出⼒ * UMD, AMD, Systemはレガシー環境向けで新しいプロジェクトでは推奨されていないので今回は触れません。 ➔ 2つの役割があることに注意 🫨
  4. © Findy Inc. 5 【結論】module オプションの役割 ① モジュール形式の指定 ◦ “CommonJS”:

    すべてCJSに変換 ◦ “ES*”: すべてESMに変換 ② モジュール解決戦略の指定 ◦ “Node*”: Node.jsランタイムの挙動 にもとづきESM/CJSやimport構⽂ を出し分け ◦ “Preserve”: ⼊⼒されたTSに書かれた モジュール構⽂を変換せず出⼒ * UMD, AMD, Systemはレガシー環境向けで新しいプロジェクトでは推奨されていないので今回は触れません。 ➔ 2つの役割があることに注意 🫨
  5. © Findy Inc. 7 ① モジュール形式について (ESM vs CJS) ES

    Module (ESM) CommonJS (CJS) • モジュールシステムとは ◦ コードを分割し連携する仕組み • よく使⽤されるモジュール形式 ◦ ES Module (ESM) ▪ import, export ▪ ⾮同期ロード。静的解析可能。 ▪ ブラウザで実⾏可能。 ◦ CommonJS (CJS) ▪ require, module.exports ▪ 同期ロード。静的解析不可。
  6. © Findy Inc. 8 ① ESModule (ESM) と CommonJS (CJS)

    の使い分け 以前はCJSが事実上の標準だったもののESM移⾏への動きが加速 • JSのエコシステム全体でESMへの移⾏が推進されている ◦ JS⾔語仕様(ECMAScript)に定義されているため、Node.js だけでなくブラウザなどエコシステム全体で活⽤可能 • ライブラリをESMでしか提供しないケース(Pure ESM)が増えた ◦ メンテナンスコストの削減が⽬的 ◦ 現在もESMとCJSを両⽅提供しているライブラリは多い ➔ 新しいプロジェクトではESMを採⽤ CJSのプロジェクトにおいてもESMへの移⾏が推奨 🚀 参考: https://sosukesuzuki.dev/advent/2022/15/
  7. © Findy Inc. 9 ① module オプションで “ES*” を指定した場合の挙動 •

    module オプションの “ES*” はECMAScriptバージョン仕様に則る ◦ 例: ”ES2022” は Top-level await が利⽤可能 • “ESNext” は最新のECMAScriptと、提案のステージ3+を反映 • トランスパイルしない場合、実⾏環境の対応バージョンに注意 “ES*” Dynamic Import Top-level await 今後追加される 新機能 “ES2015” ❌ ❌ ❌ “ES2020” ⭕ ❌ ❌ “ES2022” ⭕ ⭕ ❌ “ESNext” ⭕ ⭕ ⭕ 引⽤元: https://www.typescriptlang.org/docs/handbook/modules/reference.html#the-module-compiler-option
  8. © Findy Inc. 11 【再掲】module オプションの役割 ① モジュール形式の指定 ◦ “CommonJS”:

    すべてCJSに変換 ◦ “ES*”: すべてESMに変換 ② モジュール解決戦略の指定 ◦ “Node*”: Node.jsランタイムの挙動 にもとづきESM/CJSやimport構⽂ を出し分け ◦ “Preserve”: ⼊⼒されたTSに書かれた モジュール構⽂を変換せず出⼒ * UMD, AMD, Systemはレガシー環境向けで新しいプロジェクトでは推奨されていないので今回は触れません。 ➔ 2つの役割があることに注意 🫨
  9. © Findy Inc. 12 ② Node.jsのモジュール解決について (Node.js Dual Packages) •

    Node.js v12以上でCJSとESM両⽅をサポート (未満はCJSのみ) ◦ 拡張⼦ .cjs はCJS、.mjs はESM ◦ 拡張⼦ .js は package.json の { type: “module” } で判定 • バージョンによって対応しているインポート⽅法が異なる 引⽤元: https://nodejs.org/docs/latest/api/ Node.js インポート アサーション (廃⽌) インポート属性 JSONの インポート属性 CJSからESMへ require() Node.js 16 16.40.0 以降 ❌ assert {type "json"} ❌ Node.js 18 ⭕ ❌ assert {type "json"} ❌ Node.js 20 ⾮推奨* ⭕ assert {type "json"} * with { type: "json" } ❌ Node.js 22 ⾮推奨* ⭕ assert {type "json"} * with { type: "json" } ⭕
  10. © Findy Inc. “Node*” インポート アサーション (廃⽌) インポート属性 JSONの インポート属性

    CJSからESMへ require() “Node16” ❌ ❌ 無くてもインポート可能 ❌ “Node18” ⭕ ⭕ 要 { type: "json" } ❌ “NodeNext” ❌ ⭕ 要 { type: "json" } ⭕ 13 ② module オプションで “Node*” を指定した場合の挙動 引⽤元: https://www.typescriptlang.org/docs/handbook/modules/reference.html#the-module-compiler-option • module オプションの “Node*” はNode.jsの挙動を模倣する ◦ 例: .mts から .cts を読み込むとexportsがdefault exportに • “NodeNext” は最新の安定バージョン (現在だと22) に追従 • Node.jsとTypeScriptとの実装のタイミングはズレることがある
  11. © Findy Inc. 14 ② モジュール形式の違いによる、よくあるトラブル 正常にトランスパイルできるのにランタイムエラーが発⽣する • CJSがESMに正しく変換できていない時 ◦

    require is not defined / exports is not defined (ESMやブラウザには require 関数は存在しない) • ESMがCJSに正しく変換できていない時 ◦ Cannot use import statement outside a module (CJSでimport宣⾔を⽤いたESMのインポートができない) ➔ 最終的に出⼒されるのはESMなのかCJSなのかを意識 トラブル時は、出⼒されたJSを再確認 🕵
  12. © Findy Inc. 15 ② TypeScript 5.8 における “NodeNext” の仕様変更

    • Node.js 22 よりCJSからESMへ require() できる対応がされた ◦ CJSからESMへの移⾏負担を軽減し推進することが⽬的 • TypeScript 5.8においてこの対応をサポート ◦ “NodeNext” を指定した場合に動作 • Top level await は動作しないので注意 (ランタイムエラー) • 出⼒されたJSがNode.js 22未満の環境で動作しなくなる可能性 引⽤元: https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/ ➔ “NodeNext” を指定した場合は、 TypeScriptバージョンアップによる出⼒変更に注意が必要 ⚠
  13. © Findy Inc. 17 現時点での modules 設定例 • フロントエンド向け (バンドラーを使⽤する場合)

    ◦ バンドラーのテンプレートを参考にする ◦ “ESNext” または “Preserve” を使⽤されているケースが多い ◦ Node.js を模倣しないバンドラーでは “Node*” を指定しない • Node.jsプロジェクト向け ◦ ESM: “NodeNext” または “Node18” を使⽤ ◦ CJS: “CommonJS” は指定せず、必ず “Node*” を使⽤ • ライブラリ向け ◦ ESM/CJSの両⽅を出⼒ (“ES2020”, “CommonJS”) ◦ package.jsonの exports を⽤いて出し分け
  14. © Findy Inc. 18 まとめ ➔ モジュールシステムは今後も変化し続けるため 継続的な学習とプロジェクトへの反映が重要 👀 •

    module オプションは出⼒されるモジュールに関する設定 ① モジュール形式の指定 ② モジュール解決戦略の指定 • JavaScriptモジュールシステムの背景知識が必要 • “node*” はNode.js以外では使⽤しない • “*next” はTypeScriptのアップデートで変更されることがある
  15. © Findy Inc. 19 最後に 登壇資料 & SNS (bicstone.me) ⼤⽯貴則

    (@bicstone) もっと丁寧に解説します! https://freee.connpass.com/event/351699/ Ask the Speaker ⽋席です 🙇 懇親会 / SNS / サブイベントで是⾮!