AST--使用编译的方式修改代码

最近有一个面板搭建的需求,环境为react,而现有面板组件为vue。
就有了现在这个转换需求~
AST(抽象语法树):顾名思义,它把代码拆解成了树的形式。
接下来我会以代码增删改查的形式带大家初步了解"代码编辑"!

  • 查:先看再用!(为什么加后缀,因为觉得内容不够丰满)
  • 增:无中生有!(为什么加后缀,因为觉得内容不够丰满)
  • 删:罢黜百家!(为什么加后缀,因为觉得内容不够丰满)
  • 改:偷天换日!(为什么加后缀,因为觉得内容不够丰满)
  • 如果你正有vue-to-react需求,那么这是我想说的

一、查:先看再用

我保证要是没有它,你会很痛苦 ast explorer

我们以js代码为例:

  1. 打开必需品:ast explorer
  2. 选择JavaScript 和 babylon7
  3. 输入const a = 1 + 1
  4. 右边就会有代码树
    在这里插入图片描述
    也就是拆解到拆不了为止,我们需要的其实就是它的type以及值而已,type就是定义这个数据是什么类型的东西,分了很多类,我们可以在 @babel/types 找到相应的type。
    path.skip() 执行之后,就不会在对叶节点进行遍历
    path.stop() 执行之后,就会去下次遍历

二、增:无中生有!

现在咋们就来把 const a = 1 + 1 生成出来!为了让新手也能一起动手操练起来,咱们就从新建项目开始。

  1. 首先我们要创建项目文件夹
  2. 快速创建package.json$ npm init -yes
  3. 下载依赖 : npm install @babel/parser @babel/types @babel/generator @babel/traverse
  • @babel/parser => 字符串代码解析成语法树
  • @babel/types => 用来生成和检测数据类型
  • @babel/generator => 语法树转成代码字符串
  • @babel/traverse => 这次没用到 用来访问语法树,可以在里面进行节点删改
  1. 项目下创建index.js
  2. 写入:
const parse = require('@babel/parser').parse;
const t = require('@babel/types');
const generate = require('@babel/generator').default;
// const traverse = require('@babel/traverse').default

const code = '';
const ast = parse(code);

/**
 * 便于理解我一步步拆解,这个其实就是拆解代码的逆过程 
 * 刚接触的话可以根据ast explore中生成的语法树中的type,
 * 去到@babel/type中查找相应type,会有相应的方法和入参说明
 */

// 1.生成数字 1
const number = t.numericLiteral(1);
// 2.生成二元表达式 1 + 1
const exp = t.binaryExpression('+', number, number);
// 3.生成变量 a
const varible = t.identifier('a');
// 4.生成变量声明 a = 1 + 1
const declarations = t.variableDeclarator(varible, exp);
// 5.生成变量声明 const a = 1 + 1
const content = t.variableDeclaration('const', [declarations]);

// 将内容放入body中
ast.program.body.push(content);

const output = generate(ast, { quotes: 'single', retainLines: true });
console.log(output.code); // const a = 1 + 1 

三、删改:为什么放一起,因为改其实就是先删后增

  1. 替换: replaceWith() replaceWithSourceString()
    我们把const a = 1 + 1 改为 1 * 1,这个时候traverse就用上了
    咱们把之前生成的ast放进来
traverse(ast, {
 // 使用相应type来快速访问节点,这里快速来到二元表达式节点,即 1 + 1
 // 并使用path.replaceWith() 将节点 1 + 1替换
 BinaryExpression(path) { 
   if (path.node.operator === '+') {
     path.replaceWith(t.binaryExpression('*', path.node.left, path.node.right))
   }
 }
})

tips:对于简单的静态节点还可以直接使用**path.replaceWithSourceString(‘a*b’)**来达到目的!!!

  1. 插入操作: pushContainerunshiftContainerinsertBeforeinsertAfter
  • pushContainer:针对子节点为数组时,为数组push一个node
   const code = `
       const obj = {
           a: 'a',
           b: 'b'
       }`;
   
   const ast = parse(code);
   
   const property = t.objectProperty(t.identifier('c'), t.stringLiteral('c'));
   traverse(ast, {
     ObjectExpression(path) {
       path.pushContainer('properties', property);
     }
   });
  • unshiftContainer: 与 pushContainer 对应 =》 unshift
  • insertAfter 兄弟节点的插入操作
  1. 删除节点 path.remove()

四、vue-to-react

vue 有template、script、style 三部分
本人的方案:
style 根据lang 生成 对应style 文件在react文件直接引用
script 解析成使用@babel/parser 生成 ast,操作ast
template 使用vue-template-compiler插件解析,生成ast,根据vue语法树来生成render代码
npm包地址 vue-to-react-tool
本人目前实现的是

  • 研究vue组件转react
    目前已成功转换
  • v-if、v-else-if、v-else
  • v-for
  • v-show
  • v-bind
    v-bind:attr.sync = xxx> // 双向绑定的特殊情况
    v-bind:attr=xxx
    v-on:emiterName ==> emiterName={(new) => this.setState({xxx:new})
  • v-model:与v-bind:attr.sync 类似
  • v-on
  • v-text
    =》{{msg}}
  • v-html => dangerousHtml
  • class => className (考虑class v-bind:class同时存在的情况)
  • data() => this.state
  • Props => props
  • {{ expression }} => { expression }
  • 组件名转驼峰
  • created: ‘componentWillMount’,
  • mounted: ‘componentDidMount’,
  • updated: ‘componentDidUpdate’,
  • beforeDestroy: ‘componentWillUnmount’,
  • errorCaptured: ‘componentDidCatch’,
  • template => render
  • style => index.(css | stylus | sass | less) (目前考虑)
  • 移除ts type功能
  • this.$refs
  • V-for V-if v-show 同时存在的情况

仍需要处理

  • 事件修饰符:
  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
  • v-on:attr = handle => v-on 暂不支持模板字符串型表达式
  • v-bind.sync=“doc” => 暂不支持用对象设置多个props
  • watch
  • Vux / vue-router(目前需求是组件、模块的转换,无需,看后续需求在考虑)
  • 。。。

目前是对我们的一个vue组件库进行转换,不过实际的代码情况会更加复杂,开发同学的编码习惯差别也很大,还需要针对各种情况详细处理。同时此方案也可以运用于小程序代码互转等场景中,所以我认为学写一下还是不错的,对代码编译的过程能更加深入了解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值