Node.js 模組系統:CommonJS 與 ES Modules 詳解

為了讓程式碼更易於維護與重複使用,我們通常會將功能拆分到不同的檔案中,這就是「模組化」。Node.js 目前支援兩種主要的模組系統:傳統的 CommonJS (CJS) 和現代標準的 ES Modules (ESM)

CommonJS (CJS) 模組系統

這是 Node.js 多年來的預設標準。它使用的是 require 來匯入,以及 module.exportsexports 來匯出。

匯出範例 (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 完整支援。它使用的是 importexport 語法。

匯出範例 (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,有以下幾種方式:

  1. 副檔名法:將檔案命名為 .mjs,Node.js 就會強制以 ESM 模式執行。
  2. package.json 法:在專案的 package.json 中加入 "type": "module",該目錄下所有的 .js 都會被視為 ESM。
  3. 混合使用:若在 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。