UNPKG

@purevue/compiler-core

Version:

## 📖 Introduction

1,189 lines (1,178 loc) 40.3 kB
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