工程化篇-JS相容方案

specialCoder發表於2022-02-07

相容方案的背景

面臨的問題

  • 雖然JS很多語法已經到了正式釋出的階段,但是由於瀏覽器的支援程度不同導致我們無法放心的使用。
  • 有些已經到了 stage3 的語法我們也想嘗試使用,如何正常執行?

理想的場景

我們可以放心的編寫js程式碼,不用考慮其他問題。

如何實現

在編譯的時候透過一種方法讓瀏覽器不支援的語法轉換成瀏覽器支援的語法,這樣程式碼就可以正常執行。

透過語法轉換實現程式碼相容

前端專案通常採用 babel 來轉換 js 程式碼,寫這篇文章的時候最新的版本是 babel v7, 下面的方案也會只介紹在這個版本下的。

明確目標

在實現方案之前我們需要想清楚幾個問題:

  1. 需要相容哪些語法: 根據 TC39,你用到了 stage-x(x: 1-4) 的語法
  2. 不同瀏覽器對語法支援程度不同, 你要處理哪些瀏覽器的相容
  3. 程式碼相容的處理方式是按需引入還是全部引入(各有優劣,接下來會介紹)

思考? 3 min ...

方案實現

polyfill 方案

方案
@babel/preset-env + corejs@3

特點

  • 透過 useBuiltIns配置,可以採用按需載入和全部載入的方式實現相容
  • 會汙染全域性:在全域性和例項上新增api
  • 支援目標瀏覽器設定: 透過 targets 或 browserslist, 可是實現特定瀏覽器下的相容,減少程式碼體積

介紹
core-js:Javascript 標準語法實現庫
@babel/preset-env

  • 一系列語法轉換 plugin 的集合,它支援的 plugin 可以參考這裡。透過這些plugin 可以實現語法相容。
  • 可以透過 browserslist 實現針對特定瀏覽器下的 api 相容: 簡單來說就是 browserslist 會根據 caniuse 上的資料和目標瀏覽器對比得出需要相容哪些api。詳細的可以深入理解 browserslist

注意事項

  • @babel/preset-env 相容到 stage4也就是正式版。如果要使用 stage3這種語法,需要在 bablerc plugins裡面再引入對應 plugin
  • 你需要配置 targets 或 browserslist 才能實現特定瀏覽器語法相容

方式一: 全部引入
此時 babel 會根據當前 targets 描述,把需要的所有的 polyfills 全部引入到你的入口檔案(注意是全部,不管你是否有用到高階的 API)。

第一步:.babelrc 檔案內容:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry",
        "targets":"> 1%, not dead", // 根據情況自己設定
        "corejs": {
          "version": 3,// 使用 corejs@3
          "proposals": true
        }
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime", // 如果使用polyfill方案,這裡配置可選
      {
        "corejs": false // 關閉 runtime
      }
    ]
  ]
}

第二步:入口檔案 index.js:

import 'core-js/stable';
import 'regenerator-runtime/runtime';
// 入口檔案程式碼
  • core-js/stable 會引入所有的polyfill,好處是不用擔心以來的庫有語法不支援的情況,缺點是程式碼體積大。想減少體積也可以按需引入,例如 import 'core-js/stable/array/find';
  • regenerator-runtime/runtime : generatorasync functionruntime

方式二: 按需引入
我們在專案的入口檔案處不需要 import 對應的 polyfills 相關庫。babel 會根據使用者程式碼的使用情況,並根據 targets 自行注入相關 polyfills。

.babelrc 檔案內容:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "targets":"> 1%, not dead", // 根據情況自己設定
        "corejs": {
          "version": 3, // 使用 corejs@3
          "proposals": true
        }
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",// 如果使用polyfill方案,這裡配置可選
      {
        "corejs": false // 關閉 runtime
      }
    ]
  ]
}

runtime 方案

方案
@babel/preset-env + @babel/runtime-corejs3 + @babel/plugin-transform-runtime

特點

  • 不會汙染全域性的:@babel/plugin-transform-runtime 外掛透過模擬 api 實現相容,不會汙染全域性的,所以更適合 Library 作者使用
  • 僅支援按需引入
  • 使用到的api都會被替換, 不支援 targets 和 browserslist

介紹

  • 仍然需要 @babel/preset-env 對語法進行轉換
  • @babel/runtime-corejs3: 主用來模擬實現 api 功能的庫
  • @babel/plugin-transform-runtime:實現按需引用 @babel/runtime-corejs3 裡面的 module

注意
切換到 corejs@3 之後, runtime 方案也可以模擬例項方法了,像 array.includes 這種已經可以實現,不需要引入額外的 polyfill

具體實現

.babelrc 檔案內容:

{
  "presets": [
    [
      "@babel/preset-env",
     {
        "useBuiltIns": false, // false 這種方式下,不會引入 polyfills
        "targets":"> 1%, not dead", // 根據情況自己設定
     }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime", // runtime 方案下,必須設定
      {
        "corejs": {
          "version": 3, // 使用 runtime-corejs@3
          "proposals": true
        }
      }
    ]
  ]
}

總結

目前,babel處理相容性問題有兩種方案:

  • @babel/preset-env + corejs@3實現語法轉換、在全域性和例項上新增api,支援全量載入和按需載入,我們簡稱polyfill方案;
  • @babel/preset-env + @babel/runtime-corejs3 + @babel/plugin-transform-runtime實現語法轉換、模擬替換api,只支援按需載入,我們簡稱runtime方案。

兩種方案都依賴核心包corejs@3,只不過依賴的模組不同,導致實現方式不同。兩種方案各有優缺點:

  • polyfill方案很明顯的缺點就是會造成全域性汙染,而且會注入冗餘的工具程式碼;優點是可以根據瀏覽器對新特性的支援度來選擇性的進行相容性處理;
  • runtime方案雖然解決了polyfill方案的那些缺點,但是不能根據瀏覽器對新特性的支援度來選擇性的進行相容性處理,也就是說只要在程式碼中識別到的api,並且該api也存在core-js-pure包中,就會自動替換,這樣一來就會造成一些不必要的轉換,從而增加程式碼體積。

所以,polyfill方案比較適合單獨執行的業務專案,如果你是想開發一些供別人使用的第三方工具庫,則建議你使用runtime方案來處理相容性方案,以免影響使用者的執行環境。

稍等,思考一種更好的方案: runtime 方案也支援 api targets 不就完美了嗎?... 還真有一個正在試驗階段的:babel-polyfills

QA

babelrc 檔案 tagets、ignoreBrowserConfig 和 browserslist 的優先順序和預設配置

  • 配置:targets > browserslist
  • 設定了 ignoreBrowserConfig:true 將預設不再讀取 browserslist 配置
  • 兩個都不存在時, 參考 No targets【大概意思是全部的ES2015之後的語法都會轉換成 ES5】

@babel/polyfill 與 @babel/preset-env 的關係

  • @babel/polyfill 是 babel@6 and core-js@2時代的產物, 類似 @babel/preset-env 的 "useBuiltIns":"entry",但是不支援 browserslist,目前已經被棄用。
  • @babel/preset-env 支援 browserslist,還有很多新特性,相容必備

babel-plugin-transform-runtime 的作用

主要有兩個作用:

  • 複用透過 Babel 注入的 helper 的程式碼,減少程式碼冗餘
  • 透過模擬 api 的方式實現程式碼相容【runtime方案】

兩種方案的具體配置

如上文,也可以看這裡:https://developer.aliyun.com/...

runtime 方案可以使用 browserslist 配置嗎?

  • runtime方案的完整方法是:@babel/preset-env + @babel/runtime-corejs3 + @babel/plugin-transform-runtime
  • @babel/plugin-transform-runtime 是處理 api 部分,語法轉換仍然需要 @babel/preset-env, 所以在runtime方案下, target 對 語法轉換仍是起作用的,但是對API部分是無效的

參考文章

相關文章