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节点

相关文章链接

相关推荐
正小安1 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光2 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   2 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   2 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web3 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常3 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇3 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr4 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho4 小时前
【TypeScript】知识点梳理(三)
前端·typescript