CommonJS 和 ESModule 混合開發
- 接上文,仍舊在 abc-cli 項目中
- 參考:https://blog.csdn.net/Tyro_java/article/details/136433159
- 現在要在腳手架項目中安裝 chalk 依賴,因為在 abc-cli 項目幾乎都是 CommonJS的實現
- 而 chalk 這個依賴源碼是基于 ESModule 的,所以現在要解決的是兩者的兼容
- 先安裝 chalk 到 cli 包中,在 abc-cli 目錄下,$
npm i chalk -w packages/cli
- 但是,在使用 chalk 的時候,就會報錯,不能使用 require,現在有幾種解決方案
- 第一種,降級 chalk 到低版本, 大概在4.0左右,但是這就無法使用 chalk 的新特性了
- 第二種,修改自己的代碼,將之前的 require 全部修改成 import, 并且package.json中添加
"type":"module"
- 這種會造成更大的問題:一是,之前的語法全部要修改,包括
module.exports
- 二是,如果要使用一些只有 CommonJS 的依賴就會有問題
- 這種會造成更大的問題:一是,之前的語法全部要修改,包括
- 第三種,在CommonJS中允許使用 import 來加載依賴,但是 import 返回了一個 Promise
- 這種,只能異步拿到真實的依賴,就不好處理了
- 現在遇到了一個問題,就是如何兼容 CommonJS 和 ESModule, 怎樣才能最佳實踐
- npm 模塊有的使用CommonJS, 有的使用 ESM, 兩者混合開發成為 Nodejs 項目必須考慮的問題
1 )CommonJS
- CommonJS 單獨使用有兩種方式
- 1 )在 package.json 中指定
"type": "common"
這個不指定也是默認的 - 所有js文件的的導入都用
require
語法來引用模塊 - 所有js文件的導出都用
module.exports
語法來導出 - 2 )不管 package.json 中指定的是
"type": "common"
亦或是"type": "module"
- 只要js文件的后綴是 .cjs 都可以使用
require
和module.exports
語法 - 這樣,默認走的就是 CommonJS 規范
- 1 )在 package.json 中指定
- 注意,
module.exports
和exports.xx
不能混用,兩者混用,后者不生效 - CommonJS規范默認通過自執行函數實現,比如require源碼,它可以做一些變量注入
- 比如
__dirname
,__filename
都是通過注入的方式來顯示的 - 可以把它們直接打印出來
2 )ESModule
- ESModule 也有兩種使用方式
- 1 )在package.json中定義
"type": "module"
,包內所有 .js 文件會被認為是 ESModule - 2 ).mjs 后綴的文件,強制被認定為 ESModule
- 1 )在package.json中定義
- 在ESModule中導出
export default {}
,導入import
- 在這里,
__dirname
,__filename
這種API,統統不支持,但是網上也有兼容方案,這里先不研究- 除了網上的一些解決方案,這里暫時提供一個第三方庫來解決
dirname-filename-esm
- 除了網上的一些解決方案,這里暫時提供一個第三方庫來解決
3 )CommonJS 和 ESModule 混用
- 原則上,不應該混用,一般我們開發包的時候,需要指定一種
- 單個模塊,必須指定CommonJS 或 ESM, 如果混用,必須用webpack或babel來解決
- 另外,package.json 的 type 可以不寫,如果寫就必須指定一種,默認是
commonjs
- 越來越多的模塊采用了 ESModule, 也就是指定 type 為
module
3.1 在 CommonJS 中引用 ESM
- 如果一個模塊是ESM, 比如,它叫 “esm” 來舉例
import('esm').then(esm => esm.default())
- 這種做法非常別扭
- CommonJS 本身是一個同步的規范,require 它的實現是一個同步加載模塊的方案
- 它在模塊外圍包一層自執行函數,是同步方案實現的
- 參考:https://blog.csdn.net/Tyro_java/article/details/53574887
- ESM 本身用的是 import 用的是異步方式來加載,和CommonJS是完全不同的兩種實踐方案
- 如果是 在 CommonJS 中引用 ESM,那么代碼就會非常的奇怪
- 要想實現同步操作,就必須加一個自執行函數,并將這個函數指定為 async 方式
(async function() {const esm = await import('esm');esm.default(); })()
- 這樣,很麻煩,也很奇怪
- 但是能解決問題
3.2 在 ESM 中引用 CommonJS
- 在 ESM 包中,不管依賴是 ESM還是CommonJS方案開發的,都可以直接 import
- 假設 “cjs” 是一個 CommonJS 模塊的方案
imort cjs form 'cjs';
- 所以,推薦把源碼全部移植到ESM模塊中
常見的報錯問題和解決
-
1 )未指定 package.json 中的 type, 但是使用了
import
和export
語法- 這是缺失了 package.json 中 type 默認是 commonjs 的知識點造成的
-
2 )require 語法無法加載ESM模塊
- 必須使用 import 來加載ESM模塊
-
3 )ESM 去加載其他ESM模塊時會有找不到模塊的報錯
- 沒有構建工具時,import的時候需要添加后綴,不能省略
- 注意,還有導出用
export default
時,引入時別忘記了這個default