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