一文搞清楚前端 polyfill

polyfill 在英文中有墊片的意思,意爲兜底的東西。在計算機科學中,指的是對未能實現的客戶端上進行的"兜底"操做。打補丁在前端 er 是件習覺得常的事情,結合筆者平常工做經驗,總結出 3 種打補丁方式。涉及@babel/preset-env@babel/polyfill@babel/transform-runtime@babel/runtime 以及 core-js 。權當拋磚引玉,若有紕漏,不令賜教。javascript

總的來講,打補丁主要有三種方法:html

  1. 手動打補丁
  2. 根據覆蓋率自動打補丁
  3. 根據瀏覽器特性,動態打補丁

三種方法又能夠相互借鑑進行組合,來完成業務所需的補丁。分別介紹:前端

手動打補丁

在石器時代,咱們是手動導入所需的補丁,以 ES6 的 object#assign 爲例 ,即便在 IE 11 上,仍會報錯java

Object assign報錯

因此咱們須要打上相應的補丁。能夠用第三方成熟的 package ,也可使用 MDN 提供的模板進行打補丁:node

Object.assign = require('object-assign')
// or

// Refer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
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) {
        // TypeError if undefined or null
        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) {
          // Skip over if undefined or null
          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 和變化無窮的 Web 應用維護成本比較大。react

根據覆蓋率自動打補丁

在黑魔法 Webpack 的加持下,咱們能夠更現代化的方式打補丁。這其中相關的依賴有: @babel/preset-env@babel/plugin-transform-runtimecore-js@babel/polyfill 。先逐一介紹它們:git

  1. @babel/preset-env - 按需編譯和按需打補丁github

    @babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s). This both makes your life easier and JavaScript bundles smaller!chrome

    翻譯過來就是@babel/preset-env 會根據目標環境來進行編譯和打補丁。具體來說,是根據參數 targets 來肯定目標環境,默認狀況下它編譯爲 ES2015,能夠根據項目需求進行配置:json

    ...
      presets: [
        [
          '@babel/preset-env',
          {
            // 支持chrome 58+ 及 IE 11+
            targets: {
              chrome: '58',
              ie: '11',
            }
          },
        ],
      ]
      ...
    複製代碼

    具體 targets 參數可參見 browserlist.

  2. core-js JavaScript 標準庫

    core-js 是實現 JavaScript 標準運行庫之一,它提供了從 ES3 ~ ES7+ 以及還處在提案階段的 JavaScript 的實現。

  3. @babel/plugin-transform-runtime - 重利用 Babel helper 方法的 babel 插件

    A plugin that enables the re-use of Babel's injected helper code to save on codesize.

    @babel/plugin-transform-runtime 是對 Babel 編譯過程當中產生的 helper 方法進行從新利用(聚合),以達到減小打包體積的目的。此外還有個做用是爲了不全局補丁污染,對打包過的 bunler 提供"沙箱"式的補丁。

  4. @babel/polyfill - core-js 和 regenerator-runtime 補丁的實現庫

    Babel includes a polyfill that includes a custom regenerator runtime and core-js.

    This will emulate a full ES2015+ environment (no < Stage 4 proposals) and is intended to be used in an application rather than a library/tool. (this polyfill is automatically loaded when using babel-node)

    @babel/polyfill 經過定製 polyfillregenerator,提供了一個 ES2015+ 環境 polyfill的庫。由於它是由其餘兩個庫實現的,直接引入其餘兩個庫便可,因此已被**廢棄**。

    // 實現 @babel/polyfill 等同效果
    import 'core-js/stable'
    import 'regenerator-runtime/runtime'
    複製代碼

使用方法

根據構建的目標不一樣,筆者認爲應該分爲兩種打補丁方式:應用的補丁庫的補丁

應用的補丁 - 使用@babel/preset-env + useBuiltIns

既然core-js 包括了全部的 JavaScript 標準庫,那有什麼方法能根據應用的兼容目標來自動獲取補丁呢?這裏就用到 @babel/preset-envuseBuiltIns 參數了。useBuiltIns 告訴了@babel/preset-env 如何根據應用的兼容目標(targets)來處理 polyfill

首先,在應用入口引入core-js:

import 'core-js'
複製代碼

而後,配置 useBuiltIns 參數爲 entry,並指定 core-js 版本:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry",
        "corejs": 3
      }
    ],
    "@babel/preset-react"
  ]
}
複製代碼

代碼示例見:babel-preset-env-1: 打包後的結果爲:

polyfill-output-1

其默認轉換全部 ECMAScript 2015+ 代碼。除非業務須要,不然應該指定應用所要支持的瀏覽器環境,以免沒必要要的補丁,減小打包輸出體積。支持環境能夠經過 targets 參數來指定,其語法可參考 browserslist 來確認。使用方法也很簡單,假設應用只需支持到 Chrome 58:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry",
        "corejs": 3,
        "targets": {
          "chrome": 58
        }
      }
    ],
    "@babel/preset-react"
  ]
}
複製代碼

能夠看到,打包後的體積明顯小了很多:

polyfill-output-2

若是應用引入了多個第三庫,而且它們存在公用的 helper 方法,就應該引入@babel/plugin-transform-runtime來減小打包體積。使用方法再也不贅述,可參照文檔

此外,@babel/preset-env 的 useBuiltIns 值還有個一實驗性的值 usage ,其做用是根據不一樣文件所需的補丁來引入對應的補丁,能必定程度上減小沒必要要的補丁。參照usebuiltins-usage-experimental

以上,是根據覆蓋率來給應用打補丁的方法,簡單總結下:

  1. 在應用入口引入 core-js

    import 'core-js'
    複製代碼
  2. 使用 @babel/preset-env,指定 useBuiltInscore-js 以及根據應用肯定的targets

    {
      "presets": [
        [
          "@babel/preset-env",
          {
            "useBuiltIns": "entry",
            "corejs": 3,
            "targets": {
              "chrome": 58
            }
          }
        ],
        "@babel/preset-react"
      ]
    }
    複製代碼
  3. 如項目引入多個三方庫包含公用的 helper 方法,引入 @babel/plugin-transform-runtime 重用方法,減小 打包體積。

庫的補丁 - 只提供庫所依賴的專有補丁

因爲庫是被應用所引入的,因此自己不該該提供諸如 PromiseMap 等經常使用的補丁,這些應該由應用自己去提供,庫自己應該只引入本身所專有的庫。

舉例來講,好比我想作一個支持多語言的日期選擇器組件,就該引入多語言實現的 polyfill,好比Intl

根據瀏覽器特性,動態打補丁

以上兩種方法都有一個弊端——補丁的冗餘。以 Object#assign 來講,在支持這個特性的瀏覽器來講,就不必引入這個補丁,勢必形成了必定的補丁冗餘,這就有了根據瀏覽器特性動態打補丁的方案。

Polyfill.io 就是實現這個方案的服務,它會根據瀏覽器的 UA 不一樣,返回不同的補丁。如想要 Promise 補丁,在頁面引入:

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

如在高版本的瀏覽器(Chrome 75)上,打開連接會返回空頁面:

/* Polyfill service v3.34.0 * For detailed credits and licence information see https://github.com/financial-times/polyfill-service. * * Features requested: Promise * */

/* No polyfills found for current settings */
複製代碼

若是將瀏覽器的 UA 改成 IE 11,將會返回相應的 polyfill:

還能夠附加查詢參數來定製 Polyfill,具體可參照官方文檔

此外,若是對 Polyfill.io 的穩定性和安全性有要求,能夠根據開源的 polyfill service 搭建本身的服務,而後部署到 CDN 上。

將來

基於目前方案,筆者認爲 按需特性補丁 + 在線補丁纔是將來的終極方案。按需特性可參照 useBuiltInsusage 參數實現——只在當前文件引入必要的補丁,並且只引入一次。在線補丁使用 polyfill service,二者結合,即保證了補丁了最小化引入,也會根據設備的不一樣打不一樣的補丁。

以上。

相關文章
相關標籤/搜索