前言
https://juejin.cn/post/7208510421676982329
上文的最后,我们提到了babel配置的最佳实践
没有最佳,只有最适合
那么业内常用的脚手架是怎么配置的呢?
我们以一次线上白屏问题的排查为切入点一步步探讨。
问题描述
我们的项目是以vue-cli3脚手架生成的 vue2.6.11的SPA项目
某华为P10 手机打开页面忽然白屏,由于是app内嵌页,且是线上环境,无法抓包
我们找到同款测试机,通过google抓包工具发现JS代码在报错
定位到代码行,是crypto.js/enc-base64url.js 在报错
parse: function (base64Str, urlSafe=true) { var base64StrLength = base64Str.length; var map = urlSafe ? this._safe_map : this._map; var reverseMap = this._reverseMap; ....... },
问题排查
当看到低版本手机上,三方库报错时,第一反应是babel 是不是配置的不对。
当打开报错代码,定位到上述代码第一行,一时间竟没有发现哪里有错,朴实无华,平平无奇的代码怎么浏览器引擎就无法识别呢?
后来看到 urlSafe = true 这段给函数参数默认值的写法。
想起来这是ES6新增的语法,详情可以看下面的教程
https://es6.ruanyifeng.com/#docs/function
理想状况应该是这样
``` // 转译前 function print(str='hello word') { console.log(str) } print()
// babel 转译后
function print() { var str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "hello word"; console.log(str); } print(); ```
那么问题来了。
为什么这段代码没哟被转译成ES3,ES5的语法呢?
为什么我业务代码中的函数参数默认值的写法就没有出现任何问题呢?
vue-cli脚手架是对babel怎么配置的呢?
带着这三个问题,我打开了项目中的babel.config.js
vue/app
项目中的babel.config.js配置如下
预设的插件集合是 @vue/app
plugins 中是对两个组件库的自动引入
module.exports = { presets: [ '@vue/app' ], plugins: [ [ 'import', { libraryName: 'mand-mobile', libraryDirectory: 'components' } ], [ 'import', { libraryName: 'vant', libraryDirectory: 'es', style: true }, 'vant' ] ] }
@vue/app 是什么?
@vue/app 是 @vue/babel-preset-app的缩写
一个默认的 Vue CLI 项目会使用 @vue/babel-preset-app
通过查看node_modules源码,我们在readme中发现
这个插件内部引用的是经典插件库 babel/preset-env
它通过 @babel/preset-env 和 browserslist 配置来决定项目需要的 polyfill。
browserslist
配置源从以下位置读取
- package.json文件中的browserslist字段
- .browserslistrc配置文件
- browserslist.config.js 配置文件
- 运行环境变量BROWSERSLIST
- 默认如下
"browserslist": [ "defaults" ] // defaults => > 0.5%, last 2 versions, Firefox ESR, not dead
useBuiltIns
这个属性的配置的默认值是 usage
它会根据源代码中出现的语言特性自动检测需要的 polyfill,确保了最终包里 polyfill 数量的最小化
意思是仅仅会为我们引入目标浏览器中不支持并且我们在代码中使用到的内容,会剔除没有使用到的 polyfill 内容。
但是并不会处理 我们的npm依赖包中的 不被浏览器识别的 ES6+ 的语法
这就导致了 文章开头提到的问题, 三方库函数是crypto.js/enc-base64url.js 中ES6语法在报错,直接导致页面白屏幕。
解决方案
思路一
修改useBuiltIns 为entry
在入口文件引入polyfill
// babel.config.js module.exports = { presets: [ [ '@vue/app', { useBuiltIns: 'entry' } ] ] } //main.js import 'core-js/stable'; import 'regenerator-runtime/runtime'
优点:一劳永逸,所有的JS新语法都会进行转译,再也不用担心浏览器兼容问题
缺点:无论这个语法有没有被使用,相应的转译包都被引入了,导致代码体积变大
思路二
transpileDependencies: true
这是vue-cli 暴露给开发者的一个属性,默认值为false
大意就是,如果配置为true,会对nodemodules在的包进行转译,但是会因为遍历了所有的nodemodules 会导致构建速度变慢
思路三
transpileDependencies: ['crypto.js']
哪个有问题配置哪个
兼顾兼容性和构建速度
我们依赖的很多包,其实已经是使用babel 编译之后的包了,所以不需要全量编译,如果能提前预知哪些包需要编译,我们提前配置进去就好。
当然,这是一种最理想的状态,随着项目的迭代,我们也很难知道哪些包需要转译,哪些包不需要转译。
采纳方案
经过讨论,我们最终使用了思路二,对所有包进行遍历
transpileDependencies: true
对使用的JS 语法进行转译
修复之后依旧白屏?
配置transpileDependencies: true之后
我们在测试环境构建打包后,用有问题的机器再次打开,依然白屏
难道是配置项没生效?
我们在浏览器打开console平台,发现是vConsole在报错
vConsole.log
这个工具库相信前端开发们都不陌生,测试环境调试抓包利器,每想到在这个包竟然翻了车。
我们代码中引vconsole.log 的方式是
``` import VConsole from 'vconsole';
const vConsole = new VConsole(); ```
但是报错的地方是vConsole的三方依赖包
经过调研,我们发现transpileDependencies: true 只会广度遍历编译三方依赖
对于依赖的依赖则不会处理
这就导致了上述的问题
对于此类问题,我们可以通过正则进行匹配
module.exports = { transpileDependencies: [ /[/\]node_modules[/\]test[/\]/, /[/\]node_modules[/\][@\]test2[/\]test3[/\]/, ] }
但是依赖引用依赖,无穷无尽,配置的复杂度大大增加
现在想来还不如一开始就全量入口引入,简单粗暴,无后顾之忧
解决方案
本着遇到问题解决问题的思路
我们最后采用了在html 入口引入vConsole.min.js 文件的方式,来规避了这个问题
这个文件是使用babel 转译过的,不存在兼容性问题。
详情可参考项目
https://github.com/Yinzhuo19970516/vue-template/tree/main/public
方案总结
最后我们项目中的babel配置如下
使用vue-cli 中对默认配置,加 transpileDependencies: true
当然没有最佳的,只有最合适的
如果项目对兼容性要求较高,多人维护,我更建议你配置,useBuiltIns 为entry
牺牲项目体积,保证兼容性。
最后这是我们一次线上问题排查的过程中,记录的babel 在实际项目中的具体实践,如果想看原理可以看我上一篇文章
十问babel,用最简单的话说清楚babel
https://juejin.cn/post/7208510421676982329
如果您都读到这里了,请给个免费的赞吧!
本文正在参加「金石计划」