背景
最近研究如何将 JSON 转换成 .vue 文件,JSON 格式如下:
{
"state": { "text": { "type": "JSExpression", "value": "\"outer\"", "compiled": "var text: string = \"outer\";" }},
"lifeCycles": { "onMounted": { "type": "JSFunction", "value": "()=>{console.log(\"did mount\", text);}", "compiled": "()=>{console.log(\"did mount\", text);}" }, "onBeforeMount": { "type": "JSFunction", "value": "()=>{console.log(\"will unmount\")}", "compiled": "()=>{console.log(\"will unmount\")}" }},
"methods": { "testFunc": { "type": "JSFunction", "value": "function testFunc() {\n console.log('test func', text);\n text = 'inner';\n}", "source": "function testFunc() {\n console.log('test func', text);\n text = 'inner';\n}" }},
"children": 主要存储组件信息,用于生成template
}
生成要求如下:
1)state 中所有的变量均采用 const 变量名 = ref(xxx) 的方式定义
2)methods 中如果 value 是匿名函数,则使用 const 方法名 = 函数内容 的方式定义
3)以 composition api 的方式组织代码
根据这个 JSON 处理出 js 部分的内容不是难点,主要要解决变量定义的问题,例如 text 生成的代码:
const text = ref('outer')
我们可以看到 JSON 中 methods 或 lifeCycles 有使用该变量,但是其用法并不正确,在 JS 中使用 ref 定义的变量,正确用法应该是:变量名.value,那么我们应该怎么处理才能满足要求呢?
解决思路
要解决这个问题,很多人可能下意识都会想到处理 string ,通过正则匹配等方式做替换。这种方案理论上也可行,看起来似乎很简单,但是极可能无法考虑到所有的场景,容易出问题。
而借助 babel 插件去解决这个问题将会非常简单,主要会用到以下3个插件:
1)@babel/parser 用来将代码转成 ast
2)@babel/traverse 遍历 ast
3)@babel/generate 将处理后的 ast 转成代码
我们再来看看函数中使用到变量的场景,主要有2个:赋值、作为参数传递,而 babel 在转换的过程中,会将这两种场景分别标注为 AssignmentExpression 和 CallExpression 中的 argements
想要探索 ast 结构的可以使用这个网站:astexplorer.net/
接下来直接上代码
import * as parser from '@babel/parser';
import traverse from '@babel/traverse';
import generate from '@babel/generator';
function rewriteMethod(fnString, state) {
const fnAst = parser.parse(fnString, {
plugins: ['typescript'],
})
traverse(fnAst, {
AssignmentExpression(path: any) {
const left = path.node.left;
if (state[left.name] && left.type === 'Identifier') {
left.name += '.value';
}
},
CallExpression(path: any) {
const args = path.node.arguments;
for (const arg of args) {
if (state[arg.name] && t.isIdentifier(arg)) {
arg.name += '.value'
}
}
}
})
return generate(fnAst).code
}
通过 traverse 遍历 ast ,并更新 ast 的部分内容,最后再将修改后的 ast 生成代码。
拓展一下
在babel 插件中,@babel/types 也常用于处理代码,主要用于重新生成函数、变量等。例如,我想将一个 ts 定义的变量或函数参数,处理成普通的 js 代码,便可先 parser 得到 ast,然后 traverse ast ,并使用 @babel/types 重新定义这些参数,最后 generate 将 ast 转成代码。
具体场景代码就不贴了,有兴趣欢迎探讨👏🏻
写在最后
最近也研究了如何将 Monaco 中写的 JS 代码转换成 JSON,同样使用了这4个 babel 插件,对于要重新分析代码做各种事情的场景都非常好用。