前言
同一份代码,兼容不同版本,不同浏览器的关键,或许就是 polyfill (补丁)。以前或许是使用 shim 或 sham 或二者,或不用新的特性,es5 大概市面上的大部分的浏览器都会支持了,缩手缩脚下,还是能好好过日子的。但是,新的特性不断地诱惑你,promise , set , map , async ......所以,就应该使用补丁,然后痛快淋漓地coding,这才是我辈风范。只需后面的补丁打得全,哪有特性用不了。
解释一下,shim , sham
shim 是能用的补丁
sham 是假的方法,能保证浏览器不血红一片,但是,假的真不了,也不能真的能使用到方法来解决问题
babel-polyfill.js 是一种包含所有语言层的补丁,不管浏览器是否支持,也不管项目是否用到,全量引用,一应俱全,应有尽有,所以不难看出缺点是,内容太多了。
方案
现在比较常见的有三种方案:
- 使用
@babel/polyfill+@babel/preset-env+useBuiltIns: entry+target。
// webpack.config.js
...
import '@babel/polyfill';
...
module.exports = {
...
module: {
...
rules: [
...,
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
...,
options: {
presets: [
['@babel/preset-env',
{
"useBuiltins": 'entry',
"targets": { chrome: 62 }
}
]
]
},
},
...
],
...
}
}
解释一下 useBuiltIns 的用法:
useBuiltIns: false || entry || usage
useBuiltIns: false不操作,或引入@babel/polyfill,无视配置的浏览器兼容,引入所有的polyfill(相当于上面的babel-polyfill.js)
useBuiltIns: entry引入@babel/polyfill会根据配置的浏览器兼容引入polyfill,会根据browserslist替换为浏览器不兼容的polyfill。注意这里需要指定core-js的版本:"core-js": 2是引入@babel-polyfill而"core-js": 3需要改为import 'core-js/stable'和import 'regenerator-runtime/runtime'。
useBuiltIns: usage会根据配置的浏览器兼容以及代码用到的API来进行polyfill,实现了按需添加。
这样,替换后的内容可能是这样的:
import "core-js/modules/es7.string.pad-start";
import "core-js/modules/es7.string.pad-end";
...
只会引入chrome@62及以上所需要的补丁,已支持的将不再打包进来。
但是,缺点也很明显:
假如引入多个target浏览器版本
{
"target": {
"chrome": 62,
"ie": 9
}
}
那么将会载入两份补丁,将是一份冗余。
假如,一些我没在项目里用过的新特性,也将打包进来了,因为是根据浏览器来打包进去的,那么这部分也将造成冗余。
polyfill方案的过去,现在和未来 中还提到一个问题:
我们是基于 core-js 打的补丁,所以只会包含 ecmascript 规范里的内容,其他比如说 dom 里的补丁,就不在此列,应该如何处理?
(由于我没遇到过类似的问题,这个留待以后回答。)
- 引用在线补丁
polyfill.io。
<script src="https://polyfill.io/v3/polyfill.min.js?features=Map%2CObject.assign%2CObject.create%2CObject.defineProperties%2CObject.defineProperty%2CSet" ></script>
优点很明显,根据已知或者需要的特性,浏览器的版本,动态引入补丁。
缺点还是有的,影响比较大的就是,不安全(假如依赖网站挂了)。其次需要收集feature(即所需的新特性方法)。
- 手动引入所需的,虽然烦琐,但是性能是最符合我们要求的。也需要收集feature。
复用补丁(假设用了方案1)
这个不举例子,请看babel7的配置与优化 ,这里举例子比较具体。
组件内不应该打补丁,因为假如组件a 和 组件b 都使用了 promise,将补丁打在组件内,就会造成了冗余。这时可以用 @babel/plugin-transform-runtime 。
@babel/plugin-transform-runtime
A plugin that enables the re-use of Babel's injected helper code to save on codesize.
// webpack.config.js
...
options: {
presets: [
['@babel/preset-env',
{
"useBuiltins": 'entry',
"targets": { chrome: 62 }
}
]
],
"plugins": [
["@babel/plugin-transform-runtime"]
]
}
...
这样,helpers 是通过 require 引入的,这样就不会存在代码重复的问题了。
思考
-
node_module里面某些插件用到了新特性的方法,但是,我们不会对里面的js进行补丁,那么将会引起错误。举个遇到的具体例子:
在webpack-dev-server 2.7.1以上的版本将会将
const,let未babel地导入文件,在安卓5.1的环境下,将会报错SyntaxError: Use of const in strict mode,低版本浏览器不能辨别const,let,我已经引入了在线的polyfill了,但是没有效果。最后通过对node_modules/webpack-dev-server/client/index.jsbabel来解决的。