我们写的箭头函数,如果要通过babel转换为普通的function 函数, 可以在babel配置中添加
shell
npm install --save-dev @babel/plugin-transform-arrow-functions
babel.config.json
json
{
"plugins": ["@babel/plugin-transform-arrow-functions"]
}
源码实现
我们看一下源码实现, 箭头函数babel-官方插件-源码地址
ts
import { declare } from "@babel/helper-plugin-utils";
export interface Options {
spec?: boolean;
}
export default declare((api, options: Options) => {
// 设置上下文babel版本
api.assertVersion(
process.env.BABEL_8_BREAKING && process.env.IS_PUBLISH
? PACKAGE_JSON.version
: 7,
);
const noNewArrows = api.assumption("noNewArrows") ?? !options.spec;
return {
// babel插件所需要的插件名
name: "transform-arrow-functions",
visitor: {
// 箭头函数钩子
ArrowFunctionExpression(path) {
// In some conversion cases, it may have already been converted to a function while this callback
// was queued up.
if (!path.isArrowFunctionExpression()) return;
if (process.env.BABEL_8_BREAKING) {
path.arrowFunctionToExpression({
// While other utils may be fine inserting other arrows to make more transforms possible,
// the arrow transform itself absolutely cannot insert new arrow functions.
allowInsertArrow: false,
noNewArrows,
});
} else {
path.arrowFunctionToExpression({
allowInsertArrow: false,
noNewArrows,
// This is only needed for backward compat with @babel/traverse <7.13.0
// @ts-ignore(Babel 7 vs Babel 8) Removed in Babel 8
specCompliant: !noNewArrows,
});
}
},
},
};
});
上面代码行数不多,主要是通过 path.arrowFunctionToExpression 这个方法实现的, 这个方法是谁提供的呢 ?
答: @babel/traverse
动手实现
现在我们重新写一个demo, 手动通过 @babel/traverse
实现实现一下,学习@babel/traverse
的使用
基础环境安装
建议使用 typescript编写, babel相关插件的文档已经断层, babel-handbook(非官网)已经很久不更新了,使用ts能检查出语法错误,参数提示
shell
pnpm init
安装babel相关的核心包
shell
pnpm add @babel/generator @babel/parser @babel/traverse @babel/types -S
- @babel/parser 把字符串解析成 AST
- @babel/traverse 操作/新增/删除/修改 AST节点
- @babel/types 手动构建AST节点
- @babel/generator 把处理完成后的AST节点树,变成代码, 最终是一个字符串
安装ts, 不想使用ts, 可以不装
shell
pnpm add typescript tslib ts-node -D
- tslib 与 ts-node 搭档, ts-node依赖的
- ts-node 直接运行ts文件
安装类型声明的包
sql
pnpm add @types/node @types/babel-types @types/babel__generator @types/babel__traverse -D
代码
测试代码 demo/07.js
js
const test = () => {
console.log(1);
}
var obj = {
say: () => {
// 包裹在对象里面
console.log('say log 哈哈哈');
}
}
function gogo() {
let a = 1
console.log(a);
return () => {
console.log(222);
}
}
src/07.ts
ts
/**
* 将箭头函数转换为普通函数
*/
import generate from '@babel/generator'
import { parse } from '@babel/parser'
import traverse from '@babel/traverse'
import fs from 'node:fs'
import path from 'node:path'
const target = path.resolve(__dirname, '../demo/07.js')
const content = fs.readFileSync(target, {
encoding: 'utf-8',
})
const ast = parse(content)
traverse(ast, {
/**
* 箭头函数钩子
*/
ArrowFunctionExpression(path) {
// 官方解释: 在某些转换情况下,此回调函数已经从箭头函数转成普通函数 function ,则不需要进一步处理
if (!path.isArrowFunctionExpression()) return
path.arrowFunctionToExpression({
allowInsertArrow: false
})
},
})
const dir = path.resolve(__dirname, '../demo/07-1.js')
fs.writeFileSync(dir, generate(ast).code)
执行 ts-node ./src/07.ts
js
const test = function () {
console.log(1);
};
var obj = {
say: function () {
// 包裹在对象里面
console.log('say log 哈哈哈');
}
};
function gogo() {
let a = 1;
console.log(a);
return function () {
console.log(222);
};
}
本质跟官方的一样,这个案例主要表达的是, 我们从字符串->AST -> 修改AST -> 还原代码
这个过程
- 我们使用fs 读取了文件
- 使用
parse
将字符串 解析为AST
- 通过
traverse
处理 箭头函数转换为 普通函数 - 最后将AST 变成字符串,写入文件
官方写的插件,本质也是调用 @babel/traverse
里面的方法,只是我们将它单独拿出来学习。
问题
我怎么知道要使用什么钩子处理AST?
babel中有N个方法,对应方法名字比较冗长复杂, 我们并不需要记忆, 我们只需要知道,对应包有什么方法,做什么即可
@babel/types
提供了手动构建AST节点的能力,里面能告知我们很多创建节点的方法,这个方法都对应到 @babel/traverse
里面的 钩子函数
,也就是上面我们可以看到的 ArrowFunctionExpression
, 我们就知道要在这里拦截,并对代码进行处理
手动构建箭头函数AST
上面我们既然安装了 @babel/types
, 现在我们手动构建一个箭头函数的AST, 看看如何实现
ts
/**
* 手动构建箭头函数 AST
*/
import generate from '@babel/generator'
import * as types from '@babel/types'
/**
* 目标:手动构建一个如下的箭头函数 AST 树
let a = () => {
console.log(1)
}
*/
const ast = types.variableDeclaration('let', [
types.variableDeclarator(
types.identifier('a'),
types.arrowFunctionExpression(
// 箭头函数 函数体body; 函数体body 可以有N条语句,所以是数组
[],
// 箭头函数 函数体body
types.blockStatement([
types.expressionStatement(
// console.log 属于函数调用,所有创建这样一个节点
types.callExpression(
types.memberExpression(
types.identifier('console'),
// log本身就是console对象上的属性
types.identifier('log')
),
// console.log的参数
[types.numericLiteral(1)]
)
),
])
)
),
])
// 将 AST 生成 代码, 这里的code 是字符串
// 剩下的,我们可以将代码写入文件等等操作, 也可以做其他改造
console.log(generate(ast).code)
执行后的效果
是不是有点懵逼, 这样的一个AST节点,我怎么知道,参数要传递什么, 我怎么知道要构建什么样的 AST节点?
答: 通过 AST explorer 我们将要生成的目标代码,简单写一下
js本身提示性不强,如果要编写手动构建AST节点的应用场景,建议使用ts, 能比较明确的告知我们要传递什么类型的节点
因为 babel本身在 plugin文档方面其实比较落后,插件的API比较多,无法一一列举,所以,需要自己学会举一反三
手动构建普通函数-AST
我一般会通过 ctrl+鼠标左键
跳进去看看类型
ts
// 构建一个普通函数
const ast2 = types.functionDeclaration(
// 创建一个标识符
types.identifier('a'),
// 函数的参数-形参
[],
// 函数体 - 存在多个的情况,所以是数组
types.blockStatement([
// 创建一个表达式
types.expressionStatement(
// 表达式调用
types.callExpression(
types.memberExpression(
// 调用的是 console 对象
types.identifier('console'),
// 第二个参数书属性,对应的是 console的log属性
types.identifier('log')
),
// 调用,传递的参数
[types.numericLiteral(1)]
)
),
])
)
console.log(generate(ast2).code)
输出效果:
js
function a() {
console.log(1);
}
本文中, 我们了解了 babel 将箭头函数转为普通函数的具体实现, 也从0开始使用 对应API, 手动实现了一下。
然后,我们通过在线网站,了解了 AST 树结构,根据树结构,我们一步一步通过手动的方式创建了 箭头函数
, 普通函数
的 AST 节点, 最后将AST还原成普通代码
希望对你学习AST有帮助。
喜欢就点个赞吧。😊😊😊