23. 代码转化
23. 代码转化
AST
语法树
1. 遍历我们需要遍历ast
语法树,访问树中节点进行语法树的转化
function transformElement(node) {
console.log('元素处理', node);
}
function transformText(node) {
console.log('文本处理', node);
}
function transformExpression(node) {
console.log('表达式');
}
function traverseNode(node, context) {
context.currentNode = node;
const transforms = context.nodeTransforms;
for (let i = 0; i < transforms.length; i++) {
transforms[i](node, context); // 调用转化方法进行转化
}
switch (node.type) {
case NodeTypes.ELEMENT:
case NodeTypes.ROOT:
for (let i = 0; i < node.children.length; i++) {
context.parent = node;
traverseNode(node.children[i], context);
}
}
}
function createTransformContext(root) {
const context = {
currentNode: root, // 当前转化节点
parent: null, // 当前转化节点的父节点
nodeTransforms: [
// 转化方法
transformElement,
transformText,
transformExpression
],
helpers: new Map(), // 创建帮助映射表,记录调用方法次数
helper(name) {
const count = context.helpers.get(name) || 0;
context.helpers.set(name, count + 1);
return name;
}
};
return context;
}
function transform(root) {
// 创建转化的上下文, 记录转化方法及当前转化节点
let context = createTransformContext(root);
// 递归遍历
traverseNode(root, context);
}
export function compile(template) {
const ast = baseParse(template);
transform(ast);
}
2. 退出函数
表达式不需要退出函数,直接处理即可。元素需要在遍历完所有子节点在进行处理
function transformExpression(node) {
if (node.type == NodeTypes.INTERPOLATION) {
console.log('表达式');
}
}
function transformElement(node) {
if (node.type === NodeTypes.ELEMENT) {
return function postTransformElement() {
// 元素处理的退出函数
// 如果这个元素
console.log('元素', node);
};
}
}
function transformText(node) {
if (node.type === NodeTypes.ELEMENT || node.type === NodeTypes.ROOT) {
return () => {
console.log('元素/root', node);
};
}
}
function traverseNode(node, context) {
// ...
for (let i = 0; i < transforms.length; i++) {
let onExit = transforms[i](node, context); // 调用转化方法进行转化
if (onExit) {
exitsFns.push(onExit);
}
if (!context.currentNode) return;
}
// ...
// 最终context.currentNode 是最里面的
context.currentNode = node; // 修正currentNode;
let i = exitsFns.length;
while (i--) {
exitsFns[i]();
}
}
3.转化表达式
runtimeHelpers.ts
export const TO_DISPLAY_STRING = Symbol(`toDisplayString`);
export const helperNameMap = {
[TO_DISPLAY_STRING]: 'toDisplayString'
};
switch (node.type) {
case NodeTypes.INTERPOLATION:
context.helper(TO_DISPLAY_STRING); // 用于JSON.stringify
break;
// ...
}
最后生成的代码我们取值时需要从上下文中进行取值。
export function transformExpression(node) {
if (node.type == NodeTypes.INTERPOLATION) {
node.content.content = `_ctx.${node.content.content}`; // 修改content信息
}
}
4.转化文本元素
文本元素的转化我们需要将多个文本连接起来,如果是动态文本添加patchFlags
标识,最终生成文本调用逻辑
function isText(node) {
return node.type == NodeTypes.INTERPOLATION || node.type == NodeTypes.TEXT;
}
export function transformText(node, context) {
if (node.type === NodeTypes.ELEMENT || node.type === NodeTypes.ROOT) {
return () => {
// 如果这个元素
let hasText = false;
const children = node.children;
let currentContainer = undefined; // 合并儿子
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (isText(child)) {
hasText = true;
for (let j = i + 1; j < children.length; j++) {
const next = children[j];
if (isText(next)) {
if (!currentContainer) {
currentContainer = children[i] = {
// 合并表达式
type: NodeTypes.COMPOUND_EXPRESSION,
loc: child.loc,
children: [child]
};
}
currentContainer.children.push(` + `, next);
children.splice(j, 1);
j--;
} else {
currentContainer = undefined;
break;
}
}
}
}
if (!hasText || children.length == 1) {
// 一个元素不用管,可以执行innerHTML
return;
}
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {
const callArgs = [];
callArgs.push(child);
if (child.type !== NodeTypes.TEXT) {
// 如果不是文本
callArgs.push(PatchFlags.TEXT + '');
}
// 全部格式话成文本调用
children[i] = {
type: NodeTypes.TEXT_CALL, // 最终需要变成createTextVnode() 增加patchFlag
content: child,
loc: child.loc,
codegenNode: createCallExpression(context, callArgs) // 创建表达式调用
};
}
}
};
}
}
runtimeHelpers.ts
export const CREATE_TEXT = Symbol(`createTextVNode`);
export const helperNameMap = {
[CREATE_TEXT]: `createTextVNode`
};
ast.ts
export function createCallExpression(context, args) {
let callee = context.helper(CREATE_TEXT); // 生成代码时需要createTextVNode方法
return {
callee,
type: NodeTypes.JS_CALL_EXPRESSION,
arguments: args
};
}
5.转化元素
export function createObjectExpression(properties) {
return {
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties
};
}
export function transformElement(node, context) {
if (node.type === NodeTypes.ELEMENT) {
return function postTransformElement() {
// 元素处理的退出函数
let vnodeTag = `'${node.tag}'`;
let properties = [];
let props = node.props;
for (let i = 0; i < props.length; i++) {
// 这里属性其实应该在codegen里在处理
properties.push({
key: props[i].name,
value: props[i].value.content
});
}
const propsExpression =
props.length > 0 ? createObjectExpression(properties) : null;
let vnodeChildren = null;
if (node.children.length === 1) {
// 只有一个孩子节点 ,那么当生成 render 函数的时候就不用 [] 包裹
const child = node.children[0];
vnodeChildren = child;
} else {
if (node.children.length > 0) {
// 处理儿子节点
vnodeChildren = node.children;
}
}
// 代码生成
node.codegenNode = createVNodeCall(
context,
vnodeTag,
propsExpression,
vnodeChildren
);
};
}
}
runtimeHelpers.ts
export const CREATE_ELEMENT_VNODE = Symbol('createElementVNode');
export const helperNameMap = {
[CREATE_ELEMENT_VNODE]: 'createElementVNode' // 创建元素节点标识
};
export function createVNodeCall(context, tag, props, children) {
context.helper(CREATE_ELEMENT_VNODE);
return {
type: NodeTypes.VNODE_CALL,
tag,
props,
children
};
}
6.转化根节点
转化根节点需要添加 block 节点
export const FRAGMENT = Symbol('FRAGMENT');
export const CREATE_ELEMENT_BLOCK = Symbol(`createElementBlock`);
export const OPEN_BLOCK = Symbol(`openBlock`);
export const helperNameMap = {
[FRAGMENT]: 'Fragment',
[OPEN_BLOCK]: `openBlock`, // block处理
[CREATE_ELEMENT_BLOCK]: `createElementBlock`
};
function createTransformContext(root) {
const context = {
removeHelper(name) {
const count = context.helpers.get(name);
if (count) {
const currentCount = count - 1;
if (!currentCount) {
context.helpers.delete(name);
} else {
context.helpers.set(name, currentCount);
}
}
}
// ...
};
return context;
}
function createRootCodegen(root, context) {
let { children } = root;
if (children.length == 1) {
const child = children[0];
if (child.type === NodeTypes.ELEMENT && child.codegenNode) {
const codegenNode = child.codegenNode;
root.codegenNode = codegenNode;
context.removeHelper(CREATE_ELEMENT_VNODE); // 不要创建元素
context.helper(OPEN_BLOCK);
context.helper(CREATE_ELEMENT_BLOCK); // 创建元素block就好了
root.codegenNode.isBlock = true; // 只有一个元素节点,那么他就是block节点
} else {
root.codegenNode = child; // 直接用里面的节点换掉
}
} else {
root.codegenNode = createVNodeCall(
context,
context.helper(FRAGMENT),
undefined,
root.children
);
context.helper(OPEN_BLOCK);
context.helper(CREATE_ELEMENT_BLOCK);
root.codegenNode.isBlock = true; // 增加block fragment
}
}
export function transform(root) {
// 创建转化的上下文, 记录转化方法及当前转化节点
let context = createTransformContext(root);
// 递归遍历
traverseNode(root, context);
createRootCodegen(root, context); // 生成根节点的codegen
root.helpers = [...context.helpers.keys()];
}
整个 transform 的流程,就是给 ast 节点添加 codegenNode 属性,用于方便生成对应的代码,并且收集生成代码所需的方法。