這裏借用一下官方的定義javascript
Babel是一個工具鏈,主要用於將 ECMAScript 2015+ 版本的代碼轉換爲向後兼容的 JavaScript 語法,以便可以運行在當前和舊版本的瀏覽器或其餘環境中。前端
簡單的理解就是,對較高版本的語法作一次向下兼容,使得較低版本的瀏覽器可以識別並運行這些代碼java
先來看一下這個詞babel(bāb(ə)l,音譯爲 掰bou,中文譯爲巴別塔。node
個人理解是:react
es6
之類的只是語言規範,而瀏覽器要實現這一規範可能就須要至關漫長的一個時間了。react
的jsx因此急需一個工具去解決這些問題,而後babel
就應運而生。webpack
再來看一下百度上對巴別塔的解釋git
巴別塔;是《聖經·舊約·創世記》第11章故事中人們建造的塔。根據篇章記載,當時人類聯合起來興建但願能通往天堂的高塔;爲了阻止人類的計劃,上帝讓人類說不一樣的語言,令人類相互之間不能溝通,計劃所以失敗,人類自此各散東西。此事件,爲世上出現不一樣語言和種族提供解釋。es6
是否是感受很是🐂🍺github
這裏想到了我司的一個項目,這個項目的主要做用就是使用函數計算在移動端截圖,我司名字是moka,因此這個項目起名爲mokapture,我我的感受仍是挺有意思的web
babel
會經過具體的某個插件對相應的代碼進行轉碼,好比箭頭函數對應的插件就是@babel/plugin-transform-arrow-functions
,鏈式調用對應的插件就是@babel/plugin-proposal-optional-chaining
,咱們不可能一個一個的安裝並一個一個引入使用這些插件,那麼presets
就爲咱們提供了一組插件的集合。
這裏重點介紹一下env,這也是咱們平時用的最多的
env 的核心目的是經過配置(browserslist
, compat-table
, and electron-to-chromium
)得知目標環境的特色,而後只作必要的轉換。例如目標瀏覽器支持 es2015,那麼 es2015 這個 preset 實際上是不須要的,因而代碼就能夠小一點(通常轉化後的代碼老是更長),構建時間也能夠縮短一些。若是不寫任何配置項,env默認使用最新的JS語法(不包括Stage-X階段)。
基本的配置以及本人認爲比較重要的options以下:
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: "> 0.25%, not dead",
modules: false,
useBuiltIns: "entry",
corejs: "3",
},
],
],
};
複製代碼
targets
: 支持babel
轉換的瀏覽器環境,示例代碼的意思是僅包含瀏覽器市場份額超過0.25%的用戶所需的polyfill
和代碼轉換(忽略沒有安全更新的瀏覽器,如IE10和BlackBerry)。具體的語法能夠參考browserslistmodules
:讓 babel 以特定的模塊化格式來輸出代碼,可選值爲"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false
。默認爲auto
。
useBuiltIns
: "usage" | "entry" | false
, 默認爲false
,若是使用此配置項,須要指定corejs
版本,否則會有WARNING
usage
會自動根據咱們的環境自動引入對應的core-js
和regenerator-runtime
插件對咱們的代碼進行模塊化解析entry
確保咱們的每一個文件只引入一次polyfill
代碼false
則不自動給文件引入polyfill
,也不會給core-js
或者polyfill
作按需加載處理TC39 將提案分爲如下幾個階段:
注意:這些提案可能會有變化,所以,特別是處於 stage-3 以前的任何提案,請務必謹慎使用。咱們計劃在每次 TC39 會議以後,若是有可能,在提案變動時更新 stage-x 的 preset。
Preset 是逆序排列的(從後往前)。
{
"presets": ["a", "b", "c"]
}
複製代碼
將按以下順序執行: c、b 而後是 a。
這主要是爲了確保向後兼容,因爲大多數用戶將 "es2015" 放在 "stage-0" 以前,這樣必須先執行 stage-0 才能確保 babel 不報錯。另外:Plugin 會運行在 Preset 以前,從前向後執行。
Babel 是一個編譯器(輸入源碼 => 輸出編譯後的代碼)。就像其餘編譯器同樣,編譯過程分爲三個階段:解析、轉換和打印輸出。如今,Babel 雖然開箱即用,可是什麼動做都不作。它基本上相似於 const babel = code => code; ,將代碼解析以後再輸出一樣的代碼。若是想要 Babel 作一些實際的工做,就須要爲其添加插件。
咱們能夠看到babel主要仍是依靠各類插件來對咱們的代碼進行編譯。下面咱們來看一下babel的插件到底有多強大吧。先來看一段代碼:
// index.js
const study = (a, b) => a + b;
複製代碼
這是一段很常見的使用箭頭函數的代碼,那麼咱們如何使用babel去編譯這段代碼呢,讓咱們先依次來執行下列代碼
npm install --save-dev @babel/core
npm install --save-dev @babel/cli
npm install --save-dev @babel/plugin-transform-arrow-functions
複製代碼
安裝完這幾個模塊以後咱們再配置一下babel
// babel.config.js
module.exports = {
presets: [],
plugins: ["@babel/plugin-transform-arrow-functions"],
};
複製代碼
而後在終端裏執行
npx babel index.js --out-file index-compiled.js
複製代碼
咱們打開index-compiled.js
看一下
// index-compiled.js
const study = function (a, b) {
return a + b;
};
複製代碼
babel
把咱們的箭頭函數給轉換成了function
形式
All transformations will use your local configuration files.
全部的轉換都將用本地的配置文件(.babelrc、babel.config.js或者package.json),core即便核心嘛,咱們也能夠看到core的倉庫裏的目錄結構 主要是將代碼轉成ast,方便各個插件分析語法進行相應的處理
用於命令行使用babel,好比咱們上面👆代碼的npx babel index.js --out-file index-compiled.js
babel-node is a CLI that works exactly the same as the Node.js CLI, with the added benefit of compiling with Babel presets and plugins before running it.
babel-node
是babel
附帶的第二個CLI,工做原理與Node.js的CLI徹底相同,只是它會在運行以前編譯ES6
代碼,node
環境下能夠直接運行代碼,而不須要轉碼。babel-node
通常不用於生產環境,由於運行前動態編譯,因此內存的開銷很是的大。babel-node
至關於babel-polyfill
+ babel-register
babel-register
會在node
環境下,給require
綁定一個鉤子函數,這個鉤子會講全部以.es6
, .es
, .jsx
, .mjs
, and .js
爲後綴的文件都將用babel
進行轉碼。因此babel-register
只會對require
的文件轉碼,並不會對自身文件轉碼,由於是實時轉碼,因此不適用於生產環境。而且若是你想用babel-register
的話,還要一併引入babel-polyfill
。
babel
默認只轉換 js 語法,而不轉換新的 API,好比 Iterator
、Generator
、Set
、Maps
、Proxy
、Reflect
、Promise
這種全局Api或者對象,以及一些定義在全局對象上的方法(好比Object.assign
)都不會轉碼。因此在使用這些方法時就必需要使用babel-polyfill
(包含core-js
和regenerator-runtime
)。
這裏咱們改造一下代碼,npm install --save-dev @babel/preset-env
,而後修改一下babel.config.js
// babel.config.js
module.exports = {
presets: ["@babel/preset-env"],
plugins: [],
};
複製代碼
這裏再看一下babel-polyfill
// index.js
import "@babel/polyfill";
new Promise((resolve, reject) => {
resolve(1);
});
複製代碼
// index-compiled.js
"use strict";
require("@babel/polyfill");
new Promise(function (resolve, reject) {
resolve(1);
});
複製代碼
咱們注意到@babel/polyfill
的引用方式變成require
了,怎麼變成import
呢, 在babel.config.js
中,將preset-env
的配置項中添加modules: false
便可。因此此時的babel.config.js
文件變成了:
// babel.config.js
module.exports = {
presets: [
[
"@babel/preset-env",
{
modules: false,
useBuiltIns: "entry",
corejs: "3",
},
],
],
plugins: [],
};
複製代碼
咱們再來執行一下,會發現@babel/polyfill
的引用方式變回import
了。
通常狀況下咱們在使用polyfill
時都是經過webpack
的入口文件配置,好比:
module.exports = {
entry: ["babel-polyfill", "./app/js"]
};
複製代碼
因此,babel-polyfill
要安裝在生產環境dependencies
中。
由於babel-polyfill
會把全部的方法都加在原型上,好比Array.isArray
這個方法,babel-polyfill
會在Array
的protorype
上掛載這個方法,即Array.prototype.isArray
,這將致使:
babel-polyfill
打包出來的體積很是大,由於全部的原型上都掛載的有兼容的方法,我只想用Array.isArray
,可是Object.assign
也會被掛載兼容方法。固然也有優化的方法,配置useBuiltIns: usage
。因此plugin-transform-runtime
會是一個比較不錯的選擇
當咱們在使用一些稍複雜的語法時,babel
會借用一些函數進行轉換,好比說es6
的class
// index.js
class Circle {}
複製代碼
通過babel
的默認轉換後:
// index-compiled.js
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
複製代碼
咱們能夠看到,babel
會使用一些函數來幫助處理這些複雜的轉換,那咱們能夠預想到,每一個文件都這麼轉換以後代碼量將會是多麼的龐大,而且還重複了不少的這些helper
函數,因此babel-plugin-transform-runtime
其實就是把這些helper
函數統一收集起來,下次直接在文件中引用便可,咱們安裝一下babel-plugin-transform-runtime
npm install --save-dev @babel/plugin-transform-runtime
複製代碼
配置babel.config.js
// babel.config.js
module.exports = {
presets: [
[
"@babel/preset-env",
{
modules: false,
useBuiltIns: "entry",
corejs: "3",
},
],
],
plugins: ["@babel/plugin-transform-runtime"],
};
複製代碼
此時運行再看咱們編譯後的class
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
複製代碼
已經變成了引入的方式。咱們注意到@babel/plugin-transform-runtime
實際上是引用的@babel/runtime
的代碼。
此外@babel/plugin-transform-runtime
還有一個最重要的做用就是爲咱們提供了一個配置項corejs
,他能夠給babel-polyfill
提供一個沙箱環境,這樣就不會污染到全局變量,並且無反作用。可是這項要開啓@babel/plugin-transform-runtime
的沙箱模式,必需要配置corejs
須要注意的是corejs2
只支持全局變量(promise。。。
)和靜態屬性(Array.from。。。
),而不支持實例屬性(includes。。。
),若是要用實例屬性,就要使用corejs3
。可是corejs3
目前仍是提案階段,因此還需配置proposals: true
,因此此時babel.config.js
爲:
module.exports = {
presets: [
[
"@babel/preset-env",
{
modules: false,
useBuiltIns: "entry",
corejs: "3",
},
],
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: { version: 3, proposals: true },
},
],
],
};
複製代碼