前段时间遇到了一个vue-cli导致的bug,钻研了半天才解决,于是有空写篇文章记录下来。
一、奇怪的bug出现
有用户反馈在ie11下打不开我们的线上网站,即使开了兼容模式也打不开我们的网站,页面白屏。
初步判断,由于css样式资源、页面资源都已经加载到位,排除网络环境问题后,让用户打开控制台截图看一下,白屏的原因是由于JS执行报错阻塞了后续的逻辑执行和渲染。
代码中存在执行错误的逻辑,但是按照打包后的压缩js文件是完全无法确定问题的,如果你有接入sentry或者badjs等错误上报模块,那么你可以根据错误信息,快速定位到源码上的问题。
但是问题又出现了,sentry的引入其实最合法合规最正确的引入方式,应该是最页面资源加载的最前面,有些人会其防止在head标签中优先加载,这样页面后续无论是资源加载出错、HTML解析错误 或者 JS逻辑执行报错,都可以捕获到并且进行上报。
然而当初没有考虑到这一点,而是直接在vue-cli中的main.js中直接import sentry的方式引入,这样意味着打包后的bundle如果首次执行出错的话,sentry都未能完成初始化,导致错误内容无法上报。
根据用户的截图也不能很快的确定问题代码以及报错内容,于是先让有windows电脑的同事用ie11打开网址,查看报错内容,果然,报错了,报的错还不止一个。
定位对应的行代码:
第二个问题主要是由于使用v-model绑定type为radio值的input时,还自定义了属性:checked,导致最后编译出来的代码里出现对重复属性的定义,而正式环境下的bundle包一般都是默认'use strict',所以导致触发严格模式,ie11报错,根据属性名便可以快速定位问题并修改。
最后还是回来讲讲第一个问题,智障IE还不支持class?是的 不支持
但是 为啥我们的打包产物里会出现莫名其妙的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:
对应的第三方库的地址为./node_modules/dom7/dist/dom7.modular.js
接下来我们要继续追查,是哪里引用到了这个包,在package-lock.json里很快锁定到目标:
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 不同,默认值也会有所不同。
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的方式来达到兼容。
谢谢观看~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。