English | 中文
零依赖、Θ(n)、给够堆内存时 public parseStructural 能跑完 5000 万层嵌套(1.1.4 benchmark)的富文本 DSL 解析器。
文本进来,token 树出去——标签语义、渲染方式、目标框架,全部由你定义。
- 不是 Markdown 渲染器、富文本编辑器或 HTML 生产线
- 是 一台只认语法不认语义的 token 机器——你喂它规则,它还你结构;语法符号完全可换
- 无正则回溯、无递归——全迭代确定性扫描,输入多长跑多久
- Θ(n),n =
text.length(UTF-16 code units)。1.1.2起 inline 帧改用parenDepth计数器就地判定关闭,不再前扫findInlineClose;render 层materializeTextTokens用WeakSet跳过已处理子树——两条原 O(n²) 路径均已线性化。实际耗时取决于标签密度、节点密度、 嵌套深度和 API 路径(parseRichText≈ structural 扫描 + render 物化)。 完整复杂度分析 - inline / raw / block 三种标签形式,语法符号和标签名规则完全可换;内置转义序列让任何语法符号都能作为普通文本出现
- 写错的、未知的标签自动降级为纯文本——不抛异常,不污染上下文
- 无框架绑定、不依赖 DOM——浏览器、Node、Deno、Bun、游戏引擎或任何 JS 运行时都能跑
- 内容驱动的稳定 ID、位置追踪、handler 级管道参数——开箱即用或按需组合
parseStructural给你一张轻量的文档地图;配合yume-dsl-token-walker的parseSlice,跳到任意区域拿到带完整位置的TextToken[],不用重新解析整个文档
实时编辑标签、开关 handler、边打字边看 token 树更新。
1.1.7实测 — 鲲鹏 920 aarch64 / Node v24.14.0200 KB dense inline 全量解析:
parseRichText~30.6 ms,parseStructural~23.3 ms。 全迭代 O(n),任意嵌套深度均不会爆栈。Structural parse 后堆内存:200 KB ~21.60 MB,2 MB ~138.51 MB。
子字符串解析:
parseRichText切片 +baseOffset + tracker~20.62 µs,parseStructural同路径 ~13.47 µs。增量解析(~200 KB 文档里改一个 36 字符标签):
nodeAtOffset~456.76 µs +parseSlice~8.36 µs; 同文档全量parseRichText需要 ~19.45 ms——增量路径快约 42 倍。极限压测:5000 万层单链 inline 嵌套(~500 MB)
parseStructural~224.1 s(历史1.1.4基准,当前1.4.x未重测)。 大规模深嵌套基准使用放宽后的堆预算;具体条件见性能页。配合
yume-dsl-token-walker的parseSlice——只重解析被修改的区域。createIncrementalSession(...)把“编辑器级”的结构缓存能力直接带进连续编辑场景,让解析器在高频改动下也不用每次从头重建。parseIncremental(...)则把同一套增量模型打包成一份可继续复用的首帧快照,适合从单次解析自然接入增量管线。 完整 session 生命周期、精确签名、applyEditWithDiff(...)字段展开请看 增量解析 wiki。 完整性能数据
适用场景: 游戏对话与视觉小说 (打字机 / 抖动 / 变色——标签你自己发明)
聊天与评论(UGC 安全降级)
CMS 与博客、文档管线、本地化工作流 (翻译人员只碰文本,不碰标记)
// React — 递归渲染 token 树
const RichText: FC<{ tokens: TextToken[] }> = ({tokens}) => (
<>{tokens.map(t =>
t.type === "text" ? <span key={t.id}>{t.value as string}</span>
: <strong key={t.id}><RichText tokens={t.value as TextToken[]}/></strong>
)}</>
);<!-- Vue 3 — 同样的思路,模板语法 -->
<template>
<template v-for="t in tokens" :key="t.id">
<span v-if="t.type === 'text'">{{ t.value }}</span>
<strong v-else>
<RichText :tokens="t.value"/>
</strong>
</template>
</template>| 包 | 角色 |
|---|---|
yume-dsl-rich-text |
解析器核心 — 文本到 token 树(本包) |
yume-dsl-token-walker |
解释器 — token 树到输出节点 |
yume-dsl-shiki-highlight |
语法高亮 — 彩色 token 或 TextMate 语法 |
yume-dsl-markdown-it |
markdown-it 插件 — Markdown 中渲染 DSL 标签 |
推荐组合方式:
- 只需把 DSL 解析成 token →
yume-dsl-rich-text - 把 token 树解释为任意输出节点 → 再配合
yume-dsl-token-walker - 源码级高亮或 TextMate 语法支持 → 再配合
yume-dsl-shiki-highlight - 在 Markdown 中渲染 DSL(markdown-it) → 再配合
yume-dsl-markdown-it
- 无内置标签。 每个标签的含义由你注册的处理器定义。
- 处理器就是语义层。 处理器接收解析后的 token,返回
TokenDraft——输出结构、附加字段、行为全部由你决定。 - 渲染不是我们的工作。 解析器产出 token 树;如何渲染(React、Vue、纯 HTML、终端)完全由你负责。
- 优雅降级。 未知或不支持的标签永远不会抛出异常——静默降级。
- 一切可配置。 语法符号、标签名规则、嵌套深度——需要覆盖什么就覆盖什么,其余保持默认。
从这里开始: 安装 · 快速开始 · DSL 语法 · API
深入了解: 自定义语法 · 处理器辅助函数 · ParseOptions · 稳定 Token ID · 源码位置追踪 · 错误处理 · 导出一览 · 增量解析 · 待弃用 API · 兼容性
npm install yume-dsl-rich-text
pnpm add yume-dsl-rich-text
yarn add yume-dsl-rich-textimport {
createParser,
createSimpleInlineHandlers,
createSimpleBlockHandlers,
createSimpleRawHandlers,
declareMultilineTags,
} from "yume-dsl-rich-text";
const dsl = createParser({
handlers: {
...createSimpleInlineHandlers(["bold", "italic", "underline", "strike"]),
thin: {}, // 当前版本约等于 createSimpleInlineHandlers(["thin"])
...createSimpleBlockHandlers(["info", "warning"]),
...createSimpleRawHandlers(["code"]),
},
blockTags: declareMultilineTags(["info", "warning", "code"]),
});如果你只是想零成本声明几个标签名,也可以把其中一部分 inline 标签直接写成 bold: {} / italic: {}。可以把它理解成不经过
helper、直接手写版的 createPassthroughTags(...);如果你想固定输出形状(例如明确得到 {type, value}),再用
createSimpleInlineHandlers(...)。具体 fallback 结果和 form
规则,见 标准隐式写法:空对象 handlers。
const tokens = dsl.parse("Hello $$bold(world)$$!");结果:
[
{type: "text", value: "Hello ", id: "rt-0"},
{
type: "bold",
value: [{type: "text", value: "world", id: "rt-1"}],
id: "rt-2",
},
{type: "text", value: "!", id: "rt-3"},
]const plain = dsl.strip("Hello $$bold(world)$$!");
// "Hello world!"适用于提取可搜索的纯文本、生成摘要或构建无障碍标签。
未注册的标签会优雅降级,而不是抛出异常。
第一次使用建议按以下顺序阅读:
- 快速开始(你在这里)
- DSL 语法 — 三种标签形式
- createParser — 主入口
- 处理器辅助函数 — 批量注册标签,减少模板代码
- 编写标签处理器(进阶) — 自定义处理器逻辑
- parseStructural — 用于结构消费场景(高亮、lint、编辑器、源码检查)
实战教程 — Wiki 上的手把手指南:
- 从零实现 link 标签 —
从零到一个可用的
$$link(url | text)$$ - 游戏对话标签 — 为视觉小说打字机构建 shake/color/wait 标签
- 安全 UGC 聊天 — 白名单 inline 标签、屏蔽危险形式、处理错误
默认使用 $$ 作为标签前缀。所有语法符号(前缀、分隔符、转义字符、block/raw
标记)均可完全自定义——参见自定义语法。
标签名允许 a-z、A-Z、0-9、_、-(首字符不能是数字或 -)。
如需自定义,参见自定义标签名字符规则。
支持三种形式:
降级规则: 输入不合法或 handler 不支持用户写的形式时,解析器会优雅降级为纯文本而非抛出异常。完整规则——包括 raw/block 嵌套在 inline 内的常见陷阱——详见 DSL 语法 — 优雅降级规则 wiki 页面。
$$tagName(content)$$
Inline 内容递归解析,嵌套自然生效。
$$bold(Hello $$italic(world)$$)$$
Inline 简写示例(implicitInlineShorthand):
$$bold(1234underline()test())$$
备注:简写仅在 inline 参数区生效,且仅匹配已注册并支持 inline form 的标签。配置方式见下文 implicitInlineShorthand。
$$tagName(arg)%
Raw 内容,按原样保留
%end$$
Raw 内容不会递归解析。
关闭标记 %end$$ 必须独占一行。
$$tagName(arg)*
Block 内容,递归解析
*end$$
Block 内容递归解析。
关闭标记 *end$$ 必须独占一行。
在参数中,| 用于分隔多个参数。
$$link(https://example.com | click here)$$
$$code(js | Title | label)%
const x = 1;
%end$$
使用 \| 转义字面管道符。
在语法符号前加 \ 使其作为字面量输出。
\(→(\)→)\|→|\\→\\%end$$→%end$$\*end$$→*end$$
createParser 将你的 ParseOptions(handlers、syntax、tagName、depthLimit、onError、trackPositions)绑定为一个可复用实例。
这是推荐的使用方式 — 定义一次标签处理器,然后在各处调用 dsl.parse() / dsl.strip(),无需重复传入配置。
import {
createParser,
createSimpleInlineHandlers,
parsePipeArgs,
} from "yume-dsl-rich-text";
const dsl = createParser({
handlers: {
...createSimpleInlineHandlers(["bold", "italic", "underline"]),
link: {
inline: (tokens, ctx) => {
const args = parsePipeArgs(tokens, ctx);
return {
type: "link",
url: args.text(0),
value: args.materializedTailTokens(1),
};
},
},
},
});
// 到处使用 — handlers 已经绑定
dsl.parse("Hello $$bold(world)$$!");
dsl.strip("Hello $$bold(world)$$!");
// 单次覆盖会合并到默认值上。
// `syntax` 和 `tagName` 还会额外做一层深合并,因此局部覆盖不会冲掉其余默认配置。
dsl.parse(text, {onError: (e) => console.warn(e)});createParser 绑定了什么:
大多数场景下,createParser 主要是为了绑定 handlers;其余选项只是顺手一起固化到实例上。
| 选项 | 预绑定后的效果 |
|---|---|
handlers |
标签定义 — 使用 createParser 的主要理由 |
syntax |
自定义语法符号(如覆盖 $$ 前缀等) |
tagName |
自定义标签名字符规则 |
allowForms |
限制接受的标签形式(默认:全部启用) |
implicitInlineShorthand |
控制 inline 参数中的 name(...) 简写(默认:关闭)。1.3 起 |
depthLimit |
嵌套深度限制 — 很少需要逐次修改 |
createId |
自定义 token id 生成器(仍可按次覆盖) |
blockTags |
块级换行规范化——详见 declareMultilineTags |
onError |
默认错误处理器(仍可按次覆盖) |
trackPositions |
为所有输出节点附加源码位置(仍可按次覆盖) |
不用 createParser 的话,每次调用都需要传入完整选项:
parseRichText(text, {handlers});
stripRichText(text, {handlers});
// 用 createParser
const dsl = createParser({handlers});
dsl.parse(text);
dsl.strip(text);方法一览:
| 方法 | 输入 | 输出 | 继承的 defaults 字段 |
|---|---|---|---|
parse |
DSL 文本 + overrides? | TextToken[] |
全部 ParseOptions——syntax/tagName 的 override 会深合并 |
strip |
DSL 文本 + overrides? | string |
同 parse |
structural |
DSL 文本 + overrides? | StructuralNode[] |
handlers、allowForms、syntax、tagName、depthLimit、trackPositions |
print |
StructuralNode[] + overrides? |
string |
仅 syntax——overrides 与 defaults 深合并。无损序列化器,不做门控 |
底层无状态函数。适用于一次性调用或需要完全控制每次调用参数的场景。
function parseRichText(text: string, options?: ParseOptions): TextToken[];
function stripRichText(text: string, options?: ParseOptions): string;ParseOptions 包含 handlers、allowForms、syntax、tagName、depthLimit、createId、blockTags、
onError、trackPositions。详见 ParseOptions。
应用层通常优先使用 createParser;只有工具函数式的一次性调用才适合直接用 parseRichText()。
用于结构消费场景——高亮、lint、编辑器、源码检查。
在输出树中保留标签形态(inline / raw / block)。与 parseRichText 共享同一套语言配置。
const tree = parseStructural("$$bold(hello)$$ and $$code(ts)%\nconst x = 1;\n%end$$");
// [
// { type: "inline", tag: "bold", children: [{ type: "text", value: "hello" }] },
// { type: "text", value: " and " },
// { type: "raw", tag: "code", args: [...], content: "\nconst x = 1;\n" },
// ]怎么选? 渲染内容 → parseRichText;分析源码结构 → parseStructural。
详见 API 参考 wiki 页面:
StructuralNode 变体、StructuralParseOptions、与 parseRichText 的差异、printStructural。
这两个 API 用在“不是只解析一次,而是文档会被持续编辑”的场景。
这组增量 API 在 1.4.x 已属于稳定公开能力。
尤其要注意:session 级 fallback 是文档化契约,不是异常边角行为;
applyEdit(...) / applyEditWithDiff(...) 可能返回 mode: "full-fallback" 并带 fallbackReason,
而 diff 细化预算参数现在放在 sessionOptions.diff(也可在 applyEditWithDiff(..., diffOptions) 按次覆盖)。
parseIncremental(source, options?)—— 建好并返回第一份增量快照(IncrementalDocument)createIncrementalSession(source, options?, sessionOptions?)—— 建一个长期存活的 session,后面反复吃 editcreateIncrementalDirtyRange(diff)—— 绑定一个TokenDiffResult,返回{ getRange, touches },用于标记与新源码 dirty span 相交的带源码位置渲染 token。no-op diff 下getRange()返回null,touches(...)永远不命中; 零宽 dirty range 只命中真正包含该 offset 的半开源码范围(startOffset <= offset < endOffset)。
简单记:
- 只要第一份快照 →
parseIncremental(...) - 编辑器 / 实时预览 / 连续更新 →
createIncrementalSession(...) - 基于 diff 高亮 / 懒渲染边界标记 →
createIncrementalDirtyRange(...)
README / GUIDE 里这里只放最短说明。getDocument、applyEdit、applyEditWithDiff、rebuild 的完整用法、diff
消费方式与集成示例,统一看 增量解析 wiki 页面。
所有语法符号——前缀、开闭分隔符、管道分隔符、转义字符、block/raw 标记——均可通过 options.syntax 覆盖。
这让你可以将 DSL 适配到任何宿主标记语言而不产生冲突。
import {createEasySyntax, parseRichText} from "yume-dsl-rich-text";
const syntax = createEasySyntax({tagPrefix: "@@"});
// endTag, rawClose, blockClose 自动推导:")@@", "%end@@", "*end@@"
const finSyntax = createEasySyntax({tagPrefix: "@@", closeMiddle: "fin"});
// rawClose, blockClose 也可以变成 "%fin@@"、"*fin@@"
const tokens = parseRichText("@@bold(hello)@@", {
syntax,
handlers: {
bold: {
inline: (tokens, ctx) => ({type: "bold", value: tokens}),
},
},
});详见 自定义语法 wiki 页面
:默认符号参考、符号联动表、createEasySyntax 推导规则(含 closeMiddle)、createSyntax 底层 API。
默认标签名允许 a-z、A-Z、0-9、_、-(首字符不能是数字或 -)。
详见 自定义标签名字符 wiki 页面:
createTagNameConfig、DEFAULT_TAG_NAME、冒号/数字等字符的使用示例。
辅助函数让你批量注册标签处理器,无需重复编写样板代码。
import {
createParser,
createSimpleInlineHandlers,
createSimpleBlockHandlers,
createSimpleRawHandlers,
declareMultilineTags,
} from "yume-dsl-rich-text";
const dsl = createParser({
handlers: {
...createSimpleInlineHandlers(["bold", "italic", "underline", "strike", "code"]),
...createSimpleBlockHandlers(["info", "warning"]),
...createSimpleRawHandlers(["math"]),
},
blockTags: declareMultilineTags(["info", "warning", "math"]),
});| 辅助函数 | 输出 Token 结构 |
|---|---|
createSimpleInlineHandlers |
{ type: tagName, value: materializedTokens } |
createSimpleBlockHandlers |
{ type: tagName, arg, value: content } |
createSimpleRawHandlers |
{ type: tagName, arg, value: content }(string) |
如果你只是想声明标签存在,最短写法就是直接写空对象:
const handlers = {
bold: {},
italic: {},
};这是库里正式推荐的零成本声明语法:
| 写法 | 语义 |
|---|---|
createSimpleInlineHandlers(["bold"]) |
显式安装 inline handler,固定产出 { type: "bold", value: materializedTokens } |
bold: {} |
只声明标签名存在,依赖默认 materialization / fallback |
如果你喜欢 createPassthroughTags 的“短”,真正需要保留的通常不是那个 helper,而是这套空对象 handler 写法本身。
- 可以把
bold: {}理解成旧createPassthroughTags(["bold"])的直接手写版 - 可以把
createSimpleInlineHandlers(["bold"])理解成“显式固定输出结构”的版本 - 具体 fallback 输出和 form 规则,统一看 处理器辅助函数 — 标准隐式写法:空对象 handlers
推荐的处理器辅助函数,适用于需要管道参数、多形态、或自定义逻辑的标签。
每个 handler 接收预解析的 PipeArgs——无需手动调用 parsePipeArgs。
import {createParser, createPipeHandlers, createSimpleInlineHandlers} from "yume-dsl-rich-text";
const dsl = createParser({
handlers: {
...createSimpleInlineHandlers(["bold", "italic"]),
...createPipeHandlers({
link: {
inline: (args, ctx) => ({
type: "link",
url: args.text(0),
value: args.materializedTailTokens(1),
}),
},
code: {
raw: (args, content, ctx) => ({
type: "raw-code",
lang: args.text(0, "text"),
value: content,
}),
},
}),
},
});| 场景 | 使用 |
|---|---|
| 简单 inline(bold、italic 等) | createSimpleInlineHandlers |
| 简单 block(info、warning 等) | createSimpleBlockHandlers |
| 简单 raw(code、math 等) | createSimpleRawHandlers |
管道参数($$link(url | text)$$) |
createPipeHandlers |
| 多形态(inline + block + raw) | createPipeHandlers |
当标签渲染成块级 / 容器元素时,用它避免边界换行混进内容。它不创建处理器,只负责换行规范化。
如果你想按标签、按 form 做细粒度控制,它也支持 { tag, forms } 对象形式,不只支持字符串数组。
最短用法:
blockTags: declareMultilineTags(["info", "warning", "center"])经验法则: 如果你的标签渲染为块级元素,确保它出现在 blockTags 中。否则边界换行会混入内容,渲染时产生多余空行。
详见 处理器辅助函数 wiki 页面
:完整 API 签名、各形态规则、PipeHandlerDefinition 接口细节。
这里先只保留最短概览。完整字段说明、示例、边界行为,统一看 ParseOptions wiki 页面。
ParseOptions 和 StructuralParseOptions 均继承自 ParserBaseOptions:
interface ParserBaseOptions {
handlers?: Record<string, TagHandler>;
allowForms?: readonly ("inline" | "raw" | "block")[];
implicitInlineShorthand?: boolean | readonly string[];
depthLimit?: number;
syntax?: Partial<SyntaxInput>;
tagName?: Partial<TagNameConfig>;
baseOffset?: number;
tracker?: PositionTracker;
}
interface ParseOptions extends ParserBaseOptions {
createId?: (token: TokenDraft) => string;
blockTags?: readonly BlockTagInput[];
mode?: "render"; // 已弃用
onError?: (error: ParseError) => void;
trackPositions?: boolean;
}
interface StructuralParseOptions extends ParserBaseOptions {
trackPositions?: boolean;
}handlers:你的标签定义syntax/tagName:改语法符号或标签名规则allowForms:全局限制只接受哪些标签形式implicitInlineShorthand:在 inline 参数区启用name(...)简写depthLimit:嵌套上限trackPositions、baseOffset、tracker:源码位置映射blockTags:块级换行规范化onError:收集解析错误createId:自定义本次解析的 token id
StructuralParseOptions 是偏结构解析的轻量子集;ParseOptions 在它之上再加 createId、blockTags、onError 这类渲染侧字段。
这个字段适合“评论区只允许 inline”这类全局门控场景。没列出的形式不会报错,而是优雅降级成普通文本。
完整示例和行为细节见:ParseOptions wiki —
allowForms。
1.3 起
这个字段让 inline 参数区支持更轻量的 name(...) 简写;它只影响 inline 参数区,不影响顶层文本。可取 false、true 或标签白名单。
完整示例、白名单行为和解析优先级见:ParseOptions wiki —
implicitInlineShorthand。
TextToken 是解析器的输出形状:有 type、value、id、可选 position,以及你在 handler 里附加的额外字段。
它刻意保持开放结构,这样解析器不需要预先知道你的业务 schema。
如果你想要更强的编译期收窄,可以用 NarrowToken、NarrowDraft、createTokenGuard。
详见 强类型 wiki 章节
:完整 render 示例、NarrowTokenUnion、以及手写判别联合替代方案。
默认情况下,每次 parseRichText 调用会分配顺序 ID(rt-0、rt-1、…)。
createEasyStableId() 返回一个基于内容的 CreateId 生成器——ID 根据 token 内容而非流中位置生成,
因此文档其他位置的编辑不会使不相关的 ID 偏移。
const tokens = parseRichText("Hello $$bold(world)$$", {
handlers,
createId: createEasyStableId(), // → "s-a1b2c3"(基于内容)
});详见 稳定 Token ID wiki 页面
:稳定性保证、自定义指纹、消歧、作用域控制、EasyStableIdOptions。
大多数标签用 createPipeHandlers 或
createSimple* 辅助函数就够了。只有当辅助函数无法表达你的逻辑时——
例如条件字段映射、内容转换、动态类型选择——才需要手写 TagHandler。
即使暂时不用,也建议在手写 handler 里把 ctx 参数写上,这样更符合后续 ctx-first 方向,也能避免并发环境下的模块级状态问题。
最短示例:
const dsl = createParser({
handlers: {
code: {
raw: (arg, content, ctx) => ({
type: "code-block",
lang: arg ?? "text",
value: content,
}),
},
},
});增量解析这组 API 的表面积比核心解析更大。 如果你要接 session 或 diff,请先对照 wiki 里的精确签名和版本说明。
| 分类 | 导出 |
|---|---|
| 核心 | parseRichText、stripRichText、createParser、parseStructural、printStructural、buildZones |
| 增量解析 | parseIncremental、createIncrementalSession、createIncrementalDirtyRange |
| 配置 | DEFAULT_SYNTAX、createEasySyntax、createSyntax、DEFAULT_TAG_NAME、createTagNameConfig、createEasyStableId |
| 处理器辅助函数 | createPassthroughTags、createPipeHandlers、createPipeBlockHandlers、createPipeRawHandlers、createSimpleInlineHandlers、createSimpleBlockHandlers、createSimpleRawHandlers、declareMultilineTags |
| 处理器工具函数 | parsePipeArgs、parsePipeTextArgs、parsePipeTextList、extractText、createTextToken、splitTokensByPipe、materializeTextTokens、unescapeInline、readEscapedSequence、createToken、createTokenGuard、resetTokenIdSeed |
| Token 遍历 | walkTokens、mapTokens、filterTokens |
| 位置追踪 | buildPositionTracker |
| 兼容上下文(已弃用) | withSyntax、getSyntax、withTagNameConfig |
| 类型 | TextToken、TokenDraft、CreateId、DslContext、TagHandler、TagForm、InlineShorthandOption、ParseOptions、ParserBaseOptions、StructuralParseOptions、Parser、SyntaxInput、SyntaxConfig、TagNameConfig、BlockTagInput、BlockTagLookup、MultilineForm、ErrorCode、ParseError、StructuralNode、SourcePosition、SourceSpan、SourceOffsetRange、PositionTracker、PipeArgs、PipeHandlerDefinition、EasyStableIdOptions、PrintOptions、TokenVisitContext、WalkVisitor、MapVisitor、Zone、IncrementalDocument、IncrementalEdit、IncrementalParseOptions、IncrementalSessionOptions、IncrementalDiffRefinementOptions、TokenDiffResult、DirtyRangeGetter、DirtyRangeTester、IncrementalSessionApplyResult、IncrementalSessionApplyWithDiffResult、NarrowToken、NarrowDraft、NarrowTokenUnion |
详见 导出一览 wiki 页面 :完整签名及详细文档。
传入 trackPositions: true 可为每个输出节点附加 position(源码范围)。
const tokens = parseRichText("hello $$bold(world)$$", {
handlers: {bold: {inline: (t, ctx) => ({type: "bold", value: t})}},
trackPositions: true,
});
// tokens[0].position → { start: {offset:0, line:1, column:1}, end: {offset:6, line:1, column:7} }解析子串时,传入 baseOffset 和 buildPositionTracker(fullText) 预构建的 tracker 可将位置映射回原始文档。
详见 源码位置追踪 wiki 页面
:类型定义、子串解析指南、parseRichText 与 parseStructural 差异、性能基准。
使用 onError 收集解析错误。如果省略,错误被静默丢弃——解析器永远不会抛出异常。
const errors: ParseError[] = [];
parseRichText("$$bold(unclosed", {
onError: (e) => errors.push(e),
});
// errors[0].code === "INLINE_NOT_CLOSED"解析器默认优雅降级而不是抛异常:未知标签、不支持的形式、以及不合法输入都会尽量回到文本形态。
⚠️ 常见陷阱: raw / block 标签嵌套在 inline 参数区内时,handler 必须同时声明inline。 否则解析器无法进入子帧,整个嵌套标签会降级为纯文本。 完整的降级决策表和示例见 DSL 语法 — 优雅降级规则 wiki 页面。
详见 错误处理 wiki 页面 :错误码、触发场景、详细降级示例。
以下这些已导出的兼容 API将在未来 major 版本中移除(2026 年 9 月前不会移除):
withSyntax、getSyntax、withTagNameConfig、resetTokenIdSeed、
createPipeBlockHandlers、createPipeRawHandlers、createPassthroughTags、ParseOptions.mode
详见 待弃用 API wiki 页面 :签名、替代方案及迁移指南。
兼容性与版本升级时需要注意的行为变化,现已统一放到 wiki:
MIT