如何实现个在线的按需的 ployfill

polyfill 在英文中有垫片的意思,意为兜底的东西。在计算机科学中,指的是对未能实现进行的”兜底”操作。简单的说就是它可以让你可以毫无顾虑地使用最新的 JavaScript 特性,而不需要关注浏览器兼容性。
我们用最多的 polyfill 方式都是基于 core-js 正常使用方式一般是这两种: 1.通过 cdn 不考虑按需引入整个 core-js 文件非常大,最新的 3.15.2 版本大小 742 kB。 2.用 babel 处理,通过设置 @babel/preset-env 加上 useBuiltins 配置来按需裁剪 core-js。
目前主流做法都是选择方案 2 通过 @babel/preset-env 裁剪,事实上,在 CDN 的缓存的作用下收益会比方案 2 剪裁有更好的性能。

目标

根据 babel 的做原理,如果能可以根据浏览器的兼容性配置动态的生成裁剪后的 core-js,然后只引入 CDN 上剪裁后的 core-js 文件。

如何根据浏览器的兼容性动态剪裁

很简单,只需要按照 babel 的处理方式

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

配一个 .browserlisrc 浏览器信息

1
chrome 86

利用 rollup 打包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import babel from "@rollup/plugin-babel";
import nodeResolve from "@rollup/plugin-node-resolve";
// CommonJS格式
import commonJS from "@rollup/plugin-commonjs";
import { terser } from "rollup-plugin-terser";
export default {
input: "index.js",
output: {
file: "dist.js",
format: "iife",
},
plugins: [
babel({ babelHelpers: "bundled" }),
nodeResolve(),
commonJS(),
terser(),
],
};

经过 rollup 打包后就会生成一个根据浏览器裁剪后的 ployfill 文件了。

如何云化

上面我们通过 babel 工具去生成 polyfill,然后上传到 CDN 并修改引用地址,当需要不同浏览器兼容性的时又要重复上面的操作,成本很高,所以我们希望运用云函数的能力来实现在线生成,生成的逻辑在线化,目前云服务上都提供 Serverless 计算服务像阿里云 函数计算 FC

改造

只需要将 rollup 调用方式改成 api,通过 url 传参方式传递浏览器信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const path = require("path");
const { encode } = require("js-base64");
const { rollup } = require("rollup");
const rollupConfig = require("./factory/rollupConfig");
const terser = require("terser");
const respond = (body, statusCode = 200) => {
return {
statusCode,
body,
isBase64Encoded: false,
headers: {
"Content-Type":
statusCode === 200
? "application/javascript; charset=utf-8"
: "text/plain; charset=utf-8",
},
};
};
exports.handler = async (event) => {
const { targets } = event.queryStringParameters;
if (!targets) {
return respond("No targets specified", 400);
return;
}
const key = encode(targets, true);
try {
const bundle = await rollup(rollupConfig(targets));
const {
output: [asset],
} = await bundle.generate({ file: "dist.js", format: "iife" });
const minified = await terser.minify(asset.code);
return respond(minified.code);
} catch (ex) {
console.error(ex);
return respond(ex.message, 500);
}
};

这样在线生成搞定了,但是每次生成时间巨长,真实启动的服务并不可用,所以我们需要在生成的前面加一个 CDN 配置,需要在上线前预热 CDN 就能达到秒开。当然业界也有成熟的在线服务例如 https://polyfill.io/v3/

现实

这样就完成了加载按需剪裁 CDN 资源的 ployfill,减少了资源大小,同时增加了缓存命中。
但是 chrome 85 版本之后更新了缓存策略,新的策略生效后会大大降低缓存命中,新的策略:

  • 之前,单资源的缓存是以 URL 来作为键,并不关心请求 URL 的来源;
  • 目前,缓存的键由 URL、顶部 window 域名、当前 window 域名三元组构成;
    其他浏览器支持情况:
  • Safari 实现了顶部 window 域名 + URL 的键控制机制;
  • Firefox 即将实现同粒度的缓存键;
    更新缓存机制后,缓存未命中的情形增加了 3.6%,整体网络加载字节数增加了 4%。

后续

虽然由于浏览器的缓存策略更新从而导致跨站缓存无法共享,但是本站的收益还是很大的。另外也可以采用更加细致的方案,把每一个 core-js 的所有 api 都在线化编译成独立的 ES 模块,然后项目采用 ES 模块的方式打包,让浏览器去加载最小粒度的 polyfill,精细度更大。