babel-loader编译node_modules代码白屏报错的原因及解决方案
在日常的开发过程中,我们或多或少都会遇到这样的场景:项目使用到的某个npm包由于使用了es6语法导致我们打包后的代码在低版本浏览器上报错甚至白屏。eg:
# 报告老板,我们的H5页面在iOS11系统上白屏了!。
此时我们自然而然的会想到把需要处理的包添加到babel-loader的include属性里面去.
{
include: /node_modules\/xxx/,
}
重新打包发布后我们会发现代码依旧报错,只不过报错信息变成了这个

报错的原因是因为在同一个文件里同时出现了import和module.exports。而webpack是不支持这样混用的,所以会向打包后的代码中注入以下代码片段,在执行中抛出该错误。

但是查遍我们的代码也没有发现有混用的地方啊,那么这个错误是怎么产生的呢? 废话不多说,接下来我们直接说原因。。
问题原因
通常我们的代码编译时的调用流程是这样的.
现在我们先从下往上看babel的编译流程
babel有一个caller配置项(caller配置项主要是babel提供给preset或者plugin来使用的,如babel-loader,不能在配置文件里直接设置,只可以通过函数调用的方式来指定)。caller配置项是一个对象,用来标记使用方是否支持es6等某些特性。
preset-env在转换代码时会根据modules配置项来决定是否将import转换为require,modules的默认值是auto(会将import转换为require),auto表示是否转换要根据babel传递过来的caller对象决定。babel-loader会指定caller对象。由于babel编译后的代码是提供给webpack打包使用的,而webpack是支持import语法的,所以babel-loader就将supportStaticESM标记为true,这样babel在编译代码的时候就会跳过将import语法转换为require语法的过程。
4. 在babel编译文件时如果遇到了需要转换的es6语法或语言特性,babel就会添加对应pollyfill,以import的形式。由于babel-loader已经声明了supportsStaticESM为true,所以后续就不会再转换import语句了。而如果转换代码此时又恰好是commonjs格式的,就会出现import和module.exports同时存在的情况,当然此时babel编译的代码尚在内存中
,再然后通过webpack打包,webpack识别到该情况就会在最终的打包代码中添加__webpack_require__.hmd函数,用于标记改错误,在使用到该模块的时候抛出错误用于提醒开发者。
解决方案
方案一
将preset-env的modules配置项设为cjs。
原理: 因为modules默认值为auto,preset-env会根据babel-loader传递的caller对象的supportsStaticESM属性值来决定是否转换import语法。但是当我们设置modules为cjs的时候,preset-env会将涉及的import预发全部转换为我们指定的commonjs语法,webpack肯定也是支持commonjs的,所以完美解决。
方案二
设置babel-loader的caller属性,注意该属性是不能在babel的config文件里设置的。
{
test: /\.jsx?$/,
loader: "babel-loader",
include: [/node_modules\/xxx/],
options: {
presets: [
[
"@babel/preset-env",
{
corejs: "3",
useBuiltIns: "usage",
},
],
],
caller: {
supportsStaticESM: false,
},
targets: {
ios: "9",
},
},
}
原理:babel-loader指定的caller的supportsStaticESM默认为true,通过将其指定为false,babel在转换的时候就会将import语法转换为require的形式。
方案三
通过设置babel的sourceType选项为unambiguous,让babel自己去决定是否转换import语法,该方法通常需要和override配置配合使用,以避免潜在的问题。具体见# 报告老板,我们的H5页面在iOS11系统上白屏了!
课外问题
babel是怎么转换esm模块为commonjs模块的呢?babel提供了plugin-transform-modules-commonjs插件用以将esm模块转换为commonjs模块,preset-env也是通过配置项的方式来决定是否加载该插件,从而决定是否转换esm模块的。.babelrc和babel.config.js两种配置文件的区别? .babelrc通常用于对单个项目的配置,也可用于对某个具体文件的配置。而babel.config.js用于项目级的配置,通常用于monorepo中,也可用于单个项目。 因此,当我们需要编译node_modules里面的模块的时候,我们必须使用babel.config.js的配置方式,.babelrc的配置只会影响当前包,不会影响node_modules里面的包。
转载自:https://juejin.cn/post/7202158347650891837