背景
根据我们在C端错误治理的经验,基于庞大的用户群体,仍有些许用户存在浏览器兼容性问题(H5),如:
| api | system | error | reason |
|---|---|---|---|
| window.scrollTo | ios < 10.3 | Failed to execute 'scrollTo' on 'Window': No function was found that matched the signature provided. | 不支持window.scrollTo({ left: 0, top: 0, behavior: '' }) 这种形式的传参 |
| window.IntersectionObserver | ios < 12.2 | Can't find variable: IntersectionObserver | window上就没有这个api |
| ... | ... | ... | ... |
数据来源于:caniuse.com(ios简直是H5的IE)
解决方案
pollyfill.io
Polyfill.io 是一个服务,旨在帮助开发者更轻松地为其网站提供跨浏览器的兼容性支持。它的主要功能是根据用户的浏览器环境动态地提供所需的 JavaScript polyfills。
Polyfill.io 会根据用户的浏览器
User-Agent自动判断需要加载哪些 polyfills,并只返回那些在用户浏览器中尚未支持的功能,避免加载多余的代码。
例如可以通过url参数指定需要加载的polyfill:
<script src="https://polyfill.io/v3/polyfill.js?features=Promise"></script>
这样polyfill.io服务就可以根据浏览器请求头里携带的User-Agent,分析是否需要返回对应的polyfill代码,从而做到按需加载、同时解决冗余补丁的问题。
不过目前已经打不开了,似乎没有人维护了,因此我们mock一下他的实现原理:
server
const Koa = require('koa')
const Router = require('koa-router')
const bodyParser = require('koa-bodyparser')
const app = new Koa()
const router = new Router()
app
.use(bodyParser())
.use(router.routes())
router.get('/polyfill.js', async (ctx) => {
const ua = ctx.request.headers['user-agent']
// 这里指IE 11
if (ua.includes('rv:11.0')) {
ctx.body = '/** promise-polyfill enabeld */'
} else {
ctx.body = '/** No polyfills needed for current settings and browser */'
}
})
const PORT = 3000
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
})
我们通过node在本地启动一个koa服务,然后对用户请求进行处理,提取请求头里的user-agent字段:
- 是IE11那么就返回对应的polyfill
- 否则返回空内容(象征意义上的)
当然我们也可以对url上的参数进行处理,从而判断客户端需要哪些polyfill。
client
<script src="http://localhost:3000/polyfill.js"></script>
IE 11
chrome
polyfill-inject-plugin
这个包可以按需注入polyfill,如果在构建后的代码中检测到了预设的api(IntersectionObserver、scrollTo等),那么插件就会将对应的polyfill代码打包进输出代码里;反之,如果构建后的代码中不包含对应的api,则不会将与之对应的polyfill打进输出代码里。
安装
1.x版本仅适用于webpack@^4.x
npm install polyfill-inject-plugin -D
配置
在项目根目录的vue.config.js文件中添加以下代码:
const PolyfillInjectPlugin = require('polyfill-inject-plugin')
module.exports = {
chainWebpack: (config) => {
config
.plugin('polyfill-inject-plugin')
.use(PolyfillInjectPlugin)
}
}
目前插件提供了scroll, scrollTo, scrollBy, IntersectionObserver, requestAnimationFrame等api的默认兜底polyfill,也可以针对别的api添加自定义polyfill:
.use(PolyfillInjectPlugin, [{
// npm包或polyfill文件的绝对路径(值需要为能解析到的某个JS IIFE单文件路径)
IntersectionObserver: 'intersection-observer'
}])
示例
假设我们通过vue-cli初始化了一个vue项目:
vue create vue-demo
需要确认安装的 @vue/cli-service 为4.x,因为4.x版本的cli对应4.x版本的webpack
并添加了上述配置:
打包一下试试:
npm run build
很干净,没有polyfill.js产出。
在App.vue里添加以下代码:
mounted() {
requestAnimationFrame(() => {
console.log('hello world!')
})
}
再次打包:
dist/index.html:
可以看到因为存在requestAnimationFrame调用,因此会产生polyfill并加载。
这个webpack插件旨在将代码里用到的一些预设的、存在兼容性的api,按需打包进输出代码里,产生一个单独的polyfill.js文件,这个文件会经过preload预加载,而且经过gzip压缩后体积很小,几乎对应用的加载没有影响。
如果对性能有极致追求,那么可以将polyfill.io的加载方案与此结合,但是需要单独维护一个额外的资源提供服务端,这块儿也需要进行取舍。(但从polyfill.io已不再提供服务来看,这个方案可能不太稳妥,需谨慎考虑)