Node.js 模組系統:CommonJS 與 ES Modules 詳解
為了讓程式碼更易於維護與重複使用,我們通常會將功能拆分到不同的檔案中,這就是「模組化」。Node.js 目前支援兩種主要的模組系統:傳統的 CommonJS (CJS) 和現代標準的 ES Modules (ESM)。
CommonJS (CJS) 模組系統
這是 Node.js 多年來的預設標準。它使用的是 require 來匯入,以及 module.exports 或 exports 來匯出。
匯出範例 (math.js)
// math.js
const add = (a, b) => a + b;
const PI = 3.14159;
module.exports = { add, PI };
匯入範例 (app.js)
// app.js
const { add, PI } = require('./math.js');
console.log(add(5, PI));
CommonJS 的特點:
- 同步加載:模組是按照順序同步讀取的,非常適合伺服器本地端。
- 動態性高:你可以在
if語句或函數中呼叫require。
ES Modules (ESM) 模組系統
這是官方的 JavaScript 語言標準,現在也被 Node.js 完整支援。它使用的是 import 和 export 語法。
匯出範例 (utils.mjs)
// utils.mjs
export const greet = (name) => `哈囉, ${name}`;
export default function log(message) {
console.log(`[LOG]: ${message}`);
}
匯入範例 (main.mjs)
// main.mjs
import log, { greet } from './utils.mjs';
log(greet('Node.js 專家'));
ESM 的特點:
- 靜態分析:打包工具(如 Webpack, Rollup)可以進行 Tree Shaking,自動移除未使用的程式碼以縮減檔案體積。
- 支援非同步:更符合現代瀏覽器與高效能應用的載入需求。
- Top-level Await:可以在檔案最外層直接使用
await,不需要包在async函數中。
如何在 Node.js 中啟用 ES Modules?
Node.js 預設將 .js 檔案視為 CommonJS。要改用 ESM,有以下幾種方式:
- 副檔名法:將檔案命名為
.mjs,Node.js 就會強制以 ESM 模式執行。 - package.json 法:在專案的
package.json中加入"type": "module",該目錄下所有的.js都會被視為 ESM。 - 混合使用:若在 ESM 專案中仍需要 CJS,可將檔案命名為
.cjs。
CommonJS vs ES Modules 核心差異表
| 特性 | CommonJS (CJS) | ES Modules (ESM) |
|---|---|---|
| 匯入語法 | const module = require('./file') | import module from './file.js' |
| 匯出語法 | module.exports = ... | export default ... 或 export ... |
| 加載性質 | 執行時加載 (Runtime) | 編譯時加載 (Static) |
| __dirname | 原生支援 | 不支援 (需自行模擬) |
| Top-level Await | 不支援 | 支援 |
ESM 實戰技巧:模擬 __dirname
在 ESM 環境中直接使用 __dirname 會報錯。若有路徑處理需求,請參考以下標準做法:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log('當前目錄:', __dirname);
建議:雖然 CommonJS 在舊專案中隨處可見,但 ES Modules 是未來的趨勢。如果是全新建立的專案,強烈建議從一開始就採用 ES Modules。