@purevue/compiler-core
Version:
## 📖 Introduction
1,189 lines (1,178 loc) • 40.3 kB
JavaScript
import { isString, capitalize, camelize, isArray, isSymbol } from '@purevue/shared';
/**
* Vue3 模板编译器通用类型定义
* 包含模板解析(Parse)和转换(Transform)阶段常用的 AST 类型与上下文类型。
*/
/**
* 节点类型枚举,定义了所有可能的 AST 节点类型。
* @enum {string}
*/
var NodeTypes;
(function (NodeTypes) {
/** 根节点 */
NodeTypes["ROOT"] = "ROOT";
/** 元素节点 */
NodeTypes["ELEMENT"] = "ELEMENT";
/** 文本节点 */
NodeTypes["TEXT"] = "TEXT";
/** 简单表达式节点 */
NodeTypes["SIMPLE_EXPRESSION"] = "SIMPLE_EXPRESSION";
/** 插值节点 */
NodeTypes["INTERPOLATION"] = "INTERPOLATION";
/** 属性节点 */
NodeTypes["ATTRIBUTE"] = "ATTRIBUTE";
/** 指令节点 */
NodeTypes["DIRECTIVE"] = "DIRECTIVE";
/** 复合表达式节点 */
NodeTypes["COMPOUND_EXPRESSION"] = "COMPOUND_EXPRESSION";
/** 虚拟节点调用 */
NodeTypes["VNODE_CALL"] = "VNODE_CALL";
/** JS 对象表达式 */
NodeTypes["JS_OBJECT_EXPRESSION"] = "JS_OBJECT_EXPRESSION";
/** JS 属性 */
NodeTypes["JS_PROPERTY"] = "JS_PROPERTY";
/** 文本调用节点 */
NodeTypes["TEXT_CALL"] = "TEXT_CALL";
/** JS 函数调用表达式 */
NodeTypes["JS_CALL_EXPRESSION"] = "JS_CALL_EXPRESSION";
})(NodeTypes || (NodeTypes = {}));
/**
* 元素类型枚举,区分不同类型的元素节点。
* @enum {number}
*/
var ElementTypes;
(function (ElementTypes) {
/** 普通元素 */
ElementTypes[ElementTypes["ELEMENT"] = 0] = "ELEMENT";
/** 组件 */
ElementTypes[ElementTypes["COMPONENT"] = 1] = "COMPONENT";
/** 插槽 */
ElementTypes[ElementTypes["SLOT"] = 2] = "SLOT";
/** 模板容器 */
ElementTypes[ElementTypes["TEMPLATE"] = 3] = "TEMPLATE";
})(ElementTypes || (ElementTypes = {}));
/**
* 创建简单表达式节点
* @param content 表达式内容
* @param isStatic 是否静态表达式
*/
function createSimpleExpression(content, isStatic) {
return {
type: NodeTypes.SIMPLE_EXPRESSION,
content,
isStatic,
loc: undefined,
};
}
/**
* 创建对象属性节点,用于表示对象字面量中的一对 key-value
* @param key SimpleExpressionNode,表示属性名
* @param value SimpleExpressionNode,表示属性值
* @returns PropertyNode 对象
*/
function createObjectProperty(key, value) {
return {
type: NodeTypes.JS_PROPERTY,
key: typeof key === 'string' ? createSimpleExpression(key, true) : key,
value,
};
}
/**
* 创建复合表达式节点
* @param children 字符串或表达式节点数组,表示拼接内容
* @returns CompoundExpressionNode 对象
*/
function createCompoundExpression(children) {
return {
type: NodeTypes.COMPOUND_EXPRESSION,
children,
};
}
/**
* 创建一个 JS 调用表达式 AST 节点
* @param callee 调用的函数名,可以是字符串或 runtime helper 符号
* @param args 调用参数数组
* @returns CallExpression AST 节点
*/
function createCallExpression(callee, args = []) {
return {
type: NodeTypes.JS_CALL_EXPRESSION,
callee,
arguments: args,
};
}
function createInterpolation(content) {
return {
type: NodeTypes.INTERPOLATION,
content: isString(content) ? createSimpleExpression(content, false) : content,
};
}
function createElementNode(tag, props, children, tagType = ElementTypes.ELEMENT) {
// 判断是普通元素还是组件
if (tag === 'slot') {
tagType = ElementTypes.SLOT;
} /* else if (isFragmentTemplate(node)) {
tagType = ElementTypes.TEMPLATE
} */
else if (isComponent(tag)) {
tagType = ElementTypes.COMPONENT;
}
return {
type: NodeTypes.ELEMENT,
tag,
props,
children,
tagType,
};
}
/**
* 判断一个 AST 节点是否是组件
* @param node ElementNode
* @returns boolean
*/
function isComponent(tag) {
// 1. 大写字母开头(PascalCase)视为组件
if (/^[A-Z]/.test(tag)) {
return true;
}
// 2. 可能是 kebab-case 自定义组件(比如 my-button)
// 规则:标签中包含 "-"(HTML 保留标签名一般不含 -,除 web component 外)
if (tag.includes('-')) {
return true;
}
// 3. 内置组件
const builtInComponents = new Set(['KeepAlive', 'Teleport', 'Suspense']);
if (builtInComponents.has(tag)) {
return true;
}
// 其他情况视为普通元素
return false;
}
function createTextNode(content) {
return {
type: NodeTypes.TEXT,
content,
};
}
/**
* 创建 VNode 调用表达式
*/
function createVNodeCall(tag, props, children, _patchFlag) {
// TODO: patchFlag 用于 diff
return {
type: NodeTypes.VNODE_CALL,
tag,
props,
children,
};
}
function isText(node) {
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT;
}
/**
* 模板解析入口函数
* @param input 模板字符串,例如 "<div>{{ msg }}</div>"
* @returns 根节点 AST
*/
function baseParse(input) {
const root = createRoot([], input);
const context = createParserContext(input);
root.children = parseChildren(context).children;
return root;
}
function createRoot(children, source = '') {
return {
type: NodeTypes.ROOT,
source,
children,
helpers: new Set(),
components: [],
directives: [],
hoists: [],
codegenNode: undefined,
loc: undefined,
};
}
/**
* 创建解析上下文
* @param template 模板字符串
* @returns 解析上下文对象
*/
function createParserContext(template) {
return { source: template };
}
/**
* 解析子节点(可以是文本、插值、元素)
* @param context 解析上下文
* @param parentTag 可选,当前父标签名(用于检测对应的结束标签并停止解析)
* @returns Element 节点,tag 为 parentTag 或 'root'
*/
function parseChildren(context, parentTag) {
const nodes = [];
// 循环解析直到遇到结束条件
while (!isEnd(context)) {
if (context.source.startsWith('{{')) {
nodes.push(parseInterpolation(context));
continue;
}
if (context.source.startsWith('</')) {
const endMatch = /^<\/([a-zA-Z][\w-]*)\s*>/.exec(context.source);
if (endMatch) {
const endTagName = endMatch[1];
if (!parentTag) {
throw new Error(`缺少开始标签,遇到结束标签: </${endTagName}>`);
}
if (endTagName !== parentTag) {
throw new Error(`结束标签不匹配:遇到 </${endTagName}>,期待 </${parentTag}>`);
}
break;
}
else {
throw new Error('结束标签格式错误');
}
}
if (context.source.startsWith('<')) {
nodes.push(parseElement(context));
continue;
}
const textNode = parseText(context);
if (textNode !== null) {
nodes.push(textNode);
}
}
const tag = parentTag || 'root';
return createElementNode(tag, undefined, nodes);
}
/**
* 判断是否解析结束
* @param context 解析上下文
*/
function isEnd(context) {
return !context.source;
}
/**
* 解析插值节点 {{ expression }}
* @param context 解析上下文
*/
function parseInterpolation(context) {
const closeIndex = context.source.indexOf('}}', 2);
if (closeIndex === -1)
throw new Error('插值缺少结束符 }}');
const rawContent = context.source.slice(2, closeIndex).trim();
advanceBy(context, closeIndex + 2);
return createInterpolation(rawContent);
}
/**
* 解析元素节点 <tag ...> ... </tag>
* 支持标签名含大小写、数字、下划线、连接符
* 支持开始标签中包含属性,区分普通属性和指令
* @param context 解析上下文
*/
function parseElement(context) {
const startTagMatch = /^<([a-zA-Z][\w-]*)\b([^>]*)>/.exec(context.source);
if (!startTagMatch)
throw new Error('开始标签格式错误');
const tag = startTagMatch[1];
const attrString = startTagMatch[2] || '';
const props = parseAttributes(attrString);
advanceBy(context, startTagMatch[0].length);
const children = parseChildren(context, tag).children;
const endTagMatch = new RegExp(`^</${tag}\\s*>`).exec(context.source);
if (!endTagMatch)
throw new Error(`缺少结束标签: </${tag}>`);
advanceBy(context, endTagMatch[0].length);
return createElementNode(tag, props, children);
}
/**
* 解析属性字符串为属性数组,区分指令和普通属性
* @param attrString 属性字符串,如 ' id="foo" disabled @click="foo" :title="bar" v-if="ok"'
* @returns 属性对象数组
*/
function parseAttributes(attrString) {
const props = [];
const attrRE = /([^\s=]+)(?:="([^"]*)")?/g;
let match;
while ((match = attrRE.exec(attrString))) {
const rawName = match[1];
const value = match[2] !== undefined ? match[2] : '';
if (rawName.startsWith('v-')) {
const dirMatch = /^v-([a-zA-Z0-9\-]+)(?::([^\s]+))?/.exec(rawName);
if (dirMatch) {
const name = dirMatch[1];
const argStr = dirMatch[2];
const dirNode = {
type: NodeTypes.DIRECTIVE,
name,
// exp为空时,会保留空字符串,不会改为 true(没必要)
// 因为 html 会将空属性视为 true
exp: value ? createSimpleExpression(value, false) : undefined,
arg: argStr ? createSimpleExpression(argStr, true) : undefined,
modifiers: [],
};
props.push(dirNode);
continue;
}
}
else if (rawName.startsWith('@')) {
const eventName = rawName.slice(1);
const dirNode = {
type: NodeTypes.DIRECTIVE,
name: 'on',
exp: value ? createSimpleExpression(value, false) : undefined,
arg: createSimpleExpression(eventName, true),
modifiers: [],
};
props.push(dirNode);
continue;
}
else if (rawName.startsWith(':')) {
const bindName = rawName.slice(1);
const dirNode = {
type: NodeTypes.DIRECTIVE,
name: 'bind',
exp: value ? createSimpleExpression(value, false) : undefined,
arg: createSimpleExpression(bindName, true),
modifiers: [],
};
props.push(dirNode);
continue;
}
props.push({
type: NodeTypes.ATTRIBUTE,
name: rawName,
value,
});
}
return props;
}
/**
* 解析文本节点
* @param context 解析上下文
*/
function parseText(context) {
let endIndex = context.source.length;
const ltIndex = context.source.indexOf('<');
const delimiterIndex = context.source.indexOf('{{');
if (ltIndex !== -1 && ltIndex < endIndex)
endIndex = ltIndex;
if (delimiterIndex !== -1 && delimiterIndex < endIndex)
endIndex = delimiterIndex;
const content = context.source.slice(0, endIndex);
advanceBy(context, endIndex);
if (content.trim() === '') {
return null;
}
return createTextNode(content);
}
/**
* 向前推进解析位置
* @param context 解析上下文
* @param n 前进的字符数
*/
function advanceBy(context, n) {
context.source = context.source.slice(n);
}
// 简化版 transformBind,只保留核心功能:将 v-bind 转成属性键值对
const transformBind = (dir, node, context) => {
const arg = dir.arg;
let exp = dir.exp;
// const modifiers = dir.modifiers || []
// 如果没有表达式,尝试做同名简写,如 :foo 转成 :foo="foo"
if (!exp) {
// 这里简单处理为同名字符串表达式
if (arg.isStatic) {
exp = createSimpleExpression(arg.content, false);
}
else {
exp = createSimpleExpression('', true);
}
}
// 处理 .camel 修饰符,转驼峰名
// if (modifiers.some((mod) => mod.content === 'camel')) {
// if (arg.isStatic) {
// arg.content = camelize(arg.content)
// } else {
// arg.content = `${context.helper('camelize')}(${arg.content})`
// }
// }
// 处理动态参数,确保安全访问
if (!arg.isStatic) {
arg.content = arg.content ? `(${arg.content}) || ""` : `""`;
}
// 生成属性节点
const prop = createObjectProperty(arg, exp);
// 直接把转换结果放到元素节点 props 里(你也可以选择由调用方合并)
// node.props ||= []
// node.props.push({
// type: 'Attribute',
// name: arg.content,
// value: exp.content,
// })
return {
props: [prop],
};
};
/**
* 简化版 transformOn,处理 v-on 指令转换为普通属性
* 支持事件名转换为 onXxx 格式,表达式简单包装
* @param dir 指令节点
* @param node 元素节点
* @param context 转换上下文
* @returns 生成的属性节点数组
*/
const transformOn = (dir, node, context) => {
const arg = dir.arg;
let exp = dir.exp;
// const modifiers = dir.modifiers || []
// 事件名转驼峰并加 on 前缀,比如 click -> onClick
let eventName = '';
if (arg.isStatic) {
eventName = `on${capitalize(camelize(arg.content))}`;
}
else {
// 动态事件名,简单包裹
eventName = `on${capitalize(camelize(arg.content))}`;
}
// 处理空表达式,赋默认空函数
if (!exp || (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())) {
exp = createSimpleExpression('() => {}', false);
}
else {
// 简单包装成箭头函数表达式 (event) => handler(event)
exp = createCompoundExpression([`$event => (`, exp, `)()`]);
}
const prop = createObjectProperty(createSimpleExpression(eventName, true), exp);
return {
props: [prop],
};
};
/**
* 用于模板插值表达式,将值转换为字符串显示
*/
const TO_DISPLAY_STRING = Symbol('toDisplayString');
/**
* 用于运行时解析组件对象,在 render 阶段根据组件名返回组件实例或对象
*/
const RESOLVE_COMPONENT = Symbol('resolveComponent');
/**
* 用于创建虚拟节点 VNode,相当于 Vue 的 h()
*/
const CREATE_VNODE = Symbol('createVNode');
/**
* 用于创建普通元素的虚拟节点
*/
const CREATE_ELEMENT_VNODE = Symbol('createElementVNode');
/**
* 用于创建文本元素的虚拟节点
*/
const CREATE_TEXT = Symbol('createTextVNode');
/**
* Runtime helper 映射
* 用于 Codegen 阶段生成渲染函数时,将 helper Symbol 映射为字符串名称
* 方便生成 `_helperName()` 调用或解构 const { _helperName } = Vue
*/
const helperNameMap = {
[TO_DISPLAY_STRING]: 'toDisplayString',
[RESOLVE_COMPONENT]: 'resolveComponent',
[CREATE_VNODE]: 'createVNode',
[CREATE_ELEMENT_VNODE]: 'createElementVNode',
[CREATE_TEXT]: 'createTextVNode',
};
/**
* 创建 Transform 上下文
* @param root 根节点(用于可选扩展)
*/
function createTransformContext(root) {
const context = {
helpers: new Set(),
parent: null,
nodeTransforms: [transformExpression, transformElement, transformText],
directiveTransforms: {
bind: transformBind,
on: transformOn,
// TODO: 增加更多指令转换插件,如 v-model, v-show 等
},
helper(name) {
context.helpers.add(name);
return name;
},
currentNode: root,
};
return context;
}
/**
* 入口:执行 transform 阶段
* @param root 模板 AST 根节点
*/
function transform(root) {
const context = createTransformContext(root);
// 从根节点开始递归遍历 AST
traverseNode(root, context);
createRootCodegen(root);
// 把收集到的 helper 转成数组赋给 root
// root.helpers = [...context.helpers]
// root.helpers = new Set([...context.helpers.keys()])
root.helpers = context.helpers;
}
/**
* 深度优先遍历 AST,执行各个插件的转换
* 支持插件的先序执行和后序执行(enter/exit)
* @param node 当前节点
* @param context Transform 上下文
*/
function traverseNode(node, context) {
context.currentNode = node;
// 用于存储插件返回的 exit 函数,后序执行
const exitFns = [];
// 先序调用每个插件
const { nodeTransforms } = context;
for (let i = 0; i < nodeTransforms.length; i++) {
const onExit = nodeTransforms[i](node, context);
if (onExit)
exitFns.push(onExit);
// 节点可能被插件移除,若已不存在则中止遍历
if (!node)
return;
}
switch (node.type) {
case NodeTypes.INTERPOLATION:
context.helper(TO_DISPLAY_STRING);
break;
case NodeTypes.ELEMENT:
case NodeTypes.ROOT:
context.helper(CREATE_VNODE);
// 深度优先遍历
traverseChildren(node, context);
break;
}
context.currentNode = node;
// 后序执行插件返回的 exit 函数,顺序与先序相反
let i = exitFns.length;
while (i--) {
exitFns[i]();
}
}
function traverseChildren(parent, context) {
let i = 0;
// const nodeRemoved = () => {
// i--
// }
for (; i < parent.children.length; i++) {
const child = parent.children[i];
if (typeof child === 'string')
continue;
// context.grandParent = context.parent
context.parent = parent;
context.childIndex = i;
// context.onNodeRemoved = nodeRemoved
traverseNode(child, context);
}
}
/**
* 表达式前缀转换插件(简化版)
* - 给所有非静态 SimpleExpression 节点加 `_ctx.` 前缀
* - 同时递归处理 CompoundExpressionNode
*/
function transformExpression(node) {
// 处理插值表达式
if (node.type === NodeTypes.INTERPOLATION) {
const content = node.content;
if (content.type === NodeTypes.SIMPLE_EXPRESSION && !content.isStatic && !isAlreadyPrefixed(content.content)) {
content.content = prefixIdentifiers(content.content);
}
}
// 处理元素节点中的指令表达式
else if (node.type === NodeTypes.ELEMENT) {
if (!node.props)
return;
for (const prop of node.props) {
if (prop.type === NodeTypes.DIRECTIVE) {
// 处理指令表达式
if (prop.exp &&
prop.exp.type === NodeTypes.SIMPLE_EXPRESSION &&
!prop.exp.isStatic &&
!isAlreadyPrefixed(prop.exp.content)) {
prop.exp.content = prefixIdentifiers(prop.exp.content);
}
// 处理指令参数表达式
if (prop.arg &&
prop.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
!prop.arg.isStatic &&
!isAlreadyPrefixed(prop.arg.content)) {
prop.arg.content = prefixIdentifiers(prop.arg.content);
}
}
}
}
// 处理复合表达式,递归处理每个子节点
else if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
for (const child of node.children) {
if (typeof child === 'object') {
transformExpression(child);
}
}
}
}
/** 判断是否已经有 _ctx. 前缀 */
function isAlreadyPrefixed(content) {
return content.startsWith('_ctx.');
}
/**
* 插件示例:转换元素节点
* - 处理元素节点,后序生成 codegenNode
* - 收集 createElementVNode helper
* @param node 当前 AST 节点
* @param context Transform 上下文
*/
function transformElement(node, context) {
if (node.type !== NodeTypes.ELEMENT)
return;
// 在后序处理时生成 codegenNode:因为要等子节点(比如文本插值)都 transform 完
return () => {
const { tag, props } = node;
const isComponent = node.tagType === ElementTypes.COMPONENT;
let vnodeTag = isComponent ? resolveComponentType(node, context) : `${tag}`;
// 结构化 props 对象表达式
const { props: vnodeProps, patchFlag, dynamicProps } = buildProps(node, context, props || []);
// TODO: 真实源码会计算 patchFlag、dynamicProps 传入 createVNodeCall
// 生成 codegenNode,children 可能已经有 codegenNode
node.codegenNode = createVNodeCall(vnodeTag, vnodeProps, node.children.map((child) => {
if ('codegenNode' in child) {
// 如果子节点已经有 codegenNode,直接使用
return child.codegenNode;
}
// 如果没有 codegenNode,直接返回原节点(可能是插值或其他类型)
// 这里假设 c 已经是一个可以渲染的节点类型
return child;
})
// 这里简化不传 patchFlag, dynamicProps
);
};
}
/**
* 解析组件tag
* 仅支持用户组件(普通自定义组件)
* 其他类型(动态组件 / 内置组件)暂时留空 TODO
*/
function resolveComponentType(node, context) {
const { tag } = node;
// 1. 动态组件 <component :is="...">
// TODO: 未来支持 resolveDynamicComponent
// if (...) return `resolveDynamicComponent(...)`
// 2. 内置组件 <Teleport> / <KeepAlive> / <Transition>
// TODO: 未来支持内置组件符号
// if (...) return BuiltInComponent
// 3. 用户组件(默认情况)合法化
// 占位,将在运行时 resolveComponent 解析为子组件
context.helper(RESOLVE_COMPONENT);
return `_component_${tag.replace(/[^\w]/g, (searchValue, replaceValue) => {
return searchValue === '-' ? '_' : tag.charCodeAt(replaceValue).toString();
})}`;
}
/**
* 将属性数组转换成 ObjectExpression AST,并区分静态与动态属性
* 支持调用对应的指令转换插件 transformBind、transformOn 等
* 返回结构化的 props 和 patchFlag 信息
* @param props 属性和指令列表
* @param context transform 上下文
*/
function buildProps(node, context, props) {
if (props.length === 0)
return { props: undefined };
// 收集属性对象的属性节点(只处理静态属性)
// 指令本身需要复杂的逻辑(如事件修饰符、缓存等),直接放到 properties 会失去处理机会
const properties = [];
for (const prop of props) {
if (prop.type === NodeTypes.ATTRIBUTE) {
properties.push({
type: NodeTypes.JS_PROPERTY,
key: createSimpleExpression(prop.name, true),
value: createSimpleExpression(prop.value, true),
});
}
else if (prop.type === NodeTypes.DIRECTIVE) {
const directiveTransform = context.directiveTransforms[prop.name];
if (directiveTransform) {
const { props } = directiveTransform(prop, node, context);
if (props && props.length > 0) {
properties.push(...props);
}
}
}
}
// TODO: 这里应根据指令转换结果动态构造 ObjectExpression,同时处理 patchFlag、dynamicProps
// 当前简化:只返回静态属性合成的对象表达式
return {
props: {
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties,
},
};
}
/**
* transformText
*
* 该 transform 的主要职责:
* 1. 合并相邻的文本节点(TEXT / INTERPOLATION)
* 2. 将文本节点预转换成 `createTextVNode` 调用,避免 runtime 再做 normalize
*/
const transformText = (node, context) => {
// 只处理这些节点,它们可能包含文本 children
if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ELEMENT) {
// 在退出阶段处理(确保插值表达式已经 transform 完成)
return () => {
const children = node.children;
let currentContainer = undefined;
// 1. 遍历 children,合并相邻的文本,合并成一个 COMPOUND_EXPRESSION
// 目的:避免运行时创建多个 text vnode,节省 patch 时的对比开销
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (isText(child)) {
// 向后查找相邻的文本节点
for (let j = i + 1; j < children.length; j++) {
const next = children[j];
if (isText(next)) {
// 如果是第一次合并,创建一个 CompoundExpression
if (!currentContainer) {
currentContainer = children[i] = createCompoundExpression([child] // 初始子节点
);
}
// 将下一个节点合并进来
currentContainer.children.push(` + `, next);
children.splice(j, 1); // 移除已合并的节点
j--;
}
else {
currentContainer = undefined;
break;
}
}
}
}
// 2. 将 TEXT 或 COMPOUND_EXPRESSION 转换成 TEXT_CALL
// 目的:在 codegen 阶段 能一眼识别“这是个文本 vnode”
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {
children[i] = {
type: NodeTypes.TEXT_CALL,
content: child,
loc: child.loc,
// 生成 codegenNode => createTextVNode(...)
codegenNode: createCallExpression(context.helper(CREATE_TEXT), // runtime helper
[child] // 参数为文本本身
),
};
}
}
};
}
};
/**
* 工具:给表达式添加 _ctx. 前缀(极简)
*/
function prefixIdentifiers(content) {
// TODO: 这里极简示例,真实源码有复杂语法分析和作用域判断
return `_ctx.${content}`;
}
function createRootCodegen(root, context) {
const { children } = root;
if (children.length === 1) {
const child = children[0];
// 如果唯一的子节点是 ElementNode / VNodeCall
if (child.type === NodeTypes.ELEMENT && child.codegenNode) {
root.codegenNode = child.codegenNode;
}
// else {
// // Fragment 情况
// root.codegenNode = createVNodeCall(
// context,
// context.helper(FRAGMENT),
// undefined, // props
// children
// )
// }
}
// else if (children.length > 1) {
// // 多根节点一定会生成 Fragment
// root.codegenNode = createVNodeCall(context, context.helper(FRAGMENT), undefined, children)
// }
}
/**
* 创建 CodegenContext,负责代码字符串管理与缩进控制
* @returns CodegenContext 实例
*/
function createCodegenContext(ast, options) {
const codeParts = [];
let indentLevel = 0;
const { mode = 'function', runtimeGlobalName = `Vue`, runtimeModuleName = `vue` } = options || {};
const context = {
mode,
code: '',
runtimeModuleName,
runtimeGlobalName,
source: ast.source,
push(source) {
codeParts.push(source);
context.code = codeParts.join('');
},
newline() {
codeParts.push('\n' + ' '.repeat(indentLevel));
context.code = codeParts.join('');
},
indent() {
indentLevel++;
},
deindent() {
indentLevel = Math.max(0, indentLevel - 1);
},
helper(key) {
// 对应转换阶段收集的辅助函数,避免与用户变量冲突
return `_${helperNameMap[key]}`;
},
};
return context;
}
/**
* 生成渲染函数的入口函数
* @param root 已经经过 transform 的 Root AST,包含 codegenNode 与 helpers
* @returns 生成的渲染函数源码字符串
*/
function generate(root, options = {}) {
const context = createCodegenContext(root, options);
const { mode } = context;
// 根据 mode 决定 preamble 生成方式
if (mode === 'module') {
// 模块化构建模式,生成 import 语句 + 绑定优化
genModulePreamble(root, context);
}
else {
// 运行时编译模式 / 浏览器直接执行模板
genFunctionPreamble(root, context);
}
const functionName = 'render';
const args = ['_ctx', '_cache'];
const signature = args.join(', ');
context.push(`function ${functionName}(${signature}) {`);
// 生成函数主体:根据 codegenNode 生成渲染逻辑
if (!root.codegenNode) {
// 无 codegenNode 时,渲染函数返回 null
context.push('return null');
}
else {
context.push('return ');
genNode(root.codegenNode, context);
}
// 4. 结束函数体
context.deindent();
context.push('}');
return {
code: context.code,
ast: root,
preamble: '', // TODO
};
}
/**
* 生成模块导入前言,根据 root.helpers 生成 import 语句
* @param ast AST 根节点,包含 helpers 数组
* @param ctx Codegen 上下文
*/
function genModulePreamble(ast, ctx) {
const { runtimeModuleName, push, newline } = ctx;
const helpers = Array.from(ast.helpers);
// 生成 import 语句
if (helpers.length) {
// 在 import 阶段就加别名 import { xx as _xx } from 'vue'
const importStatements = helpers.map((h) => `${helperNameMap[h]} as _${helperNameMap[h]}`).join(', ');
push(`import { ${importStatements} } from ${JSON.stringify(runtimeModuleName)}\n`);
}
newline();
push(`export `);
}
/**
* 将 helper 名称映射为别名,例如 foo -> foo: _foo
* @param s helper 名称
* @returns 别名字符串
*/
const aliasHelper = (s) => `${helperNameMap[s]}: _${helperNameMap[s]}`;
/**
* 生成渲染函数的声明部分
* @param root AST 根节点
* @param ctx Codegen 上下文
*/
function genFunctionPreamble(ast, ctx) {
const { runtimeGlobalName } = ctx;
const helpers = Array.from(ast.helpers);
if (helpers.length > 0) {
ctx.push(`const { ${helpers.map(aliasHelper).join(', ')} } = ${runtimeGlobalName}\n`);
}
ctx.newline();
// 为渲染函数生成 return 关键字
ctx.push(`return `);
}
/**
* 根据 codegenNode 类型分发生成对应代码
* @param node codegenNode 节点
* @param ctx Codegen 上下文
*/
function genNode(node, ctx) {
if (!node)
return;
if (isString(node)) {
ctx.push(node);
return;
}
if (isSymbol(node)) {
ctx.push(ctx.helper(node));
return;
}
switch (node.type) {
case NodeTypes.VNODE_CALL:
genVNodeCall(node, ctx);
break;
case NodeTypes.SIMPLE_EXPRESSION:
genExpression(node, ctx);
break;
case NodeTypes.TEXT:
genText(node, ctx);
break;
case NodeTypes.INTERPOLATION:
genInterpolation(node, ctx);
break;
case NodeTypes.JS_OBJECT_EXPRESSION:
genObjectExpression(node, ctx);
break;
case NodeTypes.COMPOUND_EXPRESSION:
genCompoundExpression(node, ctx);
break;
case NodeTypes.TEXT_CALL:
genNode(node.codegenNode, ctx);
break;
case NodeTypes.JS_CALL_EXPRESSION:
genCallExpression(node, ctx);
break;
default:
// TODO: 拓展其他类型
ctx.push('null');
}
}
/**
* 判断 tag 是否是用户组件占位符
* Vue3 编译阶段常用 `_component_X` 作为占位符
* @param tag vnode.tag
*/
function isComponentTag(tag) {
// 简化判断:以 `_component_` 开头的都是用户组件
return typeof tag === 'string' && tag.startsWith('_component_');
}
/**
* 获取真实组件名
* @param tag vnode.tag
*/
function getComponentName(tag) {
if (!isComponentTag(tag))
return tag;
return tag.replace(/^_component_/, '').replace(/_/g, '-');
}
/**
* 生成虚拟节点调用的代码
* @param node VNodeCall 类型节点
* @param context 代码生成上下文
*/
function genVNodeCall(node, context) {
const { push, helper } = context;
// 生成 createElementVNode 函数调用代码
push(`${helper(CREATE_VNODE)}(`);
// 参数1:type 节点类型
// 如果是组件,需要把 __component__xx 解析为 xx 组件的 options
if (isComponentTag(node.tag)) {
// 调用 helper 生成 resolveComponent 引用
const resolveComp = helper(RESOLVE_COMPONENT);
push(`${resolveComp}('${getComponentName(node.tag)}', _ctx)`);
}
else {
push(JSON.stringify(node.tag));
}
push(', ');
// 参数2:prop 处理节点的属性
if (node.props) {
genNode(node.props, context);
}
else {
push('null');
}
push(', ');
// 参数3:处理节点的子节点
// if (Array.isArray(node.children)) {
// // 多个子节点 -> 数组
// genChildrenArray(node.children, context)
// } else if (typeof node.children === 'string') {
// // 文本子节点 -> JSON.stringify
// push(JSON.stringify(node.children))
// } else if (node.children) {
// // 单个节点 -> 递归生成
// genNode(node.children, context)
// } else {
// push('null')
// }
const children = node.children;
if (isArray(children)) {
genNodeListAsArray(children, context);
}
else if (typeof node.children === 'string') {
// 文本子节点 -> JSON.stringify
push(JSON.stringify(node.children));
}
else {
push('null');
}
push(')');
}
/**
* 生成文本节点代码,直接字符串化
* @param node TextNode 节点
* @param ctx Codegen 上下文
*/
function genText(node, ctx) {
ctx.push(JSON.stringify(node.content));
}
/**
* 生成对象表达式的代码
* @param node ObjectExpression 类型节点
* @param context 代码生成上下文
*/
function genObjectExpression(node, context) {
const { push } = context;
push('{');
node.properties.forEach((prop, i) => {
genNode(prop.key, context);
push(': ');
genNode(prop.value, context);
if (i < node.properties.length - 1)
push(', ');
});
push('}');
}
/**
* 生成复合表达式(CompoundExpression)的代码
* @param node CompoundExpressionNode 节点
* @param context CodegenContext 上下文
*/
function genCompoundExpression(node, context) {
// 遍历 children,字符串直接输出,节点递归调用 genNode
node.children.forEach((child) => {
if (typeof child === 'string') {
// 字符串片段直接输出
context.push(child);
}
else {
// 嵌套表达式递归处理
genNode(child, context);
}
});
}
/**
* 生成简单表达式代码
* @param node SimpleExpressionNode 节点
* @param ctx Codegen 上下文
*/
function genExpression(node, context) {
const { content, isStatic } = node;
context.push(isStatic ? JSON.stringify(content) : content);
}
/**
* 生成插值表达式代码,调用 toDisplayString helper
* @param node InterpolationNode 节点
* @param ctx Codegen 上下文
*/
function genInterpolation(node, context) {
context.push(`${context.helper(TO_DISPLAY_STRING)}(`);
genNode(node.content, context);
context.push(`)`);
}
/**
* 生成函数调用表达式的代码
* @param node 要生成的 CallExpression AST 节点
* @param context Codegen 上下文,包含 push、helper、pure 等信息
*/
function genCallExpression(node, context) {
const { push, helper } = context;
// 1. 处理 callee:如果是字符串,直接用;如果是 symbol,调用 helper 注册
const callee = helper(node.callee);
// 2. 输出函数名和左括号
push(callee + '(');
// 3. 输出参数列表
genNodeList(node.arguments, context);
// 4. 输出右括号结束函数调用
push(')');
}
/**
* 生成节点列表代码,用于函数调用、数组参数或对象属性等场景
* @param nodes 节点列表,可以是字符串、symbol、CodegenNode 或嵌套的 TemplateChildNode 数组
* @param context Codegen 上下文,包含 push、newline、helper 等方法
* @param multilines 是否多行输出(每个节点换行)
* @param comma 是否在节点之间加逗号
*/
function genNodeList(nodes, context, multilines = false, comma = true) {
const { push, newline } = context;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
// 1. 判断节点类型并调用对应生成函数
if (isString(node)) {
// 字符串直接输出
push(node);
}
else if (isArray(node)) {
// 嵌套数组递归生成
genNodeListAsArray(node, context);
}
else {
// 其他 CodegenNode 或 TemplateChildNode 调用通用生成器
genNode(node, context);
}
// 2. 如果不是最后一个节点,根据配置输出逗号和换行
if (i < nodes.length - 1) {
if (multilines) {
comma && push(',');
newline();
}
else {
comma && push(', ');
}
}
}
}
/**
* 将节点列表生成数组字面量代码
* @param nodes - 要生成的节点列表,可包含字符串、CodegenNode 或嵌套数组
* @param context - 代码生成上下文
*/
function genNodeListAsArray(nodes, context) {
// 输出数组左括号
context.push(`[`);
// 调用 genNodeList 生成数组元素
genNodeList(nodes, context);
context.push(`]`); // 输出数组右括号
}
/**
* 编译模板字符串或 AST 根节点,生成渲染函数代码。
*
* @param {string | RootNode} source - 模板字符串或已经解析好的 AST 根节点。
* @returns {string} 生成的渲染函数代码字符串。
*
* 该函数执行以下步骤:
* 1. 如果传入的是字符串,则调用 `baseParse` 将模板解析成 AST。
* 2. 对 AST 进行转换,执行各种编译阶段的转换操作。
* 3. 根据转换后的 AST 生成最终的代码字符串。
*/
function baseCompile(source, options = {}) {
// 判断传入的 source 是否为字符串,如果是,则解析成 AST,否则直接使用传入的 AST
const ast = isString(source) ? baseParse(source) : source;
// 对 AST 进行转换,处理指令、表达式等编译阶段的转换逻辑
transform(ast);
// 根据转换后的 AST 生成渲染函数代码
const res = generate(ast, options);
return res;
}
export { CREATE_ELEMENT_VNODE, CREATE_TEXT, CREATE_VNODE, ElementTypes, NodeTypes, RESOLVE_COMPONENT, TO_DISPLAY_STRING, baseCompile, baseParse, genCallExpression, genNodeList, generate, helperNameMap, resolveComponentType, transform, transformExpression, transformText, traverseChildren };
//# sourceMappingURL=index.esm.js.map