1

年初接到一个任务将 semi-ui 替换到 antd,但是能人力预算不太够,所以基于工作量和效率选择了基于 AST 的替换方案。

项目技术栈是 React + tsx

为什么要使用 AST?

AST(Abstract Syntax Tree,抽象语法树)是一种在计算机科学中表示源代码语法结构的树状数据结构
通过 AST,可以理解代码的结构和含义,实现代码分析、转换和操作
基于 AST 的替换方案可以快速而准确的对大量代码进行修改,提高开发效率和代码质量。

还有什么方案?

  1. 固定字符串替换。对于 import 和 tsx 都有严重的误伤。
  2. 正则替换,编写复杂,嵌套难以识别。
  3. 手动替换,重复性工作容易失误,耗费工时太多,适合小工作量的情况。

方案比对

方案工作量误伤import组件替换组件属性替换
固定字符串替换😭😭😒😒
正则😒😭😒😒😒
手动😭
AST

Babel 简介

Babel 是前端领域的必备工具,是一种源码到源码的转译器。
在项目中可以让我们使用一些新的语法(展开运算符) 和 api(@babel/core),会在编译的过程中将 code 转为目标环境所支持的语法并引入 polyfill

Babel 实际应用场景

  1. ()=>{} 转为 function(){}
  2. Array.isArray 添加 polyfill

除了上面提到的编译转译代码之外,babel 也可以用来做静态分析,分析代码提取信息,然后生成文档
如:

  1. 自动国际化处理
  2. Linter
  3. 压缩混淆,删除死代码,变量名混淆。
  4. 模块遍历器,分析 import 移除未被使用的资源模块

Babel 编译原理

babel 是 source to source 的转换,整体编译流程分为三步:
• parse:通过 parser 把源码转成抽象语法树(AST)
• transform:遍历 AST,调⽤各种 transform 插件对 AST 进⾏增删改
• generate:把转换后的 AST 打印成⽬标代码,并⽣成 sourcemap

Babel 中常⻅节点

  1. Literal 是字⾯量的意思,比如说 'are you sure?'
  2. Statement 是语句
  3. Identifer 是标识符的意思
  4. Declaration 声明语句是⼀种特殊的语句,它执⾏的逻辑是在作⽤域内声明⼀个变量、函数、class、import、export 等。
  5. Expression 是表达式,特点是执⾏完以后有返回值,这是和语句 (statement) 的区别。
  6. Element 是 JSX,分为 JSXElement、JSXOpeningElement、JSXClosingElement 等等

image.png

Babel 中常⽤ API

  1. @babel/parser :babel parser 叫 babylon,是基于 acorn 实现的,扩展了很多语法,可以⽀
    持 es next(现 在⽀持到 es2020)、jsx、flow、typescript 等语法的解析。babel parser 默认只
    能 parse js 代码,jsx、flow、typescript 这些⾮标准的语法的解析需要指定语法插件。
  2. @babel/traverse :parse 出的 AST 由 @babel/traverse 来遍历和修改,⽀持指定要遍
    历的 AST 节点,指定 visitor 函数。babel 会在遍历 parent 对应的 AST 时调⽤相应的 visitor 函
  3. 数。@babel/types :遍历 AST 的过程中需要创建⼀些 AST 和判断 AST 的类型,这时候就需要
    @babel/types 包。t.isIfStatement 创建,t.assertIfStatement ⽤于判断。
  4. @babel/template :⽀持通过代码来⽣成 ast 进⾏替换。相⽐ parser ,template ⽀持不同粒
  5. @babel/generator :AST 转换完之后就要打印成⽬标代码字符串,通过
    @babel/generator 包
  6. @babel/helper-module-imports

替换实战

存在的问题

  1. 项⽬技术栈 React + tsx
  2. 839 个⽂件中,使⽤ 63 个组件,共计 2485 次使⽤,最多⼀个组件被使⽤了 299 次。
  3. 项⽬周⼀、周三四,上线时间不定。更新频率较⾼
  4. 没有测试同学,研发⾃测。测试环境⾃测,⽆等待时间,即可上线

实战脚本

替换属性值&替换属性名

  1. Space 不⽀持 vertical 需要改为 direction="vertical"
  2. 替换 onChange 事件调⽤
  3. 提取 children 构建为 options
  4. 替换 Size 改为新的映射关系
  5. 将 Link 解构赋值

整体⽅案

  1. 统计待升级组件,确认替换范围和影响(839 个⽂件中,使⽤ 63 个组件,共计 2485 次使⽤)
  2. 项⽬格式化 npx eslint --fix ./src , prettier --write '*/.{tsx,ts}' , 暂
    不开启编辑器⾃动格式化
  3. 分批替换(⻚⾯维度、组件维度)每周替换4个组件,以低频⾼优作为参考项。
  4. 与业务⽅沟通确定上线节奏及变更影响范围,周⼆推 test,周四推 online,提前⼀周在业务群公布。替换计划固定变更影响范围。
  5. 测试验收(⾃测&test环境)功能、样式
  6. ⽀持快速回滚。依赖 turbo 实现快速回滚,采⽤临时 release 分⽀,实现需求迭代和组件替换不冲突,遇到问题也可以快速回滚。
  7. 加⼊业务 feedback 群,遇到问题优先回滚⽌损。
  8. 所有替换⼯作完成后,开启⾃动格式化,移除⽆效代码。

优缺点&改进

优点:
• ⽀持增量替换,不阻塞业务开发
• ⽀持快速回滚
• 适合⼤批量重复性⼯作
• 适合 jsx 这种嵌套关系的内容修改,可以减少误伤

缺点:
• 有⼀定的学习成本(当然可以使⽤ GitHub Copilot 、ChatGpt 减少⼀些⼯作量),
• 需要收集组件差异(这⾥可以考虑提取属性和⽅法,然后抓取⽂档中的定义做⽐对)
• 编码成本(如果替换数量不多,可以考虑⼿动替换)

总结

最后我们再来回顾⼀下 @babel/parser 、 @babel/traverse 、 @babel/types 、
@babel/template 、 @babel/generator 这些库的作⽤。

也可以想想有什么应⽤场景是可以落地的?⽐如说
• ⾃动国际化,通过脚本提取所有⽂案。
• 增加⼀些个性化的格式化规则,⾃闭合标签,优化 imoprt 导⼊顺序。

传送⻔

  1. Babel 插件通关秘籍 - zxg_神说要有光 - 掘⾦⼩册
  2. https://astexplorer.net/
  3. Stackblitz 测试地址:Express Starter - StackBlitz

linong
29.2k 声望9.5k 粉丝

Read-Search-Ask