Files
boos e0a5f57793 feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
2026-06-22 11:32:57 +08:00

492 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# stream-markdown-parser
[![NPM version](https://img.shields.io/npm/v/stream-markdown-parser?color=a1b858&label=)](https://www.npmjs.com/package/stream-markdown-parser)
[![English Docs](https://img.shields.io/badge/docs-English-blue)](README.md)
[![NPM downloads](https://img.shields.io/npm/dm/stream-markdown-parser)](https://www.npmjs.com/package/stream-markdown-parser)
[![Bundle size](https://img.shields.io/bundlephobia/minzip/stream-markdown-parser)](https://bundlephobia.com/package/stream-markdown-parser)
[![License](https://img.shields.io/npm/l/stream-markdown-parser)](https://github.com/Simon-He95/markstream-vue/blob/main/license)
纯 JavaScript Markdown 解析器和渲染工具,支持流式处理 - 框架无关。
该包包含从 `markstream-vue` 中提取的核心 Markdown 解析逻辑,使其可以在任何 JavaScript/TypeScript 项目中使用,无需 Vue 依赖。
## 特性
- 🚀 **纯 JavaScript** - 无框架依赖
- 📦 **轻量级** - 最小打包体积
- 🔧 **可扩展** - 基于插件的架构
- 🎯 **类型安全** - 完整的 TypeScript 支持
- ⚡ **高性能** - 性能优化
- 🌊 **流式友好** - 支持渐进式解析
> ℹ️ 自当前版本起我们基于 [`markdown-it-ts`](https://www.npmjs.com/package/markdown-it-ts)(一个 TypeScript 优先的 markdown-it 发行版)进行构建。API 与 markdown-it 保持一致,但内部仅依赖其解析流程,并提供更丰富的 token 类型定义。
## 文档
完整的使用说明与集成教程见 markstream-vue 文档站:
- English: https://markstream-vue-docs.simonhe.me/guide/api
- 中文: https://markstream-vue-docs.simonhe.me/zh/guide/api
本 README 聚焦解析器 API;如需 VitePress/Vite/Nuxt 集成、Worker 流式解析、Tailwind/UnoCSS 配置等指南,请查阅上述文档。
## 安装
```bash
pnpm add stream-markdown-parser
# 或
npm install stream-markdown-parser
# 或
yarn add stream-markdown-parser
```
## 快速 API 速览
- `getMarkdown(msgId?, options?)` — 返回一个预配置的 `markdown-it-ts` 实例;支持 `plugin`、`apply`、`i18n` 等选项(内置任务列表、上下标、数学等插件)。
- `registerMarkdownPlugin(plugin)` / `clearRegisteredMarkdownPlugins()` — 全局注册/清除插件,在所有 `getMarkdown()` 调用中生效(适合特性开关或测试环境)。
- `parseMarkdownToStructure(markdown, md, parseOptions)` — 将 Markdown 转换为可供 `markstream-vue` 等渲染器使用的 AST。
- `processTokens(tokens)` / `parseInlineTokens(children, content?, preToken?, options?)` — 更底层的 token → 节点工具,方便自定义管线。
- `applyMath`、`applyContainers`、`normalizeStandaloneBackslashT`、`findMatchingClose` 等 — 用于构建自定义解析、lint 或内容清洗流程。
## 使用
### 流式解析流程
```
Markdown 字符串
↓ getMarkdown() → 带插件的 markdown-it-ts 实例
parseMarkdownToStructure(markdown, md) → AST (ParsedNode[])
↓ 交给你的渲染器(markstream-vue、自定义 UI、Worker 等)
```
多次解析时复用同一个 `md` 实例可以避免重复注册插件。与 [`markstream-vue`](https://www.npmjs.com/package/markstream-vue) 一起使用时,你可以把 AST 传给 `<MarkdownRender :nodes="nodes" />`,或仅传入原始 `content` 并共享同一套解析配置。
### 增量 / 流式示例
处理 AI/SSE 流时,可以复用同一个 `md` 实例不停地对累积缓冲区解析,并把 AST 推送给 UI(例如 `markstream-vue`):
```ts
import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser'
const md = getMarkdown()
let buffer = ''
async function handleChunk(chunk: string) {
buffer += chunk
const nodes = parseMarkdownToStructure(buffer, md)
postMessage({ type: 'markdown:update', nodes })
}
```
在前端通过 `<MarkdownRender :nodes="nodes" />` 渲染即可避免重复解析。具体串联示例见[文档用法指南](https://markstream-vue-docs.simonhe.me/zh/guide/usage)。
### 基础示例
```typescript
import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser'
// 创建一个带有默认插件的 markdown-it-ts 实例
const md = getMarkdown()
// 将 Markdown 解析为流式友好的 AST 结构
const nodes = parseMarkdownToStructure('# Hello World', md)
console.log(nodes)
// [{ type: 'heading', level: 1, children: [...] }]
// 如果仍需 HTML 输出,markdown-it-ts 依旧提供 render()
const html = md.render?.('# Hello World\n\nThis is **bold**.')
```
### 配置数学公式选项
```typescript
import { getMarkdown, setDefaultMathOptions } from 'stream-markdown-parser'
// 设置全局数学公式选项
setDefaultMathOptions({
commands: ['infty', 'perp', 'alpha'],
escapeExclamation: true
})
const md = getMarkdown()
```
### 自定义国际化
```typescript
import { getMarkdown } from 'stream-markdown-parser'
// 使用翻译映射
const md = getMarkdown('editor-1', {
i18n: {
'common.copy': '复制',
}
})
// 或使用翻译函数
const md = getMarkdown('editor-1', {
i18n: (key: string) => translateFunction(key)
})
```
### 使用插件
```typescript
import customPlugin from 'markdown-it-custom-plugin'
import { getMarkdown } from 'stream-markdown-parser'
const md = getMarkdown('editor-1', {
plugin: [
[customPlugin, { /* 选项 */ }]
]
})
```
### 高级:自定义规则
```typescript
import { getMarkdown } from 'stream-markdown-parser'
const md = getMarkdown('editor-1', {
apply: [
(md) => {
// 添加自定义内联规则
md.inline.ruler.before('emphasis', 'custom', (state, silent) => {
// 你的自定义逻辑
return false
})
}
]
})
```
### 全局扩展
想在所有 `getMarkdown()` 实例上启用同一个插件,而无需修改调用点?可使用内置 helper:
```ts
import {
clearRegisteredMarkdownPlugins,
registerMarkdownPlugin,
} from 'stream-markdown-parser'
registerMarkdownPlugin(myPlugin)
const md = getMarkdown()
// 现在 md 会自动包含 myPlugin
// 测试或清理阶段:
clearRegisteredMarkdownPlugins()
```
- `plugin` 选项 → 针对单次 `getMarkdown` 调用传入 `md.use(...)`。
- `apply` 选项 → 直接操作实例(如 `md.inline.ruler.before(...)`)。如果需要严格模式,可自行包裹 try/catch;默认会打印错误保持兼容。
- `registerMarkdownPlugin` → 全局注册表,适用于 SSR / Worker 等场景统一开启功能。
## API
### 主要函数
#### `getMarkdown(msgId?, options?)`
创建一个配置好的 `markdown-it-ts` 实例(与 markdown-it API 兼容)。
**参数:**
- `msgId` (string, 可选): 该实例的唯一标识符。默认值:`editor-${Date.now()}`
- `options` (GetMarkdownOptions, 可选): 配置选项
**选项:**
```typescript
interface GetMarkdownOptions {
// 要使用的 markdown-it / markdown-it-ts 插件数组
plugin?: Array<Plugin | [Plugin, any]>
// 修改 md 实例的函数数组
apply?: Array<(md: MarkdownIt) => void>
// 翻译函数或翻译映射
i18n?: ((key: string) => string) | Record<string, string>
}
```
#### `parseMarkdownToStructure(content, md, options?)`
将 Markdown 内容解析为结构化节点树。
**参数:**
- `content` (string): 要解析的 Markdown 内容
- `md` (MarkdownItCore): 由 `getMarkdown()` 创建的 markdown-it-ts 实例
- `options` (ParseOptions, 可选): 带有钩子的解析选项
**返回值:** `ParsedNode[]` - 解析后的节点数组
#### `processTokens(tokens)`
将原始 markdown-it tokens 处理为扁平数组。
#### `parseInlineTokens(tokens, content?, preToken?, options?)`
解析内联 markdown-it-ts tokens 并产出节点。可传入原始 `content`(来自父 token)、可选的前一个 token,以及 inline 解析选项(`requireClosingStrong`、`customHtmlTags`、`validateLink`)。
### 配置函数
#### `setDefaultMathOptions(options)`
设置全局数学公式渲染选项。
**参数:**
- `options` (MathOptions): 数学公式配置选项
```typescript
interface MathOptions {
commands?: readonly string[] // 要转义的 LaTeX 命令
escapeExclamation?: boolean // 转义独立的 '!' (默认: true)
}
```
### 解析钩子(精细化变换)
ParseOptions 支持以下钩子与标志:
```ts
interface ParseOptions {
preTransformTokens?: (tokens: Token[]) => Token[]
postTransformTokens?: (tokens: Token[]) => Token[]
// 自定义 HTML 类标签,作为自定义节点输出(如 ['thinking'])
// 请传标签式名字,比如 'thinking'、'answer-box'、'my_component'
customHtmlTags?: string[]
// 输出 link 节点前校验 href;返回 false 时降级为纯文本
validateLink?: (url: string) => boolean
// true 表示输入已结束(end-of-stream)
final?: boolean
// 解析 strong 时要求闭合 `**`(默认 false)
requireClosingStrong?: boolean
}
```
示例 —— 标记 AI "思考" 块:
```ts
const parseOptions = {
customHtmlTags: ['thinking'],
}
const nodes = parseMarkdownToStructure(markdown, md, parseOptions)
const tagged = nodes.map(node =>
node.type === 'html_block' && /<thinking>/.test((node as any).content ?? '')
? { ...node, meta: { type: 'thinking' } }
: node,
)
```
在渲染器中读取 `node.meta` 即可渲染自定义 UI,而无需直接修改 Markdown 文本。
示例 —— 限制链接协议,拦截不安全链接:
```ts
const md = getMarkdown('safe-links')
md.set?.({
validateLink: (url: string) => !/^\s*javascript:/i.test(url.trim()),
})
const nodes = parseMarkdownToStructure(
'[ok](https://example.com) [bad](javascript:alert(1))',
md,
{ final: true },
)
// "ok" 保持为 link 节点;"bad" 会降级为纯文本
```
### 未知 HTML 类标签
默认情况下,非标准的 HTML 类标签(例如 `<question>`)在完整闭合时会按原生 HTML 渲染(作为自定义元素输出)。未闭合或格式不完整的片段会保持为**纯文本**,避免在流式或最终渲染时吞掉周围内容。若希望它们作为自定义节点输出(`type: 'question'`,携带 attrs/content),需要在 `customHtmlTags` 中显式声明。
### 工具函数
#### `isMathLike(content)`
启发式函数,用于检测内容是否类似数学符号。
**参数:**
- `content` (string): 要检查的内容
**返回值:** `boolean`
#### `findMatchingClose(src, startIdx, open, close)`
在字符串中查找匹配的闭合分隔符,处理嵌套对。
**参数:**
- `src` (string): 源字符串
- `startIdx` (number): 开始搜索的索引
- `open` (string): 开启分隔符
- `close` (string): 闭合分隔符
**返回值:** `number` - 匹配闭合的索引,如果未找到则返回 -1
## 使用建议与排障
- **复用解析实例**:缓存 `getMarkdown()` 的结果,避免重复注册插件。
- **服务端解析**:在服务端运行 `parseMarkdownToStructure` 后把 AST 下发给客户端,配合 `markstream-vue` 实现确定性输出。
- **自定义 HTML 组件**:在解析前先把 `<MyWidget>` 这类片段替换为占位符,渲染时再注入,避免在 `html_block` 上进行脆弱的字符串操作。
- **样式提示**:如果将节点交给 `markstream-vue`,务必按照文档的 [CSS 排查清单](https://markstream-vue-docs.simonhe.me/zh/guide/troubleshooting#css-looks-wrong-start-here) 调整 reset / layer,防止 Tailwind/UnoCSS 覆盖样式。
- **错误处理**:`apply` 钩子内部默认捕获异常后打印日志,如需在 CI/生产中抛出错误,可在传入前自行封装并 rethrow。
#### `parseFenceToken(token)`
将代码围栏 token 解析为 CodeBlockNode。
**参数:**
- `token` (MarkdownToken): markdown-it token
**返回值:** `CodeBlockNode`
#### `normalizeStandaloneBackslashT(content, options?)`
规范化数学内容中的反斜杠-t 序列。
**参数:**
- `content` (string): 要规范化的内容
- `options` (MathOptions, 可选): 数学选项
**返回值:** `string`
### 低阶辅助函数
需要更细粒度地控制 token → AST 流程时,可直接使用以下导出:
```ts
import type { MarkdownToken } from 'stream-markdown-parser'
import {
parseInlineTokens,
processTokens
} from 'stream-markdown-parser'
const tokens: MarkdownToken[] = md.parse(markdown, {})
const nodes = processTokens(tokens)
// 或仅解析内联内容:
const inlineNodes = parseInlineTokens(tokens[0].children ?? [], tokens[0].content ?? '')
```
`processTokens` 即 `parseMarkdownToStructure` 内部使用的同一个转换器,可在自定义管线中复用,避免重复实现 Markdown-it 遍历。
### 插件函数
#### `applyMath(md, options?)`
将数学插件应用到 markdown-it 实例。
**参数:**
- `md` (MarkdownIt): markdown-it 实例
- `options` (MathOptions, 可选): 数学渲染选项
#### `applyContainers(md)`
将容器插件应用到 markdown-it 实例。
**参数:**
- `md` (MarkdownIt): markdown-it 实例
### 常量
#### `KATEX_COMMANDS`
用于转义的常用 KaTeX 命令数组。
#### `TEX_BRACE_COMMANDS`
使用大括号的 TeX 命令数组。
#### `ESCAPED_TEX_BRACE_COMMANDS`
用于正则表达式的 TEX_BRACE_COMMANDS 转义版本。
## 类型
所有 TypeScript 类型都已导出:
```typescript
import type {
// 节点类型
CodeBlockNode,
GetMarkdownOptions,
HeadingNode,
ListItemNode,
ListNode,
MathOptions,
ParagraphNode,
ParsedNode,
ParseOptions,
// ... 更多
} from 'stream-markdown-parser'
```
### 节点类型
解析器导出各种表示不同 Markdown 元素的节点类型:
- `TextNode`, `HeadingNode`, `ParagraphNode`
- `ListNode`, `ListItemNode`
- `CodeBlockNode`, `InlineCodeNode`
- `LinkNode`, `ImageNode`
- `BlockquoteNode`, `TableNode`
- `MathBlockNode`, `MathInlineNode`
- 以及更多...
## 默认插件
该包预配置了以下 markdown-it 插件:
- `markdown-it-sub` - 下标支持(`H~2~O`)
- `markdown-it-sup` - 上标支持(`x^2^`)
- `markdown-it-mark` - 高亮/标记支持(`==highlighted==`)
- `markdown-it-task-checkbox` - 任务列表支持(`- [ ] Todo`)
- `markdown-it-ins` - 插入标签支持(`++inserted++`)
- `markdown-it-footnote` - 脚注支持
- `markdown-it-container` - 自定义容器支持(`::: warning`, `::: tip` 等)
- 数学公式支持 - 使用 `$...$` 和 `$$...$$` 渲染 LaTeX 数学公式
## 框架集成
虽然该包与框架无关,但它被设计为可以无缝配合以下框架使用:
- ✅ **Node.js** - 服务器端渲染
- ✅ **Vue 3** - 配合 `markstream-vue`(或你的自定义渲染层)使用
- ✅ **React** - 使用解析的节点进行自定义渲染
- ✅ **Vanilla JS** - 直接 HTML 渲染
- ✅ **任何框架** - 解析为 AST 并按需渲染
## 从 `markstream-vue` 迁移(解析器导出)
如果你当前是从 `markstream-vue` 引入解析器相关 helper,可以切换为使用独立包:
```typescript
// 之前
import { getMarkdown } from 'markstream-vue'
// 现在
import { getMarkdown } from 'stream-markdown-parser'
```
所有 API 保持不变。详见[迁移指南](https://markstream-vue-docs.simonhe.me/zh/monorepo-migration)。
## 性能
- **轻量级**: ~65KB 压缩后(13KB gzipped)
- **快速**: 针对实时解析优化
- **Tree-shakeable**: 只导入你需要的部分
- **依赖很少**: `markdown-it-ts` + 少量 markdown-it 插件
## 贡献
欢迎提交 Issues 和 PRs!请阅读[贡献指南](https://github.com/Simon-He95/markstream-vue/blob/main/CONTRIBUTING.md)。
## 许可证
MIT © Simon He
## 相关项目
- [markstream-vue](https://www.npmjs.com/package/markstream-vue) - 功能完整的 Vue 3 Markdown 渲染器