> 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 = <WikitextView document={prepared} lang="zh-CN" />;
```

`expandWikitext` 会：

1. 解析原始源码，得到 `original` 快照。
2. 按调用结构求值模板与函数，记录 `records`。
3. 把展开后的源码重新解析为 `document`。
4. 返回 `expansion.status`：`"complete"`、`"partial"` 或 `"fallback"`。

展开结果已经是普通解析文档；**不要再手动解析一次**，直接交给 `prepareWikitext`。

## 解析函数约定

下面把 `resolveTemplate` 回调称为解析函数。

```ts
type WikitextTemplateResolver = (
  request: WikitextTemplateRequest,
) => string | null | undefined | Promise<string | null | undefined>;
```

- 返回 `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 客户端。
- 不提供跨请求的模板缓存；需要缓存时由宿主在解析函数中实现。
- 展开结果中的分类、引用、目录等仍按默认渲染规则处理。
