代码转换实战:ES6 → ES5
本节目标
- 用前面学的 visitor + t 写一个 ES6 → ES5 转译插件
- 覆盖三种常见转换:箭头函数 → 普通函数、let/const → var、模板字符串 → 字符串拼接
- 体会“转换(Transform)”这一步的真实形态
Babel 最常见用途就是把新版 JS 转成旧版(兼容老浏览器)。这正是“在 AST 上做修改”。我们一次性实现三个小转换。注意:真实项目请用 @babel/preset-env,这里手写是为了让你看清原理——你写的每个转换,和 preset-env 内部做的事是同一类。
动手:一个迷你“ES6→ES5”插件
// 运行环境:Node.js 18+,需安装: npm install @babel/core @babel/types
// 运行方式:把整段保存为 app-l9.cjs,终端执行 node app-l9.cjs
const babel = require('@babel/core')
const t = require('@babel/types')
// 我们的插件:返回 visitor,对三类节点分别做转换
function babelPluginEs6ToEs5() {
return {
visitor: {
// ① 箭头函数 -> 普通函数表达式
ArrowFunctionExpression(path) {
const { params, body } = path.node
// 箭头函数体若是表达式(如 n * 2),包成 return 语句
const fnBody = body.type === 'BlockStatement'
? body
: t.blockStatement([t.returnStatement(body)])
const fn = t.functionExpression(null, params, fnBody)
path.replaceWith(fn)
},
// ② let / const -> var
VariableDeclaration(path) {
if (path.node.kind === 'let' || path.node.kind === 'const') {
path.node.kind = 'var'
}
},
// ③ 模板字符串 `a${x}b` -> 字符串拼接 'a' + x + 'b'
TemplateLiteral(path) {
const { quasis, expressions } = path.node
let result = t.stringLiteral(quasis[0].value.cooked) // 第一段静态文本
for (let i = 0; i < expressions.length; i++) {
result = t.binaryExpression('+', result, expressions[i]) // + 表达式
const tail = quasis[i + 1].value.cooked // 表达式后面的静态文本
if (tail !== '') result = t.binaryExpression('+', result, t.stringLiteral(tail))
}
path.replaceWith(result)
}
}
}
}
// === 调用示例 ===
const code = [
"const greet = (name) => {",
" const msg = `hi ${name}`;",
" return msg;",
"};"
].join('\n')
const out = babel.transformSync(code, { plugins: [babelPluginEs6ToEs5] })
console.log('--- 转换结果(ES5)---')
console.log(out.code)
// 大致输出:
// var greet = function (name) {
// var msg = 'hi ' + name;
// return msg;
// };名词解释
转译(Transpile):把一种语言 / 版本的代码转换成另一种(通常是更旧或更低级)代码,且保持语义不变。Babel 的 ES6→ES5 就是典型转译。它和“编译”常被混用,但转译特指“源和目标都是高级语言”(如 JS→JS),编译则常指高级语言→机器码。
预设(Preset):一组“预先打包好的插件集合”。例如 @babel/preset-env 内部就是几十个做不同语法降级的小插件。你写 presets: ['@babel/preset-env'],等价于一次性挂上这整组插件,省得自己逐个列。可以把预设理解为“插件的套餐”。
课后练习
练习 1:本节的插件把下面这段代码转成了什么?请写出关键变化。
const greet = (name) => { const msg = `hi ${name}`; return msg; }答案:三处变化——① 箭头函数变普通
function表达式;②const变var;③ 模板字符串hi ${name}被拆成字符串拼接'hi ' + name。最终大致是var greet = function (name) { var msg = 'hi ' + name; return msg; };。
练习 2:教程强调“真实项目请用 @babel/preset-env,而非手写这些转换”,为什么?
答案:手写只覆盖你想到的一小部分语法,且要手动跟进每年新增的语法;
@babel/preset-env会按你设定的目标浏览器,自动决定该降级哪些语法、用哪些插件,全面且可持续维护。手写只适合用来“看清原理”,不适合当生产方案。
本节小结(观点与完整描述)
本节让你亲手实现了 Babel 最常见的用途——把新版 JS 转成旧版。你看到的不是一个黑盒,而是一个插件同时挂多个 visitor 节点类型、各管各转换的朴素组合:箭头函数→普通函数、let/const→var、模板字符串→拼接。两个细节值得一提:箭头函数体若是“表达式”要记得补 return 语句,模板字符串要拆成“静态文本 + 表达式 + 静态文本”的拼接链。但要清醒——你写的这些只是“原理演示”,真实工程请用 @babel/preset-env,它才是按目标浏览器自动选插件的完整方案。理解了本节,你就知道每天跑的 Babel 到底在背后干了什么。下一步,我们把“转换”接到“lint 与自动修复”上,给流水线收个尾。