〇、Babel 概述
Babel 的工作原理 = “把代碼變樹 → 改樹 → 再變回代碼”。
也就是三個階段:
- Parse(解析):ESNext(ES 新語法) → AST 語法樹
- Transform(轉換):插件修改 AST
- Generate(生成):AST → 舊 JS 代碼(瀏覽器都可以識別的代碼)
Babel 是一個“代碼轉換工具”,核心就是圍繞 AST(抽象語法樹)做代碼替換,替換成兼容各瀏覽器的代碼舊語法。
一、Babel 的編譯三階段(Pipeline)
-
Parsing(解析成 AST)
作用:把 JS 源碼轉成 AST(抽象語法樹)
示例代碼:
const sum = (a, b) => a + b;
轉換成的 AST 類似這樣的樹狀結構:
Program
└─ VariableDeclaration
└─ VariableDeclarator
├─ Identifier(sum)
└─ ArrowFunctionExpression
├─ Identifier(a)
├─ Identifier(b)
└─ BinaryExpression(+)
解析流程滿足:
- babylon / @babel/parser → 把源碼變成 AST
- AST 格式遵循 ESTree 標準
-
Transform(通過插件修改 AST)
Babel 插件會遍歷 AST,並進行“節點替換”。
比如將箭頭函數轉成 ES5:
輸入(AST 節點:ArrowFunctionExpression):
const sum = (a, b) => a + b;
輸出(被插件改過的 AST):
const sum = function(a, b) {
return a + b;
};
這個過程主要由 Babel 的“Transform 插件”(如 @babel/plugin-transform-arrow-functions)完成。
插件在遍歷 AST 時,會提供“Visitor 函數”,例如:
Visitor = {
ArrowFunctionExpression(path) {
// 發現箭頭函數 → 替換成普通函數定義
}
}
所有 Babel 插件本質上都是:AST 節點識別 + 替換。
我們最常見插件有:
- 處理箭頭函數
- 處理類 class
- 處理 async/await
-
Generate(生成兼容代碼)
最後階段:把變過的 AST 再轉成 JS 字符串。
通過:
- @babel/generator
輸出:
"use strict";
var sum = function (a, b) {
return a + b;
};
至此,編譯完成。
二、Babel 與 Webpack 的協作很簡單
Webpack 使用 babel 其實只需要 babel-loader ,然後為不同的轉換提供不同的 babel 插件配置,整個過程都會在 babel-loader 內部完成:
把文件交給 Babel 處理,拿到 Babel 輸出的 JS,再丟給 webpack 的後續 loader / bundler。
流程如下:
source.js
↓ (webpack 調用 babel-loader)
Babel
1. parse
2. transform (plugins/presets)
3. generate
↓
transpiled.js
↓
webpack 打包輸出 bundle.js
身為開發者的我們只需要關注 webpack 相關配置,至於內部如何處理無需關心。
三、Preset(預設)進一步減少我們的心智負擔
如果每一個 babel 插件都需要我們手動配置,其實會大大增加我們的工作量,而且這些插件對應的語法都是固定的,有沒有這樣一種可能,我們可以將同一個類型的插件都合併為一個“插件集合”呢?
有的兄弟,有的。
一個 Preset(預設)就是一個這樣的“插件集合”。
你多多少少會聽過 @babel/preset-env,這也是我們最常用的預設。
它會自動根據瀏覽器兼容列表(browserslist)決定啓用哪些語法轉換。
例如:
// package.json
{
// 大廠中我們一般直接繼承公司內部的配置包,放到跟倉庫供所有包複用:
// "extends @your-company/browserslist-config"
"browserslist": [
"> 0.2%",
"not dead",
"not op_mini all"
]
}
然後 preset-env 就會根據瀏覽器的兼容列表自動識別哪些語法需要兼容。
四、你真的無敵了嗎 Babel ?
缺陷:Babel 不能轉換無法用舊語法表達的特性 / 運行時 API
例如:
PromiseArray.fromString.includesObject.assignWeakMapSymbolMapSetArray.prototype.flat- .....
而這些,還需要 Polyfill 支持(如 core-js)。
**
core-js 是 polyfill 的集合庫**。polyfill 就是 “在舊瀏覽器裏補上新原生 API”。
// 用 preset-env + core-js 自動按需注入 polyfill
["@babel/preset-env", {
useBuiltIns: "usage", // 用到了什麼新API,就注入對應的 polyfill,而不是注入整個 core-js
corejs: 3
}]
- 這裏再説一下,其他的兩個常用包:
- @babel/runtime:一般來説 babel 會為每個文件注入 helper 函數,使用了 runtime 包後,這些 helper 會被統一從
"@babel/runtime/helpers/*"引入,減少重複 helper,減少 bundle 體積 - @babel/plugin-transform-runtime:讓 Babel 自動從 runtime 引入 helper
這裏暫時不提供大廠完整配置代碼,因為很多複雜配置可能同學們看不太懂。
所以,點贊 + 收藏 + 關注,直到工程化系列文章更新完畢,再為大家展示真實大廠項目中的前端工程化完整配置。