本文會對babel文檔文檔從一個推導角度來闡述每個babel模塊的作用,嘗試理清其中脈絡咳榜,方便快速理解。
本文不是官網(wǎng)的copyer或者中文翻譯
<a name="xVQ3u"></a>
核心
babel的核心功能在@babel/core
包中爽锥,核心api為transform
系列函數(shù):
babel.transform(code, options, function(err, result) {
result; // => { code, map, ast }
});
該函數(shù)可以將es6+代碼轉(zhuǎn)譯成es5代碼涌韩,所以被廣泛集成在其他工具里面,完成代碼的轉(zhuǎn)譯工作氯夷,如babel-loader
內(nèi)部就是調(diào)該api臣樱。
在babel中,還提供了@babel/cli
和@babel/register
兩個工具腮考,前者提供命令行工具函數(shù)對文件進行轉(zhuǎn)譯雇毫;后者提供require
鉤子:對node的require函數(shù)改造,對后續(xù)require
函數(shù)在執(zhí)行時自動對模塊進行源碼轉(zhuǎn)譯后在導入踩蔚。
babel的目標是對代碼進行轉(zhuǎn)譯棚放,這個過程可以拆解為:解析源碼,遍歷ast改造代碼馅闽,重新生成代碼這三個過程飘蚯。為了提高使用范圍,在v7+
版本中福也,babel將功能拆解出來了多個工具局骤,主要有:
- 解析源碼:
@babel/parser
; - 遍歷ast改造代碼:
@babel/traverse
和@babel/plugin-*
; - 重新生成代碼:
@babel/generator
;
<a name="Wh9AE"></a>
解析源碼
function square(n) {
return n * n;
}
解析成:
{
type: "FunctionDeclaration",
id: {
type: "Identifier",
name: "square"
},
params: [{
type: "Identifier",
name: "n"
}],
body: {
type: "BlockStatement",
body: [{
type: "ReturnStatement",
argument: {
type: "BinaryExpression",
operator: "*",
left: {
type: "Identifier",
name: "n"
},
right: {
type: "Identifier",
name: "n"
}
}
}]
}
}
ast可以簡單的理解為源碼字符串進行語法分析后的結構化數(shù)據(jù)峦甩,方便后續(xù)進行檢查或者改造。
ast中的節(jié)點一般還會包含坐標位置现喳,如字符串下標凯傲,行數(shù),列數(shù)等拿穴,更多詳細內(nèi)容請參考官方文檔泣洞。
老版本babel中使用的是 acorn 和 acorn-jsx忧风,在v7
以上時默色,進行了fork改造為@babel/parser
。
另外@babel/core
也集成了@babel/parser
功能,可以直接從@babel/core
中導出api直接使用:
babel.parse(code: string, options?: Object, callback: Function)
當前的解析器默認只支持最新的es6代碼腿宰,如果需要兼容一些新語法(非語法糖之類的新特性呕诉,新表達式和新操作服,如對象解構吃度,可選表達式甩挫,類型等),需要擴展babel語法插件椿每。
很多工具其實只需要解析代碼即可伊者,如代碼檢驗,如語法高亮间护,源碼中數(shù)據(jù)收集亦渗。
<a name="rzqMT"></a>
遍歷ast改造代碼
講過解析器已經(jīng)將源碼解析成更好處理的結構化數(shù)據(jù)ast,如果需求是對代碼進行調(diào)整汁尺,只需對ast數(shù)據(jù)進行調(diào)整法精,然后使用生成器生成新的代碼即可。但整個babel需要解決的是將所有最新的es6+特性轉(zhuǎn)譯成向后兼容的瀏覽器可執(zhí)行代碼(es5),需要處理的情況眾多痴突,如果直接對ast進行改造搂蜓,那么代碼將非常臃腫。且es規(guī)范還在不停的迭代中辽装,臃腫的代碼的對后續(xù)維護迭代也帶來巨大的挑戰(zhàn)帮碰。針對這種困境,必須需要進行架構上的調(diào)整拾积,使用插件化架構收毫。
babel即是處于這樣一個原因,采用了訪問者模式殷勘〈嗽伲可以簡單的理解為,在對ast進行一個遍歷時玲销,每次進入一個新的節(jié)點或者退出一個節(jié)點時输拇,都會拜訪每一個插件,咨詢它們是否需要對當前的情況進行處理贤斜。這鐘架構證了性能策吠,也保證了擴展性。
另一種插件化架構瘩绒,也就是流式架構猴抹,如gulp。也就是插件隊列依次對上一個插件處理后的ast對象進行更深一層次的改造锁荔。但這種架構蟀给,需要多次循環(huán)ast, 在實際使用中,一般一個生產(chǎn)項目,文件內(nèi)容巨大跋理,文件數(shù)量居多择克,會導致性能崩潰。
所以babel核心框架中前普,只包含了訪問節(jié)點和調(diào)用插件的邏輯肚邢。實際對ast的改造,全部轉(zhuǎn)交給了插件拭卿。這也是為什么babel自帶了那么多@babel/plugin-*
的插件骡湖。同樣社區(qū)也擁有非常多的插件,從能能夠支持flow, typescipt這些新語法峻厚。
插件化的架構勺鸦,也允許使用者進行拔插式配置,根據(jù)當前使用場景進行高度定制目木。這也就是在配置文件中如babrlrc.js
可以配置插件的原因换途。
為了支持高度動態(tài)配置化來適配復雜的場景,babel會將每個插件負責的功能劃分足夠小刽射,一般每個插件只會負責一個特性军拟。這會導致使用時,需要去了解每一個插件的作用誓禁,然后在配置文件中配置超長的插件列表懈息,帶來巨大的心智負擔和維護難度。為了解決這個問題摹恰,babel提供了預設的機制辫继。簡單的理解就是一個babel配置可以繼承另一個配置,那么我們只需要繼承社區(qū)上或者官方專業(yè)人員配置的預設即可俗慈,如:
@babel/preset-env
@babel/preset-react
@babel/preset-typescript
@babel/preset-flow
為了方便插件中的復用姑宽,babel將遍歷ast的工具也開放出來為一個單獨的模塊@babel/traverse
。將節(jié)點類型的判斷和創(chuàng)建節(jié)點的工具庫闺阱,放在了@babel/types
炮车。
另外對于一些babel中多個模塊公用的一些工具,都封裝成工具模塊酣溃,也就是@babel/helper-*
系列模塊瘦穆,如:
@babel/helper-compilation-targets
@babel/helper-module-imports
由于babel自帶了那么多插件,所以很多helper其實是插件的輔助工具赊豌,如helper-module-imports
就是輔助生成一些導入節(jié)點扛或。
在es6+轉(zhuǎn)成es5的過程中,很多語法糖語法(語法上的細微調(diào)整)碘饼,如let
, const
等實現(xiàn)直接用插件調(diào)整代碼即可解決熙兔。但對于其他的需要大端代碼才能實現(xiàn)的特性悲伶,如Array#includes
,生成器黔姜,迭代器拢切,async/await
, promise
等蒂萎,如果每次都通過代碼展開秆吵,那么編譯后的代碼將會巨大。為了解決這個問題五慈,會將includes
的實現(xiàn)放在補丁(polyfill)中纳寂,然后直接使用補丁中的實現(xiàn)。如生成器泻拦,迭代器毙芜,async/await
, promise
等都是通過這種機制支持。
這些的補丁(polyfill)的導入方式也有兩種争拐,一種是全量導入腋粥,也就是導入@babel/polyfill
模塊。一種是按需導入架曹,需要使用預設@babel/preset-env
隘冲,根據(jù)實際使用情況,在使用的模塊中按需導入@babel/runtime
中的補丁(polyfill)绑雄。如:
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
@babel/polyfill
和@babel/runtime
的底層實現(xiàn)都是core-js
展辞。
實際情景下,還是存在插件無法解決的情況:一個無法用老代碼補丁實現(xiàn)万牺,也無法使用語法糖替換代碼的特性罗珍,如Proxy
對象,這種特性一般需要js引擎從底層提供脚粟。在使用這些特性時覆旱,需要注意瀏覽器兼容性。
<a name="uaLlx"></a>
生成代碼
使用@babel/generator
即可對一個ast樹重新生成為代碼核无。
<a name="S8g5j"></a>
配置
我們通常見到的babel配置就是就是用于指導babel行為的配置文件通殃,可以簡單的理解為@babel/core
中transform
函數(shù)的選項支持使用配置文件配置。
更多的配置詳細使用等請看官網(wǎng)厕宗。
<a name="sRLPS"></a>
其他官方工具
babel還提供了一些其他工具画舌,用于擴展babel生態(tài)鏈:
-
@babel/standalone
: 支持瀏覽器上運行的babel版本,用于一些在線編輯網(wǎng)站已慢,如JS Bin -
@babel/code-frame
: 代碼窗口曲聂,用于輸出類似這種:
1 | class Foo {
> 2 | constructor()
| ^
3 | }
-
@babel/template
: babel插件開發(fā)工具,支持根據(jù)代碼字符串創(chuàng)建ast節(jié)點佑惠。因為ast節(jié)點攜帶信息較多朋腋,且結構較深齐疙,在手動創(chuàng)建復雜的代碼節(jié)點時十分不便。使用官方提供的這個工具旭咽,可以快速創(chuàng)建一整段代碼節(jié)點贞奋,并且還支持占位符:
const buildRequire = template(`
var IMPORT_NAME = require(SOURCE);
`);
const ast = buildRequire({
IMPORT_NAME: t.identifier("myModule"),
SOURCE: t.stringLiteral("my-module"),
});
// || || ||
// \\// \\// \\//
const myModule = require("my-module");