前端 polyfill 解析

什么是 Polyfilll

Pollfill 一词最早是在 nodemon 的作者 Remy Sharp 于 2010 年10 月 8 日发表的博客文章 What is a Polyfill? 中首次提到,他对 polyfill 的定义是:

A shim that mimics a future API providing fallback functionality to older browsers.

翻译过来就是:

polyfill 就是一个垫片/填充/补丁程序,用于抹平浏览器之间的 API 差异,在旧的浏览器上支持新的特性。

无论在早期或者现代前端工程中,常常有以下几种方式来打补丁:

  1. 手动

    手动将补丁代码引入到项目

  2. 自动

    借助工具如 core-jsbabelPolyfill.io 等工具自动化打补丁,并且可以做到根据目标运行环境按需打补丁或动态打补丁。

手动打补丁

在前端工具还不繁荣的早期,我们需要手动引入所需要的补丁,比如以 ES6 的 Object.assign 为例,在 IE11 上仍然会报错:

所以需要手动引入补丁代码,一种是直接安装第三方包,另外一种是引入来自 MDN 的补丁代码:

// 安装 object-assign (https://github.com/sindresorhus/object-assign)
npm i object-assign

// 引入
Object.assign = require('object-assign')

或者在需要的地方放置来自 MDN 的补丁代码

if (typeof Object.assign !== 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length of function is 2
      'use strict';
      if (target === null || target === undefined) {
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource !== null && nextSource !== undefined) {
          for (var nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

很明显,手动引入补丁代码既有优势和劣势:

  • 优势:最小化引入,不会有额外的冗余代码,保证加载性能
  • 劣势:手动引入,不易维护和管理;对于支持多端的应用和多样化的 polyfill 维护成本过大

自动打补丁

按需打补丁

babelbrowerslist 的出现,彻底解决了手动打补丁的劣势问题,通过简单的配置就可以实现自动注入补丁代码。

比如有这样一段代码:

// index.js
Promise.resolve().finally();

在 Windows10 的 Edge 17 中不能运行,因为其不支持 Promise.prototype.finally 方法,借助 babel,运行以下命令:

npx babel index.js --out-file compiled.js

并且存在这样一份配置文件:

// babel.config.js
module.exports = {
  presets: [
    [
      "@babel/env",
      {
        targets: {
          edge: "17",
        },
        useBuiltIns: "usage",
        corejs: "2"
      },
    ],
  ],
};

然后就会输入这样的内容:

"use strict";

require("core-js/modules/es7.promise.finally.js");

Promise.resolve().finally();

可以看到,babel 已经自动的在代码的顶部注入了来自 JavaScript 标准库 core-jsPromise.prototype.finally 实现代码。大概的过程是这样的:

  1. npx babel 调用@babel/cli 提供的终端命令 babel 来运行 @babel/core

  2. @babel/core 开始读取配置、预设,调度各个组件执行编译流程

    1. 调用 @babel/parser 生成源码的 AST
    2. 调用 @babel/traverse 提供给插件转换代码的能力(@babel/polyfill 将在这一阶段介入)
    3. 调用 @babel/generator 生成代码
  3. 写入代码到文件

而这个过程涉及到的依赖只有 @babe/core@babel/preset-env@babel/polyfill

@babel/core

@babel/corebabel 的核心库,包含了 @babel/preser@babel/traverse@babel/generator 等解析、转换和生成代码的库。它从 @babel/cli 接受参数并调度各个组件执行编译流程,是整个流程的调度者和组织者。

@babel/preset-env

@babel/preset-env 提供了一组默认插件的预设,官方提供的预设有四个:

而这些预设最终会被解析成一组插件,以 @babel/preset-react 为例,它最终会生成以下插件列表:

这些插件则被用来支持 react 和装换 jsx 语法。

@babel/polyfill

自 Babel7.4.0 开始,@babel/polyfill 已经拆分成了两个包: core-jsregenerator runtimecore-js 用于支持 ECMAScript 的新特性,regenerator-runtime 用于支持 generator 函数。这个插件也默认被包含在 @babel/preset-env 预设中。

Babel 将检查所有的代码,以查找目标环境中缺少的功能,并且利用 @babel/polyfill 注入所需的 polyfill。

动态打补丁

动态打补丁仍然有一个最大的缺陷:补丁冗余。以 Object.assign 为例,在支持这个特性的浏览器上就没必要引入这个补丁,所以就会造成补丁的冗余。而社区就出现了根据浏览器特性动态打补丁的方案。

Polyfill.io 就是这样一个服务,它会根据浏览器 user-agent 的不同,返回不同的补丁代码。比如想加载 Promise 的补丁代码,可以直接引入:

<script src="https://polyfill.io/v3/polyfill.js?features=Promise"></script>

在较高版本的 Chrome(89) 浏览器上,就会返回空的内容:

将 User-agent 改为 IE11 后,就会返回完整的 polyfill 代码:

这个服务还提供了丰富的 URL 查询参数来定制 polyfill,具体可以查阅文档 url-builder

同时,如果对 Polyfill.io 的稳定性和安全性有要求,可以借助开源的 polyfill service 搭建自己的服务。

最佳实践

考虑目前社区的这些方案,并结合具体的业务场景,如果对加载资源有着极致的性能要求,可以考虑基于 Polyfill.io 自建 polyfill 服务,动态注入 polyfill。而对于一些对加载性能不那么敏感的项目,则完全可以使用 babel 来自动注入 polyfill。当然也可以两者结合使用,但是需要平衡维护成本和收益。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容