Slide 1

Slide 1 text

© Findy Inc. TypeScriptのmoduleオプションを 改めて整理する 1 2025.05.24 ファインディ株式会社 ⼤⽯ 貴則

Slide 2

Slide 2 text

© 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.

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

© Findy Inc. 6 ① モジュール形式の指定

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

© 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/

Slide 9

Slide 9 text

© 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

Slide 10

Slide 10 text

© Findy Inc. 10 ② モジュール解決戦略の指定

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

© 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" } ⭕

Slide 13

Slide 13 text

© 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との実装のタイミングはズレることがある

Slide 14

Slide 14 text

© 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を再確認 🕵

Slide 15

Slide 15 text

© 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バージョンアップによる出⼒変更に注意が必要 ⚠

Slide 16

Slide 16 text

© Findy Inc. 16 まとめ

Slide 17

Slide 17 text

© Findy Inc. 17 現時点での modules 設定例 ● フロントエンド向け (バンドラーを使⽤する場合) ○ バンドラーのテンプレートを参考にする ○ “ESNext” または “Preserve” を使⽤されているケースが多い ○ Node.js を模倣しないバンドラーでは “Node*” を指定しない ● Node.jsプロジェクト向け ○ ESM: “NodeNext” または “Node18” を使⽤ ○ CJS: “CommonJS” は指定せず、必ず “Node*” を使⽤ ● ライブラリ向け ○ ESM/CJSの両⽅を出⼒ (“ES2020”, “CommonJS”) ○ package.jsonの exports を⽤いて出し分け

Slide 18

Slide 18 text

© Findy Inc. 18 まとめ ➔ モジュールシステムは今後も変化し続けるため 継続的な学習とプロジェクトへの反映が重要 👀 ● module オプションは出⼒されるモジュールに関する設定 ① モジュール形式の指定 ② モジュール解決戦略の指定 ● JavaScriptモジュールシステムの背景知識が必要 ● “node*” はNode.js以外では使⽤しない ● “*next” はTypeScriptのアップデートで変更されることがある

Slide 19

Slide 19 text

© Findy Inc. 19 最後に 登壇資料 & SNS (bicstone.me) ⼤⽯貴則 (@bicstone) もっと丁寧に解説します! https://freee.connpass.com/event/351699/ Ask the Speaker ⽋席です 🙇 懇親会 / SNS / サブイベントで是⾮!