人人都在引用的那兩個 ESM/CommonJS 錯誤已經沒了,沒人提的那一個還在
現在 require() 一個 ESM 模組會成功,TypeScript 也不再抱怨。真正還會壞的是雙格式套件——而且它的症狀是 instanceof 回傳 false,不是丟出錯誤。

搜一個 ESM/CommonJS 的錯誤,你會拿到上千個講得很有把握的答案。它們大部分是對的 ——只是對的是三年前的 Node。而它們給你的那個具體做法,修的是一個你現在沒有的問題。
那個最有名的錯誤已經不會發生了
在 Node v25.9.0 上,用真的套件、真的 package.json 實測:require() 一個
ESM 模組會成功。沒有 ERR_REQUIRE_ESM。
而且你拿到的是模組的 namespace,不是 default 匯出:
keys: Token, __esModule, default, name
m[Symbol.toStringTag] === 'Module'
這個區別比大家記得的那個修法更重要。從舊世界搬過來的程式碼會去拿 .default,
而現在的 .default 是那個模組的 default 匯出、跟具名匯出並排放著的一個屬性——
通常不是它要的東西。
TypeScript 那個也一樣
TS1479——the current file is a CommonJS module whose imports will produce ‘require’
calls; however, the referenced file is an ECMAScript module——是同一套傳說的編譯期版本。
在 TypeScript 6.0.3、module: nodenext 之下,一個 .cts 檔匯入 .mjs 檔,
完全沒有任何診斷。為了確認那個檔案真的有被檢查,我先在裡面放了一個故意的型別錯誤:
main.cts(2,14): error TS2322: Type 'number' is not assignable to type 'string'.
對照組有跳,互載錯誤沒有。
那現在真正會壞的是什麼
實際拿到什麼
keys: Token, default, name · m.default === 'i-am-a-property-called-default'
錯誤訊息,逐字
沒有錯誤。這一格是成功的。
ESM 裡的 CJS 全域變數
- ReferenceError: __dirname is not defined → import.meta.dirname
- ReferenceError: __filename is not defined → import.meta.filename
- ReferenceError: require is not defined → module.createRequire(import.meta.url)
- ReferenceError: module is not defined → 沒有直接的替代品
- ReferenceError: exports is not defined → 沒有直接的替代品
每一格都於 2026-08-07 在 Node v25.9.0 上用真的套件實測。
上面每一格都是跑出來的,不是查來的。其中三格值得細看。
新的失敗是 top-level await
require() 一個模組圖裡含有 top-level await 的 ESM 模組,你會拿到:
require() cannot be used on an ESM graph with top-level await. Use import() instead.
To see where the top-level await comes from, use --experimental-print-required-tla.
這是一個好錯誤:它說出了原因、給了做法,還附一個能幫你找出兇手模組的旗標。 它也是你在 2026 年真的會遇到的那一句——所以這裡是逐字照抄,不是改寫。
從 CommonJS 具名匯入行不行,取決於原始碼怎麼寫
Node 是靠靜態分析原始碼找出 CommonJS 的具名匯出。一個在執行期才算出鍵名的匯出, 它看不見:
const key = ['com', 'puted'].join('');
module.exports[key] = '…';
於是 namespace 裡只剩 default 和一個字面上叫 module.exports 的鍵,
具名匯入失敗:
SyntaxError: Named export 'computed' not found. The requested module 'cjs-dynamic' is a
CommonJS module, which may not support all module.exports as named exports.
注意那個 may not。這不是一條關於 CommonJS 的規則,這是一句關於「靜態分析器在那個 特定檔案裡看得到什麼」的陳述。所以同一種匯入寫法對某個套件可行、對另一個不行—— 在你知道它到底在看什麼之前,那看起來像是隨機的。
雙格式套件的陷阱還在
一個同時提供兩個入口的套件,可能在同一個行程裡被載入兩次:
require(pkg).Token === (await import(pkg)).Token → false
instance instanceof otherToken → false
兩個 class、同一份原始碼、不同的身分。而且不會有任何東西丟出來。
你的 instanceof 只是回傳 false,你的錯誤型別比對開始比不中,
而症狀出現的地方離原因很遠。
這一個是活下來的那個。人人引用的那兩個錯誤之所以被修掉,是因為它們很吵、 立刻擋住所有人。這一個很安靜,所以它還在。
全域變數,順便補完
在 ESM 裡,五個 CommonJS 全域變數全部丟同一種錯:
ReferenceError: __dirname is not defined
__filename、require、module、exports 也一樣。替代品是 import.meta.dirname
與 import.meta.filename(兩者都是字串),真的需要 require 時用
module.createRequire(import.meta.url)。
這篇該帶走的東西
有用的習慣不是背下哪一種組合可行,而是意識到這個主題腐壞得比你會去搜尋的幾乎任何 東西都快,以及一個被大量按讚的答案,是一份關於過去的證據。
在你因為讀到什麼而準備重構一個套件之前:拿那兩行能重現問題的程式碼, 在你真正要出貨的那個 Node 上跑一次。2026 年有一半的機率那個問題已經不存在了, 而你原本要做的那個改動,只會多疊一層什麼都沒防到的相容性墊片。
常見追問
ERR_REQUIRE_ESM 真的不見了嗎?
對一個普通的 ESM 模組來說是的——在 Node v25.9.0 上實測,require() 一個 ESM 套件 會成功。你拿到的是模組的 namespace,所以具名匯出直接掛在你收到的那個物件上, 而不是在 default 底下。但如果模組圖裡有 top-level await,它仍然會失敗, 而且是另一個錯誤。
require() 一個 ESM 模組會回傳什麼?
namespace 物件,Symbol.toStringTag 是 "Module",並且被加上一個 __esModule 標記。 照舊世界寫法去拿 .default 的程式碼,會拿到那個模組的 default 匯出—— 而那通常不是它想要的東西。
為什麼從 CommonJS 套件具名匯入有時候會失敗?
Node 是靠靜態分析原始碼來偵測 CommonJS 的具名匯出,所以一個在執行期才算出鍵名的 匯出,它看不見。錯誤訊息寫的是這個模組「may not support all module.exports as named exports」——是 may not,因為行不行取決於那個模組的原始碼怎麼寫。
什麼是雙格式套件的陷阱(dual package hazard)?
一個同時提供 CommonJS 與 ESM 入口的套件,可能在同一個行程裡被載入兩次, 兩個入口各一次。兩份副本沒有共用身分,所以其中一份的 class 與另一份的不是同一個, 拿去做 instanceof 會回傳 false。而且不會有任何東西丟出來——那個檢查只是安靜地說不。
ESM 裡用什麼取代 __dirname?
import.meta.dirname,__filename 則用 import.meta.filename。在 ESM 裡直接參照 __dirname、__filename、require、module 或 exports,會丟出一個普通的 ReferenceError, 說那個名字沒有被定義。