react-markdown + github-markdown-css + remark-gfm + rehype-raw:让大模型的 Markdown 流式输出"长得像 GitHub"
我们做大模型应用的时候,往往需要处理大模型流式输出,一般是 md 格式的数据。不论是流式输出还是整体输出,我们都需要把它呈现成"md 格式文档"。这中间有些许的难点及痛点需要解决:
下面我们一一展示,并给出实际的应用 demo。本系列共 4 篇:
| 篇目 | 内容 | 解决痛点 |
|---|---|---|
| 第 1 篇(本文) | react-markdown 初步展示 + 插件应用 | ①②③(基础格式 / 美化 / html) |
| 第 2 篇 | 自定义标签及事件触发 | ④(html + 事件) |
| 第 3 篇 | 输出自定义的 echarts 报表 | ⑤(图表) |
| 第 4 篇 | 输出代码及高亮展示 | 代码块能力 |
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
安装依赖(本示例使用版本 tailwindcss: "^3.4.17"):
npm install tailwindcss@3 postcss autoprefixer -S
初始化 Tailwind CSS:在项目根目录运行以下命令,创建 tailwind.config.js(同时根目录会多出 postcss.config.js):
npx tailwindcss init -p
tailwind.config.js 配置如下:
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
}
postcss.config.js 配置:
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
在 src/index.css(或相应 CSS 文件)中引入 Tailwind CSS:
@tailwind base;
@tailwind components;
@tailwind utilities;
app.jsx 中使用 Tailwind CSS 的工具类:
function App() {
return (
<div className="bg-blue-500 text-white p-4 rounded">Hello, World!</div>
);
}
运行项目:
npm run dev
npm i react-markdown -S
定义一段包含丰富 Markdown 元素的内容(标题、文本样式、列表、超链接、表格),用 <ReactMarkdown> 渲染:
import React from 'react';
import ReactMarkdown from 'react-markdown';
// 定义包含丰富 Markdown 元素的内容
const richMarkdownContent = `
# 一级标题:Markdown 丰富示例
## 二级标题:文本样式
这里展示了 **加粗**、*斜体* 和 ***加粗斜体*** 的文本样式。
## 二级标题:列表
### 无序列表
- 无序列表项 1
- 无序列表项 2
- 子列表项 2.1
- 子列表项 2.2
- 无序列表项 3
### 有序列表
1. 有序列表项 1
2. 有序列表项 2
1. 子有序列表项 2.1
2. 子有序列表项 2.2
3. 有序列表项 3
## 二级标题:超链接
这是一个 [指向百度的超链接](https://www.baidu.com)。
## 二级标题:表格
| 表头 1 | 表头 2 | 表头 3 |
| ---- | ---- | ---- |
| 单元格 1 | 单元格 2 | 单元格 3 |
| 单元格 4 | 单元格 5 | 单元格 6 |
### 三级标题:嵌套结构示例
可以在表格里嵌套列表,例如:
| 列表嵌套 | 详情 |
| ---- | ---- |
| 无序列表 | - 子项 1<br>- 子项 2 |
| 有序列表 | 1. 子项 A<br>2. 子项 B |
`;
const App = () => {
return (
<div>
<h1>使用 react - markdown 渲染丰富 Markdown 内容</h1>
<ReactMarkdown>
{richMarkdownContent}
</ReactMarkdown>
</div>
);
};
export default App;
npm install github-markdown-css -S
关键点:必须在包裹元素上设置 className="markdown-body",样式才会生效:
import React from 'react';
import ReactMarkdown from 'react-markdown';
import "github-markdown-css"
// 定义包含丰富 Markdown 元素的内容
const richMarkdownContent = `...`; // 同上一节的丰富示例
const App = () => {
return (
<div className="markdown-body">
<h1>使用 react - markdown 渲染丰富 Markdown 内容</h1>
<ReactMarkdown>
{richMarkdownContent}
</ReactMarkdown>
</div>
);
};
export default App;
markdown-body 是 github-markdown-css 的作用域类名,所有 GitHub 风格样式(标题、代码块、引用等)都挂在它下面。npm i remark-gfm -S
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from "remark-gfm";
import "github-markdown-css"
// 定义包含丰富 Markdown 元素的内容
const richMarkdownContent = `...`; // 同前
const App = () => {
return (
<div className="markdown-body">
<h1>使用 react - markdown 渲染丰富 Markdown 内容</h1>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{richMarkdownContent}
</ReactMarkdown>
</div>
);
};
export default App;
大模型的输出里可能直接包含 HTML 标签(比如高亮某个词)。把代码改成下面这样,markdown 内容中有一个 <span style="color: red;">:
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from "remark-gfm";
import "github-markdown-css"
// 定义包含丰富 Markdown 元素的内容
const markdownContent = `
# 这是一个标题
这是一段包含 <span style="color: red;">HTML 标签</span> 的文本。
`;
const App = () => {
return (
<div className="markdown-body">
<h1>使用 react - markdown 渲染丰富 Markdown 内容</h1>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{markdownContent}
</ReactMarkdown>
</div>
);
};
export default App;
npm i rehype-raw -S
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import "github-markdown-css"
// 定义包含丰富 Markdown 元素的内容
const markdownContent = `
# 这是一个标题
这是一段包含 <span style="color: red;">HTML 标签</span> 的文本。
`;
const App = () => {
return (
<div className="markdown-body">
<h1>使用 react - markdown 渲染丰富 Markdown 内容</h1>
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
{markdownContent}
</ReactMarkdown>
</div>
);
};
export default App;
至此,我们基于 react-markdown 并引用 remark-gfm、rehype-raw、github-markdown-css 来处理复杂 md 格式、处理 html 标签和美化样式。以上是基础功能展示(原文对应分支是 dev1,可以自行下载)。后期的所有复杂功能都基于此来实现。
| 能力 | 缺什么插件 | 接入方式 |
|---|---|---|
| 基础 md 渲染 | — | <ReactMarkdown>{content}</ReactMarkdown> |
| GitHub 风格美化 | github-markdown-css | 包裹元素加 className="markdown-body" |
| 表格 / 任务列表 | remark-gfm | remarkPlugins={[remarkGfm]} |
| 原生 HTML 标签 | rehype-raw | rehypePlugins={[rehypeRaw]} |
用于在 React 应用中渲染 Markdown 内容的库。Markdown 是一种轻量级标记语言,使用简单的文本格式来创建富文本内容,例如标题、列表、链接等。react-markdown 可以将 Markdown 字符串转换为 React 组件,使得在 React 应用中显示 Markdown 内容变得非常方便。
一个 remark 插件,用于支持 GitHub Flavored Markdown(GFM)。GFM 是 GitHub 对标准 Markdown 的扩展,增加了一些额外的功能,如表格、任务列表、自动链接等。
作用:使用 react-markdown 渲染 Markdown 内容时,默认情况下可能不支持这些 GFM 特性。通过引入 remark-gfm 插件,可以让 react-markdown 能够正确解析和渲染这些扩展的 Markdown 语法。
一个 rehype 插件,用于处理 Markdown 中的原始 HTML 内容。在 Markdown 中,有时会嵌入一些 HTML 标签,例如 <div>、<span> 等。默认情况下,react-markdown 可能会过滤掉这些原始 HTML 内容,以确保安全性。
作用:使用 rehype-raw 插件可以让 react-markdown 解析并渲染这些原始 HTML 标签,使得 Markdown 中嵌入的 HTML 内容能够正常显示。
一个 CSS 文件,它提供了与 GitHub 上 Markdown 内容相同的样式。当你在自己的应用中渲染 Markdown 内容时,使用这个 CSS 文件可以让渲染结果看起来与 GitHub 上的 Markdown 样式一致,包括标题、列表、代码块等的样式。
作用:通过引入 github-markdown-css,可以让你的 Markdown 内容在视觉上更加美观和专业,同时保持与 GitHub 风格的一致性。
| 术语 | 定义 |
|---|---|
| react-markdown | 把 Markdown 字符串转换为 React 组件的渲染库 |
| remark / rehype | Markdown 的解析插件体系:remark 管 md→mdast 语法树,rehype 管 hast 语法树→HTML |
| remark-gfm | 支持 GitHub Flavored Markdown:表格、任务列表、自动链接等扩展语法 |
| rehype-raw | 允许 markdown 中的原始 HTML 被解析渲染(注意 XSS 风险) |
| github-markdown-css | GitHub 风格的 Markdown 样式表,需配合 markdown-body 作用域类名 |
| GFM | GitHub Flavored Markdown,GitHub 对标准 Markdown 的扩展 |
| Tailwind CSS | 原子化 CSS 框架,本系列用它做页面基础样式 |
// ========== 安装依赖 ==========
// npm create vite@latest my-react-app -- --template react
// cd my-react-app && npm install
// npm install tailwindcss@3 postcss autoprefixer -S
// npx tailwindcss init -p
// npm i react-markdown -S
// npm install github-markdown-css -S
// npm i remark-gfm -S
// npm i rehype-raw -S
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import "github-markdown-css";
const richMarkdownContent = `
# 一级标题:Markdown 丰富示例
## 二级标题:文本样式
这里展示了 **加粗**、*斜体* 和 ***加粗斜体*** 的文本样式。
## 二级标题:列表
### 无序列表
- 无序列表项 1
- 无序列表项 2
- 子列表项 2.1
- 子列表项 2.2
- 无序列表项 3
### 有序列表
1. 有序列表项 1
2. 有序列表项 2
3. 有序列表项 3
## 二级标题:超链接
这是一个 [指向百度的超链接](https://www.baidu.com)。
## 二级标题:表格
| 表头 1 | 表头 2 | 表头 3 |
| ---- | ---- | ---- |
| 单元格 1 | 单元格 2 | 单元格 3 |
| 单元格 4 | 单元格 5 | 单元格 6 |
`;
const App = () => {
return (
<div className="markdown-body">
<h1>使用 react-markdown 渲染丰富 Markdown 内容</h1>
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
{richMarkdownContent}
</ReactMarkdown>
</div>
);
};
export default App;
未完待续……下一篇(系列 2)我们学习如何借助自定义 components 实现自定义标签和事件触发;第 3 篇更进一步渲染 echarts 等报表;第 4 篇输出代码及高亮展示。所有复杂功能都基于本文这个组合。