tags:

  • Babel
  • Polyfill

categories:

  • Babel
  • Polyfill

今天看了篇文章「用了 babel 还需要 polyfill 吗???」,文章中明确回答了用了 babel 后还需要 polyfill。babel 只是一个平台,本生不提供任何功能,需要实现各种特性,必须依赖各种插件。如果 babel 没有使用任何插件,那么代码经过 babel 编译后会原封不动的返回。

什么是 Polyfill ?

A polyfill is a piece of code (usually JavaScript on the Web) used to provide modern functionality on older browsers that do not natively support it.

polyfill 就是一段补丁代码,它能够让当前浏览器不支持的方法通过用支持的方法重写来获得支持。

比如 IE11 不支持 Promise,而我们又需要在项目里用到,写了这样的代码:

1
2
3
4
5
<script>
Promise.resolve("bar").then(function (foo) {
document.write(foo);
});
</script>

这时在 IE 下运行就会报错了

然后在此之前加上补丁

1
2
3
4
5
6
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
<script>
Promise.resolve("bar").then(function (foo) {
document.write(foo);
});
</script>

刷新浏览器,就可以正常运行了

Polyfill 方案

core-js / @babel/polyfill

Core-js 是 JavaScript 标准库中最流行也最常用的 polyfill,为最新的 ECMAScript 标准和提案提供支持,包括古老的 ES5 功能到迭代器助手等前沿选项;

[@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill.html) 这是 core-js的一个包装器,引入的是 core-js 的 stable 特性,因此从 Babel 7.4.0 之后,这个包就不再维护了,取而代之的是直接引用 <font style="color:rgb(68, 73, 80);">core-js/stable</font>

Babel 7.4.0 之前:

1
import "@babel/polyfill";

Babel 7.4.0 之后改为:

1
2
import "core-js/stable";
import "regenerator-runtime/runtime";

因为 core-js 中 JavaScript 标准库的一个 polyfill,不仅支持最新的 ECMAScript 标准,而且连标准库提案都包含在内,因此引入这一个库,es6,es7 等新特性就可以随便写了,不然担心兼容性问题。

但是与此同时,这也带来个很大的缺点,不管你浏览器是否支持,也不管你项目是否有用到,都全量引入了,这在一些性能和带宽严苛的环境下会带来很大的麻烦。

@babel/preset-env

@babel/preset-env 是 babel 的一组预设,其中就有 pollyfill 的相关配置

@babel/preset-env 有两种不同的模式,可以通过 <font style="color:rgb(36, 41, 47);">useBuiltIns</font>选项启用,其有三个参数:

  • entry
  • usage
  • false

其中,entryusage都可以优化 core-js 的引用,其区别在于:

useBuiltIns: entry

使用该选项,<font style="color:rgb(36, 41, 47);">@babel/preset-env</font>将找到入口文件里引入的 core-js,并替换为 targets 浏览器/环境需要的补丁列表

.babelrc 配置如下:

1
2
3
4
5
6
7
8
{
"presets": [
["@babel/env", {
useBuiltIns: 'entry',
targets: { chrome: 72 }
}]
]
}

入口文件引入 core-js:

1
2
import "core-js/stable";
import "regenerator-runtime/runtime";

转换目标是 chrome 72 时,将被转化为:

1
2
3
4
import "core-js/modules/es.array.unscopables.flat";
import "core-js/modules/es.array.unscopables.flat-map";
import "core-js/modules/es.object.from-entries";
import "core-js/modules/web.immediate";

如果转化目标是 chrome 73(完全支持 ES2019 标准库),将得到个更简短的引入:

1
import "core-js/modules/web.immediate";

useBuiltIns: usage

当启用这个选项时,<font style="color:rgb(36, 41, 47);">@babel/preset-env</font>只会在每个文件顶部添加仅用于当前使用的、目标环境不支持的功能的 polyfills

例如:

1
2
const set = new Set([1, 2, 3]);
[1, 2, 3].includes(2);

当转化目标是老旧的低版本浏览器,比如 ie 11,上面的代码将被转化为:

1
2
3
4
5
6
7
import "core-js/modules/es.array.includes";
import "core-js/modules/es.array.iterator";
import "core-js/modules/es.object.to-string";
import "core-js/modules/es.set";

const set = new Set([1, 2, 3]);
[1, 2, 3].includes(2);

当目标是 chrome 72时,不会导入任何 polyfills,因为目标环境不需要这些 polyfills:

1
2
const set = new Set([1, 2, 3]);
[1, 2, 3].includes(2);

上面不管是 <font style="color:rgb(36, 41, 47);">entry</font>还是 <font style="color:rgb(36, 41, 47);">usage</font>都能够实现 polyfill 一定的按需引入,好是好,但是仔细想想,还是有不少问题的:

  • 特性列表是按浏览器整理的,那怎么知道哪些特性我用了,哪些没有用到,没有用到的部分也引入了是不是也是冗余?
  • 补丁是打包到静态文件的,如果我配置 targets 为 chrome: 62, ie: 9,那意味着 chrome 62 也得载入 ie 9 相关的补丁,这也是一份冗余
  • 我们是基于 core-js 打的补丁,所以只会包含 ecmascript 规范里的内容,其他比如说 dom 里的补丁,就不在此列,应该如何处理?

手动引入

前面的方案都是基于 babel 编译器的方案,虽然低效但是直观有用。如果是在一些特别在乎性能的场景,比如 H5 营销页面,宁可牺牲效率也要最求性能。那么可以手动引入 core-js/modules 下的文件,缺啥加啥就好。

在线补丁

上面啰嗦了一大堆,和本文的主题 Polyfill.io 有什么关系?前面都是为了解决如何引入特性列表的问题,有了特性列表,要做到按需下载,就需要用到在线补丁的服务了。这就到了本文的主题,polyfill.io - 目前最流行的在线补丁服务。

你可以尝试在不同的浏览器下访问 https://polyfill.io/v3/polyfill.min.js 这个文件,Polyfill.io 服务端会根据用户访问携带的浏览器 UA 返回不同的 pollfill 文件,真正做到了按需加载。你需要做的仅仅是在页面上引入这个文件:

1
<script src="https://polyfill.io/v3/polyfill.min.js"></script>

然后在 Chrome(作者用的 Chrome 是 111 版本)下的输出是:

1
2
/* Polyfill service v3.111.0
* Disable minification (remove `.min` from URL path) for more info */

啥都没有,因为新版本的 Chrome 已经完整支持了 ECMAScript 标准和提案的所有特性,没啥需要 pollyfill 的。

Polyfill.io 不仅提供了 CDN 服务,还开源了 polyfill-service 供我们自己搭建使用。针对国内访问 Polyfill.io 较慢的问题,可以使用阿里云的 polyfill 服务替换

1
https://polyfill.alicdn.com/polyfill.min.js

阿里云不仅仅是 Polyfill.io,它还做了一些优化,在作者的 chrome 上访问上面的链接:

会加载一些额外的代码,这表现和 Polyfill.io 不一样,因此不能简单的把阿里云的 polyfill 当成 Polyfill.io 的平替,至于阿里云做了哪些优化,本文就不再讨论了,有机会另开文章介绍。

当然改方案也不是完美的,最起码它有以下两个缺点:

  • 多了一次 http 请求
  • 针对国内乱七八糟的浏览器是否能够准确识别从而返回需要的 polyfill 代码就不得而知了

完整 Pollyfill 方案

本文关于 Polyfill 方案的介绍大部分都是来自大佬们的文章,同时也包含了一些自己的思考。是时候来个总结,关于 Pollyfill 我认为 按需特性探测 + 在线补丁 才是一个比较完整的方案:

按需特性探测可以使用 <font style="color:rgb(36, 41, 47);">@babel/preset-env</font>配上 <font style="color:rgb(36, 41, 47);">targets</font>以及 <font style="color:rgb(36, 41, 47);">useBuiltIns: usage</font>,可以保障特性集的最小化。

在线补丁可以使用 Polyfill.io 的方案(大公司可以使用自己搭建的服务),可以保障遇到目标浏览器不支持的特性是能做到按需加载,给业务再做一层兜底。

参考