前端科普系列(4):Babel —— 把 ES6 送上天的通天塔

本文首發(fā)于 vivo互聯(lián)網(wǎng)技術(shù) 微信公眾號(hào)
鏈接: https://mp.weixin.qq.com/s/plJewhUd0xDXh3Ce4CGpHg
作者:Morrain

image

一翘县、前言

在上一節(jié) 《CommonJS:不是前端卻革命了前端》中叮称,我們聊到了 ES6 Module萧诫,它是 ES6 中對(duì)模塊的規(guī)范,ES6 是 ECMAScript 6.0 的簡(jiǎn)稱葡幸,泛指 JavaScript 語(yǔ)言的下一代標(biāo)準(zhǔn)软驰,它的第一個(gè)版本 ES2015 已經(jīng)在 2015 年 6 月正式發(fā)布载城,本文中提到的 ES6 包括 ES2015瞧捌、ES2016、ES2017等等义黎。在第一節(jié)的《Web:一路前行一路忘川》中也提到過禾进,ES2015 從制定到發(fā)布?xì)v經(jīng)了十幾年,引入了很多的新特性以及新的機(jī)制轩缤,瀏覽器對(duì) ES6 的支持進(jìn)度遠(yuǎn)遠(yuǎn)趕不上前端開發(fā)小哥哥們使用 ES6 的熱情命迈,于是矛盾就日益顯著……

二贩绕、Babel 是什么

先來(lái)看下它在官網(wǎng)上的定義:

Babel is a JavaScript compiler

沒錯(cuò)就一句話,Babel 是 JavaScript 的編譯器壶愤。至于什么是編譯器淑倾,可以參考the-super-tiny-compiler這個(gè)項(xiàng)目,可以找到很好的答案征椒。

本文是以 Babel 7.9.0 版本進(jìn)行演示和講解的娇哆,另外建議學(xué)習(xí)者閱讀英文官網(wǎng),中文官網(wǎng)會(huì)比原版網(wǎng)站慢一個(gè)版本勃救,并且很多依然是英文的碍讨。

Babel 就是一套解決方案,用來(lái)把 ES6 的代碼轉(zhuǎn)化為瀏覽器或者其它環(huán)境支持的代碼蒙秒。注意我的用詞哈勃黍,我說(shuō)的不是轉(zhuǎn)化為 ES5 ,因?yàn)椴煌愋鸵约安煌姹镜臑g覽器對(duì) ES6 新特性的支持程度都不一樣晕讲,對(duì)于瀏覽器已經(jīng)支持的部分覆获,Babel 可以不轉(zhuǎn)化,所以 Babel 會(huì)依賴瀏覽器的版本瓢省,后面會(huì)講到弄息。這里可以先參考browerslist項(xiàng)目。

Babel 的歷史

在學(xué)習(xí)任何一門知識(shí)前勤婚,我都習(xí)慣先了解它的歷史摹量,這樣才能深刻理解它存在意義。

Babel 的作者是 FaceBook 的工程師 Sebastian McKenzie馒胆。他在 2014 年發(fā)布了一款 JavaScript 的編譯器 6to5缨称。從名字就能看出來(lái),它主要的作用就是將 ES6 轉(zhuǎn)化為 ES5祝迂。

這里的 ES6 指 ES2015具钥,因?yàn)楫?dāng)時(shí)還沒有正式發(fā)布, ES2015 的名字還未被正式確定液兽。

于是很多人評(píng)價(jià),6to5 只是 ES6 得到支持前的一個(gè)過渡方案掌动,它的作者非常不同意這個(gè)觀點(diǎn)四啰,認(rèn)為 6to5 不光會(huì)按照標(biāo)準(zhǔn)逐步完善,依然具備非常大的潛力反過來(lái)影響并推進(jìn)標(biāo)準(zhǔn)的制定粗恢。正因?yàn)槿绱?6to5 的團(tuán)隊(duì)覺得 '6to5' 這個(gè)名字并沒有準(zhǔn)確的傳達(dá)這個(gè)項(xiàng)目的目標(biāo)柑晒。加上 ES6 正式發(fā)布后,被命名為 ES2015眷射,對(duì)于 6to5 來(lái)說(shuō)更偏離了它的初衷匙赞。于是 2015 年 2 月 15 號(hào)佛掖,6to5 正式更名為 Babel。

image
image

(圖片來(lái)源于網(wǎng)絡(luò))

Babel 是巴比倫文化里的通天塔涌庭,用來(lái)給 6to5 這個(gè)項(xiàng)目命名真得太貼切了芥被!羨慕這些牛逼的人,不光代碼寫得好坐榆,還這么有文化拴魄,不像我們,起個(gè)變量名都得憋上半天席镀,吃了沒有文化的虧匹中。這也是為什么我把這篇文章起名為 《Babel:把 ES6 送上天的通天塔》的原因。

三豪诲、Babel 怎么用

了解了 Babel 是什么后顶捷,很明顯我們就要開始考慮怎么使用 Babel 來(lái)轉(zhuǎn)化 ES6 的代碼了,除了 Babel 本身提供的 cli 等工具外屎篱,它還支持和其它打包工具配合使用服赎,譬如 webpack、rollup 等等芳室,可以參考官網(wǎng)對(duì)不同平臺(tái)提供的配置說(shuō)明专肪。

本文為了感受 Babel 最原始的用法,不結(jié)合其它任何工具堪侯,直接使用 Babel 的 cli 來(lái)演示嚎尤。

1、構(gòu)建 Babel 演示的工程

使用如下命令構(gòu)建一個(gè) npm 包伍宦,并新建 src 目錄 和 一個(gè) index.js 文件芽死。

npm init -y
image
image

package.json 內(nèi)容如下:

{
  "name": "demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

2、安裝依賴包

npm install --save-dev @babel/core @babel/cli @babel/preset-env

后面會(huì)介紹這些包的作用次洼,先看用法

增加 babel 命令來(lái)編譯 src 目錄下的文件到 dist 目錄:

{
  "name": "demo",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "babel": "babel src --out-dir dist",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.8.4",
    "@babel/core": "^7.9.0",
    "@babel/preset-env": "^7.9.0"
  }
}

3关贵、增加 Babel 配置文件

在工程的根目錄添加 babel.config.js 文件,增加 Babel 編譯的配置卖毁,沒有配置是不進(jìn)行編譯的揖曾。

const presets = [
  [
    '@babel/env',
    {
      debug: true
    }
  ]
]
const plugins = []
 
module.exports = { presets, plugins }

上例中 debug 配置是為了打印出 Babel 工作時(shí)的日志,可以方便的看來(lái)亥啦,Babel 轉(zhuǎn)化了哪些語(yǔ)法炭剪。

  1. presets 主要是配置用來(lái)編譯的預(yù)置,plugins 主要是配置完成編譯的插件翔脱,具體的含義后面會(huì)講
  2. 推薦用 Javascript 文件來(lái)寫配置文件奴拦,而不是 JSON 文件,這樣可以根據(jù)環(huán)境來(lái)動(dòng)態(tài)配置需要使用的 presets 和 plugins
const presets = [
  [
    '@babel/env',
    {
      debug: true
    }
  ]
]
const plugins = []
 
if (process.env["ENV"] === "prod") {
  plugins.push(...)
}
 
module.exports = { presets, plugins }

4届吁、編譯的結(jié)果

配置好后错妖,我們運(yùn)行 npm run babel 命令绿鸣,可以看到 dist 文件夾下生成了 index.js 文件,內(nèi)容如下所示:

// src/index.js
const add = (a, b) => a + b
 
// dist/index.js
"use strict";
 
var add = function add(a, b) {
  return a + b;
};

可以看到暂氯,ES6 的 const 被轉(zhuǎn)化為 var 潮模,箭頭函數(shù)被轉(zhuǎn)化為普通函數(shù)。同時(shí)打印出來(lái)如下日志:

> babel src --out-dir dist
 
@babel/preset-env: `DEBUG` option
 
Using targets:
{}
 
Using modules transform: auto
 
Using plugins:
  proposal-nullish-coalescing-operator {}
  proposal-optional-chaining {}
  proposal-json-strings {}
  proposal-optional-catch-binding {}
  transform-parameters {}
  proposal-async-generator-functions {}
  proposal-object-rest-spread {}
  transform-dotall-regex {}
  proposal-unicode-property-regex {}
  transform-named-capturing-groups-regex {}
  transform-async-to-generator {}
  transform-exponentiation-operator {}
  transform-template-literals {}
  transform-literals {}
  transform-function-name {}
  transform-arrow-functions {}
  transform-block-scoped-functions {}
  transform-classes {}
  transform-object-super {}
  transform-shorthand-properties {}
  transform-duplicate-keys {}
  transform-computed-properties {}
  transform-for-of {}
  transform-sticky-regex {}
  transform-unicode-regex {}
  transform-spread {}
  transform-destructuring {}
  transform-block-scoping {}
  transform-typeof-symbol {}
  transform-new-target {}
  transform-regenerator {}
  transform-member-expression-literals {}
  transform-property-literals {}
  transform-reserved-words {}
  transform-modules-commonjs {}
  proposal-dynamic-import {}
 
Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set.
Successfully compiled 1 file with Babel.

四株旷、Babel 工作原理

在了解了如何使用后沼侣,我們一起來(lái)探尋一下編譯背后的事情颤诀,同時(shí)會(huì)熟悉 Babel 的組成和進(jìn)階用法象踊。

1侍郭、Babel 工作流程

前面提到 Babel 其實(shí)就是一個(gè)純粹的 JavaScript 的編譯器,任何一個(gè)編譯器工作流程大致都可以分為如下三步:

  • Parser 解析源文件

  • Transfrom 轉(zhuǎn)換

  • Generator 生成新文件

Babel 也不例外齿尽,如下圖所示:

image
image

(圖片來(lái)源于網(wǎng)絡(luò))

因?yàn)?Babel 使用是acorn這個(gè)引擎來(lái)做解析沽损,這個(gè)庫(kù)會(huì)先將源碼轉(zhuǎn)化為抽象語(yǔ)法樹 (AST),再對(duì) AST 作轉(zhuǎn)換循头,最后將轉(zhuǎn)化后的 AST 輸出绵估,便得到了被 Babel 編譯后的文件。

那 Babel 是如何知道該怎么轉(zhuǎn)化的呢卡骂?答案是通過插件国裳,Babel 為每一個(gè)新的語(yǔ)法提供了一個(gè)插件,在 Babel 的配置中配置了哪些插件全跨,就會(huì)把插件對(duì)應(yīng)的語(yǔ)法給轉(zhuǎn)化掉缝左。插件被命名為 @babel/plugin-xxx 的格式。

2浓若、Babel 組成

image

(1)@babel/preset-env

上面提到過 @babel/preset-* 其實(shí)是轉(zhuǎn)換插件的集合渺杉,最常用的就是 @babel/preset-env,它包含了 大部分 ES6 的語(yǔ)法挪钓,具體包括哪些插件是越,可以在 Babel 的日志中看到。如果源碼中使用了不在 @babel/preset-env 中的語(yǔ)法碌上,會(huì)報(bào)錯(cuò)倚评,手動(dòng)在 plugins 中增加即可。

例如 ES6 明確規(guī)定馏予,Class 內(nèi)部只有靜態(tài)方法蔓纠,沒有靜態(tài)屬性。但現(xiàn)在有一個(gè)提案提供了類的靜態(tài)屬性吗蚌,寫法是在實(shí)例屬性的前面,加上 static 關(guān)鍵字纯出。

// src/index.js
const add = (a, b) => a + b
 
class Person {
  static a = 'a';
  static b;
  name = 'morrain';
  age = 18
}

編譯時(shí)就會(huì)報(bào)如下錯(cuò)誤:

image
image

根據(jù)報(bào)錯(cuò)的提示蚯妇,添加 @babel/plugin-proposal-class-properties 即可敷燎。

npm install --save-dev @babel/plugin-proposal-class-properties
// babel.config.js
const presets = [
  [
    '@babel/env',
    {
      debug: true
    }
  ]
]
const plugins = ['@babel/plugin-proposal-class-properties']
 
module.exports = { presets, plugins }

@babel/preset-env 中還有一個(gè)非常重要的參數(shù) targets,最早的時(shí)候我們就提過箩言,Babel 轉(zhuǎn)譯是按需的硬贯,對(duì)于環(huán)境支持的語(yǔ)法可以不做轉(zhuǎn)換的。就是通過配置 targets 屬性陨收,讓 Babel 知道目標(biāo)環(huán)境饭豹,從而只轉(zhuǎn)譯環(huán)境不支持的語(yǔ)法。如果沒有配置會(huì)默認(rèn)轉(zhuǎn)譯所有 ES6 的語(yǔ)法务漩。

// src/index.js
const add = (a, b) => a + b
 
// dist/index.js  沒有配置targets
"use strict";
 
var add = function add(a, b) {
  return a + b;
};

按如下配置** targets**

// babel.config.js
const presets = [
  [
    '@babel/env',
    {
      debug: true,
      targets: {
        chrome: '58'
      }
    }
  ]
]
const plugins = ['@babel/plugin-proposal-class-properties']
 
module.exports = { presets, plugins }

編譯后的結(jié)果如下:

// src/index.js
const add = (a, b) => a + b
 
// dist/index.js  配置targets  chrome 58
"use strict";
 
const add = (a, b) => a + b;

可以看到 const 和箭頭函數(shù)都沒有被轉(zhuǎn)譯拄衰,因?yàn)檫@個(gè)版本的 chrome 已經(jīng)支持了這些特性《牵可以根據(jù)需求靈活的配置目標(biāo)環(huán)境翘悉。

為后方便后續(xù)的講解,把 targets 的配置去掉居触,讓 Babel 默認(rèn)轉(zhuǎn)譯所有語(yǔ)法妖混。

(2)@babel/polyfill

polyfill 直譯是墊片的意思,又是 Babel 里一個(gè)非常重要的概念轮洋。先看下面幾行代碼:

// src/index.js
const add = (a, b) => a + b
 
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise()

按之前的方法制市,執(zhí)行 npm run babel 后,我們驚奇的發(fā)現(xiàn)弊予,Array.prototype.includes 和 Promise 竟然沒有被轉(zhuǎn)譯祥楣!

// dist/index.js
"use strict";
 
var add = function add(a, b) {
  return a + b;
};
 
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise();

原來(lái) Babel 把 ES6 的標(biāo)準(zhǔn)分為 syntax 和 built-in 兩種類型。syntax 就是語(yǔ)法块促,像 const荣堰、=> 這些默認(rèn)被 Babel 轉(zhuǎn)譯的就是 syntax 的類型。而對(duì)于那些可以通過改寫覆蓋的語(yǔ)法就認(rèn)為是 built-in竭翠,像 includes 和 Promise 這些都屬于 built-in振坚。而 Babel 默認(rèn)只轉(zhuǎn)譯 syntax 類型的,對(duì)于 built-in 類型的就需要通過 @babel/polyfill 來(lái)完成轉(zhuǎn)譯斋扰。@babel/polyfill 實(shí)現(xiàn)的原理也非常簡(jiǎn)單渡八,就是覆蓋那些 ES6 新增的 built-in。示意如下:

Object.defineProperty(Array.prototype, 'includes',function(){
  ...
})

由于 Babel 在 7.4.0 版本中宣布廢棄 @babel/polyfill 传货,而是通過 core-js 替代屎鳍,所以本文直接使用 core-js 來(lái)講解 polyfill 的用法。

  • 安裝 core-js
npm install --save core-js
  • 注意 core-js 要使用 --save 方式安裝问裕,因?yàn)樗切枰蛔⑷氲皆创a中的逮壁,在執(zhí)行代碼前提供執(zhí)行環(huán)境,用來(lái)實(shí)現(xiàn) built-in 的注入

  • 配置 useBuiltIns

    在 @babel/preset-env 中通過 useBuiltIns 參數(shù)來(lái)控制 built-in 的注入粮宛。它可以設(shè)置為 'entry'窥淆、'usage' 和 false 卖宠。默認(rèn)值為 false,不注入墊片忧饭。

    設(shè)置為 'entry' 時(shí)扛伍,只需要在整個(gè)項(xiàng)目的入口處,導(dǎo)入 core-js 即可词裤。

// src/index.js
import 'core-js'
 
const add = (a, b) => a + b
 
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise()
 
// dist/index.js
"use strict";
 
require("core-js/modules/es7.array.includes");
require("core-js/modules/es6.promise");
//
// ……  這里還有很多
//
require("regenerator-runtime/runtime");
var add = function add(a, b) {
  return a + b;
};
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise();
  • 編譯后刺洒,Babel 會(huì)把目標(biāo)環(huán)境不支持的所有 built-in 都注入進(jìn)來(lái),不管是不是用到吼砂,這有一個(gè)問題逆航,對(duì)于只用到比較少的項(xiàng)目來(lái)說(shuō)完全沒有必要,白白增加代碼帅刊,浪費(fèi)包體大小纸泡。

設(shè)置為 'usage' 時(shí),就不用在項(xiàng)目的入口處赖瞒,導(dǎo)入 core-js了女揭,Babel 會(huì)在編譯源碼的過程中根據(jù) built-in 的使用情況來(lái)選擇注入相應(yīng)的實(shí)現(xiàn)。

// src/index.js
const add = (a, b) => a + b
 
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise()
 
// dist/index.js
"use strict";
 
require("core-js/modules/es6.promise");
 
require("core-js/modules/es6.object.to-string");
 
require("core-js/modules/es7.array.includes");
 
var add = function add(a, b) {
  return a + b;
};
 
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise();
  • 配置 corejs 的版本

當(dāng) useBuiltIns 設(shè)置為 'usage' 或者 'entry' 時(shí)栏饮,還需要設(shè)置 @babel/preset-env 的 corejs 參數(shù)吧兔,用來(lái)指定注入 built-in 的實(shí)現(xiàn)時(shí),使用 corejs 的版本袍嬉。否則 Babel 日志輸出會(huì)有一個(gè)警告境蔼。

最終的 Babel 配置如下:

// babel.config.js
const presets = [
  [
    '@babel/env',
    {
      debug: true,
      useBuiltIns: 'usage',
      corejs: 3,
      targets: {}
    }
  ]
]
const plugins = ['@babel/plugin-proposal-class-properties']
 
module.exports = { presets, plugins }

(3)@babel/plugin-transform-runtime

在介紹 @babel/plugin-transform-runtime 的用途之前,先前一個(gè)例子:

// src/index.js
const add = (a, b) => a + b
 
const arr = [1, 2]
const hasThreee = arr.includes(3)
new Promise(resolve=>resolve(10))
 
class Person {
  static a = 1;
  static b;
  name = 'morrain';
  age = 18
}
 
// dist/index.js
"use strict";
 
require("core-js/modules/es.array.includes");
 
require("core-js/modules/es.object.define-property");
 
require("core-js/modules/es.object.to-string");
 
require("core-js/modules/es.promise");
 
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
 
var add = function add(a, b) {
  return a + b;
};
 
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise(function (resolve) {
  return resolve(10);
});
 
var Person = function Person() {
  _classCallCheck(this, Person);
 
  _defineProperty(this, "name", 'morrain');
 
  _defineProperty(this, "age", 18);
};
 
_defineProperty(Person, "a", 1);
 
_defineProperty(Person, "b", void 0);

在編譯的過程中伺通,對(duì)于 built-in 類型的語(yǔ)法通過 require("core-js/modules/xxxx") polyfill 的方式來(lái)兼容箍土,對(duì)于 syntax 類型的語(yǔ)法在轉(zhuǎn)譯的過程會(huì)在當(dāng)前模塊中注入類似 _classCallCheck 和 _defineProperty 的 helper 函數(shù)來(lái)實(shí)現(xiàn)兼容。對(duì)于一個(gè)模塊而言罐监,可能還好吴藻,但對(duì)于項(xiàng)目中肯定是很多模塊,每個(gè)模塊模塊都注入這些 helper 函數(shù)弓柱,勢(shì)必會(huì)造成代碼量變得很大沟堡。

而 @babel/plugin-transform-runtime 就是為了復(fù)用這些 helper 函數(shù),縮小代碼體積而生的矢空。當(dāng)然除此之外航罗,它還能為編譯后的代碼提供一個(gè)沙箱環(huán)境,避免全局污染屁药。

使用 @babel/plugin-transform-runtime

  • ①安裝
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime

其中 @babel/plugin-transform-runtime 是編譯時(shí)使用的粥血,安裝為開發(fā)依賴,而 @babel/runtime 其實(shí)就是 helper 函數(shù)的集合,需要被引入到編譯后代碼中立莉,所以安裝為生產(chǎn)依賴

  • ②修改 Babel plugins 配置绢彤,增加@babel/plugin-transform-runtime
// babel.config.js
const presets = [
  [
    '@babel/env',
    {
      debug: true,
      useBuiltIns: 'usage',
      corejs: 3,
      targets: {}
    }
  ]
]
const plugins = [
  '@babel/plugin-proposal-class-properties',
  [
    '@babel/plugin-transform-runtime'
  ]
]
 
module.exports = { presets, plugins }
  • 之前的例子,再次編譯后蜓耻,可以看到,之前的 helper 函數(shù)械巡,都變成類似require("@babel/runtime/helpers/classCallCheck") 的實(shí)現(xiàn)了刹淌。
// dist/index.js
"use strict";
 
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
 
require("core-js/modules/es.array.includes");
 
require("core-js/modules/es.object.to-string");
 
require("core-js/modules/es.promise");
 
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
 
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
 
var add = function add(a, b) {
  return a + b;
};
 
var arr = [1, 2];
var hasThreee = arr.includes(3);
new Promise(function (resolve) {
  return resolve(10);
});
 
var Person = function Person() {
  (0, _classCallCheck2["default"])(this, Person);
  (0, _defineProperty2["default"])(this, "name", 'morrain');
  (0, _defineProperty2["default"])(this, "age", 18);
};
 
(0, _defineProperty2["default"])(Person, "a", 1);
(0, _defineProperty2["default"])(Person, "b", void 0);
  • 配置 @babel/plugin-transform-runtime

到目前為止,對(duì)于 built-in 類型的語(yǔ)法還是通過 require("core-js/modules/xxxx") polyfill 的方式來(lái)實(shí)現(xiàn)的讥耗,例如為了支持 Array.prototype.includes 方法有勾,需要 require

("core-js/modules/es.array.includes") 在 Array.prototype 中添加 includes 方法來(lái)實(shí)現(xiàn)的,但這會(huì)導(dǎo)致一個(gè)問題古程,它是直接修改原型的蔼卡,會(huì)造成全局污染。如果你開發(fā)的是獨(dú)立的應(yīng)用問題不大挣磨,但如果開發(fā)的是工具庫(kù)雇逞,被其它項(xiàng)目引用,而恰好該項(xiàng)目自身實(shí)現(xiàn)了 Array.prototype.includes 方法茁裙,這樣就出了大問題塘砸!而 @babel/plugin-transform-runtime 可以解決這個(gè)問題,只需要配置 @babel/plugin-transform-runtime 的參數(shù) corejs晤锥。該參數(shù)默認(rèn)為 false掉蔬,可以設(shè)置為 2 或者 3,分別對(duì)應(yīng) @babel/runtime-corejs2 和 @babel/runtime-corejs3矾瘾。

把 @babel/plugin-transform-runtime 的 corejs 的值設(shè)置為3女轿,把 @babel/runtime 替換為 @babel/runtime-corejs3。

去掉 @babel/preset-env 的 useBuiltIns 和 corejs 的配置壕翩,去掉 core-js蛉迹。因?yàn)槭褂?@babel/runtime-corejs3 來(lái)實(shí)現(xiàn)對(duì) built-in 類型語(yǔ)法的兼容,不用再使用 useBuiltIns了戈泼。

npm uninstall @babel/runtime
npm install --save @babel/runtime-corejs3
npm uninstall core-js
// babel.config.js
const presets = [
  [
    '@babel/env',
    {
      debug: true,
      targets: {}
    }
  ]
]
const plugins = [
  '@babel/plugin-proposal-class-properties',
  [
    '@babel/plugin-transform-runtime',
    {
      corejs: 3
    }
  ]
]
 
module.exports = { presets, plugins }
 
 
// dist/index.js
"use strict";
 
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
 
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
 
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty"));
 
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
 
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
 
var add = function add(a, b) {
  return a + b;
};
 
var arr = [1, 2];
var hasThreee = (0, _includes["default"])(arr).call(arr, 3);
new _promise["default"](function (resolve) {
  return resolve(10);
});
 
var Person = function Person() {
  (0, _classCallCheck2["default"])(this, Person);
  (0, _defineProperty2["default"])(this, "name", 'morrain');
  (0, _defineProperty2["default"])(this, "age", 18);
};
 
(0, _defineProperty2["default"])(Person, "a", 1);
(0, _defineProperty2["default"])(Person, "b", void 0);

可以看到 Promise 和 arr.includes 的實(shí)現(xiàn)已經(jīng)變成局部變量婿禽,并沒有修改全局上的實(shí)現(xiàn)。

3大猛、Babel polyfill 實(shí)現(xiàn)方式的區(qū)別

截至目前為止扭倾,對(duì)于 built-in 類型的語(yǔ)法的 polyfill,一共有三種方式:

  • 使用 @babel/preset-env 挽绩,useBuiltIns 設(shè)置為 'entry'

  • 使用 @babel/preset-env 膛壹,useBuiltIns 設(shè)置為 'usage'

  • 使用 @babel/plugin-transform-runtime

前兩種方式支持設(shè)置 targets ,可以根據(jù)目標(biāo)環(huán)境來(lái)適配。useBuiltIns 設(shè)置為 'entry' 會(huì)注入目標(biāo)環(huán)境不支持的所有 built-in 類型語(yǔ)法模聋,useBuiltIns 設(shè)置為 'usage' 會(huì)注入目標(biāo)環(huán)境不支持的所有被用到的 built-in 類型語(yǔ)法肩民。注入的 built-in 類型的語(yǔ)法會(huì)污染全局。

第三種方式目前不支持設(shè)置 targets链方,所以不會(huì)考慮目標(biāo)環(huán)境是否已經(jīng)支持持痰,它是通過局部變量的方式實(shí)現(xiàn)了所有被用到的 built-in 類型語(yǔ)法,不會(huì)污染全局祟蚀。

針對(duì)第三種方式不支持設(shè)置 targets 的問題工窍,Babel 正在考慮解決,目前意向的方案是通過** Polyfill provider** 來(lái)統(tǒng)一 polyfill 的實(shí)現(xiàn):

  • 廢棄 @babel/preset-env 中 useBuiltIns 和 corejs 兩個(gè)參數(shù)前酿,不再通過 @babel/preset-env 實(shí)現(xiàn) polyfill患雏。

  • 廢棄 @babel/plugin-transform-runtime 中的 corejs 參數(shù),也不再通過 @babel/plugin-transform-runtime 來(lái)實(shí)現(xiàn) polyfill罢维。

  • 增加 polyfills 參數(shù)淹仑,類似于現(xiàn)在 presets 和 plugins,用來(lái)取代現(xiàn)在的 polyfill 方案肺孵。

  • 把 @babel/preset-env 中 targets 參數(shù)匀借,往上提一層,和 presets悬槽、plugins怀吻、polyfills 同級(jí)別,并由它們共享初婆。

這個(gè)方案實(shí)現(xiàn)后蓬坡,Babel 的配置會(huì)是下面的樣子:

// babel.config.js
const targets = [
  '>1%'
]
const presets = [
  [
    '@babel/env',
    {
      debug: true
    }
  ]
]
const plugins = [
  '@babel/plugin-proposal-class-properties'
]
const polyfills = [
  [
    'corejs3',
    {
      method: 'usage-pure'
    }
  ]
]
 
module.exports = { targets, presets, plugins, polyfills }

配置中的 method 值有 'entry-global'、'usage-global'磅叛、'usage-pure' 三種屑咳。

  • 'entry-global' 等價(jià)于 @babel/preset-env 中的 useBuiltIns: 'entry'

  • 'usage-global' 等價(jià)于 @babel/preset-env 中的 useBuiltIns: 'usage'

  • 'usage-pure' 等價(jià)于 @babel/plugin-transform-runtime 中的 corejs

本文為了講解方便,都是用 Babel 原生的 @babel/cli 來(lái)編譯文件弊琴,實(shí)際使用中兆龙,更多的是結(jié)合 webpack、rollup 這樣第三方的工具來(lái)使用的敲董。

所以下一節(jié)紫皇,我們聊聊打包工具 webpack。

五腋寨、參考文獻(xiàn)

  1. 6to5 JavaScript Transpiler Changes Name to Babel

  2. Babel學(xué)習(xí)系列2-Babel設(shè)計(jì)聪铺,組成

  3. 初學(xué) Babel 工作原理

  4. RFC: Rethink polyfilling story

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市萄窜,隨后出現(xiàn)的幾起案子铃剔,更是在濱河造成了極大的恐慌撒桨,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件键兜,死亡現(xiàn)場(chǎng)離奇詭異凤类,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)普气,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門谜疤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人现诀,你說(shuō)我怎么就攤上這事茎截。” “怎么了赶盔?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)榆浓。 經(jīng)常有香客問我于未,道長(zhǎng),這世上最難降的妖魔是什么陡鹃? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任烘浦,我火速辦了婚禮,結(jié)果婚禮上萍鲸,老公的妹妹穿的比我還像新娘闷叉。我一直安慰自己,他們只是感情好脊阴,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布握侧。 她就那樣靜靜地躺著,像睡著了一般嘿期。 火紅的嫁衣襯著肌膚如雪品擎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天备徐,我揣著相機(jī)與錄音萄传,去河邊找鬼。 笑死蜜猾,一個(gè)胖子當(dāng)著我的面吹牛秀菱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蹭睡,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼衍菱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了棠笑?” 一聲冷哼從身側(cè)響起梦碗,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后洪规,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體印屁,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年斩例,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了雄人。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡念赶,死狀恐怖础钠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叉谜,我是刑警寧澤旗吁,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站停局,受9級(jí)特大地震影響很钓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜董栽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一码倦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锭碳,春花似錦袁稽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至闻葵,卻和暖如春民泵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背槽畔。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工栈妆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厢钧。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓鳞尔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親早直。 傳聞我的和親對(duì)象是個(gè)殘疾皇子寥假,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容