import { h, type VNode } from 'vue'; import { parseDocument } from 'htmlparser2'; import type { Element, Text, Node } from 'domhandler'; /** 标签映射的类型:key=标签名,value=渲染函数 */ export type HtmlRenderMap = Record< string, (node: Element, children: VNode[]) => VNode >; export interface HtmlRenderOptions { /** 自定义标签渲染映射 */ map?: HtmlRenderMap; /** 默认是否将未知标签渲染为原生标签(默认 true) */ allowUnknownTags?: boolean; } export const useHtmlRenderer = ( html: string, options: HtmlRenderOptions = {} ) => { const { map = {}, allowUnknownTags = true } = options; const doc = parseDocument(html); function render(node: Node): VNode | string | null { // 文本节点 if (node.type === 'text') { const textNode = node as Text; const content = textNode.data; // ❗忽略"纯空白" 文本(换行、空格) if (!content || /^\s+$/.test(content)) { return null; } return content; } // 标签节点 if (node.type === 'tag') { const elem = node as Element; const rawName = elem.name ?? ''; const name = rawName.trim().toLowerCase(); // 标签名有效性校验 const isValidTag = /^[a-zA-Z][a-zA-Z0-9-]*$/.test(name); if (!isValidTag) { logger.warn(`Invalid tag name ignored: "${rawName}"`); return null; } const children: VNode[] = (elem.children || []) .map((child) => render(child)) .filter(Boolean) as VNode[]; // 自定义映射 if (map[name]) { return map[name](elem, children); } // 默认将未知标签渲染为原生标签 if (allowUnknownTags) { return h(name, elem.attribs || {}, children); } // 忽略无法渲染节点 return null; } return null; } const nodes = doc.children .map((child) => render(child)) .filter(Boolean) as VNode[]; return nodes; };