` 或行内徽标 | 占位 | 列出参数;文案来自 `labels.templateNotExpanded` |
| 解析器函数 `{{#if:…}}` | 占位结构 | 占位 | 明确标记为未求值 |
| 参数 `{{{name\|default}}}` | 参数名与默认值 | 占位 | |
| `/expand` 支持的模板与函数 | 展开后的源码重新解析 | 渲染 | 展开失败保留的调用仍按占位渲染 |
| `#invoke`、Lua、`subst` | 保留原文 | 占位 | 不执行,并产生诊断 |
模板、函数与参数中的魔术字不参与目录与分类判断。
## HTML 与扩展标签
| 构造 | 默认输出 | 状态 | 说明 |
| ------------------------------------ | ----------------------------------------- | -- | --------------------------------------- |
| 行内 HTML(`b`、`i`、`u`、`span`、`code` 等) | 对应元素 | 渲染 | 属性只保留白名单 |
| 块级 HTML(`div`、`p`、`blockquote` 等) | 对应元素 | 渲染 | 可以包裹标题,目录位置按其所在块计算 |
| 空元素(`br`、`wbr`、`hr`) | 对应元素 | 渲染 | |
| `meta`、`link` | 无输出 | 忽略 | |
| `script`、`style`、`iframe`、表单控件、`svg` | 文本转义,或被拦截时的可读提示 | 拦截 | 默认配置不解析这些标签,源码按纯文本转义;被配置纳入 HTML 解析时整对丢弃 |
| 未知但非危险标签 | 源码卡片 | 降级 | 不渲染为真实元素 |
| `` | 纯文本 | 渲染 | 不解析链接或模板 |
| `` | 保留空白 | 渲染 | |
| `[` | `` + 分组引用区 | 渲染 | 命名引用复用,`group` 分组;重复定义产生诊断 |
| `` | 占位 | 渲染 | 对应分组引用区放到占位处;无占位时仍追加文末 |
| `` | `]` 原文 | 渲染 | 输出语言 class,不接语法高亮 |
| 其他受支持的扩展 | 按标签处理 | 渲染 | `semanticComponents.extension` 可覆盖 |
## 页面级结构
| 结构 | 行为 |
| --------- | ---------------------------------------------------------------- |
| 目录 | 阈值可通过 `toc` 调整;`__TOC__` 指定位置,`__FORCETOC__` 强制生成,`__NOTOC__` 优先 |
| 引用区 | 默认位于正文之后、分类底栏之前;`` 可指定分组位置 |
| 分类 | 汇总到文末 `div.catlinks.wtr-categories` |
| 重定向 | 首行 `#REDIRECT` 渲染横幅并跳过正文 |
| 无法识别的语义节点 | 源码卡片 + 诊断,不中断整页 |
## 如何确认实际行为
1. 检查 `document.diagnostics` 的 `stage` 与 `code`。
2. 用 `/ast` 的 `flattenNodes` 查看语义树,并与准备结果中的 HAST 对照。
3. 用 `labels` 区分“未求值”“已拦截”“未知结构”等占位文案。
4. 以 `src/` 与 `tests/` 为准;本页随默认行为更新。
行为细节与原因见[渲染行为](/zh/guide/rendering.md);安全相关的拦截规则见[安全模型](/zh/guide/security.md)。
---
url: /zh/guide/links-and-images.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 链接与图片
库默认不映射站点路由,也不加载图片。链接与媒体地址由宿主决定;库只做校验、清洗与渲染。
## 链接
### 默认行为
| 链接类型 | 默认输出 |
| ------------------------- | -------------------------------------------- |
| 内部链接 `[[Page]]` | 无法导航的 ``,`title` 为页面名 |
| 带标签外链 `[https://… label]` | 经过安全校验的 ``,标签为 `label` |
| 裸外链 `https://…` | ``,标签为 URL 文本 |
| 无标签外链 `[https://…]` | ``,标签按文档顺序生成 `[N]` |
外链只允许 `http:`、`https:`、`mailto:`;无法安全解析的地址降级为被拦截的可读文本。绝对 HTTP(S) / mailto 外链默认带 `target="_blank"` 与 `rel="nofollow noopener noreferrer"`。
### resolveLink
```tsx
import type { WikitextLinkResolver } from "react-wikitext/hast";
const resolveLink: WikitextLinkResolver = (request) => {
if (request.kind === "internal") {
const page = request.page ?? request.target.replace(/^:/u, "");
return {
href: `/wiki/${encodeURIComponent(page)}${request.fragment ? `#${request.fragment}` : ""}`,
target: "_self",
};
}
return undefined;
};
```
返回值语义:
| 返回值 | 行为 |
| --------------------------------- | ------------------------- |
| `undefined` | 沿用默认行为;内部链接仍是无法导航的文本 |
| `null` | 关闭导航 |
| `{ href }` | 生成 ``;`href` 仍会经过安全校验 |
| `{ label }` | 替换纯文本标签;只有 `label` 时不生成链接 |
| `{ target: "_blank" \| "_self" }` | 覆盖窗口目标 |
`request` 提供 `kind`、`target`,内部链接还可能有 `page` 与 `fragment`。`context` 提供原始解析快照 `document`、当前工作树 `ast` 与当前节点 `node`。
::: note
解析函数只返回普通数据,不返回 React 元素。需要完全自定义链接外观与交互时,使用 [`semanticComponents.link`](/zh/api/react.md#语义组件)。
:::
下面的示例为内部链接提供站内地址,外链沿用默认处理:
```tsx preview
import { Wikitext } from "react-wikitext";
import "react-wikitext/styles.css";
export default function Demo() {
return (
request.kind === "internal"
? { href: "/zh/guide/quick-start", target: "_self" }
: undefined,
}}
/>
);
}
```
### 外部链接策略
- `policy.allowRelativeLinks` 默认为 `true`,允许解析函数返回相对地址。
- `policy.externalLinkTarget` 默认 `"_blank"`;设为 `null` 时外链不写 `target`。
- 相对链接在 `_blank` 下会加上 `noopener noreferrer`;最终整理会删除非法的 target 与未知的 `rel` 值。
## 图片
### 默认行为
文件节点默认渲染为带说明的占位。没有 `resolveImage` 时不会生成 `
`,库也不会请求任何图片地址。
### resolveImage
```tsx
import type { WikitextImageResolver } from "react-wikitext/hast";
const resolveImage: WikitextImageResolver = (request) => ({
src: `https://cdn.example.com/wiki/${encodeURIComponent(request.name)}`,
alt: request.alt ?? request.name,
...(request.width === undefined ? {} : { width: request.width }),
...(request.height === undefined ? {} : { height: request.height }),
});
```
- 返回 `null` 保持占位;返回对象才授权图片资源。
- `src` 只允许 `http:` / `https:`;需要相对图片时显式设置 `policy.allowRelativeImages: true`。
- `request` 提供 `name`、`parameters`、解析出的 `width` / `height`、`alt` 与 `caption`。
- 宽高最终限制在 1–4096;解析函数返回的值优先。
- 生成的 `
` 固定带 `loading="lazy"` 与 `decoding="async"`,`alt` 缺省使用文件名。
### 文件说明与链接
- 说明优先取显式 `caption=`,否则取未命名说明;说明中的链接、模板占位会递归渲染。
- 文件参数 `link=` 经 `sanitizeHref` 后生成链接;`class=` 作为附加类名。
- 行内文件使用 ``,块级文件使用 ``;外层保留 `mw-default-size`、`mw-halign-*` 等兼容类名。
## 授权与安全
准备阶段会记录解析函数实际返回并通过校验的图片地址。最终整理时:
- 未授权的 `
` 会被删除;有 `alt` 时保留文本,并产生 `UNAUTHORIZED_IMAGE` 诊断。resolver 产物使用 `authorizedImages`,源码 HTML 使用 `policy.allowSourceImages`,插件产物使用 `policy.allowPluginImages`。
- 插件不能通过插入任意 `
` 绕过默认不出图策略。
- URL 在写入属性前会先进行实体解码、去除控制字符,再按协议白名单校验。
需要预先处理 URL 时,可以使用 `/hast` 导出的 `sanitizeHref`、`sanitizeImageUrl` 与 `sanitizedAttributesToProps`。库在最终阶段仍会再次校验。
## 策略与组件
如果只需要调整默认策略,不需要接入解析函数:
```tsx
```
`policy` 与默认值做单层浅合并;`extraTags`、`stripTags` 是整体替换,不会追加。需要保留默认数组时以 `DEFAULT_PREPARE_POLICY` 为基准展开。
链接与文件的最终外观可以通过 [HTML 组件或语义组件](/zh/guide/customization.md#组件覆盖) 自定义。自定义组件属于可信代码,输出不再经过库清洗。
---
url: /zh/guide/templates.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 模板展开
默认管线不执行模板。需要模板结果时,可以用 `/expand` 在解析前展开受支持的模板与解析器函数。库不发起网络请求,模板源码由宿主的 `resolveTemplate` 提供。
## 基本用法
```tsx
import { expandWikitext } from "react-wikitext/expand";
import { prepareWikitext } from "react-wikitext/hast";
import { WikitextView } from "react-wikitext/view";
import "react-wikitext/styles.css";
const expanded = await expandWikitext(source, {
pageName: "Example",
resolveTemplate: async ({ title }) => {
const response = await fetch(`/api/templates/${encodeURIComponent(title)}`);
return response.ok ? await response.text() : null;
},
});
const prepared = prepareWikitext(expanded.document);
const view = ;
```
`expandWikitext` 会:
1. 解析原始源码,得到 `original` 快照。
2. 按调用结构求值模板与函数,记录 `records`。
3. 把展开后的源码重新解析为 `document`。
4. 返回 `expansion.status`:`"complete"`、`"partial"` 或 `"fallback"`。
展开结果已经是普通解析文档;**不要再手动解析一次**,直接交给 `prepareWikitext`。
## 解析函数约定
下面把 `resolveTemplate` 回调称为解析函数。
```ts
type WikitextTemplateResolver = (
request: WikitextTemplateRequest,
) => string | null | undefined | Promise;
```
- 返回 `null` / `undefined` 表示模板不存在;空字符串是合法模板。
- 同一标题在一次展开中只解析一次,解析函数必须为同一 `title` 返回稳定源码。
- `request.pageName` 是原始页面;`request.callerTitle` 是当前调用所属模板;在原始页面中调用时省略。
- `request.invocation` 指向 `original.source` 中的顶层调用范围;嵌套请求复用所属顶层调用范围。
- 换行归一化、include 标签过滤与解析由库完成;解析函数只取回源码。
## 支持的语法
- 模板嵌入与参数替换,包括匿名参数、具名参数与参数默认值。
- `noinclude` / `includeonly` / `onlyinclude`。
- `#if`、`#ifeq`、`#switch`、`#iferror`。
- `{{!}}` 与 `{{=}}`。
- 模板与函数输出的隐式换行。
按需计算:模板名、参数名与条件表达式只在所属作用域需要时求值。未选中的分支、未读取的参数与未使用的默认值不会调用解析函数,也不计入调用预算。
以下调用默认保留原文并产生诊断:
- `#invoke` 与 Lua。
- `subst` 等嵌入修饰符。
- 依赖站点状态或外部运行环境的函数,例如 `#ifexist`、`#time`。
## 预算与错误
| 选项 | 默认值 | 说明 |
| ------------------- | -------------------- | ---------------------- |
| `maxDepth` | 40 | 嵌套展开深度 |
| `maxInvocations` | 1,000 | 单次展开的调用次数 |
| `maxExpandedLength` | 与原始页面长度上限相同(100,000) | 展开后源码长度 |
| `strict` | `false` | 为 `true` 时任意可恢复失败都抛错 |
| `signal` | — | 取消展开;取消时以 `ABORTED` 拒绝 |
- 单次调用超长时保留调用并记录 `LENGTH_BUDGET_EXCEEDED`。
- 连保留源码都超过 `maxExpandedLength` 时抛 `EXPANSION_TOO_LARGE`。
- `strict: true` 下预算超限抛 `EXPANSION_BUDGET_EXCEEDED`。
- 默认模式下,最终源码解析失败会退回原始快照,`status` 为 `"fallback"`。
- 通过 `AbortSignal` 取消展开,原始原因保存在错误的 `cause` 中。
所有错误都是 `WikitextExpandError`,常见错误码包括 `TEMPLATE_NOT_FOUND`、`TEMPLATE_LOOP`、`UNSUPPORTED_CALL`、`FINAL_PARSE_FAILED`、`ABORTED`。
## 结果与诊断
```ts
const {
original, // 原始解析快照
document, // 展开后的解析快照
expansion, // status / records / diagnostics
} = expanded;
```
- `expansion.records` 记录每次调用的 `kind`、名称、原始范围、展开范围与状态(`expanded` / `preserved`)。
- `expansion.diagnostics` 的坐标基于 `original.source`。
- `document.diagnostics` 合并最终解析诊断与换算到展开源码的展开诊断。
- `prepareWikitext(document)` 会把诊断带入渲染结果。
嵌套调用只映射到所属顶层记录,不提供模板内部的精确来源映射。
## 未展开的调用如何显示
展开失败保留的模板、函数与参数继续按[占位结构](/zh/guide/rendering.md#模板函数与参数)渲染,不会显示成已求值内容。模板源码与展开结果都按不可信 wikitext 处理,仍会经过语义归一化、HAST 转换与最终清洗。
## 限制
- include 标签过滤是对模板源码的单遍扫描;`includeonly` 内容中的嵌套 `noinclude` / `onlyinclude` 不会再次识别。
- 不执行 Lua,不内置站点 API 客户端。
- 不提供跨请求的模板缓存;需要缓存时由宿主在解析函数中实现。
- 展开结果中的分类、引用、目录等仍按默认渲染规则处理。
---
url: /zh/guide/customization.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 样式与组件定制
库输出稳定的 `wtr-*` 类名与一组 CSS 变量。页面结构、文案、标题锚点和组件外观都可以在不改源码的情况下调整。
## 样式
引入一次页面样式:
```tsx
import "react-wikitext/styles.css";
```
- 库不设置全局元素样式,页面字体默认继承自宿主。
- 根容器是 `div.wtr-view`;自有类名以 `wtr-` 前缀开头,同时保留一部分 MediaWiki 兼容类名。
- 内联样式只保留白名单内的排版与外观声明,危险值整条丢弃;严格 CSP 下可按需放行 `style-src`。
### CSS 变量
| 变量 | 用途 |
| ---------------------- | ------- |
| `--wtr-text` | 正文颜色 |
| `--wtr-muted` | 次要文本 |
| `--wtr-border` | 边框与分隔线 |
| `--wtr-accent` | 链接与强调色 |
| `--wtr-accent-soft` | 强调背景 |
| `--wtr-placeholder-bg` | 占位背景 |
| `--wtr-danger` | 错误与阻止提示 |
| `--wtr-font-mono` | 等宽文本 |
变量定义在 `:root`,可以在任意祖先覆盖:
```css
.article-theme {
--wtr-text: #1f2328;
--wtr-accent: #0969da;
--wtr-border: #d0d7de;
}
```
深色主题可以通过 `[data-wtr-theme="dark"]` 或宿主自己的选择器覆盖同一组变量。
### 容器属性
```tsx
```
`className` 与 `containerProps.className` 会和 `wtr-view` 合并。`containerProps` 在类型层排除了 `dangerouslySetInnerHTML`。
## 文案
所有用户可见的占位与提示都来自 `labels`。准备选项接受 `Partial`,未提供的字段沿用默认英文文案。
```tsx
```
| 字段 | 默认值 |
| ---------------------------- | ------------------------------- |
| `templateNotExpanded` | `Template not expanded` |
| `parserFunctionNotEvaluated` | `Parser function not evaluated` |
| `filePlaceholder` | `File` |
| `reference` | `Reference` |
| `toc` | `Contents` |
| `categories` | `Categories` |
| `unknownNode` | `Unsupported node` |
| `redirectTo` | `Redirect to` |
| `blockedLink` | `Blocked link` |
| `blockedHtml` | `Blocked HTML` |
| `renderFailed` | `Render failed` |
| `nestedTableUnsupported` | `Nested table not supported` |
实际生效的文案会写入 `WikitextRenderDocument.meta.labels`,视图与组件读取同一份文案。
## 标题锚点
```ts
type WikitextHeadingIdStrategy = "instance" | "mediawiki" | "none";
```
| 策略 | 行为 |
| -------------- | ---------------------------------- |
| `instance`(默认) | 准备阶段生成逻辑锚点;视图按实例前缀映射 DOM id,多实例不冲突 |
| `mediawiki` | 写入裸锚点,视图不追加前缀;适合与宿主约定一致的单实例页面 |
| `none` | 不写 id,也不生成目录链接 |
- 锚点由移除标记后的标题文本生成:空白转为 `_`,重复 id 按大小写不敏感追加 `_2`、`_3`,空标题使用 `section-{n}`。
- 语义标题与源码 `h1`–`h6` 都参与锚点生成。
- `idPrefix` 只在 `instance` 下生效;未传时使用 React `useId` 生成的实例前缀。
- 视图同时映射标题 `id`、文档内 `#fragment` 链接和表格 `headers` 属性。
标题列表直接读取渲染快照:
```ts
for (const heading of prepared.headings) {
console.log(heading.level, heading.text, heading.id);
}
```
## 组件覆盖
覆盖点按优先级从高到低排列:
1. `semanticComponents[type]`:替换整个 wiki 语义结构。
2. `components[tag]`:替换默认 HAST 结构内部的 HTML 标签。
3. 原生元素。
### HTML 组件
```tsx
(
{children}
),
}}
/>
```
`components` 按 HAST 标签名映射,键可以是 `p`、`table`、`img`、`a` 等。组件会收到经过安全校验的属性、`node`(HAST 元素)、`document`、可选的 `semantic` 节点与 `origin`。不要把非 DOM 属性直接展开到原生元素上。
### 语义组件
```tsx
import { WikitextFragment, type WikitextTemplateComponentProps } from "react-wikitext/view";
function TemplateCard({ displayName, node }: WikitextTemplateComponentProps) {
return (
);
}
;
```
可覆盖的语义类型:
| 类型 | 属性中的额外字段 |
| ----------------------------- | ---------------------------------- |
| `link` | `target`、`kind`、`href?`、`external` |
| `template` / `parserFunction` | `displayName` |
| `argument` | — |
| `file` | `name` |
| `reference` | `number` |
| `extension` | `name`、`block` |
| `unknown` | `parsedType`、`block` |
| `table` | — |
- `semanticComponents.extension` 会收到除 `ref` 外的扩展节点;`ref` 使用 `reference`,`` 不产生组件调用。
- `semanticComponents.reference` 只替换 ``,文末引用区仍由库生成。
- `WikitextFragment` 必须在 `WikitextView` 的 Context 内使用,`nodes` 必须是当前渲染文档中的同一批节点对象。它适合渲染默认转换函数不会输出的子内容,例如模板参数、文件说明和 `ref` 内容。
::: warning
`components` 与 `semanticComponents` 属于可信代码;组件输出的 HTML 不再经过库清洗。解析函数返回的 URL 已在准备阶段校验,但组件仍应避免把未经验证的输入插入危险属性。
:::
### 读取上下文
```tsx
import { useWikitextIds, useWikitextLabels } from "react-wikitext/view";
function SectionLink() {
const labels = useWikitextLabels();
const ids = useWikitextIds();
return {labels.toc};
}
```
- `useWikitextContext()` 提供 `document`、索引、组件表、`idPrefix`、`mapId`、`mapHref` 与 `onRenderError`。
- `useWikitextLabels()` 读取当前渲染快照的文案。
- `useWikitextIds()` 提供 `toDomId` 与 `toHref`。
这些 Hook 与 `WikitextFragment` 必须在 `` 内使用。
## 插件
```tsx
(tree, file) => {
// 在克隆的语义 AST 上运行
return tree;
},
],
rehypePlugins: [
() => (tree) => {
for (const child of tree.children) {
if (child.type === "element" && child.tagName === "p") {
const classes = child.properties.className;
child.properties.className = [
"doc-paragraph",
...(Array.isArray(classes) ? classes : []),
];
}
}
return tree;
},
],
}}
/>
```
- `wikitextPlugins` 在克隆的语义树上运行;`rehypePlugins` 在 HAST 转换后、最终清洗前运行。
- 插件签名沿用 unified 的 `(tree, file)`,其中 `file` 是 VFile。
- 原始解析快照不会被修改;准备后会对工作树重新分配 id。
- 同步入口不接受异步插件,异步插件需要 `prepareWikitextAsync`。
- 最终安全清洗始终在 rehype 插件之后执行,插件不能绕过白名单与图片授权。
插件列表是结构化数组,元素可以是插件函数或 `[plugin, options]` 元组,按顺序执行。
## 安全策略
`prepareOptions.policy` 可以调整 URL 与标签白名单。默认值:
```ts
{
allowRelativeLinks: true,
allowRelativeImages: false,
allowPluginImages: false,
externalLinkTarget: "_blank",
extraTags: [],
stripTags: [
"script", "style", "iframe", "object", "embed", "form", "input",
"button", "textarea", "select", "option", "svg", "math", "meta", "link",
],
}
```
`policy` 与默认值单层浅合并;数组字段整体替换。`class` 只按 `[A-Za-z0-9_-]` 安全字符集过滤,不提供放宽或收窄选项。需要白名单之外的标签、属性或富展示时,优先通过 `/view` 的组件覆盖实现,不要放宽清洗规则。
## 自定义语义节点
自定义节点需要同时扩展 `WikitextNodeMap` 并在 `prepareOptions.handlers` 注册对应的 HAST 转换函数。未注册转换函数的节点会产生诊断并降级为源码卡片。转换函数的上下文、预算与转换细节见[准备 API](/zh/api/prepare.md#handlers)。
---
url: /zh/guide/performance.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 性能与预算
默认管线是同步的:解析与准备都在调用时完成。库不内置调度器、Worker 或虚拟列表;性能策略由宿主决定。本页给出可调的预算与常用的开销控制方式。
## 成本在哪里
| 阶段 | 主要成本 | 可以省掉吗 |
| -- | ------------------------ | ------------------ |
| 解析 | 上游 token 化、源码语法树、语义归一化 | 不能;但可以缓存或放到 Worker |
| 准备 | 克隆语义树、插件、HAST 转换、清洗与最终整理 | 展示层变化时可以复用已有渲染快照 |
| 渲染 | 建立语义/HAST 索引、逐顶层块渲染与错误边界 | 不能;大文档需要宿主分页 |
`retainSyntax: true` 会保留源码语法树,显著增加内存与构建成本;只在需要源码坐标或 playground 语法树时开启。
## 预算
### 解析
| 选项 | 默认值 | 说明 |
| ------------------ | ------- | ------------ |
| `maxSourceLength` | 100,000 | LF 归一化后的源码长度 |
| `maxSyntaxNodes` | 50,000 | 源码语法树节点数 |
| `maxSemanticNodes` | 50,000 | 语义 AST 节点数 |
| `maxDepth` | 1,000 | 语法树与语义树深度 |
有效预算按“单次解析选项 → 创建时 `defaults` → 共享默认值”合并。非法值抛 `INVALID_CONFIG`,超限抛 `SOURCE_TOO_LONG` 或 `SOURCE_TOO_COMPLEX`。
### 准备
准备阶段的工作树与最终 HAST 使用固定预算,**不能**通过解析选项调整:
| 预算 | 上限 |
| ------- | ------ |
| 语义工作树节点 | 50,000 |
| 语义工作树深度 | 1,000 |
| HAST 节点 | 50,000 |
| HAST 深度 | 1,000 |
这些值是防护阈值,不是性能目标。触发时准备阶段直接失败;库产生的合法快照总能被 `restoreWikitextRenderDocument` 恢复。
### 模板展开
| 选项 | 默认值 |
| ------------------- | -------------------- |
| `maxDepth` | 40 |
| `maxInvocations` | 1,000 |
| `maxExpandedLength` | 与原始页面长度上限相同(100,000) |
## 把解析放到 Worker
库不提供 Worker,但快照是普通数据,可以直接跨线程传递:
```ts
// worker.ts
import { parseWikitext } from "react-wikitext/parse";
import { serializeWikitextDocument } from "react-wikitext/ast";
self.onmessage = (event: MessageEvent) => {
const document = parseWikitext(event.data, { pageName: "Example" });
self.postMessage(serializeWikitextDocument(document));
};
```
```ts
// main.ts
import { restoreWikitextDocument } from "react-wikitext/ast";
import { prepareWikitext } from "react-wikitext/hast";
const document = restoreWikitextDocument(payload);
const prepared = prepareWikitext(document);
```
- 也可以把准备放进 Worker,传回渲染快照;渲染必须在主线程。
- 传输前先确认快照版本;跨版本缓存应由恢复函数的校验结果判断是否可用。
- 同一份源码重复发送时,缓存快照比重复解析更划算。
## 缓存
缓存键必须覆盖所有影响输出的输入:
| 缓存 | 键 |
| ---- | ---------------------------------------------------------------- |
| 解析快照 | 源码、`pageName`、站点配置、解析选项、解析器版本 |
| 渲染快照 | 解析快照键 + 解析函数、插件、`policy`、`labels`、`headingIdStrategy`、`handlers` |
快照内的 `AST_SCHEMA_VERSION`、`SYNTAX_SCHEMA_VERSION` 与 `RENDER_SCHEMA_VERSION` 变化意味着结构不兼容,应让缓存整体失效。缓存与恢复的安全边界见[安全模型](/zh/guide/security.md)。
## 避免重复工作
- 把 `parseOptions` 与 `prepareOptions` 放在组件外,或用 `useMemo` 固定引用;内联对象会让 `Wikitext` 每次渲染都重新解析。
- 解析器在模块加载时创建一次;配置变化时创建新解析器。
- 只改组件或样式时复用渲染快照,不重新准备。
- 不需要源码坐标时关闭 `retainSyntax`。
- 服务端准备、客户端只渲染,可以减少浏览器下载与执行;见[服务端与框架](/zh/guide/server-rendering.md)。
## 按入口拆分打包
| 入口 | 加载解析器 | 加载 React | 典型用途 |
| --------- | ----- | -------- | ------------- |
| 根入口 | 是 | 是 | 客户端直接渲染 |
| `/parse` | 是 | 否 | 解析、Worker、构建期 |
| `/expand` | 是 | 否 | 模板展开 |
| `/view` | 否 | 是 | 只渲染准备好的快照 |
| `/hast` | 否 | 否 | 准备、序列化 |
| `/ast` | 否 | 否 | AST 查询与数据处理 |
只做展示时,客户端可以动态加载 `/view`,避免把解析器带进首屏包:
```tsx
async function loadViewer() {
const [{ WikitextView }, { restoreWikitextRenderDocument }] = await Promise.all([
import("react-wikitext/view"),
import("react-wikitext/hast"),
]);
return { WikitextView, restoreWikitextRenderDocument };
}
```
## 大文档
- 100 KB 级别的密集文本可能阻塞主线程;优先放到 Worker,或按页拆请求。
- 库没有虚拟列表;长页面由宿主分页、折叠或分段渲染。
- 错误边界只隔离失败块,不减少整体渲染成本。
- 目录、引用与分类是文档级结构,需要整份文档准备后才能生成。
## 测量
性能数字随源码、配置、浏览器与硬件变化,不在文档中固定。需要对比时运行仓库的 `pnpm bench`,并记录输入、解析器版本与改动前后的差异。
---
url: /zh/guide/security.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 安全模型
wikitext 通常来自不可信用户。库的目标是:即使源码包含恶意 HTML、URL 或模板,也只输出经过清洗的 React 元素,不执行脚本,不加载未授权的媒体。
## 库保证什么
### 不注入 HTML
- 库不使用 `dangerouslySetInnerHTML`,不把 wikitext 或解析器输出的 HTML 字符串直接交给 React。
- 输出由 HAST 经 `hast-util-to-jsx-runtime` 转换为 React 元素。
- 源码中的危险标签(`script`、`style`、`iframe`、表单控件、`svg`、HTML 版 `math` 等)默认作为纯文本转义;如果被配置纳入 HTML 解析,或被插件插入 HAST,则整对丢弃并显示可读提示,不执行其中的内容。
### 清洗顺序固定
```text
wikitextPlugins
→ 语义 AST → HAST
→ rehypePlugins
→ sanitize(hast-util-sanitize + wiki schema)
→ finalize(链接/图片属性、引用 id、标题锚点、目录)
→ React
```
- 插件运行在最终清洗之前;插件不能绕过安全阶段。
- 原始解析快照只读;准备阶段在克隆的工作树上操作。
- 最终整理会再次校验 `target`、`rel`、图片授权与 URL 协议。
### 属性与 URL 白名单
- 源码 HTML 只保留 `class`、`title`、`dir`、`lang` 与表格相关属性。
- `style`、`id`、事件属性、`srcdoc` 不会进入 DOM。
- `href` 只允许 `http:`、`https:`、`mailto:`;`src` 只允许 `http:`、`https:`。
- 校验前会先解码实体,避免 `javascript:` 之类的绕过。
- `class` 只经过 `[A-Za-z0-9_-]` 安全字符集过滤;`colspan` / `rowspan` 等有范围限制。
### 图片授权
- 没有 `resolveImage` 时不会生成 `
`。
- 只有解析函数返回并通过 URL 校验的 `src` 会被授权;最终整理会删除未授权图片。
- 授权图片带 `loading="lazy"` 与 `decoding="async"`。
- 库不发起网络请求;图片由浏览器按普通 `
` 加载。
### 模板展开仍按不可信内容处理
- `expandWikitext` 只处理宿主提供的模板源码,不执行其中的 HTML 或脚本。
- 不内置 Lua,不执行 `#invoke`。
- 展开结果和模板源码都会经过正常的语义归一化、HAST 转换与最终清洗。
### 错误隔离
- 每个顶层块由错误边界包裹;一个块渲染失败不影响其他内容。
- 失败时,占位内容显示节点类型与源码,并通过 `onRenderError` 上报。
- 解析、准备错误属于调用阶段,不会由视图错误边界处理。
## 宿主需要做什么
### 可信代码
`components`、`semanticComponents`、`handlers`、`wikitextPlugins`、`rehypePlugins`、`resolveLink`、`resolveImage` 都是宿主代码,属于可信边界。这些代码可以输出白名单之外的 DOM,或把输入插入危险属性;这些内容不再经过库清洗。请像对待普通 React 组件一样审查它们。
### 路由与媒体地址
解析函数返回的 URL 会经过协议校验,但是否指向正确页面、是否泄露私有资源由宿主决定。接入站内路由或媒体服务时应:
- 使用受信任的映射表或经过验证的页面标题。
- 不要把未经检查的用户输入插入可能执行 `javascript:` 的位置。
- 图片 CDN 域名应写入 CSP 的 `img-src`。
### 序列化快照
`serializeWikitextRenderDocument` 生成普通数据;`restoreWikitextRenderDocument` 会校验结构版本、数据结构和节点预算,并重新清洗。仍应只恢复来自可信服务端或可信缓存的快照。不要把任意第三方 HAST 直接交给 `WikitextView`。
### 策略调整
`policy` 是安全边界的唯一可调项:
- 放宽 `extraTags` 或减少 `stripTags` 会扩大可输出范围。
- `allowRelativeLinks` / `allowRelativeImages` 会改变 URL 校验。
- `allowPluginImages` 允许 rehype 插件插入图片,仍受协议校验限制。
修改前请确认威胁模型;默认值面向不可信 wikitext。
### CSP
库不依赖 `eval`;内联样式只保留白名单声明(颜色、字体、对齐、宽高、边框、间距等),涉及 `url()`、`expression()`、`var()`、`@import`、`!important` 的声明整条丢弃并产生 `STYLE_DECLARATION_DROPPED` 诊断,因此不会借样式加载远程资源。CSP 禁止 `style-src` 内联样式时这些声明不生效,但不会执行脚本。如果宿主通过图片解析函数授权远程图片,需要允许对应 `img-src`;如果使用外部字体或样式,则由宿主自己的 CSP 决定。
## 已知边界
- 安全模型针对 DOM 与 URL,不阻止用户通过合法外链离开站点。
- 引用、目录、分类等生成结构不携带源码 `id`,不会与宿主 DOM 冲突。
- 自定义组件可以产生任意 React 输出,这属于宿主信任范围。
- 解析器固定为 `wikiparser-node@1.48.1`;升级解析器需要重新审查渲染与安全基线。
- 没有内置对清洗结果的审计或 CSP 报告;需要时由宿主接入。
## 检查清单
- [ ] 引入 `react-wikitext/styles.css`,并给容器设置正确的 `lang` / `dir`。
- [ ] 只为确实需要的页面接入 `resolveLink` / `resolveImage`。
- [ ] 审查所有组件覆盖与插件,把它们视为可信代码。
- [ ] 只恢复可信来源的序列化快照。
- [ ] 按站点需求设置 CSP 与图片域名白名单。
- [ ] 对不可信输入保持默认策略,不要为了样式放宽清洗。
---
url: /zh/guide/recipes.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 常用配方
以下片段都只使用公开 API,省略了应用自身的错误处理与数据加载。组件覆盖、解析函数与插件属于可信代码,接入前请按[安全模型](/zh/guide/security.md)审查。
## 站点主题与深色模式
库的样式由 CSS 变量驱动,容器上的 `class` 与属性都可以参与选择器:
```css
.wiki-article {
--wtr-text: #1f2328;
--wtr-accent: #0969da;
--wtr-border: #d0d7de;
--wtr-placeholder-bg: #f6f8fa;
}
.wiki-article[data-theme="dark"] {
--wtr-text: #e6edf3;
--wtr-accent: #4493f8;
--wtr-border: #30363d;
--wtr-placeholder-bg: #161b22;
}
```
```tsx
import { Wikitext } from "react-wikitext";
import "react-wikitext/styles.css";
;
```
同一份源码在浅色与深色下的效果:
```tsx preview
import { Wikitext } from "react-wikitext";
import "react-wikitext/styles.css";
const source = `== 主题示例 ==
'''正文'''、''强调'' 与 [[链接]]。
{{Infobox|title=模板占位}}`;
const css = `
.recipe-theme {
--wtr-text: #1f2328;
--wtr-muted: #57606a;
--wtr-border: #d0d7de;
--wtr-accent: #0969da;
--wtr-accent-soft: #ddf4ff;
--wtr-placeholder-bg: #f6f8fa;
padding: 12px;
border: 1px solid #d0d7de;
border-radius: 8px;
background: #ffffff;
}
.recipe-theme--dark {
--wtr-text: #e6edf3;
--wtr-muted: #8b949e;
--wtr-border: #30363d;
--wtr-accent: #58a6ff;
--wtr-accent-soft: #172a45;
--wtr-placeholder-bg: #161b22;
border-color: #30363d;
background: #0d1117;
}
.recipe-theme + .recipe-theme { margin-top: 12px; }
`;
export default function Demo() {
return (
);
}
```
变量清单见[样式与组件](/zh/guide/customization.md)。
## 站内路由与图片 CDN
把路由与媒体地址交给宿主,库只负责校验与渲染。把解析函数放在模块作用域,避免每次渲染重新准备:
```tsx
import type { WikitextImageResolver, WikitextLinkResolver } from "react-wikitext/hast";
const resolveLink: WikitextLinkResolver = (request) => {
if (request.kind !== "internal") return undefined;
const page = request.page ?? request.target;
const fragment = request.fragment ? `#${request.fragment}` : "";
return { href: `/wiki/${encodeURIComponent(page)}${fragment}`, target: "_self" };
};
const resolveImage: WikitextImageResolver = (request) => ({
src: `https://cdn.example.com/wiki/${encodeURIComponent(request.name)}`,
alt: request.alt ?? request.name,
...(request.width === undefined ? {} : { width: request.width }),
...(request.height === undefined ? {} : { height: request.height }),
});
```
```tsx
```
返回值语义与策略见[链接与图片](/zh/guide/links-and-images.md)。
## 模板卡片
不展开模板,但可以把调用显示成站点自己的卡片:
```tsx preview
import { Wikitext } from "react-wikitext";
import { WikitextFragment, type WikitextTemplateComponentProps } from "react-wikitext/view";
import "react-wikitext/styles.css";
function TemplateCard({ displayName, node }: WikitextTemplateComponentProps) {
return (
);
}
export default function Demo() {
return (
);
}
```
`WikitextFragment` 必须在 `WikitextView` 的 Context 内使用,并且只能渲染当前文档中的节点。
## 文章目录
目录不一定由库生成。需要站点侧栏时,直接读取渲染快照的 `headings`,并固定锚点策略:
```tsx
import { useMemo } from "react";
import { createWikiparser } from "react-wikitext/parse";
import { prepareWikitext } from "react-wikitext/hast";
import { WikitextView } from "react-wikitext/view";
import "react-wikitext/styles.css";
const parser = createWikiparser();
export function Article({ source }: { source: string }) {
const prepared = useMemo(
() => prepareWikitext(parser.parse(source), { headingIdStrategy: "mediawiki" }),
[source],
);
return (
);
}
```
- `mediawiki` 策略写入裸锚点,适合单实例页面。
- 多实例页面用默认 `instance` 策略;在视图内部可以用 `useWikitextIds().toHref` 取得实际链接。
- 页内自动目录仍然会生成;只想保留侧栏时,可以用宿主 CSS 隐藏 `.wtr-toc`,或在源码层面使用 `__NOTOC__`。
## 渲染失败上报
视图按顶层块隔离失败;解析与准备的错误需要调用方捕获:
```tsx
import type { WikitextDocument } from "react-wikitext/ast";
import type { WikitextPrepareOptions } from "react-wikitext/hast";
import { prepareWikitext, WikitextPrepareError } from "react-wikitext/hast";
import { WikitextView } from "react-wikitext/view";
function renderPrepared(document: WikitextDocument, prepareOptions: WikitextPrepareOptions) {
try {
return ;
} catch (error) {
if (error instanceof WikitextPrepareError) {
report(error.code, error.stage, error.cause);
}
throw error;
}
}
```
```tsx
{
report("render", error, { nodeId: node.id, nodeType: node.type });
}}
/>
```
`labels.renderFailed` 控制块级占位的标题;错误码与处理方式见[错误与诊断](/zh/api/errors.md)。
---
url: /zh/guide/troubleshooting.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 故障排查
先做三件事,通常能直接定位问题:
1. 看占位文案:`labels` 能区分“未求值”“已拦截”“未知结构”。
2. 看 `document.diagnostics`:每条诊断带有 `stage`、`code` 与源码位置。
3. 缩小到最小源码与最小选项;去掉自定义插件与组件后复现。
不确定某个占位是否正常时,先对照[概览的默认输出](/zh/guide/introduction.md#默认输出)与[支持矩阵](/zh/guide/syntax-support.md)。
## 内容显示
| 症状 | 原因 | 处理 |
| ------------------------ | -------------------------------------------------- | ------------------------------------------------------------------------ |
| 模板区域显示占位 | 默认管线不求值模板 | 用 [`/expand`](/zh/guide/templates.md);或自定义 `semanticComponents.template` |
| 解析器函数显示占位 | 同上,默认不执行 | 受支持的纯 wikitext 函数可用 `/expand`;其余保留占位 |
| 图片只有说明文字 | 没有 `resolveImage`,或返回了 `null` | 返回通过校验的 `src`;相对图片需 `allowRelativeImages` |
| 内部链接不可点击 | 默认输出不可导航的文本 | 用 `resolveLink` 返回 `href` |
| 外链变成普通文本 | 协议不在白名单 | 只使用 `http:`、`https:`、`mailto:` |
| HTML 变成源码卡片 | 标签未知或无法配对 | 确认标签是否受支持;自定义语义节点需要注册 `handlers` |
| 危险标签只剩提示 | `script`、`iframe` 等被拦截 | 预期行为;不要为了展示放宽 `stripTags` |
| 整张表格变成源码卡片 | wikitext 嵌套表格 | 展平嵌套表,或改用 HTML `` 嵌套 |
| 段落被合并 | 单个换行会折叠为空格 | 用两个空行分段 |
| 目录没有出现 | 标题少于 4 个、`__NOTOC__`、或 `headingIdStrategy: "none"` | 检查标题数量与行为开关;需要固定位置时用 `__TOC__` |
| 引用区不在 `` 的位置 | 引用区固定位于正文之后、分类之前 | 预期行为;位置不可通过标签控制 |
| 分类不在正文中 | 分类汇总到文末底栏 | 预期行为;文案由 `labels.categories` 控制 |
| 同一页标题锚点冲突 | 多个实例共享裸 id | 用默认 `instance` 策略,或显式传 `idPrefix` |
更多默认输出见[支持矩阵](/zh/guide/syntax-support.md)。
## 运行与构建
| 症状 | 原因 | 处理 |
| ------------------------------------------------ | ----------------------------------------------- | ----------------------------------------------------------------- |
| `ASYNC_PLUGIN_IN_SYNC_PATH` | 同步入口遇到异步插件 | 改用 `prepareWikitextAsync`,或在 React 外准备 |
| 每次渲染都重新解析 | `parseOptions` / `prepareOptions` 是内联新对象 | 移到组件外或用 `useMemo` 固定引用 |
| `PARSER_UNAVAILABLE` | `wikiparser-node` 浏览器包没有初始化 `globalThis.Parser` | 确认打包器执行了副作用导入;避免重复副本或手工裁剪依赖 |
| `Cannot find module 'react-wikitext/styles.css'` | 打包器未处理包内 CSS,或产物缺少 `dist/styles.css` | 让打包器处理包内 CSS 导入;确认解析到 `dist/styles.css` |
| Next.js Server Component 报 `createContext` 错误 | 在支持 RSC 边界的环境里由服务端调用了 React 入口 | 确认产物带有 `"use client"`;服务端改用[服务端准备](/zh/guide/server-rendering.md) |
| Hydration 不一致或锚点不同 | 服务端与客户端渲染了不同结构,或没有固定 id 前缀 | 保持同一棵树;显式传 `idPrefix`;必要时用 `mediawiki` 策略 |
| 类型报错:根入口没有错误类 | 错误类按子路径导出 | 从 `/parse`、`/hast`、`/expand` 导入对应错误类 |
| CJS 环境加载失败 | 运行时版本过低或打包器不支持 `exports` | 使用 Node `>=18.18` 与支持条件导出的打包器 |
## 错误码
错误对象带有 `code`;准备阶段错误还带 `stage`。不要根据 `message` 文本分支。
| 错误码 | 阶段 | 含义 | 处理 |
| --------------------------- | -- | ----------------- | ------------------------------------- |
| `SOURCE_TOO_LONG` | 解析 | 归一化后的源码超过长度预算 | 调大 `maxSourceLength`,或拆分内容 |
| `SOURCE_TOO_COMPLEX` | 解析 | 节点数或深度超过预算 | 调大对应预算,或简化结构 |
| `INVALID_CONFIG` | 解析 | 站点配置或预算字段非法 | 检查传入值是否为非负安全整数 |
| `PARSE_FAILED` | 解析 | 上游解析或语义构建失败 | 保留源码与 `cause`,报告问题 |
| `PARSER_UNAVAILABLE` | 解析 | 默认浏览器包未初始化 | 检查构建与依赖解析 |
| `PREPARE_FAILED` | 准备 | 选项、插件或转换函数非法 | 检查 `policy`、`labels`、`handlers` 与插件签名 |
| `ASYNC_PLUGIN_IN_SYNC_PATH` | 准备 | 同步路径遇到异步插件 | 改用异步入口 |
| `INVALID_DOCUMENT` | 恢复 | 快照结构、版本或预算不合法 | 只恢复可信来源;版本升级后让缓存失效 |
| `EXPANSION_TOO_LARGE` | 展开 | 展开结果或保留源码超过长度上限 | 调大 `maxExpandedLength`,或限制模板输出 |
| `EXPANSION_BUDGET_EXCEEDED` | 展开 | `strict` 模式下预算超限 | 调大预算,或关闭 `strict` 接受保留调用 |
| `TEMPLATE_LOOP` | 展开 | `strict` 模式下检测到循环 | 检查模板依赖 |
| `ABORTED` | 展开 | 通过 `signal` 取消 | 预期行为;原始原因在 `cause` |
完整错误码与类层次见[错误与诊断](/zh/api/errors.md)。
## 诊断怎么读
```ts
for (const diagnostic of prepared.diagnostics) {
console.warn(diagnostic.stage, diagnostic.code, diagnostic.message);
}
```
- `stage` 可能是 `wikitext`、`hast`、`rehype`、`sanitize` 或 `finalize`。
- 单条诊断不会中断整页渲染;渲染失败会由错误边界替换对应顶层块。
- 视图层失败通过 `onRenderError` 上报,占位文案来自 `labels.renderFailed`。
- `meta` 记录实际生效的 `policy`、`labels`、`headingIdStrategy` 与授权图片,适合排查“配置有没有生效”。
## 还是无法定位
报告问题时附上:
- 最小 wikitext 源码与 `pageName`。
- 使用的入口、解析与准备选项(可脱敏)。
- 库版本、`parserVersion`、React 与打包器版本。
- `document.diagnostics` 与完整错误对象的 `code`、`stage`、`cause`。
更多渠道见[参与与反馈](/zh/project/contributing.md)。
---
url: /zh/api/index.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 入口总览
库按子路径提供功能,宿主可以只加载需要的部分。根入口是主入口;`react-wikitext/view` 是不加载解析器的精简入口。
## 处理管线
```text
source
└─ /parse ─▶ WikitextDocument(原始解析快照)
└─ /hast prepare ─▶ WikitextRenderDocument(HAST + headings + meta)
└─ /view ─▶ React
```
可选的前置阶段:
```text
source ─▶ /expand ─▶ 展开后的 WikitextDocument ─▶ /hast ─▶ /view
```
## 子路径
| 子路径 | 内容 | 加载解析器 | 加载 React |
| --------------------------- | ----------------------------------------------------------------------- | --------- | -------- |
| `react-wikitext` | `Wikitext`、`WikitextView`、`WikitextFragment`、Context Hook、组件类型与精选语义类型 | 是 | 是 |
| `react-wikitext/ast` | 语义模型、查询、辅助函数、纯解析器工厂、源码归一化、序列化 | 否 | 否 |
| `react-wikitext/parse` | 默认解析器、`parseWikitext`、解析选项、解析错误 | 是 | 否 |
| `react-wikitext/expand` | `expandWikitext`、模板展开类型与错误 | 是 | 否 |
| `react-wikitext/hast` | `prepareWikitext`、`prepareWikitextAsync`、解析函数、策略、文案、标题策略、URL 工具、渲染快照序列化 | 否 | 否 |
| `react-wikitext/unified` | `wikitextParse`、`wikitextRehype`、`wikitextFinalize` | 否(需注入解析器) | 否 |
| `react-wikitext/view` | `WikitextView`、`WikitextFragment`、Context Hook、组件类型 | 否 | 是 |
| `react-wikitext/configs` | `MEDIAWIKI_CONFIG`、`ParserConfig` | 否 | 否 |
| `react-wikitext/styles.css` | 页面渲染样式 | — | — |
根入口不导出 parse、expand、hast 与 unified 的功能。`/ast`、`/hast`、`/unified`、`/configs` 不加载默认解析器;`/parse` 与 `/expand` 会加载;`/view` 只加载 React 与已准备好的文档。AST 树与节点检查器属于内部 playground,不是公共入口。
## 解析快照与渲染快照
### 解析快照 `WikitextDocument`
```ts
interface WikitextDocument {
readonly source: string;
readonly ast: WikitextRoot;
readonly syntax?: WikitextSyntaxRoot;
readonly meta: WikitextDocumentMeta;
readonly diagnostics: readonly WikitextDiagnostic[];
}
```
- `ast` 是原始归一化结果,准备阶段不会修改它。
- `syntax` 只在 `retainSyntax: true` 时出现,供 playground 与源码坐标使用。
- `meta` 记录解析器名称与版本、页面名、归一化状态与原长度。
### 渲染快照 `WikitextRenderDocument`
```ts
interface WikitextRenderDocument {
readonly source: string;
readonly ast: WikitextRoot; // 本次工作树结果
readonly hast: import("hast").Root; // 已清洗的最终 HAST
readonly headings: readonly WikitextHeadingInfo[];
readonly meta: WikitextRenderMeta;
readonly diagnostics: readonly WikitextDiagnostic[];
}
```
- `ast` 是插件处理后的工作树,节点 id 已重新分配。
- `hast` 已完成清洗与最终整理,包含目录、引用区与分类底栏。
- `meta` 记录实际生效的策略、文案、标题策略与授权图片。
- `WikitextView` 只接受渲染快照,不接收源码。
查询转换后的节点时,必须使用渲染快照;不要用渲染快照里的 id 查原始解析快照。
## 如何选择
| 需求 | 使用 |
| -------------- | ----------------------------- |
| 客户端直接渲染源码 | 根入口 `Wikitext` |
| 服务端准备、客户端只渲染 | `/parse` + `/hast` + `/view` |
| 只做解析或 AST 分析 | `/parse` + `/ast` |
| 展开模板后再渲染 | `/expand` + `/hast` + `/view` |
| 接入 unified 处理器 | `/unified`(显式注入解析器) |
| 只替换站点配置 | `/configs` + `/parse` |
## 类型与错误
- 错误类按入口分别导出:`/ast`、`/parse` 提供 `WikitextError` / `WikitextParseError`;`/expand` 提供 `WikitextExpandError`;`/hast` 提供 `WikitextPrepareError`。根入口与 `/view` 不导出错误类。
- `WikitextLabels` 类型可从 `/ast` 与 `/view` 导入;默认值 `DEFAULT_WIKITEXT_LABELS` 与 `DEFAULT_PREPARE_POLICY` 从 `/hast` 导出。
- 完整节点子类型从 `/ast` 导入;根入口只导出视图组件会用到的精选语义类型。
- 错误码、`stage` 与诊断字段见[错误与诊断](/zh/api/errors.md)。
## 参考页面
| 子路径 | 页面 |
| ----------- | ----------------------------- |
| 根入口、`/view` | [React 组件](/zh/api/react.md) |
| `/parse` | [解析](/zh/api/parse.md) |
| `/ast` | [AST](/zh/api/ast.md) |
| `/hast` | [准备](/zh/api/prepare.md) |
| `/expand` | [展开](/zh/api/expand.md) |
| `/unified` | [unified](/zh/api/unified.md) |
| `/configs` | [configs](/zh/api/configs.md) |
## 下一步
- [React 组件](/zh/api/react.md):源码组件、视图、Fragment 与 Context Hook。
- [解析 `/parse`](/zh/api/parse.md):解析器、预算与自定义适配器。
- [准备 `/hast`](/zh/api/prepare.md):插件、解析函数、策略与快照恢复。
- [错误与诊断](/zh/api/errors.md):错误码与处理建议。
- [状态与兼容性](/zh/project/status.md):版本、快照版本与运行时要求。
---
url: /zh/api/react.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# React 组件
根入口与 `/view` 都导出视图组件;只有根入口额外导出源码组件 `Wikitext`。
:::warning
这些组件在模块加载时创建 Context,并使用 `useId` / `useMemo`,只能在客户端组件中运行。根入口与 `/view` 的产物顶部带有 `"use client"` 指令,在 Next.js App Router 等环境中可以直接使用;服务端只调用 `/parse` 与 `/hast`。
:::
## Wikitext
```tsx
import { Wikitext } from "react-wikitext";
import "react-wikitext/styles.css";
;
```
源码组件在内部完成解析、准备与渲染,只支持同步管线。
### 属性
```ts
interface WikitextProps extends Omit {
source: string;
parser?: WikitextParser;
parseOptions?: WikitextParseOptions;
prepareOptions?: WikitextPrepareOptions;
onError?: (error: Error, info: { stage: "parse" | "prepare" }) => void;
fallback?:
React.ReactNode | ((error: Error, info: { stage: "parse" | "prepare" }) => React.ReactNode);
}
```
| 属性 | 说明 |
| -------------------- | ------------------------------------------------------------------ |
| `source` | 待渲染的 wikitext 源码 |
| `parser` | 复用已创建的解析器;缺省使用默认配置的共享解析器 |
| `parseOptions` | 解析选项,见 [`/parse`](/zh/api/parse.md) |
| `prepareOptions` | 准备选项,见 [`/hast`](/zh/api/prepare.md) |
| `onError` | 解析/准备失败回调,`info.stage` 区分阶段 |
| `fallback` | 失败时渲染的内容 |
| `components` | 按 HTML 标签覆盖组件 |
| `semanticComponents` | 按 wiki 语义类型覆盖组件 |
| `lang` / `dir` | 写入根容器的语言与方向 |
| `idPrefix` | 实例标题 id 前缀;只在 `headingIdStrategy: "instance"` 下生效,非法/空值回退到 `useId` |
| `className` | 与 `wtr-view` 合并 |
| `containerProps` | 根容器的额外属性;类型层排除 `dangerouslySetInnerHTML` |
| `onRenderError` | 单个块渲染失败时的回调 |
### 行为
- 解析与准备分别用 `useMemo` 缓存,依赖为 `[parser, source, parseOptions]` 与 `[document, prepareOptions]`。
- 每次渲染传入新的选项对象会导致重新解析;请固定引用。
- 插件与解析函数必须是纯函数;异步插件会抛 `ASYNC_PLUGIN_IN_SYNC_PATH`。
- 默认解析器在模块加载时初始化;不需要解析器时使用 `/view`。
## WikitextView
```tsx
import { WikitextView } from "react-wikitext/view";
;
```
`WikitextView` 只接收准备完成的渲染快照。渲染期间不解析源码、不运行插件或解析函数。
### 属性
| 属性 | 说明 |
| -------------------- | ---------------------------------- |
| `document` | `WikitextRenderDocument` |
| `components` | 按 HAST 标签名覆盖组件 |
| `semanticComponents` | 按 wiki 语义类型覆盖组件 |
| `lang` / `dir` | 写入根容器的语言与方向 |
| `idPrefix` | `instance` 策略下的实例前缀;未传时使用 `useId` |
| `className` | 与 `wtr-view` 合并 |
| `containerProps` | 根容器属性;排除 `dangerouslySetInnerHTML` |
| `onRenderError` | 块级渲染失败回调 |
- 根节点固定为 `div.wtr-view`。
- `idPrefix` 同时影响标题 id、文档内 `#fragment` 链接与表格 `headers`。
- 每个顶层块包在错误边界内;一个块失败不会影响其他块。
- 视图内部建立语义索引与 HAST 索引,供组件与 `WikitextFragment` 使用。
## WikitextFragment
```tsx
import { WikitextFragment } from "react-wikitext/view";
;
```
| 属性 | 说明 |
| ----------- | ------------------------- |
| `nodes` | 当前渲染文档中的语义节点数组 |
| `mode` | `"inline"`(默认)或 `"block"` |
| `className` | 可选外层类名 |
- 必须在 `` 的 Context 内使用,否则抛错。
- `nodes` 必须是当前视图渲染文档中的**同一批节点对象**;仅 id 相同并不够。
- 只渲染能在准备结果 HAST 索引中找到的节点,适合模板参数、文件说明与 `ref` 内容。
- inline 模式无 `className` 时不额外包裹 DOM;有 `className` 时使用 ``;block 模式使用 `wtr-fragment` 容器。
- 同一视图内重复渲染同一节点会产生重复内容与重复标题 id。
## 组件覆盖
### HTML 组件
```ts
type WikitextHtmlComponents = Partial<
Record>
>;
interface WikitextHtmlComponentProps extends React.HTMLAttributes {
node?: import("hast").Element;
document: WikitextRenderDocument;
semantic?: WikitextNode;
origin?: WikitextOrigin;
children?: React.ReactNode;
href?: string;
src?: string;
alt?: string;
}
```
按键名映射,例如 `p`、`table`、`img`、`a`。组件收到经过安全校验的 DOM 属性,以及 HAST 元素、文档和可选的语义节点。
### 语义组件
```ts
type WikitextSemanticComponents = {
link?: React.ComponentType;
template?: React.ComponentType;
parserFunction?: React.ComponentType;
argument?: React.ComponentType>;
file?: React.ComponentType;
reference?: React.ComponentType;
extension?: React.ComponentType;
unknown?: React.ComponentType;
table?: React.ComponentType>;
};
```
所有语义组件都会收到:
```ts
interface WikitextSemanticComponentProps {
document: WikitextRenderDocument;
node: Node;
hastNode: import("hast").Element;
origin?: WikitextOrigin;
children?: React.ReactNode;
}
```
额外字段:
| 类型 | 额外属性 |
| ----------------------------- | ---------------------------------- |
| `link` | `target`、`kind`、`href?`、`external` |
| `template` / `parserFunction` | `displayName` |
| `file` | `name` |
| `reference` | `number` |
| `extension` | `name`、`block` |
| `unknown` | `parsedType`、`block` |
优先级:显式语义组件替换整个语义结构;未替换时,默认 HAST 内部的标签继续应用 `components`;最后回退到原生元素。
### 自定义模板卡片
```tsx
import { WikitextFragment, type WikitextTemplateComponentProps } from "react-wikitext/view";
function TemplateCard({ displayName, node }: WikitextTemplateComponentProps) {
return (
);
}
;
```
### 自定义链接
```tsx
href ? (
{children}
) : (
{children}
),
}}
/>
```
## Context Hook
```ts
interface WikitextViewContextValue {
document: WikitextRenderDocument;
semanticIndex: ReadonlyMap;
hastIndex: ReadonlyMap;
components: WikitextHtmlComponents;
semanticComponents: WikitextSemanticComponents;
idPrefix: string;
mapId: (id: string) => string;
mapHref: (href: string) => string;
onRenderError?: (error: Error, info: { node: WikitextNode }) => void;
}
function useWikitextContext(): WikitextViewContextValue;
function useWikitextLabels(): WikitextLabels;
function useWikitextIds(): { toDomId(id: string): string; toHref(href: string): string };
```
- 这些 Hook 必须在 `` 内使用。
- `useWikitextLabels()` 读取渲染快照中的最终文案。
- `useWikitextIds()` 适合需要在自定义组件中生成锚点或片段链接的场景。
## 渲染错误
```tsx
{
reportError(error, { nodeType: node.type, nodeId: node.id });
}}
/>
```
错误边界按顶层 HAST 块隔离。占位内容显示 `labels.renderFailed`、节点类型与源码。解析与准备错误不会进入错误边界,应由调用方捕获。
## 相关类型
根入口额外导出视图组件会用到的精选语义类型:`WikitextLink`、`WikitextTemplate`、`WikitextFile`、`WikitextReference`、`WikitextTable`、`WikitextUnknown` 等。完整节点子类型与 AST 工具从 [`/ast`](/zh/api/ast.md) 导入。
---
url: /zh/api/parse.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 解析 `/parse`
```ts
import {
createWikiparser,
parseWikitext,
createWikitextParser,
WikitextParseError,
DEFAULT_MAX_SOURCE_LENGTH,
} from "react-wikitext/parse";
```
`/parse` 加载默认解析器(`wikiparser-node@1.48.1` 浏览器包),不加载 React。解析是同步的。
## parseWikitext
```ts
const document = parseWikitext(source, {
pageName: "Example",
retainSyntax: false,
});
```
使用默认配置的共享解析器,适合一次性解析或简单场景。
## createWikiparser
```ts
const parser = createWikiparser({
config: MEDIAWIKI_CONFIG,
defaults: { pageName: "Example" },
});
const document = parser.parse(source, { maxSourceLength: 50_000 });
```
- 默认配置为 `MEDIAWIKI_CONFIG`,来源是 `wikiparser-node@1.48.1` 的站点配置快照。
- 配置在工厂创建时深拷贝并归一化;之后修改原对象不会影响解析器。
- 更换配置需要创建新的解析器。
- 库不导出最小配置;需要自定义时以默认配置为基础复制并调整字段。
## 解析选项
```ts
interface WikitextParseOptions {
pageName?: string;
maxSourceLength?: number; // 默认 100_000
maxSyntaxNodes?: number; // 默认 50_000
maxSemanticNodes?: number; // 默认 50_000
maxDepth?: number; // 默认 1_000
retainSyntax?: boolean; // 默认 false
}
```
| 选项 | 说明 |
| ------------------ | --------------------------- |
| `pageName` | 页面上下文(例如模板或魔术字中的页面名);库不自动生成 |
| `maxSourceLength` | LF 归一化后的源码长度上限 |
| `maxSyntaxNodes` | 源码语法树节点上限 |
| `maxSemanticNodes` | 语义 AST 节点上限 |
| `maxDepth` | 语法树深度上限 |
| `retainSyntax` | 是否在文档中保留源码语法树 |
有效预算按“单次解析选项 → 创建时 `defaults` → 共享默认值”合并。解析前统一把 CRLF / CR 归一化为 LF,预算在归一化后检查。
`retainSyntax` 会显著增加内存与构建成本,只在需要源码坐标或 playground 语法树时开启。
## 返回的文档
```ts
interface WikitextDocument {
source: string;
ast: WikitextRoot;
syntax?: WikitextSyntaxRoot;
meta: WikitextDocumentMeta;
diagnostics: WikitextDiagnostic[];
}
```
`meta` 包含:
- `parser` / `parserVersion`。
- `pageName`。
- `astSchemaVersion` 与可选的 `syntaxSchemaVersion`。
- `sourceNormalized`、`originalLength`、`retainSyntax`。
同一份源码使用不同配置或 `pageName` 时,可能得到不同的 AST;它们属于解析输入,不属于渲染选项。
## 自定义适配器
`createWikitextParser` 不加载默认 IIFE,适合接入其他解析器:
```ts
import { createWikitextParser, type WikitextParserAdapter } from "react-wikitext/parse";
const adapter: WikitextParserAdapter = {
name: "my-parser",
version: "1.0.0",
parse(source, context) {
// 必须返回符合 WikitextSyntaxRoot 的普通对象;
// range 基于 LF 归一化后的源码,行列从 1 开始,offset 从 0 开始。
return buildSyntaxRoot(source, context);
},
};
const parser = createWikitextParser(adapter, {
defaults: { pageName: "Example" },
});
```
- 适配器只把输入转换为源码语法树;归一化、预算合并、语义 AST 构建、诊断与 meta 由工厂完成。
- `context.limits` 提供有效的 `maxSyntaxNodes` 与 `maxDepth`,适配器可以据此提前停止。
- 适配器返回后,工厂仍会校验结构、源码范围与预算。
- 适配器的 `name` / `version` 会写入文档 meta。
## 错误
```ts
class WikitextParseError extends WikitextError {
constructor(
code:
| "SOURCE_TOO_LONG"
| "SOURCE_TOO_COMPLEX"
| "INVALID_CONFIG"
| "PARSE_FAILED"
| "PARSER_UNAVAILABLE",
message: string,
options?: { cause?: unknown },
);
}
```
| 错误码 | 触发条件 |
| -------------------- | ----------------- |
| `SOURCE_TOO_LONG` | 归一化后的源码超过长度预算 |
| `SOURCE_TOO_COMPLEX` | 节点数或深度超过预算 |
| `INVALID_CONFIG` | 站点配置缺少必需字段 |
| `PARSE_FAILED` | 上游解析或语义构建失败 |
| `PARSER_UNAVAILABLE` | 默认 IIFE 未初始化全局解析器 |
节点数与深度上限属于内部防护阈值,只在触发错误时在 `error.message` 中给出实际生效值,不作为常量导出。
## 相关
- 解析结果的查询、序列化与 AST 节点类型见 [`/ast`](/zh/api/ast.md)。
- 配置常量见 [`/configs`](/zh/api/configs.md)。
- 需要缓存,或需要避免阻塞主线程时,把解析放到 Worker 中执行,序列化后传回主线程。
---
url: /zh/api/ast.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# AST `/ast`
```ts
import {
AST_SCHEMA_VERSION,
SYNTAX_SCHEMA_VERSION,
RENDER_SCHEMA_VERSION,
flattenNodes,
findNodeById,
findNodeAtOffset,
getSourceText,
toPlainText,
isNodeType,
getTemplateParameters,
getFileParameters,
normalizeSource,
serializeWikitextDocument,
restoreWikitextDocument,
} from "react-wikitext/ast";
```
`/ast` 不加载解析器与 React,可以在服务端、Worker 或纯数据处理脚本中使用。
## 节点基础
```ts
interface WikitextNodeBase {
readonly type: TType;
readonly id: string;
readonly position?: WikitextPosition;
readonly origin?: WikitextOrigin;
readonly data?: Readonly>;
}
```
- `id` 在单个快照内唯一,不保证跨编辑稳定。
- `position` 的行列从 1 开始,offset 从 0 开始;生成节点会省略该字段。
- `origin` 指向原始源码语法节点与范围。
- `data` 用于自定义扩展数据。
## 节点类型
| 节点 | 关键字段 |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `root` / `paragraph` / `strong` / `emphasis` | `children` |
| `text` | `value`(已解码语义文本) |
| `heading` | `level` 1–6、`children` |
| `thematicBreak` | — |
| `link` | `kind: "internal" \| "external" \| "bareExternal"`、`target`、可选 `page` / `fragment`、`children` |
| `file` | `name`、`parameters`、`captionKind`、`attributes`、可选 `block`、`children` |
| `list` | `ordered`、`children` |
| `listItem` | `children` |
| `definitionList` / `definitionTerm` / `definitionDescription` | `children` |
| `table` / `tableRow` / `tableCell` | `attributes`;单元格另有 `kind: "th" \| "td"` |
| `tableCaption` | `children` |
| `template` | `name`、`displayName`、可选 `block`、`children`(`templateParameter`) |
| `templateParameter` | `name`、`anonymous`、可选 `index`、`children` |
| `parserFunction` | `name`、可选 `block`、`children` |
| `argument` | `name`、`children`(默认值) |
| `extension` | `name`、`attributes`、`selfClosing`、`block`、可选 `raw`、`children` |
| `reference` | 可选 `name` / `group`、`attributes`、可选 `raw`、`children` |
| `category` | `target`、可选 `sortKey` |
| `behaviorSwitch` | `name` |
| `redirect` | `target`、可选 `page` / `fragment` |
| `htmlElement` | `tag`、`attributes`、`selfClosing`、`block`、可选 `blocked`、`children` |
| `code` | `value`、可选 `language` |
| `comment` | `value` |
| `unknown` | `parserType`、`value`、`block`、可选 `reason` |
扩展标签即使没有专门的默认展示,也使用 `extension` 节点并保留名称与内部原文,不会进入 `unknown`。
自定义节点需要同时扩展 `WikitextNodeMap` 并在准备阶段注册转换函数;未注册转换函数的节点会产生诊断并降级为源码卡片。
## 源码语法树
`retainSyntax: true` 时,解析快照会包含 `syntax`:
```ts
interface WikitextSyntaxNode {
id: string; // 路径式 id,例如 "0.0.1"
type: string; // 上游 token 类型
range: WikitextRange; // [start, end)
position: WikitextPosition;
props: Readonly>;
attributes: readonly WikitextSyntaxAttribute[];
children: readonly WikitextSyntaxNode[];
}
```
源码语法树供 playground、源码范围与源码映射使用;它不决定渲染结构。语义 AST 才是渲染输入。
## 查询
```ts
interface SourceDocumentLike {
readonly source: string;
readonly ast: WikitextRoot;
}
```
| 函数 | 行为 |
| ---------------------------------------------- | ---------------------------------------------------------------------- |
| `flattenNodes(root)` | 深度优先返回全部语义节点 |
| `findNodeById(document, id)` | 按 id 查找节点;找不到时返回 `undefined` |
| `findNodeAtOffset(document, offset, options?)` | 返回包含该 offset 的最深节点;范围为 `[start, end)`;`offset === source.length` 返回根节点 |
| `getSourceText(document, node)` | 返回节点对应的 LF 源码片段 |
| `toPlainText(node)` | 返回已转换的语义文本,不再做实体解码 |
| `isNodeType(node, type)` | 泛型上下文中的类型收窄 |
- 零长度节点默认不参与 `findNodeAtOffset` 的选择;可用 `includeZeroLength` 调整。
- 生成内容没有 `origin` 时,`getSourceText` 返回空字符串。
- 需要按 id 建索引时用 `flattenNodes` 自行生成 `Map`。
- `flattenNodes` 与 `findNodeById` 的内部缓存在准备阶段重新编号后自动失效;插件在此之前持有的旧数组或 Map 不会被原地更新。
- 查询转换后的节点时,必须使用渲染快照;不要用渲染快照里的 id 查原始解析快照。
## 结构化辅助函数
```ts
getTemplateParameters(node): WikitextParameter[]
getFileParameters(file): WikitextFileInfo
```
`getTemplateParameters` 同时接受 `template` 与 `parserFunction`,返回参数节点、名称、是否匿名与从 1 开始的序号。
`getFileParameters` 派生:
- `name`、`parameters`、`caption`、`captionNodes`。
- `alt`、`link`、`lang`、`page`、`className`、`upright`。
- `width` / `height`(支持 `200`、`200px`、`200x100px`,最终显示时限制在 1–4096)。
- `format`、`alignment`、`verticalAlignment`、`border`。
说明文字优先取显式 `caption=`,否则取未命名说明;键名不会进入说明文本。
## 源码归一化
```ts
interface NormalizedSource {
readonly source: string;
readonly normalized: boolean;
readonly originalLength: number;
}
const normalized = normalizeSource(raw, maxSourceLength);
```
- 只做 CRLF / CR → LF,不改变其他字符。
- `DEFAULT_MAX_SOURCE_LENGTH` 为 100,000;超过时抛 `WikitextParseError("SOURCE_TOO_LONG")`。
- 所有坐标基于归一化后的 LF 源码;库不提供原源码的 offset 映射。
## 序列化
解析快照:
```ts
const payload = serializeWikitextDocument(document);
const restored = restoreWikitextDocument(payload);
```
渲染快照:
```ts
import {
serializeWikitextRenderDocument,
restoreWikitextRenderDocument,
} from "react-wikitext/hast";
```
恢复函数会校验结构版本、源码字符串、根节点、meta、`headings`、`diagnostics` 与 policy/labels 的完整形状,以及固定节点预算;渲染快照还会重新执行最终清洗与整理。快照内的 policy、labels 与 `authorizedImages` 属于可信边界,重新清洗只用于重建派生结构。不合法时抛:
- 解析快照:`WikitextError("INVALID_DOCUMENT")`。
- 渲染快照:`WikitextError("INVALID_DOCUMENT")`(与解析快照同一基类,不含 `stage` 字段)。
## 版本常量
| 常量 | 当前值 | 适用 |
| ----------------------- | --- | ------ |
| `AST_SCHEMA_VERSION` | `1` | 语义 AST |
| `SYNTAX_SCHEMA_VERSION` | `1` | 源码语法树 |
| `RENDER_SCHEMA_VERSION` | `1` | 渲染快照 |
版本变化意味着快照结构不兼容;缓存或跨进程传输时应把版本写入缓存键。
---
url: /zh/api/prepare.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 准备 `/hast`
```ts
import {
prepareWikitext,
prepareWikitextAsync,
DEFAULT_PREPARE_POLICY,
DEFAULT_WIKITEXT_LABELS,
sanitizeHref,
sanitizeImageUrl,
sanitizedAttributesToProps,
serializeWikitextRenderDocument,
restoreWikitextRenderDocument,
} from "react-wikitext/hast";
```
`/hast` 不加载解析器与 React。它把解析快照转换为已清洗的渲染快照。
## 基本用法
```ts
const prepared = prepareWikitext(document, {
resolveLink: (request) => {
if (request.kind === "internal") {
return { href: `/wiki/${request.page ?? request.target}` };
}
return undefined;
},
headingIdStrategy: "instance",
labels: { toc: "目录" },
});
```
```ts
const prepared = await prepareWikitextAsync(document, {
rehypePlugins: [asyncPlugin],
});
```
准备阶段依次执行以下步骤:
1. 克隆语义树,运行 `wikitextPlugins`。
2. 校验工作树的结构、节点数与深度,重新分配路径式 id,并清除该 root 的查询缓存。
3. 调用 `resolveLink`、`resolveImage` 与 `handlers`,把语义树转换为 HAST。
4. 运行 `rehypePlugins`。
5. 执行最终清洗与整理,写入目录、引用区、分类底栏与图片授权,生成 `WikitextRenderDocument`。
原始解析快照不会被修改。
## 选项
```ts
interface WikitextPrepareOptions {
wikitextPlugins?: WikitextPluginList;
rehypePlugins?: WikitextPluginList;
resolveLink?: WikitextLinkResolver;
resolveImage?: WikitextImageResolver;
labels?: Partial;
policy?: WikitextPreparePolicy;
headingIdStrategy?: WikitextHeadingIdStrategy;
toc?: false | WikitextTocOptions;
handlers?: WikitextHastHandlers;
}
interface WikitextTocOptions {
minHeadings?: number;
columns?: 1 | 2 | 3;
}
```
| 选项 | 说明 |
| ------------------- | ------------------------------------ |
| `wikitextPlugins` | 在克隆的语义树上运行 |
| `rehypePlugins` | 在 HAST 转换后、最终清洗前运行 |
| `resolveLink` | 决定链接地址与标签 |
| `resolveImage` | 授权图片资源 |
| `labels` | 覆盖用户可见文案 |
| `policy` | 调整 URL、标签与类名白名单 |
| `headingIdStrategy` | `instance`(默认)/ `mediawiki` / `none` |
| `toc` | 目录阈值、列数,或 `false` 关闭目录 |
| `handlers` | 自定义语义节点的 HAST 转换器 |
插件列表是结构化数组,元素可以是插件函数或 `[plugin, options]` 元组,按顺序执行。插件签名沿用 unified 的 `(tree, file)`,`file` 是 VFile。
- 同步接口遇到异步插件抛 `WikitextPrepareError`,错误码 `ASYNC_PLUGIN_IN_SYNC_PATH`。
- 异步接口支持异步插件;React 渲染前应完成准备。
- 准备后的工作树仍受固定预算约束:50,000 节点、深度 1,000。该值不能通过解析选项修改。
- 插件处理后的节点会重新编号;此前持有的 `flattenNodes` 结果或 id 索引不会自动更新。
- `handlers` 返回 `undefined` 表示回退到内建规则,返回 `null` 表示显式丢弃节点。
## 解析函数
本节把 `resolveLink` 与 `resolveImage` 回调统称为解析函数。
### 链接
```ts
type WikitextLinkResolver = (
request: WikitextLinkRequest,
context: WikitextResolverContext,
) => WikitextResolvedLink | null | undefined;
interface WikitextLinkRequest {
kind: "internal" | "external" | "bareExternal";
target: string;
page?: string;
fragment?: string;
}
interface WikitextResolvedLink {
href?: string;
label?: string;
target?: "_blank" | "_self";
}
```
| 返回值 | 行为 |
| ------------ | ------------------------- |
| `undefined` | 沿用默认行为 |
| `null` | 关闭导航 |
| `{ href }` | 生成 ``;地址仍会清洗 |
| `{ label }` | 替换纯文本标签;只有 `label` 时不生成链接 |
| `{ target }` | 覆盖窗口目标 |
### 图片
```ts
type WikitextImageResolver = (
request: WikitextImageRequest,
context: WikitextResolverContext,
) => WikitextResolvedImage | null;
interface WikitextResolvedImage {
src: string;
alt?: string;
width?: number;
height?: number;
}
```
- `null` 保持占位;返回对象才授权资源。
- `src` 只允许 `http:` / `https:`;相对图片默认关闭。
- 尺寸限制在 1–4096,最终 `
` 固定带 `loading="lazy"`。
- `context.document` 是原始解析快照,`context.ast` 与 `context.node` 是本次工作树。
解析函数只返回普通数据,不返回 React 元素。需要自定义外观时使用视图的 [`semanticComponents`](/zh/api/react.md#语义组件)。
## 安全策略
```ts
interface WikitextPreparePolicy {
allowRelativeLinks?: boolean;
allowRelativeImages?: boolean;
allowPluginImages?: boolean;
allowSourceImages?: boolean;
externalLinkTarget?: "_blank" | "_self" | null;
extraTags?: readonly string[];
stripTags?: readonly string[];
}
```
默认值:
```ts
{
allowRelativeLinks: true,
allowRelativeImages: false,
allowPluginImages: false,
allowSourceImages: false,
externalLinkTarget: "_blank",
extraTags: [],
stripTags: [
"script", "style", "iframe", "object", "embed", "form", "input",
"button", "textarea", "select", "option", "svg", "math", "meta", "link",
],
}
```
- 与默认值做单层浅合并。
- 数组字段整体替换;需要保留默认数组时以 `DEFAULT_PREPARE_POLICY` 为基准展开。
- `class` 统一按 `[A-Za-z0-9_-]` 安全字符集过滤;不存在放宽或收窄 class 的 policy 选项。
- `DEFAULT_PREPARE_POLICY` 已冻结。
- 非法字段(policy 字段类型、labels 字段类型、`headingIdStrategy` 枚举、`handlers` 形状)在开始处理前抛 `WikitextPrepareError("PREPARE_FAILED")`。
- 最终 HAST 与恢复快照共用同一组固定预算(50,000 节点、深度 1,000);超限时准备阶段直接失败,库产生的合法快照总能被恢复。
## 文案与标题
- `labels` 由准备阶段与默认值浅合并,最终写入 `meta.labels`。
- 默认文案与字段见[定制指南](/zh/guide/customization.md#文案)。
- `headingIdStrategy` 决定逻辑锚点与 DOM id 的映射;`headings` 保存 `nodeId`、`level`、`text` 与可选 `id`。
- 锚点由移除标记后的标题文本生成;语义标题与源码 `h1`–`h6` 都参与。
## 渲染快照
```ts
interface WikitextRenderDocument {
source: string;
ast: WikitextRoot;
hast: import("hast").Root;
headings: readonly WikitextHeadingInfo[];
meta: WikitextRenderMeta;
diagnostics: readonly WikitextDiagnostic[];
}
```
`meta` 包含实际生效的策略、文案、标题策略与解析函数授权的图片地址。目录、引用区与分类底栏已经包含在 `hast` 中,不新增快照字段。
## handlers
`handlers` 把自定义语义节点转换为 HAST。未注册转换函数的节点会产生诊断并降级为源码卡片。
```ts
interface WikitextHastHandlers {
[type: string]: (
node: WikitextNode,
context: WikitextToHastContext,
) => import("hast").ElementContent | import("hast").ElementContent[] | null | undefined;
}
```
`context` 提供原始文档、工作树、文案、策略、解析函数、转换函数、只读的 `authorizedImages` 与 `diagnostics`,以及 `reportDiagnostic`、`convert`、`convertChildren`。`handlers` 优先于内建转换,注册内建类型(例如 `heading`)会覆盖默认实现。转换结果仍会经过 rehype 插件与最终清洗;链接与图片的最终属性、图片授权和目录由整理阶段统一决定。
## URL 与属性工具
```ts
sanitizeHref(value, { allowRelative? }): string | null
sanitizeImageUrl(value, { allowRelative? }): string | null
sanitizedAttributesToProps(attributes, tag?): Properties
```
- `sanitizeHref` 允许 `http:`、`https:`、`mailto:`;`sanitizeImageUrl` 只允许 `http:`、`https:`。
- `allowRelative` 默认关闭。
- `sanitizedAttributesToProps` 传入 `tag` 时按标签白名单过滤;省略时返回表格相关属性的并集。
- 源码 HTML 的 `data-node-id`、`data-node-type`、`data-wikitext` 等库保留属性会被移除。
## 序列化
```ts
const payload = serializeWikitextRenderDocument(prepared);
const restored = restoreWikitextRenderDocument(payload);
```
恢复函数校验 AST 与渲染快照的结构版本、根节点、HAST 结构与固定节点预算,并重新执行最终清洗与整理。仍不能把任意第三方 HAST 直接交给视图。
## 诊断
准备阶段的诊断会合并到渲染快照,`stage` 可能是:
- `wikitext`:语义树插件或转换。
- `hast`:HAST 转换。
- `rehype`:rehype 插件。
- `sanitize`:清洗。
- `finalize`:最终整理。
`diagnostics` 与 `meta` 一起可用于调试或上报;单条诊断不会中断整页渲染。
---
url: /zh/api/expand.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 展开 `/expand`
```ts
import { expandWikitext, WikitextExpandError } from "react-wikitext/expand";
```
`/expand` 在解析前展开受支持的模板与纯 wikitext 解析器函数。它加载解析器,不加载 React。展开是异步阶段。
## 签名
```ts
function expandWikitext(
source: string,
options: WikitextExpandOptions,
): Promise;
```
```ts
interface WikitextExpandOptions {
parser?: WikitextParser;
pageName?: string;
parseOptions?: Omit;
resolveTemplate: WikitextTemplateResolver;
maxDepth?: number;
maxInvocations?: number;
maxExpandedLength?: number;
strict?: boolean;
signal?: AbortSignal;
}
```
| 选项 | 默认值 | 说明 |
| ------------------- | -------------------- | ---------------------------- |
| `parser` | 默认解析器 | 可注入自定义解析器 |
| `pageName` | — | 原始页面名 |
| `parseOptions` | — | 原始与最终解析共用,`pageName` 由顶层字段提供 |
| `resolveTemplate` | 必填 | 模板源码解析函数 |
| `maxDepth` | 40 | 嵌套深度 |
| `maxInvocations` | 1,000 | 调用次数 |
| `maxExpandedLength` | 与原始页面长度上限相同(100,000) | 展开后源码长度 |
| `strict` | `false` | 可恢复失败是否抛错 |
| `signal` | — | 取消信号 |
## 解析函数
本节把 `resolveTemplate` 回调称为解析函数。
```ts
interface WikitextTemplateRequest {
title: string;
pageName?: string;
callerTitle?: string;
invocation: WikitextRange;
signal?: AbortSignal;
}
type WikitextTemplateResolver = (
request: WikitextTemplateRequest,
) => string | null | undefined | Promise;
```
- `null` / `undefined` 表示模板不存在;空字符串是合法模板。
- 同一标题在一次展开中只解析一次;同一 `title` 必须返回稳定源码。
- `callerTitle` 只作为上下文,不参与缓存键。
- `invocation` 指向 `original.source` 中的顶层调用范围;嵌套请求复用所属顶层范围。
- 远程获取由解析函数完成;库不发起网络请求。
## 结果
```ts
interface WikitextExpandedDocument {
original: WikitextDocument;
document: WikitextDocument;
expansion: WikitextExpansionInfo;
}
interface WikitextExpansionInfo {
status: "complete" | "partial" | "fallback";
records: readonly WikitextExpansionRecord[];
diagnostics: readonly WikitextExpansionDiagnostic[];
}
interface WikitextExpansionRecord {
kind: "template" | "parserFunction" | "argument";
name: string;
originalRange: WikitextRange;
expandedRange: WikitextRange;
status: "expanded" | "preserved";
}
```
- `original` 是原始源码的解析快照。
- `document` 是展开后源码的解析快照,可直接交给 `prepareWikitext`。
- `status`:
- `complete`:全部调用已展开。
- `partial`:存在保留的调用或未解析依赖。
- `fallback`:最终源码解析失败,返回原始快照。
- `expansion.diagnostics` 的坐标基于 `original.source`;`document.diagnostics` 合并最终解析诊断与换算后的展开诊断。
## 展开内容
支持模板嵌入与参数替换、include 标签、`#if`、`#ifeq`、`#switch`、`#iferror`、`{{!}}`、`{{=}}` 与隐式换行。
默认保留并产生诊断的调用:
- `#invoke` 与 Lua。
- `subst` 等嵌入修饰符。
- `#ifexist`、`#time` 等依赖站点状态或外部运行环境的函数。
细节见[模板展开指南](/zh/guide/templates.md)。
## 预算与失败
- 单次调用超长:保留调用并记录 `LENGTH_BUDGET_EXCEEDED`。
- 连保留源码都超过 `maxExpandedLength`:抛 `EXPANSION_TOO_LARGE`。
- `strict: true` 下预算超限:抛 `EXPANSION_BUDGET_EXCEEDED`。
- 默认模式下最终解析失败:退回原始有效快照,`status: "fallback"`。
- 取消时,Promise 以 `WikitextExpandError` 拒绝,错误码为 `ABORTED`,原始原因保存在 `cause` 中。
`parseOptions.maxSourceLength`、模板源码长度与 `maxExpandedLength` 分别生效;最终解析使用实际生效的 `maxExpandedLength`。
## 错误
```ts
class WikitextExpandError extends WikitextError {
readonly templateTitle?: string;
readonly range?: WikitextRange;
constructor(
code: WikitextExpandErrorCode,
message: string,
options?: { cause?: unknown; templateTitle?: string; range?: WikitextRange },
);
}
```
常见错误码:
| 错误码 | 触发条件 |
| --------------------------- | ------------------ |
| `INVALID_EXPAND_OPTIONS` | 选项不合法 |
| `INCOMPATIBLE_PARSER` | 注入的解析器不满足展开要求 |
| `TEMPLATE_NOT_FOUND` | strict 模式下模板缺失 |
| `TEMPLATE_RESOLVE_FAILED` | strict 模式下解析函数失败 |
| `TEMPLATE_PARSE_FAILED` | strict 模式下模板源码解析失败 |
| `TEMPLATE_LOOP` | strict 模式下检测到循环 |
| `UNSUPPORTED_CALL` | strict 模式下遇到不支持的调用 |
| `UNRESOLVED_DEPENDENCY` | strict 模式下依赖未解析 |
| `EXPANSION_BUDGET_EXCEEDED` | strict 模式下预算超限 |
| `EXPANSION_TOO_LARGE` | 展开结果或保留源码超过长度上限 |
| `FINAL_PARSE_FAILED` | strict 模式下最终解析失败 |
| `ABORTED` | 通过 `signal` 取消 |
## 与视图组合
```tsx
import { expandWikitext } from "react-wikitext/expand";
import { prepareWikitext } from "react-wikitext/hast";
import { WikitextView } from "react-wikitext/view";
const expanded = await expandWikitext(source, { resolveTemplate });
const prepared = prepareWikitext(expanded.document);
const view = ;
```
展开后的文档已经是解析结果,不必再次解析。未展开而保留的调用仍按模板、函数或参数占位渲染。
---
url: /zh/api/unified.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# unified
```ts
import { wikitextParse, wikitextRehype, wikitextFinalize } from "react-wikitext/unified";
```
`/unified` 不加载默认解析器,也不加载 React。它把处理管线拆成三个 unified 插件,适合已经有处理器、需要在中间插入自定义转换的场景。
:::warning
raw 模式不会自动补上安全阶段,也不会替你安排插件顺序。需要固定的安全顺序时,优先使用 [`prepareWikitext`](/zh/api/prepare.md)。
:::
## 典型处理器
```ts
import { unified } from "unified";
import { createWikiparser } from "react-wikitext/parse";
import { wikitextParse, wikitextRehype, wikitextFinalize } from "react-wikitext/unified";
const parser = createWikiparser();
const processor = unified()
.use(wikitextParse({ parser }))
.use(semanticPlugin) // 在 wiki 语义树上运行
.use(wikitextRehype({ resolveLink })) // 语义树 → HAST,尚未清洗
.use(rehypePlugin) // 自定义 HAST 转换
.use(wikitextFinalize()); // 清洗 + 最终整理 + 渲染快照
```
## wikitextParse
```ts
wikitextParse({ parser, parseOptions? }): WikitextUnifiedPlugin
```
- 把源码解析为 `WikitextDocument`,写入 VFile。
- 返回一份独立的工作树;同一个 VFile 再次解析会重置库字段,不清空调用方其他 `data`。
- 必须显式提供解析器;`/unified` 不会隐式加载 `wikiparser-node`。
## wikitextRehype
```ts
wikitextRehype(options?): WikitextUnifiedPlugin
```
- 读取 VFile 中的解析结果,使用处理器实际传入的树,而不是缓存的旧树。
- 校验工作树结构与预算、重新分配 id、转换为 HAST。
- 保存尚未执行安全阶段的准备上下文。
- 选项只包含准备设置(解析函数、`policy`、`labels`、`headingIdStrategy` 与 `handlers`)。
- **不执行** `wikitextPlugins` / `rehypePlugins`;显式传入这两个字段(包括空数组)会抛 `PREPARE_FAILED`。插件应通过外层处理器的 `.use(...)` 注册。
## wikitextFinalize
```ts
wikitextFinalize({ headingIdStrategy? }): WikitextUnifiedPlugin
```
- 必须在所有 rehype 插件之后调用。
- 完成清洗与最终整理,并把 `wikitextRenderDocument` 写入 VFile。
- 显式 `headingIdStrategy` 优先于准备设置,并同时用于最终标题与快照 meta。
- 之后不能再插入内容;跳过它的 HAST 不能作为渲染文档。
## 错误与前置条件
- 缺少 `wikitextParse` 时,`wikitextRehype` 抛 `WikitextPrepareError("INVALID_DOCUMENT")`,`stage` 为 `prepare`。
- 缺少 `wikitextRehype` 时,`wikitextFinalize` 抛 `WikitextPrepareError("PREPARE_FAILED")`。
- 这些错误不会自动修复插件顺序;请检查处理器链。
## 什么时候不用 raw 模式
| 需求 | 建议 |
| ----------------- | ------------------------------------------ |
| 只需要插入语义树或 HAST 插件 | `prepareWikitext` 的插件选项 |
| 需要固定的清洗顺序 | `prepareWikitext` / `prepareWikitextAsync` |
| 需要展开模板 | `/expand` + `/hast` |
| 需要视图组件 | `/view` 或根入口 |
## 相关
- [准备 `/hast`](/zh/api/prepare.md):固定安全顺序、解析函数与策略。
- [AST `/ast`](/zh/api/ast.md):语义节点与查询。
- [错误与诊断](/zh/api/errors.md):`WikitextPrepareError` 的字段。
---
url: /zh/api/configs.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# configs
```ts
import { MEDIAWIKI_CONFIG } from "react-wikitext/configs";
import type { ParserConfig } from "react-wikitext/configs";
```
`/configs` 只包含站点配置常量与类型,不加载解析器或 React。配置在创建解析器时固定;之后修改原对象不会影响解析器。
## 常量
| 常量 | 说明 |
| ------------------ | ----------------------------------------- |
| `MEDIAWIKI_CONFIG` | 默认配置,来源是 `wikiparser-node@1.48.1` 的站点配置快照 |
```ts
type ParserConfig = ConfigData & { excludes?: string[] };
```
字段来自上游 `wikiparser-node`。库不导出归一化函数或来源常量;最小配置不属于公共 API,需要时应从上游包读取并自行承担退化行为。
## 创建解析器
```ts
import { createWikiparser } from "react-wikitext/parse";
import { MEDIAWIKI_CONFIG } from "react-wikitext/configs";
const parser = createWikiparser({
config: MEDIAWIKI_CONFIG,
defaults: { pageName: "Example" },
});
```
- 配置在工厂创建时深拷贝并归一化:补齐 `excludes`、归一化 `doubleUnderscore`,并做最小结构校验。
- 非法配置抛 `WikitextParseError("INVALID_CONFIG")`。
- 更换配置需要创建新的解析器,避免每次解析隐式修改共享配置。
- `defaults` 是每次解析的默认选项,可以按调用覆盖;预算合并规则见[解析 `/parse`](/zh/api/parse.md)。
## 自定义站点配置
需要站点差异时,以 `MEDIAWIKI_CONFIG` 为基础复制并调整字段;这是唯一受支持的配置路径。不要依赖上游的最小配置作为公共选项:它会让 `[`、`` 与文件参数静默退化,库不为它提供兼容承诺。确实需要最小识别时,可以从 `wikiparser-node/config/minimum.json` 读取并自行转换为 `ParserConfig`,但要先确认这些退化可接受。
## 与预算的关系
站点配置不包含解析预算。源码长度、节点数与深度上限属于解析选项,按“单次解析 → 创建时 `defaults` → 共享默认值”合并;见[性能与预算](/zh/guide/performance.md)。
## 相关
- [解析 `/parse`](/zh/api/parse.md):`createWikiparser` 与解析选项。
- [状态与兼容性](/zh/project/status.md):固定的上游版本。
---
url: /zh/api/errors.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 错误与诊断
库区分两类信号:
- **错误**(`Error`):当前调用无法继续,抛出异常;需要调用方捕获。
- **诊断**(`WikitextDiagnostic`):单点问题或降级记录,写入快照,不中断整页渲染。
## 错误类
```ts
class WikitextError extends Error {
readonly code: WikitextErrorCode;
cause?: unknown;
}
class WikitextParseError extends WikitextError {}
class WikitextPrepareError extends WikitextError {
readonly stage: string;
}
class WikitextExpandError extends WikitextError {
readonly templateTitle?: string;
readonly range?: WikitextRange;
}
```
所有错误都带 `code`;`cause` 保留底层异常。`WikitextPrepareError.stage` 记录失败阶段,常见值为 `prepare`、`wikitext`、`rehype` 与 `finalize`。
## 按入口导出
| 入口 | 错误导出 |
| --------------- | --------------------------------------------------------------- |
| `/ast`、`/parse` | `WikitextError`、`WikitextParseError`、`WikitextErrorCode` |
| `/hast` | `WikitextError`、`WikitextPrepareError` |
| `/expand` | `WikitextError`、`WikitextExpandError`、`WikitextExpandErrorCode` |
| 根入口、`/view` | 不导出错误类;渲染异常由错误边界处理 |
## 错误码
| 错误码 | 阶段 | 触发条件 |
| --------------------------- | ----- | -------------------- |
| `SOURCE_TOO_LONG` | 解析 | 归一化后的源码超过长度预算 |
| `SOURCE_TOO_COMPLEX` | 解析 | 语法树或语义树超过节点/深度预算 |
| `INVALID_CONFIG` | 解析 | 站点配置或预算字段非法 |
| `PARSE_FAILED` | 解析 | 上游解析或语义构建失败 |
| `PARSER_UNAVAILABLE` | 解析 | 默认浏览器包未初始化全局解析器 |
| `PREPARE_FAILED` | 准备 | 选项、插件、转换函数或预算校验失败 |
| `ASYNC_PLUGIN_IN_SYNC_PATH` | 准备 | 同步入口遇到异步插件 |
| `INVALID_DOCUMENT` | 恢复/准备 | 快照结构、版本、根节点或预算不合法 |
| `INVALID_EXPAND_OPTIONS` | 展开 | 展开选项非法 |
| `INCOMPATIBLE_PARSER` | 展开 | 注入的解析器不满足展开要求 |
| `TEMPLATE_NOT_FOUND` | 展开 | `strict` 模式下模板缺失 |
| `TEMPLATE_RESOLVE_FAILED` | 展开 | `strict` 模式下解析函数失败 |
| `TEMPLATE_PARSE_FAILED` | 展开 | `strict` 模式下模板源码解析失败 |
| `TEMPLATE_LOOP` | 展开 | `strict` 模式下检测到循环 |
| `UNSUPPORTED_CALL` | 展开 | `strict` 模式下遇到不支持的调用 |
| `UNRESOLVED_DEPENDENCY` | 展开 | `strict` 模式下依赖未解析 |
| `EXPANSION_BUDGET_EXCEEDED` | 展开 | `strict` 模式下预算超限 |
| `EXPANSION_TOO_LARGE` | 展开 | 展开结果或保留源码超过长度上限 |
| `FINAL_PARSE_FAILED` | 展开 | `strict` 模式下最终解析失败 |
| `ABORTED` | 展开 | 通过 `AbortSignal` 取消 |
展开阶段在默认模式下会尽量保留调用并记录诊断;`strict: true` 时上述可恢复错误改为抛出。
## 处理示例
```ts
import { WikitextParseError } from "react-wikitext/parse";
try {
const document = parser.parse(source, { pageName: "Example" });
return document;
} catch (error) {
if (error instanceof WikitextParseError) {
if (error.code === "SOURCE_TOO_LONG") {
return loadSourceInPages(source);
}
throw error;
}
throw error;
}
```
```ts
import { prepareWikitextAsync, WikitextPrepareError } from "react-wikitext/hast";
try {
return await prepareWikitextAsync(document, { rehypePlugins });
} catch (error) {
if (error instanceof WikitextPrepareError) {
report(error.code, error.stage, error.cause);
}
throw error;
}
```
- 不要根据 `message` 文本分支;使用 `code`(必要时加上 `stage`)。
- 解析与准备错误应在调用层捕获,并区分预算、配置与插件问题。
- 视图渲染错误通过 `onRenderError` 上报;错误边界只替换失败的顶层块。
- 恢复快照失败不应静默降级为重新解析;先确认版本与来源。
## 诊断
```ts
interface WikitextDiagnostic {
readonly code: string;
readonly stage: "parse" | "expand" | "wikitext" | "hast" | "rehype" | "sanitize" | "finalize";
readonly severity: "error" | "warning" | "info";
readonly message: string;
readonly position?: WikitextPosition;
readonly origin?: WikitextOrigin;
readonly rule?: string;
}
```
- `position` 与 `origin` 基于归一化后的源码;生成内容可能没有来源。
- `rule` 标识产生诊断的规则或插件。
- 渲染快照的 `diagnostics` 合并了解析、准备与最终整理阶段的记录。
- 单条诊断不会中断整页渲染;是否上报、是否阻断由宿主决定。
## 相关
- [故障排查](/zh/guide/troubleshooting.md):按症状定位问题。
- [准备 `/hast`](/zh/api/prepare.md):准备阶段与插件错误。
- [展开 `/expand`](/zh/api/expand.md):`strict` 模式与展开错误。
---
url: /zh/project/status.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 状态与兼容性
## 版本状态
| 项目 | 当前状态 |
| ---- | ------------------------------------------------------------------------------ |
| 版本 | `0.1.0-alpha.1` |
| 发布渠道 | npm,alpha dist-tag;安装命令为 `pnpm add react-wikitext@alpha` |
| 稳定性 | 0.x 阶段按语义化版本约定,次版本可能包含破坏性变更 |
| 变更记录 | 以 [GitHub Releases](https://github.com/BearBin1215/react-wikitext/releases) 为准 |
| 文档站 | [react-wikitext.pages.dev](https://react-wikitext.pages.dev),与主分支同步;不提供按版本切换 |
安装步骤见[快速开始](/zh/guide/quick-start.md)。
## 运行时兼容
| 项目 | 支持范围 |
| ------ | --------------------------------------- |
| React | `>=18.2 <20`(peer dependency) |
| Node | 运行时 `>=18.18`;仓库开发与 CI 主 job 使用 Node 24 |
| 模块格式 | ESM 与 CJS,均提供类型声明 |
| 打包器 | Vite 与 webpack 5 的消费者构建有测试覆盖 |
| 浏览器 | 产物语法目标为 ES2022;不支持 IE 或更老的浏览器 |
| 运行环境依赖 | 不依赖 Wikimedia、字体 CDN 或外部 API |
npm 包只包含 `dist/`、`NOTICE` 与 `THIRD_PARTY_LICENSES`;从源码构建与本地调试流程见[贡献指南](/zh/project/contributing.md)。
## 固定的上游
| 项目 | 值 |
| ------ | --------------------------------- |
| 解析器 | `wikiparser-node@1.48.1` |
| 默认站点配置 | `MEDIAWIKI_CONFIG`,来源为同一版本的站点配置快照 |
升级解析器会改变 AST、源码范围与渲染基线,需要重新审查渲染与安全测试。
## 快照版本
| 常量 | 当前值 | 适用 |
| ----------------------- | --- | ------ |
| `AST_SCHEMA_VERSION` | `1` | 语义 AST |
| `SYNTAX_SCHEMA_VERSION` | `1` | 源码语法树 |
| `RENDER_SCHEMA_VERSION` | `1` | 渲染快照 |
版本变化意味着快照结构不兼容。缓存与跨进程传输时应把版本写入键;恢复函数会拒绝不匹配的数据。
## 样式与安全基线
- 样式在 `react-wikitext/styles.css`,需要显式引入;库不设置全局元素样式。
- 内联样式只保留白名单内的排版与外观声明,`url()`、`expression()`、`!important` 等值整条丢弃并产生诊断;不依赖 `eval`,严格 CSP 下如需这些声明生效,按需放行 `style-src`。
- 不使用 `dangerouslySetInnerHTML`;最终 HAST 由清洗器与 wiki 策略生成。
- 图片默认不加载;只有解析函数授权的地址(或显式开启的插件图片策略)才会成为 `]
`。
- 渲染入口依赖 React Context,只能在客户端组件中运行;根入口与 `/view` 的产物顶部已带 `"use client"` 指令,宿主侧不需要重复标记。
完整边界见[安全模型](/zh/guide/security.md)与[渲染行为](/zh/guide/rendering.md)。
## 验证兼容性
仓库提供分层检查:
| 命令 | 检查内容 |
| -------------------------- | ------------------------------------------ |
| `pnpm test` | 单元与渲染测试 |
| `pnpm typecheck` | 源码类型 |
| `pnpm test:types:consumer` | 消费者类型解析(Node16 与 bundler 解析) |
| `pnpm test:consumer` | dist 入口、子路径隔离、CSS、Vite/webpack 与 view-only |
| `pnpm bench` | 解析与渲染基准 |
升级 React、解析器或打包配置后,至少运行 `test:consumer` 与 `test`。
## 许可
- 库以 **GPL-3.0-or-later** 发布。
- `wikiparser-node@1.48.1` 使用同一许可。
- 第三方依赖与署名见仓库根目录的 `NOTICE` 与 `THIRD_PARTY_LICENSES`。
在分发包含本库的产物前,请确认你的项目与 GPL-3.0-or-later 的兼容性。
---
url: /zh/project/contributing.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# 参与与反馈
## 报告问题
在 GitHub 仓库提交 issue 时,附上下面的信息能显著缩短定位时间:
- 最小 wikitext 源码与 `pageName`。
- 使用的入口与关键选项(`parseOptions`、`prepareOptions`、`policy`、`labels`)。
- 库版本、`parserVersion`、React 与打包器版本。
- 完整错误对象的 `code`、`stage` 与 `cause`,以及 `document.diagnostics`。
- 期望输出与实际输出;如果涉及样式,附上屏幕截图或 DOM 片段。
请先确认问题不是[渲染行为](/zh/guide/rendering.md)或[支持矩阵](/zh/guide/syntax-support.md)中标注的行为。
## 运行文档站
文档使用 Rspress,源码位于 `website/`:
```bash
pnpm install
pnpm docs:dev # 本地预览
pnpm docs:build # 构建到 website/doc_build/
pnpm docs:preview # 预览构建产物
```
- 中文内容在 `website/docs/zh/`,英文在 `website/docs/en/`;新增页面时两边都要补齐。
- 目录结构由各目录的 `_meta.json` 与顶部的 `_nav.json` 决定。
- 指南中的可运行示例使用 ` ```tsx preview ` 代码块;示例通过包公共入口导入,由 `@rspress/plugin-preview` 渲染。
- 首页在线示例由 `website/theme/HomeExample.tsx` 在客户端按需加载库源码。
## 修改文档
- 行为、默认值与导出以 `src/` 与 `tests/` 为准;文档不复制容易漂移的完整测试清单或固定性能数字。
- 指南负责“怎么做”,参考负责“有什么”;同一个事实只写一处,其他页面链接过去。
- 代码片段应能独立理解;涉及安全边界时明确标注“可信代码”或“不可信输入”。
- 中英文段落保持一一对应,术语保持一致:解析快照、渲染快照、解析函数、策略、文案。
## 提交前检查
```bash
pnpm format:check
pnpm lint
pnpm typecheck
pnpm test
pnpm docs:build
```
改动构建、导出或依赖时再运行:
```bash
pnpm test:consumer
pnpm build
```
完整贡献流程、环境要求与 CI 说明见仓库根目录的 [`CONTRIBUTING.md`](https://github.com/BearBin1215/react-wikitext/blob/main/CONTRIBUTING.md)。
## 许可
提交内容按仓库的 GPL-3.0-or-later 许可分发;新增第三方代码或资源时,需要同步更新 `NOTICE` 与 `THIRD_PARTY_LICENSES`。
---
url: /zh/index.md
---
> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.
# react-wikitext
> 用于渲染 wikitext 的 React 组件
[快速开始](/zh/guide/quick-start) | [GitHub](https://github.com/BearBin1215/react-wikitext)
## Features
- **全流程强类型**: 解析器 → 语义 AST → HAST → React,每一阶段都有类型、可检查、可替换。
- **默认安全**: 只输出 React 元素,不输出 HTML 字符串,清洗始终在插件之后执行。
- **模板按需展开**: 支持通过宿主提供的解析函数按需展开模板。