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