跳至主要內容

23. 代码转化


23. 代码转化

1. 遍历AST语法树

我们需要遍历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 属性,用于方便生成对应的代码,并且收集生成代码所需的方法。