babel: 将单行声明改为多行-AST

当我们遇到代码中有大量如下代码,有时候格式化不一样的时候,是非常头痛的,有没有办法处理?

js 复制代码
let a,b,c,v,d;

var name,age,email, pageSize;

我们希望变成

js 复制代码
let a;
let b;
let c;
let v;
let d;

var name;
var age;
var email;
var pageSize;

答: 有的

今天我们通过AST 的方式操作一下, 顺便学习一下 babel 操作 AST

思路

  1. 拿到字符串(可以是当前ts/js中写的测试代码, 也可以是读取到的文件)
  2. 把字符串js代码或者 ts代码, 转成 AST
  3. 使用 traverse 遍历 AST, 根据 AST 特性,找到 VariableDeclaration 这个钩子,在这里做拦截处理
  4. 将多个变量声明变成同节点的单个变量声明(具体看下面代码实现), 需要手动构建相同的类型声明,比如之前使用let 或者const定义的, 拆分成多条声明语句,仍旧要保持一样的类型声明。

注意: 我们改变的是写法,并不是对原来代码做破坏性改动

  1. 使用 @babel/generatorAST还原成代码

环境安装

makefile 复制代码
node: 20.10.0
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, 可以不装

csharp 复制代码
pnpm add typescript tslib ts-node -D
  • tslib 与 ts-node 搭档, ts-node依赖的
  • ts-node 直接运行ts文件

安装类型声明的包

shell 复制代码
pnpm add @types/node @types/babel-types @types/babel__generator @types/babel__traverse -D

注意:

  • 不要把 package.json 中的 type设置为 module, 直接不设置这个字段
  • 修改ts.config.json里面的 module: "NodeNext", 其他配置可以不动, 保证 ts-node 运行ts文件的时候不会有问题

package.json

json 复制代码
"dependencies": {
    "@babel/generator": "^7.23.6",
    "@babel/parser": "^7.23.9",
    "@babel/traverse": "^7.23.9",
    "@babel/types": "^7.23.9"
  },
  "devDependencies": {
    "@types/babel-types": "^7.0.15",
    "@types/babel__core": "^7.20.5",
    "@types/babel__generator": "^7.6.8",
    "@types/babel__traverse": "^7.20.5",
    "@types/node": "^20.11.19",
    "ts-node": "^10.9.2",
    "tslib": "^2.6.2",
    "typescript": "^5.3.3",
  }

代码实现

测试代码: demo/05.js

js 复制代码
let a, b = 1

var c,d

const kk = 2

function test() {
  var b,g

  return () => {
    b = 10
    console.log(b);

    let n, m

    return {
      test() {
        var L1, L2
      }
    }
  }
}

demo.ts

ts 复制代码
import generate from '@babel/generator'
import { parse } from '@babel/parser'
import traverse from '@babel/traverse'
import * as types from '@babel/types'

import fs from 'node:fs'
import path from 'node:path'

const target = path.resolve(__dirname, '../demo/05.js')

const content = fs.readFileSync(target, {
    encoding: 'utf-8',
})

let ast = parse(content, {
    sourceType: 'module',
})

traverse(ast, {
    /**
     * 变量声明
     * @param path
     */
    VariableDeclaration(path) {
        let declarations = path.node.declarations
        // 只针对 path.node里面 声明有多个的情况处理,已经是单个的,不管
        if (declarations.length > 1) {
            const list = declarations.slice(1)

            const first = declarations.shift()
            // ts里面就需要这样做一下类型判断, 判断我们拿出来的节点是一个 类型声明节点
            if (types.isVariableDeclarator(first)) {
                // 把原来的多个声明改为一个
                path.node.declarations = [first]
            }

            // 注意: 
            // 这里要考虑2种情况,一是全局环境,也就是global 最外层的声明
            // 另一种是函数作用域里面的变量声明要改写
            if ((path.parent.type === 'Program' || path.parent.type === 'BlockStatement') && path.parent.body) {
                // 逐个遍历,把节点插入到父节点中
                list.forEach((item) => {
                    // 先拿到之前的声明,之前是 let, 改为多行声明仍旧用let,之前为 const,仍旧用const
                    let kind = path.node.kind
                    const currentNode = types.variableDeclaration(kind, [
                        types.variableDeclarator(
                            // 原来的名字
                            item.id,
                            // 原来的值
                            item.init
                        ),
                    ])

                    currentNode.declarations = [item]

                    // 在指定位置插入,不能直接  body.push
                    // body.splice(pos + 1, 0, currentNode)
                    path.insertAfter(currentNode)
                })
            }
        }
    }
})

console.log('----处理后的结果-----');

console.log(generate(ast).code)

运行:

bash 复制代码
ts-node demo.ts

结果:

js 复制代码
let a;
let b = 1;
var c;
var d;
const kk = 2;        
function test() {    
  var b;
  var g;
  return () => {     
    b = 10;
    console.log(b);  
    let n;
    let m;
    return {
      test() {       
        var L1;      
        var L2;      
      }
    };
  };
}

效果杠杠的。

总结

从上面案例中,你可以学习到

  • 学习了使用ts-node 运行typescript代码
  • 操作代码的AST, 完整思路
  • AST还原成代码
  • @babel/types 手动构建 AST节点

相关文章链接

相关推荐
sg_knight1 分钟前
VSCode如何修改默认扩展路径和用户文件夹目录到D盘
前端·ide·vscode·编辑器·web
一个处女座的程序猿O(∩_∩)O10 分钟前
完成第一个 Vue3.2 项目后,这是我的技术总结
前端·vue.js
mubeibeinv11 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
逆旅行天涯17 分钟前
【Threejs】从零开始(六)--GUI调试开发3D效果
前端·javascript·3d
m0_7482552639 分钟前
easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层
前端·excel
web147862107231 小时前
C# .Net Web 路由相关配置
前端·c#·.net
m0_748247801 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
飞的肖1 小时前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
青灯文案11 小时前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
m0_748254881 小时前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl