babel6和babel7中關於polyfill和preset-env和babel-plugin-transform-runtime等總結

記錄自己零散的收穫,隨筆。

一些基礎

  1. babel的作用是轉換JS新的特性代碼爲大部分瀏覽器能運行的代碼。

  2. babel轉碼又分爲兩部分,一個是語法轉換,一個是API轉換。

  3. 對於API的轉換又分爲兩部分,一個是全局API例如Promise,Set,Map還有靜態方法Object.assign,另一個是實例方法例如Array.prototype.includes。對於實例方法core-js@2是轉換不了的,只有core-js@3纔會轉換。

  4. babel代碼轉換依賴plugin,沒有plugin的情況下babel做的事情只是 code => code

  5. plugin 有很多,一個個導入又特別麻煩,這時候我們又知道了preset。

preset是很多plugin的集合,配置如下:

.babelrc文件

{
  "presets": ["env"]
}

如果preset-env是有配置項的:

{
  "presets": [
    [
      "env",
      {
        // 這裏就是配置項
      }
    ]
  ]
}

可以觀察出如果某個preset需要配置可以將字符串換成一個數組,第一項是preset的name,第二項就是options。plugin同preset。

注:preset是從右往左執行,plugin是從左往右執行,並且plugin先於preset執行。

babel6到babel7的升級是具有破壞性的,主要總結下polyfill的用法和在babel6和babel7中的不同。

babel-polyfill

上面說到babel主要做兩件事,一個是轉換語法,一個是兼容新的API。

babel-polyfill 的作用是兼容新的API。

babel7

babel7進行了較大的改動,廢棄了 stage-x的preset,還增加了命名空間區分官方插件和非官方插件,@babel/core,@babel/cli等。

建議使用@babel/preset-env 。

@babel/plugin-transform-runtime

該插件的引入主要有兩個作用

在babel7中,原先的插件babel-plugin-transform-runtime也做了修改 -> @babel/plugin-transform-runtime。並且在功能上也變強大了。

移除了polyfill的配置項添,加了corejs配置項。

@babel/plugin-transform-runtime的默認配合如下:

{
  plugins: [
    ["@babel/plugin-transform-runtime", {
      "absoluteRuntime": false, // 不是很清除幹啥的
      "corejs": false, // 下面詳解
      "helpers": true, // 助手函數是否提取,同babel-plugin-transform
      "regenerator": true, // 同babel-plugin-transform
      "useESModules": false
    }]
  ]
}

對於配置項 corejs: false | 2 | 3

false 依賴 @babel/runtime,是默認選項,認爲需要填充的API以被填充,所以不對API作polyfill(對於@babel/runtime的依賴是提取的helper的依賴)。
2 依賴@babel/runtime-corejs2
3 依賴@babel/runtime-corejs3

corejs2和corejs3還是有很大區別的。corejs2只轉換全局變量(Object)和靜態方法(Object.assign),並不轉換原型上的方法(Array.prototype.includes),corejs3會轉換原型上的方法。

注意transform-runtime這個插件添加的polyfill都是私有的,不會影響到全局環境,而且還是按需引入,非常nice。

小結:
@babel/plugin-transform-runtime主要有三個作用:
- 當使用 generators/async的時候自動引入 @babel/runtime/regenerator
- 爲新特性的API添加實現。
- 提取每個模塊內聯的helper們問引用。

@babel/preset-env

除了上面說的 @babel/plugin-transform-runtime插件,還想記錄下 @babel/preset-env關於polyfill的改變。

這個preset在babel6的時候就承擔了很多功能,本文只記錄和polyfill相關的配置,useBuiltInscorejs

useBuiltIns

在babel-preset-env的配置項中是一個boolean值,在@babel/preset-env的時候則擴展了幾個選項 "useage""entry"

默認值是false,表示不會自動引入polyfills,並且不會處理 import “@babel/polyfill” 和 import “corejs”。

注:babel7.4會放棄 @babel/polyfill,所以建議直接使用 corejs

entry

在入口文件有 import "core-js/stable"import "regenerator-runtime/runtime"
會被自動分割爲各個模塊的導入。

require("core-js/modules/es.symbol");

require("core-js/modules/es.symbol.description");

require("core-js/modules/es.symbol.async-iterator");

require("core-js/modules/es.symbol.has-instance");

...
...
...

require("core-js/modules/web.url-search-params");

require("regenerator-runtime/runtime");

只能在入口引入一次,多次會報錯。

usage

按需引入。同樣會造成全局污染。在我的理解中這個選項只是entry的一種增強,不需要在入口手動引入一次,並且可以按照使用特性多少按需引入。

corejs

值:2, 3 或者 { version: 2 | 3, proposals: boolean }, 默認是 2.

這是新加的一個配置項。該選項只會在 useBuiltIns選項爲 usage或者 entry並且 @babel/preset-env正確導入對應的corejs版本的情況下起作用。

默認情況下只會注入穩定功能的 ECMAScript 特性。有三個特性可以修改:

  • 當時用配置 useBuiltIns: "entry"可以直接導入提案 import "core-js/proposals/string-replace-all"
  • 當時用 useBuiltIns: usage 的時候又有兩個可選:
    • 將shippedProposals(@babel/preset-env的另一個配置項)選項設置爲true。這將啓用已在瀏覽器中提供一段時間的提議的polyfill和轉換。
    • 使用 corejs: {version:3,proposal:true}。這樣可以實現對core-js支持的每個提案的填充。

babel6

據我所知,在babel6中使用polyfill有四種方法:

  1. 直接引入(影響全局,一勞永逸)

    在入口文件中 import 'babel-polyfill' / require('babel-polyfill')。使用webpack的話也可以在entry中添加 entry: ['babel-polyfill', 'src/index.js']

    優點:
    一次引入,全局使用。
    會轉換實例方法和靜態方法,比較全面。

    缺點:
    影響全局作用域。
    打出來的包比較大,不管用沒用到都打進去了。

    使用場景:
    開發業務項目,比較全面,不會漏了從而出問題,比如Object.assign這樣的方法在ios8上面還是需要polyfill的。

  2. 在babel-runtime中單獨引入

    和直接在入口引入polyfill不同,該插件引入的polyfill是模塊私有的。
    對於需要的polyfill需要手動引入,import Promise from 'babel-runtime/core-js/promise'

    優點:
    該模塊私有,不會影響到全局作用域。
    打出來的包因爲按需引入包不會很大。

    缺點:
    因爲不影響全局作用域,所以不會轉實例和靜態方法這樣的API。
    手動引入所需,搞不好會漏掉。

    使用場景:
    開發庫,框架之類可以使用,因爲別人用你的東西然後不知情的情況下你改了別人的全局環境,然後出錯了就尷尬了。

  3. 使用babel-plugin-transform-runtime按需引入

    這個插件可不簡單,有好幾個功能:

    1. 和插件babel-runtime一樣引入的polyfill是私有的,不會影響全局作用域。並且是自動按需引入需要的polyfill,不需要想babel-runtime一樣一個一個手動引入。
    2. 可以提取語法轉換時候每個模塊都生成的各種helper,成一個引用。
    3. 自動轉換generators/async。

    優點:
    該模塊私有,不會影響到全局作用域。
    打出來的包因爲按需引入包不會很大。
    自動按需引入,不需要手動,防止遺漏。
    提取helper,大大減少冗餘代碼。

    缺點:
    因爲不影響全局作用域,所以不會轉實例和靜態方法這樣的API。

    使用場景:
    同babel-runtime。

    注:該插件依賴babel-runtime。

  4. 在babel-preset-env中設置配置項

{
   "presets": [
   	[
   		"env", 
   		{
   			"useBuiltIns": boolean
   		}
   	]
   ]
}

useBuiltIns 選項是爲了分割入口的 import 'babel-polyfill' / require(babel-polyfill)成按環境引入polyfill。該方式同第一中引入polyfill的方式,但是會按照配置的環境去按需引入,稍微好點。

小結:
1. babel6的核心有 babel-core babel-cli babel-node babel-register babel-polyfill,這些在babel7會有所修改。
2. polyfill 是依賴core-js的
3. babel7.4放棄了@babel/polyfill直接依賴core-js@2或者3。

babel-plugin-transform-runtime的配置項

{
  "helpers": false, // defaults to true
  "polyfill": false, // defaults to true
  "regenerator": true, // defaults to true
  "moduleName": "babel-runtime" // defaults to "babel-runtime"
}

對於polyfill的自動處理和helper的提取都是依賴babel-runtime完成的,所以該插件依賴babel-runtime。

polyfill例子

input:

var promise = new Promise;

output:

var _promise = require("babel-runtime/core-js/promise");  // 注意這裏,根本是從core-js裏面引入的
 
var _promise2 = _interopRequireDefault(_promise);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var promise = new _promise2.default();

helper的例子:

input:

class Person {}

usually turns into:

"use strict";
 // 這就是helper函數,每個模塊都會被實現一遍,十分浪費,冗餘。
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
var Person = function Person() {
  _classCallCheck(this, Person);
};

通過runtime轉一下:

"use strict";
 
var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); // 從runtime中引入,沒有再實現一遍
 
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
var Person = function Person() {
  (0, _classCallCheck3.default)(this, Person);
};

generator同樣是 require("babel-runtime/regenerator");引入的。

參考

babel官網@babel/plugin-transform-runtime
npm->babel-plugin-transform-runtime
babel到底該如何配置?
babel手冊
Babel是一個JavaScript編譯器
babel7簡單升級

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章