21
前段时间遇到了一个vue-cli导致的bug,钻研了半天才解决,于是有空写篇文章记录下来。

一、奇怪的bug出现

有用户反馈在ie11下打不开我们的线上网站,即使开了兼容模式也打不开我们的网站,页面白屏。
image.png

初步判断,由于css样式资源、页面资源都已经加载到位,排除网络环境问题后,让用户打开控制台截图看一下,白屏的原因是由于JS执行报错阻塞了后续的逻辑执行和渲染。

image.png

代码中存在执行错误的逻辑,但是按照打包后的压缩js文件是完全无法确定问题的,如果你有接入sentry或者badjs等错误上报模块,那么你可以根据错误信息,快速定位到源码上的问题。
但是问题又出现了,sentry的引入其实最合法合规最正确的引入方式,应该是最页面资源加载的最前面,有些人会其防止在head标签中优先加载,这样页面后续无论是资源加载出错、HTML解析错误 或者 JS逻辑执行报错,都可以捕获到并且进行上报。
然而当初没有考虑到这一点,而是直接在vue-cli中的main.js中直接import sentry的方式引入,这样意味着打包后的bundle如果首次执行出错的话,sentry都未能完成初始化,导致错误内容无法上报。
根据用户的截图也不能很快的确定问题代码以及报错内容,于是先让有windows电脑的同事用ie11打开网址,查看报错内容,果然,报错了,报的错还不止一个。

image.png

定位对应的行代码:
image.png

image.png
第二个问题主要是由于使用v-model绑定type为radio值的input时,还自定义了属性:checked,导致最后编译出来的代码里出现对重复属性的定义,而正式环境下的bundle包一般都是默认'use strict',所以导致触发严格模式,ie11报错,根据属性名便可以快速定位问题并修改。

最后还是回来讲讲第一个问题,智障IE还不支持class?是的 不支持
image.png
但是 为啥我们的打包产物里会出现莫名其妙的ES6代码?
按理说vue-cli已经帮我们引入了babel-loader针对ES6代码进行了转换,为啥还会出现class呢?
带着这个问题我们继续往下看

二、分析原因、确定问题

根据报错文件chunk-vendors,我们来开一下我们的vue.config.js中针对不同chunk的配置文件:

optimization: {
      splitChunks: {
        cacheGroups: {
          echarts: {
            name: 'chunk-echarts',
            test: /[\\/]node_modules[\\/]echarts[\\/]/,
            chunks: 'all',
            priority: 10,
            reuseExistingChunk: true,
            enforce: true,
          },
          demo: {
            name: 'chunk-demo',
            test: /[\\/]src[\\/]views[\\/]demo[\\/]/,
            chunks: 'all',
            priority: 20,
            reuseExistingChunk: true,
            enforce: true,
          },
          page: {
            name: 'chunk-page',
            test: /[\\/]src[\\/]/,
            chunks: 'all',
            priority: 10,
            reuseExistingChunk: true,
            enforce: true,
          },
          vendors: {
            name: 'chunk-vendors',
            test: /[\\/]node_modules[\\/]/,
            chunks: 'all',
            priority: 5,
            reuseExistingChunk: true,
            enforce: true,
          },
        },
      },
    },

出现es6代码都来自于node_modules下的chunk包,为啥呢?
接下来为了进一步确定我们的es6代码是从哪个第三方库引入的,我们需要暂时关闭掉uglify压缩代码,配置如下:

module.exports = {
  ...
  optimization: {
    minimize: false,
    ...
  }
};  

再进行以上配置,即可以关闭webpack4的默认压缩配置,这时候我们再来我们打包产物中找找,大片的const和let,还有我们要找的罪魁祸首,class:

image.png

对应的第三方库的地址为./node_modules/dom7/dist/dom7.modular.js

接下来我们要继续追查,是哪里引用到了这个包,在package-lock.json里很快锁定到目标:

image.png

swiper是移动端用户轮播的一个第三方库,而这里引用到了dom7模块,
我们翻一下swiper的源码 在它的源码中的swiper.esm.bundle.js打包产物中,的确看到这么一条引入:

import { $, addClass, removeClass, hasClass, toggleClass, attr, removeAttr, data, transform, transition as transition$1, on, off, trigger, transitionEnd as transitionEnd$1, outerWidth, outerHeight, offset, css, each, html, text, is, index, eq, append, prepend, next, nextAll, prev, prevAll, parent, parents, closest, find, children, remove, add, styles } from 'dom7/dist/dom7.modular';

但是为啥我们引入的是esm的产物代码呢?默认webpack不会帮我们引入main字段对应的产物吗?
翻看一下webpack的官方文档,我们可以看到有这么一个配置项:
resolve.mainFields:
当从 npm 包中导入模块时(例如,import * as D3 from "d3"),此选项将决定在 package.json 中使用哪个字段导入模块。根据 webpack 配置中指定的 target 不同,默认值也会有所不同。

image.png

webpack的默认配置下,mainfields字段所指定的是优先以module为入口,这么设计的原因是为了顺应时代的潮流,让大家使用es6导出的模块,逐步淘汰掉以往的Commonjs模块规范,毕竟import、export能带来更多的好处。
而我们的vue-cli在构建编译 默认target是为node,所以我们的mainFields字段也默认为['module','main']

原因分析:

到这里我们已经明白问题的关键是啥了,由于webpack不会对node_modules中引入的第三方库内的代码进行二次的ES6转码处理,而webpack默认又会引入module字段所指向的打包产物,module产物一般是ES6规范输出,main产物一般是commonjs或UMD规范输出,所以部分ES6代码的残留导致IE11不兼容,最终导致报错。

三、修改配置

竟然确定了问题所在,接下来我们只需要按照vue-cli官方文档所介绍,自行配置一下webpack配置即可:

configureWebpack: {
    resolve: {
      mainFields: ['main', 'module'],
    },

vue-cli中默认引入了一些loader和plugin,所以内部已经有一份webpack的配置了,vue-cli通过暴露configureWebpack参数的方式,允许使用者对webpack进行再配置,如果是以对象形式传入,会与内部的webpack配置对象进行merge操作。

修改完配置后,最后检查一下我们的打包产物,的确已经没有了const、let、class等这类es6语法,大功告成,bug解决。

当然,如果你的网站需要兼容大多数浏览器和不同场景的话,你还需要为你的代码引入polyfill,毕竟ES6转ES5只是针对部分语法,然而ES6所新增的部分API是不会进行转换的,这时候只能通过引入前置polyfill的方式来达到兼容。

谢谢观看~


曾培森
1.1k 声望875 粉丝

学海无涯皮蛋瘦肉粥