一、背景

笔者在本地开发了一个三方包-画板库,该库主要是完成一些图形绘制的功能。开发完成后,画板库本地打包没有报错。
然后笔者在 web 项目中 npm link 该包,进行相关的调试,用 web 项目的 webpack 构建时,报了很多 core-js 的报错:

1
2
Module not found: Error: Can't resolve 'core-js/modules/web.dom.iterable'
...

画板库用了@babel/preset-env + @babel/plugin-transform-runtime + @babel/runtime-corejs3 的方案,具体 babel 配置如下:

1
2
3
4
5
6
7
8
9
10
11
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
]
]
}

web 项目用了@babel/preset-env + corejs3 的方案,具体 babel 配置如下:

1
2
3
4
5
6
7
8
9
10
11
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
]
}

二、原因

当画板库中使用 @babel/runtime-corejs3 后,转换后的文件中会注入垫片模块,这些垫片模块从 @babel/runtime-corejs3 中导入。本来该文件已经被 babel 处理过了,但是 npm link 到 web 项目使用时,babel-loaderexclude 排除不了,会再用 web 项目的 babel 处理一遍,引入对 core-js 的依赖,而画板库本身有没有对该包的依赖,最终导致了报错。

下面,笔者会结合几个主要的问题探讨造成的原因。

1、为什么 babel-loader 会重新编译画板库?

首先将 web 项目的 babel-loader 修改如下:

1
2
3
4
5
6
7
8
9
10
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: (path) => {
if (/draw[b|B]oard/.test(path)) {
console.log(path, /node_modules/.test(path));
}
return /node_modules/.test(path);
},
use: ['babel-loader'],
},

首先 exclude 的最终判断效果还是等同于exclude: /node_modules/的,然后我们通过/draw[b|B]oard/.test(path)正则找到画板库包,看看他的真实路径以及最终的 exclude 返回值。最终结果如下:

image-20210718182910357

可以看到,path 是画板库的真实路径地址,是不包含 node_modules 的,exclude 并没有排除画板库的文件,所以最终 web 项目的 babel-loader 仍然会转换画板库

2、为什么编译时画板库文件有 import corejs 的代码?

首先我们简化画板库中的文件,测试一下在 web 项目中调用 babel 后的效果。

简化后的代码如下:

1
2
3
4
5
6
// 省略逻辑,测试转换后的文件
const Drawboard = {};

const a = [1, 2, 3].map((n) => console.log);

export default Drawboard;

然后我们在 web 项目中手动执行 babel 代码,看看转换后的文件。

执行命令:

1
./node_modules/.bin/babel node_modules/@xxx/drawBoard/dist/index.js --out-dir dist

转换后的结果:

1
2
3
4
5
6
import 'core-js/modules/es.array.map.js';
var Drawboard = {};
var a = [1, 2, 3].map(function (n) {
return console.log;
});
export default Drawboard;

可以看到,web 项目的 babel 会往画板库中注入import "core-js/modules/es.array.map.js";,这正是报错的来源。说明在 web 项目的 webpack 编译时,babel-loader 会执行 web 项目的 babel 配置,然后重新转换画板库的文件,将import "core-js/modules/es.array.map.js";这样的代码注入到画板库文件中

3、为什么 import core-js 会报错?

由于画板库的 babel 用的是 preset-env 加上@babel/runtime-corejs3 的方案,而@babel/runtime-corejs3 默认依赖的是 core-js-pure,所以画板库的 node_modules 是没有 core-js 包的。
所以,代码执行时因为找不到 core-js 包,所以报错。

三、解决

综上,我们想要解决这个问题可以通过babel-loader再次编译或者画板库没有core-js两个方向着手。

一、针对 babel-loader 再次编译的问题

1、使用 webpacksymlinks 属性

image-20210718185857102

webpack 有个 symlinks 就是专门解决这类问题的,默认是 true ,会将资源映射到其真实的路径。改为 false 后,就会变成 link 后的地址。

当我在 web 项目中开启后,web 项目的 babel-loader 打印如下:

image-20210718190136860

可以看到,路径变成了[web 项目]/node_modules/[画板库]的格式,这样 babel-loaderexclude 就会排除画板库,就不会再次处理了。

2、修改画板库的 package.json 的 main,指向 src 文件
由于问题源于两次 babel 转换,所以只需要将画板库的 main 改为指向 src 文件,在调试时统一用 web 项目的 babel 转换就可以了。
当然,需要注意在发画板库的包时,将 main 恢复为指向 dist 目录的文件。

二、针对画板库没有 core-js

1、修改 web 项目 的 babel 配置

将 web 项目 babelusage 改为 entry 。这样 babel 就只会在入口处引入所有垫片,就不会在画板库的代码中单独添加 core-js 的依赖代码了。

编译后的文件:

1
2
3
4
5
var Drawboard = {};
var a = [1, 2, 3].map(function (n) {
return console.log;
});
export default Drawboard;

缺点:修改项目的 babel 配置为 entry ,会使得项目的垫片数量多非常多。

2、修改画板库的 babel 配置

将画板库的 babel 配置改为 preset-env + core-js 的方案

1
2
3
4
5
6
7
8
9
10
11
12
{
"presets": [
[
"@babel/preset-env", // 预设
{
"useBuiltIns": "usage",
"corejs": 3 // 垫片
}
]
],
"plugins": ["@babel/plugin-transform-runtime"]
}

这样,画板库的 node_modules 中就有 core-js 包了

3、不改配置,安装 core-js

如题所示,仍然使用 @babel/runtime-corejs3 的方案,只是在画板库中单独安装个 core-js 的包,解决编译问题。

四、总结

综上,我们讨论了 npm link 导致问题的原因,并总结了几种解决方式。在解决问题的过程中,笔者也更深入地了解了这几种 babel 配置的联系。后续如果遇到类似的问题,可参考上述的问题分析的过程和解决方案。

参考资料

Using @babel/runtime-corejs2 and @babel/runtime-corejs3 leads to larger bundle sizes