一個提出五年的 Node.js 模塊問題,終被解決!
一直以來 Node.js 中存在一個問題,CommonJS 與 ES Modules 如何更好的共存? 是令大多數 Node.js 開發者頭疼的問題。
問題
當在 ES Modules 模塊中引入 CommonJS 模塊代碼,一切是 Ok 的。如下代碼所示:
// c.js
module.exports = {
moduleName: 'a'
}
// m.mjs
import C_Module from './c.js'
console.log(C_Module); // { moduleName: 'a' }
換一種方式,讓 CommonJS 引入 ES Modules,如下代碼所示:
// m.mjs
export default 'm'
// c.js
const M_Module = require('./m.mjs')
console.log(M_Module);
終端運行 node c.js,會得到如下提示
圖片
ERR_REQUIRE_ESM 這個錯誤太熟悉不過了,它困惑了很多的 Node.js 開發者,為什么換個順序就不行?
看到的很多答案是這樣的 “不支持使用 require 加載 ES 模塊,因為 ES 模塊是異步執行的”,后面大家就默認了 “CommonJS 是同步,ES Modules 是異步” 這樣的一個規則。
2019 提出后很遺憾未能繼續推進
CommonJS 模塊如何加載 ES Modules 模塊,這個問題 2019 年就已經提出,參考 “Support requiring .mjs files” https://github.com/nodejs/node/pull/30891 這個問題在當時沒有被解決。
圖片
ES Modules 在文件頂層可以使用 Top-Level Await,該方法看之前的介紹,是在使用 esm 加載器加載的 .mjs 文件上使用 require 的功能時,使用了與 esm Top-Level Await 相同的權衡。
這意味著:如果可能,所有執行和評估都是同步進行的,通過立即展開執行的組件承諾。這意味著任何現有的代碼都不應該有可觀察到的行為變化,因為到目前為止還不存在任何異步模塊。問題在于,一旦使用需要異步執行的模塊,它必須讓出事件循環來執行該操作,這反過來又允許其他代碼在異步操作之后的繼續執行之前執行,這對于現在變成了異步模塊的調用者是可觀察到的。如果這對你的調用者很重要,那么意味著將你的模塊執行異步化可能被視為庫的破壞性更改,但實際上,對于大多數調用者來說,這并不重要。而且,由于當前的生態系統,零個模塊是異步執行的,因此在有異步執行的模塊之前,這種方法沒有任何缺點,因為沒有執行會改變人們今天所期望的(當然,除了不再錯誤地要求("./foo.mjs"))。
最后,問題被關閉了,原因是 “因為純粹從技術角度來看,目前嘗試在事件循環已經運行時旋轉它是行不通的”。
圖片
問題是挺錯綜復雜的,感興趣的可以去看看 2019 年提出的這個 Issue。
2024 由 Joyee Cheung's 提出解決方案
2019 ~ 2024 這一令大多數開發者頭疼的問題,由 Node.js 的維護者成員 “Joyee Cheung” 再次提出了解決方案。參考 Issue “module: support require()ing synchronous ESM graphs” https://github.com/nodejs/node/pull/51977
圖片
參考 Joyee Cheung 博客的介紹 https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/
圖片
去年年底左右,Joyee Cheung 發現了 ESM 的求值可以基于語法同步,而只是 Node.js 將異步性扔到加載過程中后,便與 @GeoffreyBooth 開始討論重新啟動同步 require(esm)。
因此,才有了這個 PR。與 2019 年的 PR 相比主要區別在于它試圖將 require(esm) 的范圍保持小型,并且僅支持加載同步 ESM。
后續會通過 --experimental-require-module 標志啟用,則加載的 ECMAScript 模塊 require() 時要滿足以下要求:
- 在最接近的 package.json 文件或 .mjs 擴展中明確標記為 ES 模塊,具有 "type": "module" 字段
- 完全同步(不包含 Top-Level Await)
有網友就在下面問了,這能向后移至到 Node.js 18? 大家還是很喜歡這個功能的!Node.js v20 可能還有希望,這要取決于 Node.js 的發布團隊,期待下個 Node.js 版本!
圖片
這一問題的解決對 Node.js 模塊來講是里程碑式的,很敬佩 Joyee Cheung 的才華!
參考
- https://github.com/nodejs/node/pull/30891
- https://github.com/nodejs/node/pull/51977
- https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/