一个Vue3项目兼容10年前谷歌内核的解决方案
背景
这几天一个不太联系的朋友突然找到我,让我帮他看看他的项目,他的甲方是一个政企单位,对方的内网中部分浏览器页面打不开,打开就是一篇空白,而且在内网还不方便 调试、查看,于是我让他安排人去看了看显示空白的电脑是否有控制台报错,以及网络请求是否有错误。因为是内网,无法把截图发出来,于是我收到了几张 手机拍的照片
看起来确实是浏览器遇到了无法解析的代码,导致了了vue没有初始化,无法渲染页面,所有空白。
然后他给我看了空白的几个浏览器的版本,有用360的,也有用谷歌的,可是这内核版本,发布了快10年了吧?
既然找到我了,能帮还是得帮一下。
拿到代码
拿到代码之后,先看了看代码结构,发现是一个vue3项目,然后我看了看vite.config.js,我首先想的是通过babel加polyfill, 然后去vite官网找了找,还真找到了一个兼容老版本的解决方案,vite提供了一个插件来生成兼容老版本浏览器和现代浏览器都可以访问的插件:
因为这个项目用的是vite3.x,我去看了一下vite的github源码,v3版本对应的@vitejs/plugin-legacy
是2.x,于是我把他们安装到项目中。
然后去修改vite的配置,添加了legacy的插件
import legacy from '@vitejs/plugin-legacy';
plugins: [
...
mode === 'production'
? legacy({
// 指定目标浏览器,兼容 Chrome 70 及以下版本
targets: ['Chrome <= 70'],
// 额外添加 async/await,生成器
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
// 额外生成现代浏览器版本的chunk
modernPolyfills: true
// 为打包后的文件生成 legacy 版本
renderLegacyChunks: true,
})
: null,
...
]
通过这个配置之后运行打包,每个chunk文件会打包两份代码,一个是现代浏览器支持的,一个是文件名中有-legacy的用来兼容老浏览器, 浏览器会自动根据是否现代浏览器选择加载哪一个chunk文件。
legacy插件的参数说明,可以查看github:
targets: 指定legacy的目标浏览器,他的语法就是browserlist 一样的
这个参数设置之后在使用之后,创建legacy chunk时会传给 @babel/preset-env
- Type:
string
|string[]
|{ [key: string]: string }
- Default:
'last 2 versions and not dead, > 0.3%, Firefox ESR'
modernTargets: 4.0版本新增,指定现代目标浏览器,相较于targets参数,此参数用于现代浏览器减少不必要的polyfill
- Type:
string
|string[]
- Default:
'edge>=79, firefox>=67, chrome>=64, safari>=12, chromeAndroid>=64, iOS>=12'
polyfills: 显式指定要用哪些polyfills,不指定则由browserlist和@babel/preset-env自动根据情况生成polyfills。
- Type:
Boolean
|string[]
- Default: true
配置示例:
polyfills: [
'es.promise.finally',
'es/map',
'es/set'
]
// 或者
polyfills:true
modernPolyfills 同上,指定modern浏览器的polyfills
- Type:
Boolean
|string[]
- Default: false
additionalLegacyPolyfills : 额外给旧版本浏览器指定polyfills,比如我就用了’regenerator-runtime/runtime’来给await、async和生成器添加polyfill
- Type:
string[]
additionalModernPolyfills : 额外给现代浏览器指定polyfills
- Type:
string[]
renderLegacyChunks: 生成legacy版本的chunk文件,modernPolyfills要设置为值才生效。
- Type: boolean
- Default: true
renderModernChunks: 控制chunk在现代浏览器的加载逻辑
打包之后发现新问题
因为现在要打包两份代码,所以打包的时间会增加,而且打包后的文件会比之前大很多,因为每个chunk文件都要打包两份代码。 我心里已经预计会变慢了,在没有加legacy配置之前,打包大概33秒。但是我万万没想到,加了之后打包时间变成了380秒左右,性能下降了11倍多。
于是我先去掉legacy配置,研究一下他的项目情况,安装了一个分析插件
import { visualizer } from 'rollup-plugin-visualizer';
plugins: [
...
visualizer(),
...
]
先来看看打包后的组成图
首先发现的是index.js过于大了,里面打包了很多三方依赖,本来想给他把三方依赖都提出来,该放cdn就放cdn了,但是我朋友提醒我,他们客户的内网电脑普遍比较老, 排查问题困难,也没有cdn访问。看起来打包大小是没法去压缩的,就算我能在public中放静态依赖js文件,但是这样也会绕过legacy的处理,万一三方依赖在客户的电脑上 也不兼容咋办,于是只有放弃这个办法了,但是可以先拆包看看,于是我加入了拆包插件。
import { chunkSplitPlugin } from 'vite-plugin-chunk-split';
chunkSplitPlugin({
strategy: 'default',
customSplitting: {
'vendor-vue': ['vue', 'vue-router', 'pinia'],
'vendor-element-plus': ['element-plus'],
// eslint-disable-next-line prettier/prettier
'utils': [/src\/utils/],
'vendor-node-forge': ['node-forge'],
'vendor-vform3': ['vform3-builds'],
'vendor-wangeditor': ['@wangeditor/editor']
}
})
然后对代码做了一些细小的改动,有的地方做了按需加载。他这个项目用了vform3低代码,所以element-plus也不好用按需加载了,用AutoImport
和Components
按需加载的话,代码中的element组件能正常打包,但是表单中的组件会出现一些问题。所以基本上还真是没啥办法去动它。
打包结果:左侧是拆包后,右侧是拆包前
拆包后的index已经不存在4000多k的情况了。
这下看起来可算正常一点了,三方包都拆出来了。
意外收获
拆包之后,重新加入legacy的配置,惊喜的发现打包时间降低了一大截,在我n100的小主机上打包时间从370秒降低到180秒左右。
在我自己用的m1pro芯片的电脑上,编译时间只需要90多秒了。
我推测这个变化原因有这几种可能
- legacy也用了babel转换,拆包后的小文件ast解析+转换 比不拆包大文件ast解析+转换 快,资源占用少。
- 还有可能vite在构建时,会有一些缓存,对于没有变化的部分,不会重新构建。
- 拆包后,公共依赖被提取出来,这部分代码只需要编译一次,不拆分的话在打包现代浏览器和legacy浏览器时,这部分代码会被重复编译。
有空我再去看看legacy的源码,看能否找到这个问题的答案。
处理样式兼容
打包legacy的时候会出现一些样式的警告,说在chrome 70上无法解析。 于是我又加了postcss进去
"postcss": "^8.5.3",
"postcss-nesting": "^13.0.1",
import postcssNesting from 'postcss-nesting';
css: {
postcss: {
plugins: [
postcssNesting()
]
}
},
再打包就没有告警了。
测试方法
因为浏览器内核比较老了,要想测试也有些不容易,我自己也是用的linux和mac(arm),这种老版本的浏览器在arm的mac上最老的版本号也是100多,于是我去买了个按量付费 的云windows服务器,然后去下载了50、60、70内核的谷歌浏览器去测试。
历史浏览器版本下载,这个网站历史版本齐全,但是好像需要留学生才能访问,有空我还想把这些浏览器版本的资源爬回来,放到自己的服务器上,方便大家下载。
https://vikyd.github.io/download-chromium-history-version/#/
最后
本来也没啥想说的,忍不住,2025年了,还有用10年前浏览器的人…
果然我还是无知的。