如何将vue2源码一键升级到vue3(1)

一天,在我喝着咖啡改着bug的时候,领导过来慢悠的问了一句:xx有没有办法把vue2的代码改造成vue3的? 我们有个项目需要.... 我就知道这杯咖啡买对了。

分析

我们知道,在vue3中是可以使用选项式api风格来书写我们的代码,所以我们把重点放在 Vue 2 对 Vue 3 的所有非兼容性改变上。没错,我们要读取源码并将不兼容的写法进行改造。

思路

获取源码抽象语法树(ast),通过babel工具链进行解析-改造-生成,需要准备以下工具包:

  • babel-parser: 解析源代码,生成AST。
  • babel-traverse: 遍历AST,找到对应的节点。
  • babel-types: 生成AST节点。
  • babel-generator: 通过改造后的ast生成源代码。

工具包下载方式

关于AST

源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构。我们写一段js代码来看一下这颗树的细节。(不同解析器表现形式不同,以下内容通过babel-parser进行解析)。ast在线转换网站

我们现在对ast有了基本的印象,接下来我们通过借助babel工具链去生成ast并改造它。

配合babel工具包改造ast

1.删除节点,尝试删除console.log()。

js 复制代码
//引入所需工具包
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;

//我们先尝试一下去掉源代码中的console.log
/**
 * 转换函数 
 * @param {string} source 源代码
 * @returns {string} 转换后的代码
 */
 function fillterCode(source) {
    // 1. 生成AST
    const ast = parser.parse(source, {
        sourceType: 'script'
    });
    // 2. 遍历AST,找到对应的节点
    traverse(ast, {
        // 遍历所有的函数调用表达式
        CallExpression(path) {
            // 判断是否是console.log节点
            if (t.isMemberExpression(path.node.callee)
                && t.isIdentifier(path.node.callee.object, { name: 'console' })
                && t.isIdentifier(path.node.callee.property, { name: 'log' })) {
                // 删除节点
                path.remove();
            }
        }
    });
    // 3. 生成代码
    return generator(ast).code;
}
// 定义源代码
const sourceCode = `
function add(a, b) {
  console.log(a + b);
  return a + b;
}
`
// 调用转换函数
const resultCode = fillterCode(sourceCode);
console.log(resultCode);

traverse方法内的 CallExpression 代表函数调用表达式类型,path.node 对应当前节点,babel可以针对语法树中特定类型进行遍历,在if判断条件中,我们使用了babel-types 提供的api(查询babel-types的api)去判断节点类型与节点的标识符是否对应,符合条件进行path.remove()删除该节点。 可以配合ast在线转换网站查询节点信息。

2.修改节点:将函数名改为 minus,a + b 改为 a - b。

js 复制代码
    traverse(ast, {
        // 遍历 函数调用表达式 类型的节点
        CallExpression(path) {...
        },
        // 遍历 函数声明 类型的节点
        FunctionDeclaration(path) {
            if (path.node.id.name === 'add') {
                path.node.id.name = 'minus';
            }
        },
        // 遍历 二元运算表达式 类型的节点
        BinaryExpression(path) {
            if (path.node.operator === '+') {
                path.node.operator = '-';
            }
        }
    });

3.新增节点:方法体新增条件判断, a < b 时抛出异常

js 复制代码
    traverse(ast, {
        // 遍历 函数调用表达式 类型的节点
        CallExpression(path) {...
        },
        // 遍历 函数声明 类型的节点
        FunctionDeclaration(path) {...
        },
        // 遍历 二元运算表达式 类型的节点
        BinaryExpression(path) {...
        },
        // 方法体新增条件判断, a < b 时抛出异常
        BlockStatement(path) {
            path.node.body.unshift(
                t.ifStatement(
                    t.binaryExpression('<', t.identifier('a'), t.identifier('b')),
                    // 抛出异常
                    t.throwStatement(new t.identifier('new Error("a < b")')),
                )
            );
        }
    });

通过在ast转换网站查询树结构,我们知道方法体在ast树中type为BlockStatement,它包含了一个名为body的数组,通过unshift在数组首位插入了一个if语句,这个if语句由babel-types提供的api进行生成。

运行以上代码打印结果:

结语

其实到这里我们就知道了下一步需要做什么,无非就是列举vue2到vue3不兼容的api或者语法,通过自定义loader在编译层一个个的攻破,或者是直通过源码生成一份新的兼容vue3的代码进行替换。接下来我会从这两个方面分别进行,也希望各位伙伴能提出宝贵的建议,共同学习。

相关推荐
lovingsoft26 分钟前
Chrome Lighthouse 详解
前端·chrome
Qlly36 分钟前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝40 分钟前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦41 分钟前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡1 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI1 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
汉堡大王95271 小时前
单次输出拉到100万Token:拆完 Gemini 4 Argon 的续写协议,才看懂谷歌这7个月在憋什么
前端·人工智能·后端
看浪的路人1 小时前
第9讲:大模型应用监控与可观测性——OpenTelemetry + Prometheus + Grafana 全链路追踪
java·前端·spring
内存过载1 小时前
用表格模拟甘特图:项目排期的土办法
前端·低代码
葡萄城技术团队2 小时前
用 SpreadJS 数据验证实现省市区三级联动
前端