在 TypeScript 的 tsconfig.json 中,這兩種配置組合代表了兩種完全不同的模組解析與建置哲學。
JSON
// tsconfig.json : Environment Settings
// See also https://aka.ms/tsconfig/module
// 開發 Web 前端應用
{
"compilerOptions": {
"target": "es2025",
"module": "preserve",
"moduleResolution": "Bundler"
}
}
// 開發純 Node.js 後端應用
{
"compilerOptions": {
"target": "es2025",
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}核心結論先行:
- 如果是開發 Web 前端應用(如使用 Vite、Webpack、Turbopack 等打包工具),強烈建議使用 module: “preserve” + moduleResolution: “Bundler”。
- 如果是開發純 Node.js 後端應用,則建議使用 module: “NodeNext” + moduleResolution: “NodeNext”。
核心差異對比
| 核心維度 | NodeNext 組合 | preserve + Bundler 組合 |
|---|---|---|
| 目標運行環境 | Node.js 執行環境 (v14.17+ / v16+) | 打包工具 (Bundler) (如 Vite, Webpack, esbuild) |
模組輸出語法 (module) | 根據 package.json 的 “type” 決定輸出為 ESM (.js) 或 CommonJS (.cjs)。 | 保留(Preserve)原生的 ESM 語法,完全不轉換 import/export,直接交給打包工具處理。 |
路徑後綴要求 (moduleResolution) | 極度嚴格。導入本地檔案時必須寫出副檔名(例如 import ‘./utils.js’,即便源檔是 .ts)。 | 非常寬鬆。支援省略副檔名、支援路徑別名(Path Mapping),允許由打包工具去尋找檔案。 |
package.json 導出支援 | 完整支援 exports 與 imports 欄位(遵從 Node.js 官方規範)。 | 完整支援 exports 與 imports 欄位(遵從打包工具的解析規範)。 |
詳細深入解析
NodeNext 組合(Node.js 原生現代規範)
JSON
// 開發純 Node.js 後端應用
{
"compilerOptions": {
"target": "es2025",
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}- 運作邏輯:這組設定是為了讓 TypeScript 完美契合 Node.js 原生的模組解析規則。Node.js 為了同時相容 CommonJS (CJS) 與 ECMAScript Modules (ESM),制定了非常嚴格的規則。
- 副檔名的地獄:在 NodeNext 下,如果你有一個 file.ts,你在別的檔案引用它時不能寫 import ‘./file’,而必須寫 import ‘./file.js’。因為 TypeScript 認為它的職責是編譯出能直接在 Node.js 運行的程式碼,而 Node.js 執行 ESM 時必須要副檔名。
- 適用場景:編譯後不需要透過任何打包工具,直接使用 node dist/index.js 執行的後端專案或 npm 套件。
理由:
- 原生的力量:後端程式碼通常不需要打包成一個大檔案,而是直接由 Node.js 執行編譯後的 JS 檔案。
- 環境一致性:NodeNext 保證了 TypeScript 在編譯時的模組查找邏輯,與 Node.js 執行時的邏輯 100% 一致,避免出現「TS 檢查沒問題,Node 執行卻找不到模組」的窘境。
Preserve + Bundler 組合(現代前端/打包工具首選)
JSON
// 開發 Web 前端應用
{
"compilerOptions": {
"target": "es2025",
"module": "preserve",
"moduleResolution": "Bundler"
}
}- 運作邏輯:這是 TypeScript 5.4+ 引入的現代前端黃金組合。
- module: “preserve”:告訴 TypeScript「請不要動我的
import和export語法,不管是 ESM 還是 CJS,原封不動傳下去」。 - moduleResolution: “Bundler”:告訴 TypeScript「解析路徑時,請模擬 Vite/Webpack 的行為」。
- module: “preserve”:告訴 TypeScript「請不要動我的
- 副檔名的解脫:打包工具非常聰明,它們不需要你寫 .js 後綴就能找到 .ts 檔案。因此這組設定允許你寫 import ‘./file’。
- 適用場景:Vite、Next.js (Turbopack)、Nuxt 等現代前端框架專案。這些專案後續會有專門的工具來做搖樹優化(Tree Shaking)和打包,TypeScript 只需要負責「型別檢查」和「語法降級(將 ES2025 轉為目標語法)」即可。
理由:
- 免除後綴痛苦:你不需要在引入 .ts 檔案時痛苦地寫成 .js。
- 職責分離:讓 TypeScript 專注在「型別檢查」,把「模組打包與優化」的重任完全交給 Vite 或 Webpack,這能避免 TypeScript 的編譯結果與打包工具的預期產生衝突。
- 完美的目標語法:配合 “target”: “es2025″,TS 會把高階語法降級,但保留模組結構,讓 Vite 等工具進行最佳的 Tree Shaking。



