likes
comments
collection
share

babel-loader编译node_modules代码白屏报错的原因及解决方案

作者站长头像
站长
· 阅读数 34

在日常的开发过程中,我们或多或少都会遇到这样的场景:项目使用到的某个npm包由于使用了es6语法导致我们打包后的代码在低版本浏览器上报错甚至白屏。eg: # 报告老板,我们的H5页面在iOS11系统上白屏了!

此时我们自然而然的会想到把需要处理的包添加到babel-loaderinclude属性里面去.

{
    include: /node_modules\/xxx/,
}

重新打包发布后我们会发现代码依旧报错,只不过报错信息变成了这个

babel-loader编译node_modules代码白屏报错的原因及解决方案

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

babel-loader编译node_modules代码白屏报错的原因及解决方案

但是查遍我们的代码也没有发现有混用的地方啊,那么这个错误是怎么产生的呢? 废话不多说,接下来我们直接说原因。。

问题原因

通常我们的代码编译时的调用流程是这样的.

webpack
babel-loader
preset-env
babel-core

现在我们先从下往上看babel的编译流程

  1. babel有一个caller配置项caller配置项主要是babel提供给preset或者plugin来使用的,如babel-loader,不能在配置文件里直接设置,只可以通过函数调用的方式来指定)。caller配置项是一个对象,用来标记使用方是否支持es6等某些特性。babel-loader编译node_modules代码白屏报错的原因及解决方案
  2. preset-env 在转换代码时会根据modules配置项来决定是否将import转换为require,modules的默认值是auto(会将import转换为require),auto表示是否转换要根据babel传递过来的caller对象决定。
  3. babel-loader会指定caller对象。由于babel编译后的代码是提供给webpack打包使用的,而webpack是支持import语法的,所以babel-loader就将supportStaticESM标记为true,这样babel在编译代码的时候就会跳过将import语法转换为require语法的过程。

babel-loader编译node_modules代码白屏报错的原因及解决方案 4. 在babel编译文件时如果遇到了需要转换的es6语法或语言特性,babel就会添加对应pollyfill,以import的形式。由于babel-loader已经声明了supportsStaticESMtrue,所以后续就不会再转换import语句了。而如果转换代码此时又恰好是commonjs格式的,就会出现importmodule.exports同时存在的情况,当然此时babel编译的代码尚在内存中 ,再然后通过webpack打包,webpack识别到该情况就会在最终的打包代码中添加__webpack_require__.hmd函数,用于标记改错误,在使用到该模块的时候抛出错误用于提醒开发者。

解决方案

方案一

将preset-env的modules配置项设为cjs。 原理: 因为modules默认值为autopreset-env会根据babel-loader传递的caller对象的supportsStaticESM属性值来决定是否转换import语法。但是当我们设置modulescjs的时候,preset-env会将涉及的import预发全部转换为我们指定的commonjs语法,webpack肯定也是支持commonjs的,所以完美解决。

方案二

设置babel-loadercaller属性,注意该属性是不能在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指定的callersupportsStaticESM默认为true,通过将其指定为falsebabel在转换的时候就会将import语法转换为require的形式。

方案三

通过设置babelsourceType选项为unambiguous,让babel自己去决定是否转换import语法,该方法通常需要和override配置配合使用,以避免潜在的问题。具体见# 报告老板,我们的H5页面在iOS11系统上白屏了!

课外问题

  1. babel是怎么转换esm模块为commonjs模块的呢? babel提供了plugin-transform-modules-commonjs插件用以将esm模块转换为commonjs模块,preset-env也是通过配置项的方式来决定是否加载该插件,从而决定是否转换esm模块的。
  2. .babelrcbabel.config.js两种配置文件的区别? .babelrc通常用于对单个项目的配置,也可用于对某个具体文件的配置。而babel.config.js用于项目级的配置,通常用于monorepo中,也可用于单个项目。 因此,当我们需要编译node_modules里面的模块的时候,我们必须使用babel.config.js的配置方式,.babelrc的配置只会影响当前包,不会影响node_modules里面的包。
转载自:https://juejin.cn/post/7202158347650891837
评论
请登录