很是深刻的理解一下babel

什麼是babel

這裏借用一下官方的定義javascript

Babel是一個工具鏈,主要用於將 ECMAScript 2015+ 版本的代碼轉換爲向後兼容的 JavaScript 語法,以便可以運行在當前和舊版本的瀏覽器或其餘環境中。前端

簡單的理解就是,對較高版本的語法作一次向下兼容,使得較低版本的瀏覽器可以識別並運行這些代碼java

爲何會出現babel

先來看一下這個詞babel(bāb(ə)l,音譯爲 掰bou,中文譯爲巴別塔。node

個人理解是:react

  • es6之類的只是語言規範,而瀏覽器要實現這一規範可能就須要至關漫長的一個時間了。
  • 各類前端技術形成了各類js方言,尤爲是react的jsx

因此急需一個工具去解決這些問題,而後babel就應運而生。webpack

再來看一下百度上對巴別塔的解釋git

巴別塔;是《聖經·舊約·創世記》第11章故事中人們建造的塔。根據篇章記載,當時人類聯合起來興建但願能通往天堂的高塔;爲了阻止人類的計劃,上帝讓人類說不一樣的語言,令人類相互之間不能溝通,計劃所以失敗,人類自此各散東西。此事件,爲世上出現不一樣語言和種族提供解釋。es6

是否是感受很是🐂🍺github

這裏想到了我司的一個項目,這個項目的主要做用就是使用函數計算在移動端截圖,我司名字是moka,因此這個項目起名爲mokapture,我我的感受仍是挺有意思的web

重點:presets

babel會經過具體的某個插件對相應的代碼進行轉碼,好比箭頭函數對應的插件就是@babel/plugin-transform-arrow-functions,鏈式調用對應的插件就是@babel/plugin-proposal-optional-chaining,咱們不可能一個一個的安裝並一個一個引入使用這些插件,那麼presets就爲咱們提供了一組插件的集合。

官方 Preset,包括:env,flow,react,typescript等

這裏重點介紹一下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)。具體的語法能夠參考browserslist
  • modules:讓 babel 以特定的模塊化格式來輸出代碼,可選值爲"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false。默認爲auto
    1. auto會根據caller的配置數據決定是否模塊化處理,通常狀況下不須要咱們配置。
    2. false不進行模塊化處理。
  • useBuiltIns: "usage" | "entry" | false, 默認爲false,若是使用此配置項,須要指定corejs版本,否則會有WARNING
    1. usage會自動根據咱們的環境自動引入對應的core-jsregenerator-runtime插件對咱們的代碼進行模塊化解析
    2. entry確保咱們的每一個文件只引入一次polyfill代碼
    3. false則不自動給文件引入polyfill,也不會給core-js或者polyfill作按需加載處理

Stage-X (實驗性質的 Presets)

TC39 將提案分爲如下幾個階段:

  • Stage 0 - 設想(Strawman):只是一個想法,可能有 Babel插件。
  • Stage 1 - 建議(Proposal):這是值得跟進的。
  • Stage 2 - 草案(Draft):初始規範。
  • Stage 3 - 候選(Candidate):完成規範並在瀏覽器上初步實現。
  • Stage 4 - 完成(Finished):將添加到下一個年度版本發佈中。

注意:這些提案可能會有變化,所以,特別是處於 stage-3 以前的任何提案,請務必謹慎使用。咱們計劃在每次 TC39 會議以後,若是有可能,在提案變動時更新 stage-x 的 preset。

注意Preset的排列順序

Preset 是逆序排列的(從後往前)。

{
  "presets": ["a", "b", "c"]
}
複製代碼

將按以下順序執行: c、b 而後是 a。

這主要是爲了確保向後兼容,因爲大多數用戶將 "es2015" 放在 "stage-0" 以前,這樣必須先執行 stage-0 才能確保 babel 不報錯。另外:Plugin 會運行在 Preset 以前,從前向後執行。

重點:babel插件

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形式

babel-core

All transformations will use your local configuration files.

全部的轉換都將用本地的配置文件(.babelrc、babel.config.js或者package.json),core即便核心嘛,咱們也能夠看到core的倉庫裏的目錄結構 babel-core 主要是將代碼轉成ast,方便各個插件分析語法進行相應的處理

babel-cli

用於命令行使用babel,好比咱們上面👆代碼的npx babel index.js --out-file index-compiled.js

babel-node

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-nodebabel附帶的第二個CLI,工做原理與Node.js的CLI徹底相同,只是它會在運行以前編譯ES6代碼,node環境下能夠直接運行代碼,而不須要轉碼。babel-node通常不用於生產環境,由於運行前動態編譯,因此內存的開銷很是的大。babel-node至關於babel-polyfill + babel-register

babel-register

babel-register會在node環境下,給require綁定一個鉤子函數,這個鉤子會講全部以.es6, .es, .jsx, .mjs, and .js爲後綴的文件都將用babel進行轉碼。因此babel-register只會對require的文件轉碼,並不會對自身文件轉碼,由於是實時轉碼,因此不適用於生產環境。而且若是你想用babel-register的話,還要一併引入babel-polyfill

babel-polyfill

babel 默認只轉換 js 語法,而不轉換新的 API,好比 IteratorGeneratorSetMapsProxyReflectPromise 這種全局Api或者對象,以及一些定義在全局對象上的方法(好比Object.assign)都不會轉碼。因此在使用這些方法時就必需要使用babel-polyfill(包含core-jsregenerator-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會在Arrayprotorype上掛載這個方法,即Array.prototype.isArray,這將致使:

  • babel-polyfill打包出來的體積很是大,由於全部的原型上都掛載的有兼容的方法,我只想用Array.isArray,可是Object.assign也會被掛載兼容方法。固然也有優化的方法,配置useBuiltIns: usage
  • 在原型上掛載方法污染全局變量,若是咱們是在維護一個公共組件庫,就十分的危險了

因此plugin-transform-runtime會是一個比較不錯的選擇

babel-runtime 和 babel-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 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 },
      },
    ],
  ],
};
複製代碼

參考連接

相關文章
相關標籤/搜索