Vue官方例子
依旧是根据Vue编译器生成code string来实现
用例
import { codegen } from "../src/codegen";
test("interpolation", () => {
const template = "{{message}}";
const ast = baseParse(template);
console.log("baseP", ast);
transform(ast);
const code = codegen(ast);
expect(code).toMatchSnapshot();
});
实现
抽取公用常量
如果有去玩编译器不难发现,每个类型对应的一些函数引用不是一样的,为了提高代码健壮性,我们可以把对应类型的函数常量抽取出来
runtimeHelpers.ts
/*
* @Author: Lin ZeFan
* @Date: 2022-04-17 12:10:36
* @LastEditTime: 2022-04-17 12:10:36
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\src\compiler-core\src\runtimeHelpers.ts
*
*/
// 插值
export const TO_DISPLAY_STRING = Symbol('toDisplayString')
// 映射表,解决Symbol不能当做string引用的问题
export const HelperNameMapping = {
[TO_DISPLAY_STRING]: 'toDisplayString',
}
处理头部函数引入
上面的截图可以看到,处理插值
类型的时候,会引入对应的函数
根据类型,添加头部函数
拓展context
// transform.ts
function createTransformContext(root: any, options: any) {
const { nodeTransforms = [] } = options;
const context = {
root,
nodeTransforms: nodeTransforms || [],
helpers: new Map(),
helper(name: string) {
this.helpers.set(name, 1);
},
};
return context;
}
根据类型添加头部引入
import { TO_DISPLAY_STRING } from "./runtimeHelpers";
function traverseNode(node, context) {
const { nodeTransforms } = context;
for (let index = 0; index < nodeTransforms.length; index++) {
const transform = nodeTransforms[index];
transform(node);
}
// 在这里遍历整棵树的时候,将根据不同的 node 的类型存入不同的 helper
switch (node.type) {
case NodeType.INTERPOLATION:
context.helper(TO_DISPLAY_STRING);
break;
case NodeType.ROOT:
case NodeType.ELEMENT:
// 只有在 ROOT 和 ELEMENT 才会存在 children
traverseChildren(node, context);
break;
default:
break;
}
}
引入函数
import { HelperNameMapping, TO_DISPLAY_STRING } from "./runtimeHelpers";
export function codegen(ast) {
const context = createCodeGenContext();
const { push } = context;
// 处理头部函数引入
if (ast.helpers.length) {
genFunctionPreamble(ast, context);
}
// other code
}
function genFunctionPreamble(ast: any, context) {
// 引入都来自 Vue
const VueBinding = "Vue";
const { push, addLine } = context;
// 因为是Symbol,需要用映射表匹配
const aliasHelper = (s) =>
`${HelperNameMapping[s]} as _${HelperNameMapping[s]}`;
// 处理头部引入
push(`const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinding}`);
addLine();
}
function createCodeGenContext() {
const context = {
code: "",
push(source: string) {
context.code += source;
},
// 换行
addLine() {
context.code += "\n";
},
};
return context;
}
根据类型处理return内容
拓展context函数,新增helper查询
function createCodeGenContext() {
const context = {
code: "",
helper(key) {
return `_${HelperNameMapping[key]}`;
},
push(source: string) {
context.code += source;
},
addLine() {
context.code += "\n";
},
};
return context;
}
上节只处理了 text 的情况,但实际上node类型至少有3种,所以我们要拓展一下,根据type处理
function genNode(node, context) {
// 根据 node 的类型进行不同的处理
switch (node.type) {
case NodeType.TEXT:
genText(node, context);
break;
case NodeType.INTERPOLATION:
genInterpolation(node, context);
break;
case NodeType.SIMPLE_EXPRESSION:
genExpression(node, context);
break;
}
}
function genExpression(node, context) {
// 处理 SIMPLE_EXPRESSION
const { push } = context;
push(`_ctx.${node.content}`)
}
function genInterpolation(node, context) {
const { push, helper } = context;
// 插值对应引用的函数
push(`${helper(TO_DISPLAY_STRING)}(`);
// 前面处理插值类型的时候,真正的值是包在content.content里的
// { type: NodeType.INTERPOLATION, content: { type: NodeType.SIMPLE_EXPRESSION, content: 'message'} }
genNode(node.content, context);
push(`)`);
}
function genText(node, context) {
const { push } = context;
push(`'${node.content}'`);
}
处理 SIMPLE_EXPRESSION 类型返回值
为了让代码可维护性更高,我们把 SIMPLE_EXPRESSION
内部返回值抽离做一层封装,用plugin
的形式,让 transfrom
帮我们处理 SIMPLE_EXPRESSION
返回的 content
修改用例
import { codegen } from "../src/codegen";
import { transformExpression } from "../src/transforms/transformExpression";
test("interpolation", () => {
const template = "{{message}}";
const ast = baseParse(template);
console.log("baseP", ast);
transform(ast, {
// 处理插值类型content
nodeTransforms: [transformExpression],
});
const code = codegen(ast);
expect(code).toMatchSnapshot();
});
处理插值类型的plugin
transformExpression.ts
/*
* @Author: Lin ZeFan
* @Date: 2022-04-10 10:45:42
* @LastEditTime: 2022-04-17 12:19:33
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\src\compiler-core\src\transforms\transformExpression.ts
*
*/
import { NodeType } from "../ast";
// 处理多层包装
export function transformExpression(node) {
if (node.type === NodeType.INTERPOLATION) {
node.content = processExpression(node.content);
}
}
function processExpression(node) {
node.content = `_ctx.${node.content}`;
return node;
}
修改 genExpression
因为通过 transformExpression
帮我们处理了深层的content,所以我们直接取值即可,不用再手动去拼接 _ctx
// codegen.ts
function genExpression(node, context) {
// 处理 SIMPLE_EXPRESSION
const { push } = context;
push(`'${node.content}'`);
}