👆 點擊上方卡片關注前端
當聊到Babel
的做用,不少人第一反應是:用來實現API polyfill
。
node
事實上,Babel
做爲前端工程化的基石,做用遠不止這些。webpack
做爲一個龐大的家族,Babel
生態中有不少概念,好比:preset
、plugin
、runtime
等。git
這些概念使初學者對Babel
望而生畏,對其理解也止步於webpack
的babel-loader
配置。github
本文會從Babel
的核心功能出發,一步步揭開Babel
你們族的神祕面紗,向前端架構師邁出一小步。web
Babel是什麼
Babel 是一個 JavaScript 編譯器。面試
做爲JS
編譯器,Babel
接收輸入的JS
代碼,通過內部處理流程,最終輸出修改後的JS
代碼。json

在Babel
內部,會執行以下步驟:前端工程化
-
將
Input Code
解析爲AST
(抽象語法樹),這一步稱爲parsing
數組 -
編輯
AST
,這一步稱爲transforming
-
將編輯後的
AST
輸出爲Output Code
,這一步稱爲printing

從Babel倉庫[1]的源代碼,能夠發現:Babel
是一個由幾十個項目組成的Monorepo
。
其中babel-core
提供了以上提到的三個步驟的能力。
在babel-core
內部,更細緻的講:
-
babel-parser
實現第一步 -
babel-generator
實現第三步
要了解第二步,咱們須要簡單瞭解下AST
。
AST的結構
進入AST explorer[2],選擇@babel/parser
做爲解析器,在左側輸入:
const name = ['ka', 'song'];
能夠解析出以下結構的AST
,他是JSON
格式的樹狀結構:

在babel-core
內部:
-
babel-traverse
能夠經過「深度優先」的方式遍歷AST
樹 -
對於遍歷到的每條路徑,
babel-types
提供用於修改AST
節點的節點類型數據
因此,整個Babel
底層編譯能力由以下部分構成:

當咱們瞭解Babel
的底層能力後,接下來看看基於這些能力,上層能實現什麼功能?
Babel的上層能力
基於Babel
對JS
代碼的編譯處理能力,Babel
最多見的上層能力爲:
-
polyfill
-
DSL
轉換(好比解析JSX
) -
語法轉換(好比將高級語法解析爲當前可用的實現)
因爲篇幅有限,這裏僅介紹polyfill
與「語法轉換」相關功能。
polyfill
做爲前端,最多見的Babel
生態的庫想必是@babel/polyfill
與@babel/preset-env
。
使用@babel/polyfill
或@babel/preset-env
能夠實現高級語法的降級實現以及API
的polyfill
。
從上文咱們知道,Babel
自己只是JS
的編譯器,以上二者的轉換功能是誰實現的呢?
答案是:core-js
core-js簡介
core-js
是一套模塊化的JS
標準庫,包括:
-
一直到
ES2021
的polyfill
-
promise
、symbols
、iterators
等一些特性的實現 -
ES
提案中的特性實現 -
跨平臺的
WHATWG / W3C
特性,好比URL

從core-js倉庫[3]看到,core-js
也是由多個庫組成的Monorepo
,包括:
-
core-js-builder
-
core-js-bundle
-
core-js-compat
-
core-js-pure
-
core-js
咱們介紹其中幾個庫:
core-js
core-js
提供了polyfill
的核心實現。
import 'core-js/features/array/from';
import 'core-js/features/array/flat';
import 'core-js/features/set';
import 'core-js/features/promise';
Array.from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3]
[1, [2, 3], [4, [5]]].flat(2); // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x => console.log(x)); // => 32
直接使用core-js
會污染全局命名空間和對象原型。
好比上例中修改了Array
的原型以支持數組實例的flat
方法。
core-js-pure
core-js-pure
提供了獨立的命名空間:
import from from 'core-js-pure/features/array/from';
import flat from 'core-js-pure/features/array/flat';
import Set from 'core-js-pure/features/set';
import Promise from 'core-js-pure/features/promise';
from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3]
flat([1, [2, 3], [4, [5]]], 2); // => [1, 2, 3, 4, 5]
Promise.resolve(32).then(x => console.log(x)); // => 32
這樣使用不會污染全局命名空間與對象原型。
core-js-compat
core-js-compat
根據Browserslist
維護了不一樣宿主環境、不一樣版本下對應須要支持特性的集合。
Browserslist[4]提供了不一樣瀏覽器、
node
版本下ES
特性的支持狀況

好比:
"browserslist": [
"not IE 11",
"maintained node versions"
]
表明:非IE
11的版本以及全部Node.js
基金會維護的版本。
@babel/polyfill與core-js關係
@babel/polyfill
能夠看做是:core-js
加regenerator-runtime
。
regenerator-runtime
是generator
以及async/await
的運行時依賴
單獨使用@babel/polyfill
會將core-js
全量導入,形成項目打包體積過大。
從Babel v7.4.0[5]開始,
@babel/polyfill
被廢棄了,能夠直接引用core-js
與regenerator-runtime
替代
爲了解決全量引入core-js
形成打包體積過大的問題,咱們須要配合使用@babel/preset-env
。
preset的含義
在介紹@babel/preset-env
前,咱們先來了解preset
的意義。
初始狀況下,Babel
沒有任何額外能力,其工做流程能夠描述爲:
const babel = code => code;
其經過plugin
對外提供介入babel-core
的能力,相似webpack
的plugin
對外提供介入webpack
編譯流程的能力。
plugin
分爲幾類:
-
@babel/plugin-syntax-*
語法相關插件,用於新的語法支持。好比babel-plugin-syntax-decorators[6]提供decorators
的語法支持 -
@babel/plugin-proposal-*
用於ES
提案的特性支持,好比babel-plugin-proposal-optional-chaining
是可選鏈操做符
特性支持 -
@babel/plugin-transform-*
用於轉換代碼,transform
插件內部會使用對應syntax
插件
多個plugin
組合在一塊兒造成的集合,被稱爲preset
。
@babel/preset-env
使用@babel/preset-env
,能夠「按需」將core-js
中的特性打包,這樣能夠顯著減小最終打包的體積。
這裏的「按需」,分爲兩個粒度:
-
宿主環境的粒度。根據不一樣宿主環境將該環境下所需的全部特性打包
-
按使用狀況的粒度。僅僅將使用了的特性打包
咱們來依次看下。
宿主環境的粒度
當咱們按以下參數在項目目錄下配置browserslist
文件(或在@babel/preset-env
的targets
屬性內設置,或在package.json
的browserslist
屬性中設置):
not IE 11
maintained node versions
會將「非IE11」且「全部Node.js基金會維護的node版本」下須要的特性打入最終的包。
顯然這是利用了剛纔介紹的core-js
這個Monorepo
下的core-js-compat
的能力。
按使用狀況的粒度
更理想的狀況是隻打包咱們使用過的特性。
這時候能夠設置@babel/preset-env
的useBuiltIns
屬性爲usage
。
好比:
a.js
:
var a = new Promise();
b.js
:
var b = new Map();
當宿主環境不支持promise
與Map
時,輸出的文件爲:
a.js
:
import "core-js/modules/es.promise";
var a = new Promise();
b.js
:
import "core-js/modules/es.map";
var b = new Map();
當宿主環境支持這兩個特性時,輸出的文件爲:
a.js
:
var a = new Promise();
b.js
:
var b = new Map();
進一步優化打包體積
打開babel playground[7],輸入:
class App {}
會發現編譯出的結果爲:
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var App = function App() {
"use strict";
_classCallCheck(this, App);
};
其中_classCallCheck
爲輔助方法。
若是多個文件都使用了class
特性,那麼每一個文件打包對應的module
中都將包含_classCallCheck
。
爲了減小打包體積,更好的方式是:須要使用「輔助方法」的module
都從同一個地方引用,而不是本身維護一份。
@babel/runtime
包含了Babel
全部「輔助方法」以及regenerator-runtime
。
單純引入@babel/runtime
還不行,由於Babel
不知道什麼時候引用@babel/runtime
中的「輔助方法」。
因此,還須要引入@babel/plugin-transform-runtime
。
這個插件會在編譯時將全部使用「輔助方法」的地方從「本身維護一份」改成從@babel/runtime
中引入。
因此咱們須要將@babel/plugin-transform-runtime
置爲devDependence
,由於他在編譯時使用。
將@babel/runtime
置爲dependence
,由於他在運行時使用。
總結
本文從底層向上介紹了前端平常業務開發會接觸的Babel
你們族成員。他們包括:
底層
@babel/core
(由@babel/parser
、@babel/traverse
、@babel/types
、@babel/generator
等組成)
他們提供了Babel
編譯JS
的能力。
注:這裏
@babel/core
爲庫名,前文中babel-core
爲其在倉庫中對應文件名
中層
@babel/plugin-*
Babel
對外暴露的API
,使開發者能夠介入其編譯JS
的能力
上層
@babel/preset-*
平常開發會使用的插件集合。
對於立志成爲前端架構師的同窗,Babel
是前端工程化的基石,學懂、會用他是頗有必要的。
能看到這裏真不容易,給本身鼓鼓掌吧。

參考資料
Babel倉庫: https://github.com/babel/babel/tree/main/packages
[2]AST explorer: https://astexplorer.net/
[3]core-js倉庫: https://github.com/zloirock/core-js/tree/master/packages
[4]Browserslist: https://github.com/browserslist/browserslist
[5]Babel v7.4.0: https://babeljs.io/docs/en/babel-polyfill#docsNav
[6]babel-plugin-syntax-decorators: https://github.com/babel/babel/tree/main/packages/babel-plugin-syntax-decorators
[7]babel playground: https://babeljs.io/repl#?browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=MYGwhgzhAECCAO9oG8C-Q&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=script&lineWrap=true&presets=env&prettier=false&targets=&version=7.13.7&externalPlugins=babel-plugin-transform-regenerator%406.26.0
交流討論
歡迎關注公衆號「前端試煉」,公衆號平時會分享一些實用或者有意思的東西,發現代碼之美。專一深度和最佳實踐,但願打造一個高質量的公衆號。
❤️
公衆號後臺回覆【小煉】
邀請你加入純淨技術交流羣(上班划水摸魚羣)
🙏
若是以爲這篇文章還不錯
來個【分享、點贊、在看】三連吧
讓更多的人也看到~
本文分享自微信公衆號 - 前端試煉(code-photo)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。