[Vue] TemplateAST to JsAST
我们现在已经有了模板AST,那么根据之前的顺序,我们现在需要把模板AST转化为JSAST。
要知道,模板AST是对模板的描述,那么JSAST就是对JS代码的描述。
也就是说,我们最终希望得到的代码是下面这样的:
function render(){
return h('div',[
h('p','Vue'),
h('p','Template')
])
}
那JSAST,其实就是对这段代码的描述,而这个描述,应该是从模板AST转换而来的。
所以本质上,我们首先应该知道,上面这段JS代码,应该怎么被描述,简单来说,我们应该设计一些数据结构来描述渲染函数的代码。最终,我们只需要在转换函数中,将模板AST,依据我们需要的描述就行转换,就形成了JSAST。
渲染函数的描述
function render(){
return ...
}
根据函数声明语句,我们可以确定以下的特点
- id:函数名称,它是一个标识符Identifier
- params:函数参数,它是一个数组
- body:函数体,由于函数体可以包含多个语句,因此它也是一个数组
所以,一个针对render函数最简单的数据结构描述,可以声明成下面这个样子
const FunctionDeclNode = {
type: 'FunctionDecl' // 代表该节点是函数声明
id: {
type: 'Identifier',
name: 'render' //渲染函数名称标识
},
params:[], // 参数
body:[
{
type: 'ReturnStatement' // 返回
return: null // 暂时设置为null
}
]
}
而具体的函数体中,首先就是一个h函数的调用
h(......)
我们使用下面的形式描述函数调用语句:
const CallExp = {
type: 'CallExpression',
callee: {
type: 'Identifier', // 被调用函数的名称标识符
name: 'h'
},
arguments: [] // 参数
}
类型为CallExpression的节点拥有两个属性
- callee:描述被调用函数的名称,本身是一个标识符节点
- arguments:被调用函数的形参,多个参数使用数组来描述
而,具体h函数的参数,是这样的:
h('div',[/*......*/])
第一个参数就是一个字符串字面量,我们可以定义描述的数据结构
const Str = {
type: 'StringLiteral',
value: 'div'
}
第二个参数是一个数组, 同样定义描述的数据结构
const Arr = {
type: 'ArrayExpression',
elements: []
}
最终,我们把描述结构结合到一起:
// 最终的render函数
function render(){
return h('div',[
h('p','Vue'),
h('p','Template')
])
}
const FunctionDeclNode = {
type: "FunctionDecl", // 代表该节点是函数声明
// 函数的名称是一个标识符,标识符本身也是一个节点
id: {
type: "Identifier",
name: "render",
},
params: [], // 参数
// 渲染函数的函数体只有一个return 语句
// body 是一个数组,一个函数体可以包含多个语句,每个语句都是一个节点
body: [
{
type: "ReturnStatement",
// 最外层的 h 函数调用
return: {
type: "CallExpression",
callee: { type: "Identifier", name: "h" },
arguments: [
// 第一个参数是字符串字面量 'div'
{
type: "StringLiteral",
value: "div",
},
// 第二个参数是一个数组
{
type: "ArrayExpression",
elements: [
// 数组的第一个元素是 h 函数的调用
{
type: "CallExpression",
callee: { type: "Identifier", name: "h" },
arguments: [
// 该 h 函数调用的第一个参数是字符串字面量
{ type: "StringLiteral", value: "p" },
// 第二个参数也是一个字符串字面量
{ type: "StringLiteral", value: "Vue" },
],
},
// 数组的第二个元素也是 h 函数的调用
{
type: "CallExpression",
callee: { type: "Identifier", name: "h" },
arguments: [
{ type: "StringLiteral", value: "p" },
{ type: "StringLiteral", value: "Template" },
],
},
],
},
],
},
},
],
};
所以,我们现在任务,就是编写转换函数,将模板AST转换为上面的JSAST描述
首先编写一些辅助函数用来创建上面说到的字符串字面量节点(StringLiteral),标识符节点(Identifier),数组表达式节点(ArrayExpression),函数调用表达式节点(CallExpression)
function createStringLiteral(value) {
return {
type: "StringLiteral",
value,
};
}
function createIdentifier(name) {
return {
type: "Identifier",
name,
};
}
function createArrayExpression(elements) {
return {
type: "ArrayExpression",
elements,
};
}
function createCallExpression(callee, arguments) {
return {
type: "CallExpression",
callee: createIdentifier(callee),
arguments,
};
}
接下来,当然就是在转换函数上做做文章了。再次去改写我们之前已经写过的transformText和transformElement函数
function transformElement(node) {
return () => {
if (node.type !== "Element") {
return;
}
// 1.创建h函数调用语句
const callExp = createCallExpression("h", [
createStringLiteral(node.tag),
]);
// 2.处理h函数调用参数
node.children.length === 1
// 如果只有一个子节点,直接传入子节点的jsNode作为参数
? callExp.arguments.push(node.children[0].jsNode)
// 如果有多个子节点,创建一个ArrayExpression节点作为参数
: callExp.arguments.push(
createArrayExpression(node.children.map((c) => c.jsNode))
);
// 3.将当前标签节点对应的JSAST添加到jsNode属性上
node.jsNode = callExp;
};
}
function transformText(node, context) {
if (node.type !== "Text") {
return;
}
node.jsNode = createStringLiteral(node.content);
}
当上面这两个只是用来描述渲染函数render的返回值的,所以我们还需要把render函数本身的函数声明语句节点附加到JSAST中
function transformRoot(node) {
return () => {
// 如果不是root节点,直接返回
if (node.type !== "Root") {
return;
}
// node.children[0]是根节点的第一个子节点
// 不考虑多个根节点的情况
const vnodeJSAST = node.children[0].jsNode;
// 创建render函数的声明语句节点
// 将vnodeJSAST作为render函数的返回值
node.jsNode = {
type: "FunctionDecl",
id: { type: "Identifier", name: "render" },
params: [],
body: [
{
type: "ReturnStatement",
return: vnodeJSAST,
},
],
};
};
}
当然,我们在transform函数中进行调用即可:
function transform(ast) {
const context = {
//......
nodeTransforms: [transformRoot, transformElement, transformText],
};
//......
}

浙公网安备 33010602011771号