背景
今天要做个 CLI
工具,一路调研学习加实践都比较顺利,但是在引入 globby
这个库时,就开始报错了。
/Users/xusheng/workspace/test/mit-cli/dist/lib/utils/zip.js:4
var globby_1 = require("globby");
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/xusheng/workspace/test/mit-cli/node_modules/globby/index.js from /Users/xusheng/workspace/test/mit-cli/dist/lib/utils/zip.js not supported.
Instead change the require of index.js in /Users/xusheng/workspace/test/mit-cli/dist/lib/utils/zip.js to a dynamic import() which is available in all CommonJS modules.
at Object.<anonymous> (/Users/xusheng/workspace/test/mit-cli/dist/lib/utils/zip.js:4:16)
at Object.<anonymous> (/Users/xusheng/workspace/test/mit-cli/dist/lib/publish.js:5:13)
at Object.<anonymous> (/Users/xusheng/workspace/test/mit-cli/dist/lib/index.js:5:17) {
code: 'ERR_REQUIRE_ESM'
错误信息简化一下就是 require() of ES Module xxxx from xxxxx not supported
。
字面意思来理解,就是不支持 require 一个 ES Module 的包。
这个时候就很迷了,我的 tsconfig
中设置的 module: "commonjs"
,检查了一下 tsc
编译后的文件,也都转换为了 commonjs
模块化方案。
虽然说目前 nodejs
已经原生支持了 esm
,浏览器也提供 type="module"
来支持 esm
,但是考虑到兼容性问题,一般我们在编译项目时,还是会输出 commonjs
模块标准的代码,所以一开始压根就没往第三方库的问题上去想。
问题定位
经过一番检索和思考,发现 globby
这个库居然直接发布的 esm
模块标准的代码,也就是说,我的代码虽然 tsc
转换为了 commonjs
标准,但是引入的 globby
还是 esm
标准的代码,这就导致了错误。
这个时候引入一个概念 Pure ESM package
,也就是纯 ESM
模块化的包,如果要使用这种第三方库,可以阅读文档:https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
如果一个库是 Pure ESM package
的话,它就没办法再被 commonjs
标准的代码使用 require
引用了,如果要解决这个问题,文档中提出了三种方案:
- Use ESM yourself. **(preferred)**Use
import foo from 'foo'
instead of const foo = require('foo')
to import the package. You also need to put "type": "module"
in your package.json and more. Follow the below guide.
- If the package is used in an async context, you could use
[await import(…)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports)
from CommonJS instead of require(…)
.
- Stay on the existing version of the package until you can move to ESM.
关于 nodejs
中如何处理 ES6 模块的,可以参考:https://www.ruanyifeng.com/blog/2020/08/how-nodejs-use-es6-module.html
尝试解决
参考上面文档中的三种解决方式。
1. Use ESM yourself
第一种方式比较扯,就是把你自己的库也改成 ESM
标准,这就很坑了,这不是扩大了兼容性的问题了嘛。
2. use await import(…)
第二种方式,就是将静态的 import
语句,改为动态的 import()
方法,例如:
// before
import { xxx } from 'globby';
// after
const { xxx } = await import('globby');
理论上讲好像可以,但是我实际尝试的时候,发现如果 tsc
编译后为 commonjs
标准的话, import()
方法会被转化为一个 __importStar(require('globby'))
方法,本质上还是 require()
?所以还是会报错。
需要进一步调研看看。
3. Stay on the existing version of the package until you can move to ESM
这个方法也很扯淡,就是在你可以将你的项目改为 ESM
标准之前,使用旧版本的 commonjs
标准的第三方库。
解决方法
上面的三种方式,都没有解决问题,只能采取一种治标不治本的方式了。
既然第三方库是 ESM
标准,那么我们在 tsc
编译时,把它也编译一下好了。
以 globby
为例,在 tsconfig
文件中加入以下代码:
{
"compilerOptions": {
...
// 因为 globby 是用 js 写的,所以在 tsconfig 中要将 allowJs 设置为 true
"allowJs": true
},
"include": [
"node_modules/globby/**/*"
]
}
此时再运行 tsc
编译,会发现在输出的 dist
目录中,新增了一个 node_modules
目录,其中包含了编译后的 globby
包代码。
但是这里需要注意下,再次运行项目,发现还是报同样的错,只是报错的库由 globby
变成了 array-union
,这是因为 globby
是 pure ESM package
,经过 tsc
编译后变成了 commonjs
标准,但是 globby
引用了 array-union
,而 array-union
也是 pure ESM package
。
以此类推,需要把所有的 pure ESM package
都编译一下:
{
"include": [
"node_modules/globby/**/*",
"node_modules/array-union/**/*",
"node_modules/slash/**/*"
]
}
完美,问题解决。
这里要吐槽一下,array-union 这个库,其实就一行代码:
constarrayUnion = (...arguments_) => [...newSet(arguments_.flat())];
就这还引入一个额外的库,坑!