〇、Babel 概述

Babel 的工作原理 = “把代碼變樹 → 改樹 → 再變回代碼”。

也就是三個階段:

  1. Parse(解析):ESNext(ES 新語法) → AST 語法樹
  2. Transform(轉換):插件修改 AST
  3. Generate(生成):AST → 舊 JS 代碼(瀏覽器都可以識別的代碼)

Babel 是一個“代碼轉換工具”,核心就是圍繞 AST(抽象語法樹)做代碼替換,替換成兼容各瀏覽器的代碼舊語法。

一、Babel 的編譯三階段(Pipeline)

  1. 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 標準
  1. 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
  1. 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

例如:

  • Promise
  • Array.from
  • String.includes
  • Object.assign
  • WeakMap
  • Symbol
  • Map
  • Set
  • Array.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

這裏暫時不提供大廠完整配置代碼,因為很多複雜配置可能同學們看不太懂。

所以,點贊 + 收藏 + 關注,直到工程化系列文章更新完畢,再為大家展示真實大廠項目中的前端工程化完整配置。