今天要做个 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
引用了,如果要解决这个问题,文档中提出了三种方案:
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.[await import(…)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports)
from CommonJS instead of require(…)
.关于 nodejs
中如何处理 ES6 模块的,可以参考:https://www.ruanyifeng.com/blog/2020/08/how-nodejs-use-es6-module.html
参考上面文档中的三种解决方式。
第一种方式比较扯,就是把你自己的库也改成 ESM
标准,这就很坑了,这不是扩大了兼容性的问题了嘛。
第二种方式,就是将静态的 import
语句,改为动态的 import()
方法,例如:
// before
import { xxx } from 'globby';
// after
const { xxx } = await import('globby');
理论上讲好像可以,但是我实际尝试的时候,发现如果 tsc
编译后为 commonjs
标准的话, import()
方法会被转化为一个 __importStar(require('globby'))
方法,本质上还是 require()
?所以还是会报错。
需要进一步调研看看。
这个方法也很扯淡,就是在你可以将你的项目改为 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())];
就这还引入一个额外的库,坑!