解剖 Babel —— 向前端架構師邁出一小步

爆肝日更    前端面試    前端工程化

👆  點擊上方卡片關注前端


當聊到Babel的做用,不少人第一反應是:用來實現API polyfill
node

事實上,Babel做爲前端工程化的基石,做用遠不止這些。webpack

做爲一個龐大的家族,Babel生態中有不少概念,好比:presetpluginruntime等。git

這些概念使初學者對Babel望而生畏,對其理解也止步於webpackbabel-loader配置。github

本文會從Babel的核心功能出發,一步步揭開Babel你們族的神祕面紗,向前端架構師邁出一小步。web

Babel是什麼

Babel 是一個 JavaScript 編譯器。面試

做爲JS編譯器,Babel接收輸入的JS代碼,通過內部處理流程,最終輸出修改後的JS代碼。json

Babel內部,會執行以下步驟:前端工程化

  1. Input Code解析爲AST(抽象語法樹),這一步稱爲parsing數組

  2. 編輯AST,這一步稱爲transforming

  3. 將編輯後的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的上層能力

基於BabelJS代碼的編譯處理能力,Babel最多見的上層能力爲:

  • polyfill

  • DSL轉換(好比解析JSX

  • 語法轉換(好比將高級語法解析爲當前可用的實現)

因爲篇幅有限,這裏僅介紹polyfill「語法轉換」相關功能。

polyfill

做爲前端,最多見的Babel生態的庫想必是@babel/polyfill@babel/preset-env

使用@babel/polyfill@babel/preset-env能夠實現高級語法的降級實現以及APIpolyfill

從上文咱們知道,Babel自己只是JS的編譯器,以上二者的轉換功能是誰實現的呢?

答案是:core-js

core-js簡介

core-js是一套模塊化的JS標準庫,包括:

  • 一直到ES2021polyfill

  • promisesymbolsiterators等一些特性的實現

  • ES提案中的特性實現

  • 跨平臺的WHATWG / W3C特性,好比URL

core-js做者Denis Pushkarev

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([12321]));          // => [1, 2, 3]
[1, [23], [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([12321]));                // => [1, 2, 3]
flat([1, [23], [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

好比:

"browserslist": [
    "not IE 11",
    "maintained node versions"
  ]

表明:非IE11的版本以及全部Node.js基金會維護的版本。

@babel/polyfill與core-js關係

@babel/polyfill能夠看做是:core-jsregenerator-runtime

regenerator-runtimegenerator以及async/await的運行時依賴

單獨使用@babel/polyfill會將core-js全量導入,形成項目打包體積過大。

Babel v7.4.0[5]開始,@babel/polyfill被廢棄了,能夠直接引用core-jsregenerator-runtime替代

爲了解決全量引入core-js形成打包體積過大的問題,咱們須要配合使用@babel/preset-env

preset的含義

在介紹@babel/preset-env前,咱們先來了解preset的意義。

初始狀況下,Babel沒有任何額外能力,其工做流程能夠描述爲:

const babel = code => code;

其經過plugin對外提供介入babel-core的能力,相似webpackplugin對外提供介入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-envtargets屬性內設置,或在package.jsonbrowserslist屬性中設置):

not IE 11
maintained node versions

會將「非IE11」「全部Node.js基金會維護的node版本」下須要的特性打入最終的包。

顯然這是利用了剛纔介紹的core-js這個Monorepo下的core-js-compat的能力。

按使用狀況的粒度

更理想的狀況是隻打包咱們使用過的特性。

這時候能夠設置@babel/preset-envuseBuiltIns屬性爲usage

好比:

a.js

var a = new Promise();

b.js

var b = new Map();

當宿主環境不支持promiseMap時,輸出的文件爲:

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, Constructorif (!(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是前端工程化的基石,學懂、會用他是頗有必要的。

能看到這裏真不容易,給本身鼓鼓掌吧。

希斯特利亞筆芯

參考資料

[1]

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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索