首页 / 资讯中心 / 文章详情

Streamdown:为 AI 流式输出而生的 react-markdown 即插即用替代方案

Streamdown:为 AI 流式输出而生的 react-markdown 即插即用替代方案 ★ FEATURED ARTICLE
前端AI 应用UI组件【免费下载链接】streamdownA drop-in replacement for react-markdown, designed for AI-powered streaming.项目地址https://gitcode.com/gh_mirrors/stre/streamdown点击查看免费下载Streamdown 是一个面向 AI 流式输出的 Markdown 渲染器可作为react-markdown的即插即用drop-in替代品。本文以仓库根目录 README.md 为主体结合 packages/streamdown 源码与测试系统讲解它的安装配置、AI SDK 集成、未终止块解析原理、插件体系与fallbackComponent等核心用法读完即可在自己的 AI 聊天应用中落地流式 Markdown 渲染。为什么流式渲染是 Markdown 的新挑战格式化一段完整的 Markdown 很容易但当模型按 token 逐个输出时问题随之而来你拿到的是未闭合的粗体、残缺的链接语法甚至是一个只写了三行的代码围栏。如果直接把这样的半成品交给普通 Markdown 解析器页面会闪烁、块结构错乱、样式中途跳变。Streamdown 正是为处理这些场景而构建的它由remend仓库内独立包见 packages/remend/README.md负责自愈未终止的 Markdown 块它按块block粒度增量解析文档只重新解析发生变化的尾部已稳定的块直接复用渲染结果它同时驱动 Vercel AI SDK 的 AI Elements Message 组件README 中明确说明也可作为独立包接入你自己的流式场景。核心特性一览根据 README 的 Features 列表Streamdown 的核心能力包括特性说明 Drop-in replacement与react-markdown的 API 兼容可平滑替换 Streaming-optimized优雅处理未完成、未终止的 Markdown Unterminated block parsing基于 remend 构建提升流式渲染质量 GFM 支持表格、任务列表、删除线 数学渲染通过 KaTeX 渲染 LaTeX 公式 Mermaid 图表将 Mermaid 代码块渲染为可交互图表 代码高亮基于 Shiki 的美观代码块️ 安全优先内置rehype-harden等安全加固⚡ 性能优化记忆化memoized渲染更新高效注意上表中以 / 等 emoji 开头的描述为 README 的原始表述本文仅作忠实转述不额外夸大任何性能或兼容性声明。安装与 Tailwind 配置安装包npm i streamdownStreamdown 采用 React 组件形态packages/streamdown/package.json显示其 peerDependencies 为react/react-dom的^18.0.0 || ^19.0.0主入口为dist/index.jsESM并额外导出./styles.css。必须配置Tailwind 扫描 dist 文件这是最容易遗漏的一步。Streamdown 的样式基于 Tailwind 工具类必须让 Tailwind 扫描到node_modules里的产物文件否则组件会光秃秃地渲染。在项目的globals.css中加入source ../node_modules/streamdown/dist/*.js;source是 Tailwind v4 的指令。路径必须相对于你的 CSS 文件指向包含 streamdown 的node_modules目录。在标准 Next.js 项目中globals.css位于app/上面的默认路径即可生效。如果你还安装了可选的 Streamdown 插件请为已安装的包逐一追加对应的source行例如source ../node_modules/streamdown/code/dist/*.js;只应为实际安装的插件添加对应条目未安装的插件不要写否则 Tailwind 会报错。各插件的精确路径见仓库文档 code.mdx、cjk.mdx、math.mdx、mermaid.mdx。Monorepo 中的路径调整在 monoreponpm workspaces、Turbo、pnpm 等中依赖通常被提升hoist到根目录node_modules需要调整../的数量monorepo/ ├── node_modules/streamdown/ ← hoisted here ├── apps/ │ └── web/ │ └── app/ │ └── globals.css ← your CSS file/* apps/web/app/globals.css → 3 levels up to reach root node_modules */ source ../../../node_modules/streamdown/dist/*.js;按你的 CSS 文件与根node_modules的相对深度调整../段数插件同理。CSS 自定义属性Design TokensStreamdown 组件基于 shadcn/ui 的设计系统构建依赖 CSS 自定义属性custom properties来获得颜色、圆角与间距。如果没有定义这些变量组件可能出现背景缺失、边框缺失或间距错乱。如果你已经在用 shadcn/ui这些变量会自动就绪否则在全局 CSS 中加入 README 提供的这组最小变量:root { --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); --card: oklch(1 0 0); --card-foreground: oklch(0.145 0 0); --muted: oklch(0.97 0 0); --muted-foreground: oklch(0.556 0 0); --border: oklch(0.922 0 0); --input: oklch(0.922 0 0); --primary: oklch(0.205 0 0); --primary-foreground: oklch(0.985 0 0); --radius: 0.625rem; } .dark { --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); --card: oklch(0.205 0 0); --card-foreground: oklch(0.985 0 0); --muted: oklch(0.269 0 0); --muted-foreground: oklch(0.708 0 0); --border: oklch(0.269 0 0); --input: oklch(0.269 0 0); --primary: oklch(0.985 0 0); --primary-foreground: oklch(0.205 0 0); --radius: 0.625rem; }同时不要忘记引入组件自己的样式import streamdown/styles.css;与katex的 CSS 一起在 JSX 使用示例中可见。在 React AI SDK 中快速使用README 给出了与 AI SDK 集成的标准写法——通过ai-sdk/react的useChat拿到消息流把每条 assistant 消息的文本部分交给Streamdownimport { useChat } from ai-sdk/react; import { Streamdown } from streamdown; import { code } from streamdown/code; import { mermaid } from streamdown/mermaid; import { math } from streamdown/math; import { cjk } from streamdown/cjk; import katex/dist/katex.min.css; import streamdown/styles.css; export default function Chat() { const { messages, status } useChat(); return ( div {messages.map(message ( div key{message.id} {message.role user ? User: : AI: } {message.parts.map((part, index) part.type text ? ( Streamdown key{index} animated plugins{{ code, mermaid, math, cjk }} isAnimating{status streaming} {part.text} /Streamdown ) : null, )} /div ))} /div ); }要点解读isAnimating直接绑定status streaming告诉 Streamdown 当前文本仍在增长animated开启逐词/逐字入场动画配合isAnimating让新 token 平滑浮现plugins一次性注入代码高亮、Mermaid、数学、CJK 四个可选插件message.parts中只渲染type text的片段其余如工具调用结果跳过。更简洁的入门写法仓库 basic-streaming.tsx则是直接渲染message.content并同样用isAnimating标记最后一条 assistant 消息Streamdown isAnimating{isLoading message.role assistant} {message.content} /Streamdown静态模式博客与文档如果内容一次性到达、不需要流式特性显式切换到静态模式可跳过 remend 预处理与块级增量解析获得更简单的渲染路径Streamdown modestatic plugins{{ code }} {content} /Streamdown从 index.tsx 的源码结构看modestatic走一条直接渲染整份 Markdown 的简化分支而modestreaming默认则走预处理 → 分块 → 按块 memo 渲染的完整管线。原理纵深remend 如何自愈未终止的 MarkdownStreamdown 的流式质量建立在 remend 之上。remend 是仓库内的独立包见 packages/remend/README.md定位为 Self-healing markdown——智能解析并补全不完整的 Markdown 块。它补全哪些语法语法示例转换粗体**text→**text**斜体*text/_text→*text*/_text_粗斜体***text→***text***行内代码code→code删除线~~text→~~text~~链接text图片[![alt](https://gitcode.com/gh_mirrors/stre/streamdown/blob/1ddd8f4accd87dfc0e933f257c9d6ffe0fa85ccc/apps/test/components/ui/kibo-ui/combobox/index.tsx?utm_sourcegitcode_repo_files#L375-L394)](https://link.gitcode.com/i/56cd8b03516894de6a7b43c78a7ae464) 的defaultSanitizeSchema。可配置项import remend from remend; // 选择性关闭某些补全 const completed remend(partialMarkdown, { links: false, katex: false, });选项说明links补全未完成链接images补全未完成图片bold补全粗体**italic补全斜体*/_boldItalic补全粗斜体***inlineCode补全行内代码singleTilde转义单词字符间的单个~防止误判删除线如20~25strikethrough补全删除线~~katex补全块级 KaTeX 数学$$inlineKatex补全行内 KaTeX$默认false避免与货币符号冲突setextHeadings处理未完成的 setext 标题handlers自定义处理器扩展 remend自定义 Handler可以注册自己的处理器为JOKE这类领域特定语法补全闭合import remend, { type RemendHandler } from remend; const jokeHandler: RemendHandler { name: joke, handle: (text) { // 补全未闭合的 JOKE 标记 const match text.match(/JOKE([^]*)$/); if (match !text.endsWith(/JOKE)) { return ${text}/JOKE; } return text; }, priority: 80, // 在内置处理器0-70之后运行 }; const result remend(content, { handlers: [jokeHandler] });Handler 接口为{ name, handle: (text) string, priority? }内置处理器占用优先级 0–75singleTilde0、links20、inlineCode50、katex70 等自定义处理器默认 100、在其后运行。remend 还导出了isWithinCodeBlock、isWithinMathBlock、isWithinLinkOrImageUrl、isWordChar等上下文探测工具供自定义 handler 跳过代码块、数学块等不应干预的区域。为什么必须先于 unified 运行remend 是字符串级预处理必须在把 Markdown 交给 unified/remark 管线之前执行import remend from remend; import { unified } from unified; import remarkParse from remark-parse; import remarkRehype from remark-rehype; import rehypeStringify from rehype-stringify; const streamedMarkdown This is **incomplete bold; // 先补全再走管线 const completedMarkdown remend(streamedMarkdown); const file await unified() .use(remarkParse) .use(remarkRehype) .use(rehypeStringify) .process(completedMarkdown); console.log(String(file));原因在于remend 在原始字符串层面工作而 remark/unified 工作在 AST 层面——解析之后再补全已无意义。Streamdown 内部的执行顺序同样如此在 index.tsx 中children先经过 remend仅流式模式且parseIncompleteMarkdown开启时再被拆分为块、逐块进入 remark/rehype 管线。插件体系按需扩展四类能力Streamdown 的核心包只内置基础 Markdown 渲染高阶能力全部通过插件注入类型定义见 plugin-types.ts插件包名能力Codestreamdown/code基于 Shiki 的语法高亮支持 200 语言、明暗双主题、懒加载语言包、token 缓存Mermaidstreamdown/mermaid流程图 / 时序图等图表提供全屏、下载、复制等交互控件Mathstreamdown/math通过 KaTeX 渲染 LaTeX必须额外引入katex/dist/katex.min.cssCJKstreamdown/cjk中日韩文本支持含强调处理与自动链接边界优化用法统一为plugins{{ code, mermaid, math, cjk }}。以 code 插件为例安装后同样要追加 Tailwindsource行source ../node_modules/streamdown/code/dist/*.js;Tailwind v3 则把./node_modules/streamdown/code/dist/*.js加入tailwind.config.js的content数组。插件在管线中的编排从源码可以确认插件并非简单平铺而是有严格顺序index.tsxremark 阶段CJK 的remarkPluginsBefore→ 默认插件含remark-gfm→disableAutolinkProtocols可选→ CJK 的remarkPluginsAfter→ math 的 remark 插件rehype 阶段默认rehype-raw→rehype-sanitize→rehype-harden→ 自定义 allowedTags 扩展 →literalTagContent处理 → math 的 rehype 插件。默认 remark 与 rehype 插件组也以常量形式导出defaultRemarkPlugins/defaultRehypePlugins见 index.tsx可通过remarkPlugins/rehypePlugins属性整体替换或追加自定义插件。默认安全加固内置的rehype-harden配置同样定义于 index.tsx{ allowedImagePrefixes: [*], allowedLinkPrefixes: [*], allowedProtocols: [*], defaultOrigin: undefined, allowDataImages: true, }生产环境可收紧协议与域名白名单仓库 custom-security.tsx 给出了严格配置示例仅放行https/mailto协议、限定公司域名前缀、禁用 data 图片可作为 AI 生成内容的加固参考。fallbackComponent为映射表之外的标签提供兜底渲染README 专门用一个章节讲解fallbackComponent这是 Streamdown 与纯react-markdown的一大差异点。背景Streamdown 为常见 Markdown 标签内置了一组渲染器defaultComponents包含a、h1–h6、code、table、ul/ol/li等见 index.tsx。对于不在该映射表中、且未通过components覆盖的标签——典型如allowedTags声明的自定义元素以及span、em、div、br等未覆盖 HTML 标签——可以提供一个fallbackComponent兜底渲染。注意这不是完全无样式模式内置条目以及显式传入components的覆盖优先级仍然最高。如果你想重定义已有默认渲染器的标签如h1、p、code应通过components传入而不是依赖 fallback。基础用法透传渲染import { createElement } from react; import { Streamdown } from streamdown; // 对缺失映射条目 / allowedTags 标签做透传渲染 Streamdown allowedTags{{ mention: [user_id] }} fallbackComponent{({ node, children, ...props }) createElement(node!.tagName, props, children) } {markdown} /Streamdownnode.tagName是 hast 节点提供的真实标签名createElement按原名重建元素并透传props与children。与显式覆盖组合当部分标签需要特殊处理时让components与 fallback 协同工作Streamdown allowedTags{{ mention: [user_id] }} fallbackComponent{({ node, children, ...props }) createElement(node!.tagName, props, children) } components{{ code: MyCodeBlock, a: MyLink, }} {markdown} /Streamdown此时code/a走自定义组件mentionallowedTags 且无组件条目与span等未覆盖标签则落入 fallback。源码级的生效机制从 index.tsx 的实现可以看到两层兜底逻辑对allowedTags中没有对应组件条目的标签直接把fallbackComponent注册进合并后的 components 映射再用一个Proxy拦截getOwnPropertyDescriptor与get让任何其他未覆盖的小写 HTML/自定义标签名如span、em、div也解析到fallbackComponent而不是渲染成裸的内置元素。仓库测试 fallback-component.test.tsx 验证了三条行为allowedTags 无组件条目时走 fallback、多个 allowedTags 标签各自走 fallback、显式components条目优先级高于 fallback。性能设计按块增量解析与记忆化Streamdown 的流式性能来自两处关键设计均有源码与基准佐证。按块增量解析parse-blocksparse-blocks.tsx 用 marked 的Lexer把文档拆成块并在流式场景下只重新解析变化的尾部缓存上一次解析的块数组input仍以相同前缀扩展时复用所有稳定块以空行结尾、后续块完整的字符串实例只对新尾部执行词法分析遇到脚注[^1]、链接定义[label]: url等会跨块共享语义的语法时放弃增量复用回退到完整解析或整篇单块对div这类 HTML 块做开闭标签配对合并避免内层闭合标签提前截断外层块。与之配套index.tsx 中的Block组件对每个块做深度 memo仅当content、isIncomplete、dir、components 引用、插件引用等实际影响输出的值变化时才重渲染。整份文档的 memo 比较器则只对children等顶层 prop 做浅比较保证高频流式 tick 下最小化 React 渲染量。动画时间轴AnimateTimelineanimated属性的底层实现在 animate.ts每个块拥有独立的 rehype 动画插件记录各自的已呈现字符数但它们共享一条基于墙钟的时间轴AnimateTimeline从而跨块、跨流式 tick 序列化逐词/逐字的入场延迟即使已稳定块被 memo 跳过也不会打乱节奏。内置参数默认值animationfadeIn、duration150ms、stagger40ms、maxBacklogMs320软预算防止快速流累积过多 opacity 队列。交互控件与定制Streamdown 默认启用代码块、表格、Mermaid、图片的交互控件controls默认true。ControlsConfigindex.tsx支持逐类开关与细粒度配置例如完整示例 full-featured.tsx 中的Streamdown caretblock controls{{ code: true, table: true, mermaid: { download: true, copy: true, fullscreen: true, panZoom: true, }, }} isAnimating{isLoading index messages.length - 1 message.role assistant} linkSafety{{ enabled: true, onLinkCheck: (url) { const trusted [github.com, npmjs.com]; const hostname new URL(url).hostname; return trusted.some((d) hostname.endsWith(d)); }, }} plugins{{ code, mermaid, math }} {message.content} /Streamdown其中的定制能力包括caretblock▋或circle●两种光标样式通过 CSS 变量--streamdown-caret注入容器末尾由 use-caret-host.ts 管理——当最后一块是未闭合代码围栏或表格时自动隐藏光标避免与内容重叠linkSafety默认{ enabled: true }点击外链前弹出确认模态框可用onLinkCheck自定义域名白名单判断用renderModal替换默认弹窗接口见 index.tsxtranslations通过 translations-context.tsx 覆盖复制代码、下载图表、全屏等全部 UI 文案实现界面国际化icons / prefix / lineNumbers分别覆盖控件图标、Tailwind 前缀如tw产出tw:flex、代码块行号显隐默认true。常见问题速查结合 README 与仓库文档 plugins/index.mdx 的 Common Gotchas以下问题最容易踩坑Tailwind 样式缺失—— 在globals.css添加source ../node_modules/streamdown/dist/*.js;Tailwind v4或在tailwind.config.js的content中追加该路径v3。数学公式不渲染—— 忘记import katex/dist/katex.min.css;。光标不出现—— 必须同时满足caret属性和isAnimating{true}。流式期间复制按钮——isAnimating{true}时自动禁用避免复制到半截内容。链接安全弹窗出现—— 默认启用可通过linkSafety{{ enabled: false }}关闭。allowedTags不生效—— 仅在默认 rehype 插件组下工作自定义了 sanitize 插件时需要自行扩展。数学用$$而非$—— 单个$默认关闭避免与货币符号冲突。结语Streamdown 把流式 AI 内容渲染拆解为三个可靠层remend 负责语法自愈、按块增量解析负责性能、memo 化 动画时间轴负责视觉与渲染开销同时以插件形式提供代码高亮、图表、数学与 CJK 支持并默认内置安全净化。如果你正在构建 AI 聊天、Agent 界面或任何需要实时渲染模型输出的应用本文涉及的 README.md 与仓库源码index.tsx、parse-blocks.tsx、animate.ts、remend值得进一步深入阅读。赞分享前端AI 应用UI组件【免费下载链接】streamdownA drop-in replacement for react-markdown, designed for AI-powered streaming.项目地址https://gitcode.com/gh_mirrors/stre/streamdown点击查看免费下载相关推荐streamdown/code 2.0为 AI 流式输出而生的 Shiki 增量高亮与缓存优化实践streamdown/code 2.0为 AI 流式输出而生的 Shiki 增量高亮与缓存优化实践 streamdown/code 是 Streamdow前端AI 应用UI组件FrankenPHP 经典模式Classic Mode完全指南作为 PHP-FPM / Apache mod_php 的即插即用替代方案FrankenPHP 经典模式Classic Mode完全指南作为 PHP FPM / Apache mod_php 的即插即用替代方案 FrankenP后端go-deadlock 在线死锁检测指南为 Go 并发代码加装 sync.Mutex 的即插即用替代品go deadlock 在线死锁检测指南为 Go 并发代码加装 sync.Mutex 的即插即用替代品 导读 死锁是 Go 并发程序中最隐蔽、最难以复现的故障构建工具云原生后端上一篇EssentialsX完整指南如何快速搭建你的Minecraft服务器管理系统下一篇ClickHouse数据采样革命3层架构实现PB级数据分析秒级响应创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站