年初接到一个任务将 semi-ui 替换到 antd,但是能人力预算不太够,所以基于工作量和效率选择了基于 AST 的替换方案。
项目技术栈是 React + tsx
为什么要使用 AST?
AST(Abstract Syntax Tree,抽象语法树)是一种在计算机科学中表示源代码语法结构的树状数据结构。
通过 AST,可以理解代码的结构和含义,实现代码分析、转换和操作。
基于 AST 的替换方案可以快速而准确的对大量代码进行修改,提高开发效率和代码质量。
还有什么方案?
- 固定字符串替换。对于 import 和 tsx 都有严重的误伤。
- 正则替换,编写复杂,嵌套难以识别。
- 手动替换,重复性工作容易失误,耗费工时太多,适合小工作量的情况。
方案比对
方案 | 工作量 | 误伤 | import | 组件替换 | 组件属性替换 |
---|---|---|---|---|---|
固定字符串替换 | ✅ | 😭 | 😭 | 😒 | 😒 |
正则 | 😒 | 😭 | 😒 | 😒 | 😒 |
手动 | 😭 | ✅ | ✅ | ✅ | ✅ |
AST | ✅ | ✅ | ✅ | ✅ | ✅ |
Babel 简介
Babel 是前端领域的必备工具,是一种源码到源码的转译器。
在项目中可以让我们使用一些新的语法(展开运算符) 和 api(@babel/core),会在编译的过程中将 code 转为目标环境所支持的语法并引入 polyfill
Babel 实际应用场景
- 将
()=>{}
转为function(){}
- 为
Array.isArray
添加 polyfill
除了上面提到的编译转译代码之外,babel 也可以用来做静态分析,分析代码提取信息,然后生成文档
如:
- 自动国际化处理
- Linter
- 压缩混淆,删除死代码,变量名混淆。
- 模块遍历器,分析 import 移除未被使用的资源模块
Babel 编译原理
babel 是 source to source 的转换,整体编译流程分为三步:
• parse:通过 parser 把源码转成抽象语法树(AST)
• transform:遍历 AST,调⽤各种 transform 插件对 AST 进⾏增删改
• generate:把转换后的 AST 打印成⽬标代码,并⽣成 sourcemap
Babel 中常⻅节点
- Literal 是字⾯量的意思,比如说
'are you sure?'
- Statement 是语句
- Identifer 是标识符的意思
- Declaration 声明语句是⼀种特殊的语句,它执⾏的逻辑是在作⽤域内声明⼀个变量、函数、class、import、export 等。
- Expression 是表达式,特点是执⾏完以后有返回值,这是和语句 (statement) 的区别。
- Element 是 JSX,分为 JSXElement、JSXOpeningElement、JSXClosingElement 等等
Babel 中常⽤ API
- @babel/parser :babel parser 叫 babylon,是基于 acorn 实现的,扩展了很多语法,可以⽀
持 es next(现 在⽀持到 es2020)、jsx、flow、typescript 等语法的解析。babel parser 默认只
能 parse js 代码,jsx、flow、typescript 这些⾮标准的语法的解析需要指定语法插件。 - @babel/traverse :parse 出的 AST 由 @babel/traverse 来遍历和修改,⽀持指定要遍
历的 AST 节点,指定 visitor 函数。babel 会在遍历 parent 对应的 AST 时调⽤相应的 visitor 函 - 数。@babel/types :遍历 AST 的过程中需要创建⼀些 AST 和判断 AST 的类型,这时候就需要
@babel/types 包。t.isIfStatement 创建,t.assertIfStatement ⽤于判断。 - @babel/template :⽀持通过代码来⽣成 ast 进⾏替换。相⽐ parser ,template ⽀持不同粒
度 - @babel/generator :AST 转换完之后就要打印成⽬标代码字符串,通过
@babel/generator 包 - @babel/helper-module-imports
替换实战
存在的问题
- 项⽬技术栈 React + tsx
- 839 个⽂件中,使⽤ 63 个组件,共计 2485 次使⽤,最多⼀个组件被使⽤了 299 次。
- 项⽬周⼀、周三四,上线时间不定。更新频率较⾼
- 没有测试同学,研发⾃测。测试环境⾃测,⽆等待时间,即可上线
实战脚本
替换属性值&替换属性名
- Space 不⽀持 vertical 需要改为 direction="vertical"
- 替换 onChange 事件调⽤
- 提取 children 构建为 options
- 替换 Size 改为新的映射关系
- 将 Link 解构赋值
整体⽅案
- 统计待升级组件,确认替换范围和影响(839 个⽂件中,使⽤ 63 个组件,共计 2485 次使⽤)
- 项⽬格式化 npx eslint --fix ./src , prettier --write '*/.{tsx,ts}' , 暂
不开启编辑器⾃动格式化 - 分批替换(⻚⾯维度、组件维度)每周替换4个组件,以低频⾼优作为参考项。
- 与业务⽅沟通确定上线节奏及变更影响范围,周⼆推 test,周四推 online,提前⼀周在业务群公布。替换计划固定变更影响范围。
- 测试验收(⾃测&test环境)功能、样式
- ⽀持快速回滚。依赖 turbo 实现快速回滚,采⽤临时 release 分⽀,实现需求迭代和组件替换不冲突,遇到问题也可以快速回滚。
- 加⼊业务 feedback 群,遇到问题优先回滚⽌损。
- 所有替换⼯作完成后,开启⾃动格式化,移除⽆效代码。
优缺点&改进
优点:
• ⽀持增量替换,不阻塞业务开发
• ⽀持快速回滚
• 适合⼤批量重复性⼯作
• 适合 jsx 这种嵌套关系的内容修改,可以减少误伤
缺点:
• 有⼀定的学习成本(当然可以使⽤ GitHub Copilot 、ChatGpt 减少⼀些⼯作量),
• 需要收集组件差异(这⾥可以考虑提取属性和⽅法,然后抓取⽂档中的定义做⽐对)
• 编码成本(如果替换数量不多,可以考虑⼿动替换)
总结
最后我们再来回顾⼀下 @babel/parser 、 @babel/traverse 、 @babel/types 、
@babel/template 、 @babel/generator 这些库的作⽤。
也可以想想有什么应⽤场景是可以落地的?⽐如说
• ⾃动国际化,通过脚本提取所有⽂案。
• 增加⼀些个性化的格式化规则,⾃闭合标签,优化 imoprt 导⼊顺序。
传送⻔
- Babel 插件通关秘籍 - zxg_神说要有光 - 掘⾦⼩册
- https://astexplorer.net/
- Stackblitz 测试地址:Express Starter - StackBlitz
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。