import type { Components } from "react-markdown"; import type { ReactElement } from "react"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import { toJsxRuntime } from "hast-util-to-jsx-runtime"; import { unified } from "unified"; import remarkParse from "remark-parse"; import remarkGfm from "remark-gfm"; import remarkRehype from "remark-rehype"; import rehypePrettyCode from "rehype-pretty-code"; import type { Root } from "hast"; import { mergeMarkdownComponents } from "./mergeComponents"; export const prettyCodeOptions = { theme: { light: "github-light", dark: "github-dark-dimmed", }, keepBackground: false, defaultLang: "plaintext", // 空行在 display:grid 下会塌缩成 0 高,补一个空格撑住行高 onVisitLine(element: { children: unknown[] }) { if (element.children.length === 0) { element.children = [{ type: "text", value: " " }]; } }, } as const; /** 服务端异步管线:GFM + Shiki 双主题高亮 → React 树 */ export async function renderMarkdownToReact( content: string, overrides?: Components ): Promise { const processor = unified() .use(remarkParse) .use(remarkGfm) .use(remarkRehype) .use(rehypePrettyCode, { ...prettyCodeOptions }); const mdast = processor.parse(content || ""); const hast = (await processor.run(mdast)) as Root; return toJsxRuntime(hast, { Fragment, jsx, jsxs, components: mergeMarkdownComponents(overrides) as Record< string, React.ElementType >, }) as ReactElement; }