Babel 基础
约 1285 字大约 4 分钟
Babel编译原理AST
2026-04-15
编译理论基础
什么是编译器
编译器是将一种语言(源代码)转换为另一种语言(目标代码)的程序。
源代码 → 编译器 → 目标代码编译器的三个阶段
1. Parse(解析)
将源代码转换为抽象语法树(AST)。
const code = 'const a = 1 + 2;';
const ast = parser.parse(code);2. Transform(转换)
遍历 AST,对其进行修改。
traverse(ast, {
VariableDeclaration(path) {
// 转换逻辑
}
});3. Generate(生成)
将转换后的 AST 转换为目标代码。
const output = generate(ast);Token 词法分析
Token 是代码的最小单元。
// 源代码
const a = 1 + 2;
// Token 列表
[
{ type: 'keyword', value: 'const' },
{ type: 'identifier', value: 'a' },
{ type: 'operator', value: '=' },
{ type: 'number', value: '1' },
{ type: 'operator', value: '+' },
{ type: 'number', value: '2' },
{ type: 'punctuation', value: ';' }
]AST 抽象语法树
AST 是代码的树形结构表示。
// 源代码
const a = 1 + 2;
// AST
{
type: 'Program',
body: [
{
type: 'VariableDeclaration',
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
id: {
type: 'Identifier',
name: 'a'
},
init: {
type: 'BinaryExpression',
operator: '+',
left: {
type: 'NumericLiteral',
value: 1
},
right: {
type: 'NumericLiteral',
value: 2
}
}
}
]
}
]
}Babel 简介
Babel 是什么
Babel 是一个 JavaScript 编译器,主要用于:
- 将 ES6+ 代码转换为 ES5
- 支持 JSX、TypeScript 等语法
- 代码转换和优化
Babel 的工作原理
源代码 → @babel/parser → AST → 插件转换 → @babel/generator → 目标代码Babel 的架构
@babel/core
├── @babel/parser (词法分析 + 语法分析)
├── @babel/traverse (遍历 AST)
├── @babel/types (AST 节点操作)
├── @babel/template (模板生成)
└── @babel/generator (代码生成)Babel 核心包
@babel/core - 核心 API
const babel = require('@babel/core');
const result = babel.transformSync('const a = 1;', {
presets: ['@babel/preset-env']
});
console.log(result.code); // 'var a = 1;'@babel/parser - 解析代码生成 AST
const parser = require('@babel/parser');
const ast = parser.parse('const a = 1 + 2;', {
sourceType: 'module',
plugins: ['jsx', 'typescript']
});@babel/traverse - 遍历和更新 AST
const traverse = require('@babel/traverse').default;
traverse(ast, {
VariableDeclaration(path) {
console.log('找到变量声明:', path.node);
},
FunctionDeclaration(path) {
console.log('找到函数:', path.node.id.name);
}
});@babel/generator - AST 生成代码
const generate = require('@babel/generator').default;
const { code, map } = generate(ast, {
minified: true,
sourceMaps: true
});@babel/types - AST 节点构造器
const t = require('@babel/types');
// 创建节点
const identifier = t.identifier('a');
const literal = t.numericLiteral(1);
const binary = t.binaryExpression('+', literal, literal);
const declaration = t.variableDeclaration('const', [
t.variableDeclarator(identifier, binary)
]);Babel 安装和使用
命令行使用
# 安装
npm install --save-dev @babel/core @babel/cli
# 编译文件
npx babel src -d lib
# 编译并 watch
npx babel src -d lib --watch配置文件
babel.config.js(推荐)
module.exports = {
presets: [
['@babel/preset-env', {
targets: '> 1%, last 2 versions',
useBuiltIns: 'usage',
corejs: 3
}]
],
plugins: [
['@babel/plugin-transform-runtime', {
corejs: 3
}]
]
};.babelrc
{
"presets": ["@babel/preset-env"],
"plugins": []
}Preset 配置
module.exports = {
presets: [
// 编译现代 JavaScript
'@babel/preset-env',
// 支持 React
['@babel/preset-react', {
runtime: 'automatic'
}],
// 支持 TypeScript
'@babel/preset-typescript'
]
};插件配置
module.exports = {
plugins: [
// 类属性
'@babel/plugin-proposal-class-properties',
// 可选链
'@babel/plugin-proposal-optional-chaining',
// 空值合并
'@babel/plugin-proposal-nullish-coalescing-operator',
// 装饰器
['@babel/plugin-proposal-decorators', {
legacy: true
}]
]
};Babel 插件编写
插件的工作原理
Babel 插件在 AST 遍历阶段执行。
module.exports = function babelPlugin(api) {
const t = api.types;
return {
visitor: {
// 访问特定类型的节点
}
};
};Visitor 模式
Visitor 模式用于访问 AST 节点。
module.exports = function() {
return {
visitor: {
// 进入节点时调用
FunctionDeclaration(path) {
console.log('进入函数:', path.node.id.name);
},
// 离开节点时调用
'FunctionDeclaration:exit'(path) {
console.log('离开函数');
}
}
};
};路径(Path)和节点(Node)
Path:表示两个节点之间的连接,包含节点和状态信息。
traverse(ast, {
VariableDeclaration(path) {
// 节点
console.log(path.node);
// 父节点
console.log(path.parent);
// 替换节点
path.replaceWith(t.functionDeclaration());
// 删除节点
path.remove();
// 插入兄弟节点
path.insertBefore(t.expressionStatement());
}
});实战 1:箭头函数转换插件
// arrow-function-plugin.js
module.exports = function(api) {
const t = api.types;
return {
visitor: {
ArrowFunctionExpression(path) {
// 转换为普通函数
const params = path.node.params;
const body = path.node.body;
// 如果 body 是表达式,需要包装为 block
let newBody = body;
if (!t.isBlockStatement(body)) {
newBody = t.blockStatement([
t.returnStatement(body)
]);
}
// 创建函数表达式
const funcExpr = t.functionExpression(
null, // 匿名函数
params,
newBody
);
// 替换
path.replaceWith(funcExpr);
}
}
};
};使用
module.exports = {
plugins: [
require('./arrow-function-plugin')
]
};转换前
const add = (a, b) => a + b;转换后
const add = function(a, b) {
return a + b;
};实战 2:console.log 移除插件
// remove-console-plugin.js
module.exports = function() {
return {
visitor: {
CallExpression(path) {
const { node } = path;
// 检查是否是 console.log
if (
node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'console' &&
node.callee.property.name === 'log'
) {
// 删除该语句
path.remove();
}
}
}
};
};使用
module.exports = {
plugins: [
require('./remove-console-plugin')
]
};转换前
console.log('debug');
const a = 1;转换后
const a = 1;实战 3:自定义语法糖
将 def 语法糖转换为 function。
// def-plugin.js
module.exports = function(api) {
const t = api.types;
return {
visitor: {
FunctionDeclaration(path) {
// 检查函数名是否是 def
if (path.node.id && path.node.id.name === 'def') {
const params = path.node.params;
const body = path.node.body;
// 创建新函数
const newFunc = t.functionDeclaration(
t.identifier(params[0].name),
params.slice(1),
body
);
// 替换
path.replaceWith(newFunc);
}
}
}
};
};使用
// 转换前
def greet(name) {
return `Hello, ${name}!`;
}
// 转换后
function greet(name) {
return `Hello, ${name}!`;
}Babel 在实际项目中的应用
与 Webpack 集成
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
}
]
}
};按需加载
// .babelrc
{
"plugins": [
["import", {
"libraryName": "antd",
"libraryDirectory": "es",
"style": "css"
}]
]
}常见问题和最佳实践
1. modules: false 配置
为了支持 Tree Shaking:
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
modules: false // 保持 ES Module 语法
}]
]
};2. useBuiltIns
module.exports = {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage', // 按需加载 polyfill
corejs: 3
}]
]
};3. 排除 node_modules
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
}学习资源推荐
- 官方文档:https://babeljs.io/docs
- AST Explorer:https://astexplorer.net/
- Babel 插件手册:https://github.com/jamiebuilds/babel-handbook
- @babel/types 文档:https://babeljs.io/docs/en/babel-types
总结
Babel 是前端开发者的必备工具,掌握 Babel:
- 可以理解代码转换原理
- 可以编写自定义插件
- 可以优化构建流程
- 可以提高代码质量
