Command Palette

Search for a command to run...

Command Palette

Search for a command to run...

Tech
Next

moduleResolution: Choosing Between bundler and nodenext

A real-world case of switching from nodenext to bundler, understanding the differences and when to use each

tsconfig.base.jsonmoduleResolutionnodenext 改成 bundler,同時所有 .js extension 都被移除了——這兩個改動是連動的嗎?

答案是:是的。這兩個改動背後牽涉到 TypeScript 對模組解析策略的根本差異


nodenext 是什麼

TypeScript 4.7 引入了 nodenext,嚴格遵循 Node.js 的 ESM 規範。最明顯的特徵:import 必須加 .js extension

// nodenext 要求 .js extension
export * from "./lib/fixtures.js";
export * from "./lib/testable-page.js";
export * from "./lib/steps/index.js";

nodenext 的核心理念是:TypeScript 不該替你處理模組解析,你寫的 import path 就是最終 runtime 會看到的 path。

這表示:

  • 不能省略副檔名
  • 不能 import 一個資料夾(除非有 package.jsonexports
  • 完全遵循 Node.js 的 ESM 演算法

bundler 是什麼

TypeScript 5.0 引入了 bundler,它假設你的程式碼會經過 bundler(Webpack、Vite、esbuild)或其他 transformer 處理,因此模組解析的規則可以更寬鬆。

// bundler 不需要 extension
export * from "./lib/fixtures";
export * from "./lib/testable-page";
export * from "./lib/steps/index";

bundler 的核心理念是:你的 import path 不是給 Node.js 直接解析的,中間有 bundler 幫你處理。所以 TypeScript 不需要強制你遵循 Node.js 的嚴格規範


對照表

特性nodenextbundler
Import extension必須加 .js不需要
適用場景直接在 Node.js 執行有 bundler/transformer
ESM 嚴格度嚴格遵循 Node.js 規範寬鬆,由 bundler 處理
package.json exports完整支援完整支援
發佈到 npm適合不適合(消費者可能沒有 bundler)
搭配的 module 設定nodenextesnext

什麼時候用哪個

簡單的判斷流程:

  1. 程式碼會發佈到 npm 嗎? → nodenext
  2. 程式碼會直接在 Node.js 執行,沒有 bundler? → nodenext
  3. 有 bundler 或 transformer? → bundler

實際案例

背景是一個 E2E testing monorepo,用 Playwright 跑測試。原本設定為 nodenext,後來切換成 bundler

改了什麼

tsconfig.base.jsonmodulemoduleResolution

// tsconfig.base.json
- "module": "nodenext",
- "moduleResolution": "nodenext",
+ "module": "esnext",
+ "moduleResolution": "bundler",

所有 packages 的 import 移除 .js extension:

// packages/e2e/src/index.ts
- export * from './lib/fixtures.js';
- export * from './lib/testable-page.js';
+ export * from './lib/fixtures';
+ export * from './lib/testable-page';

為什麼適合 bundler

這個案例切換到 bundler 有三個原因:

  1. Playwright 有自己的 TypeScript transformer,測試程式碼不會直接透過 Node.js ESM 執行,所以不需要嚴格遵循 Node.js 的模組解析規則
  2. 所有 packages 都是 private: true,不會發佈到 npm,不需要考慮消費者的環境
  3. Import 更乾淨,不用寫看起來很奇怪的 .js extension

參考資源


系列文章

  1. Nx Monorepo 架構演進
  2. 型別解析:paths 與 Project References
  3. moduleResolution:bundler vs nodenext ← 目前位置