对 polyfill 的一点了解

1,121 阅读4分钟

前言

同一份代码,兼容不同版本,不同浏览器的关键,或许就是 polyfill (补丁)。以前或许是使用 shimsham 或二者,或不用新的特性,es5 大概市面上的大部分的浏览器都会支持了,缩手缩脚下,还是能好好过日子的。但是,新的特性不断地诱惑你,promise , set , map , async ......所以,就应该使用补丁,然后痛快淋漓地coding,这才是我辈风范。只需后面的补丁打得全,哪有特性用不了。

解释一下,shim , sham

shim 是能用的补丁

sham 是假的方法,能保证浏览器不血红一片,但是,假的真不了,也不能真的能使用到方法来解决问题

babel-polyfill.js 是一种包含所有语言层的补丁,不管浏览器是否支持,也不管项目是否用到,全量引用,一应俱全,应有尽有,所以不难看出缺点是,内容太多了。

方案

现在比较常见的有三种方案:

  1. 使用 @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 里的补丁,就不在此列,应该如何处理?

(由于我没遇到过类似的问题,这个留待以后回答。)

  1. 引用在线补丁 polyfill.io
    <script src="https://polyfill.io/v3/polyfill.min.js?features=Map%2CObject.assign%2CObject.create%2CObject.defineProperties%2CObject.defineProperty%2CSet" ></script>

优点很明显,根据已知或者需要的特性,浏览器的版本,动态引入补丁。

缺点还是有的,影响比较大的就是,不安全(假如依赖网站挂了)。其次需要收集feature(即所需的新特性方法)。

  1. 手动引入所需的,虽然烦琐,但是性能是最符合我们要求的。也需要收集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 引入的,这样就不会存在代码重复的问题了。

思考

  1. node_module里面某些插件用到了新特性的方法,但是,我们不会对里面的js进行补丁,那么将会引起错误。举个遇到的具体例子:

    在webpack-dev-server 2.7.1以上的版本将会将 constletbabel 地导入文件,在安卓5.1的环境下,将会报错 SyntaxError: Use of const in strict mode ,低版本浏览器不能辨别 constlet,我已经引入了在线的 polyfill 了,但是没有效果。最后通过对 node_modules/webpack-dev-server/client/index.js babel来解决的。