共计 3228 个字符,预计需要花费 9 分钟才能阅读完成。
前段时间遇到了一个 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 的方式来达到兼容。
谢谢观看~