---
title: "静态渲染器"
description: "使用静态渲染器将 JSON 内容渲染为 HTML、markdown 或 React 组件，而无需编辑器实例。在我们的文档中了解更多信息！"
canonical_url: "https://tiptap.zhcndoc.com/editor/api/utilities/static-renderer"
---

# 静态渲染器

使用静态渲染器将 JSON 内容渲染为 HTML、markdown 或 React 组件，而无需编辑器实例。在我们的文档中了解更多信息！

静态渲染器帮助将 JSON 内容渲染为 HTML、markdown 或 React 组件，而无需编辑器实例。它只需要 JSON 内容和扩展列表。

> **Interactive demo:** [staticrendering](https://embed.tiptap.dev/preview/examples/staticrendering)

## 为什么选择静态渲染？

静态渲染的主要用例是在服务器端渲染 Tiptap/ProseMirror JSON 文档，例如在 Next.js 或 Nuxt.js 应用程序中。通过这种方式，您可以在将内容发送到客户端之前将编辑器的内容渲染为 HTML，这可以通过不再需要在客户端或服务器上加载编辑器来提高应用程序的性能。

另一个用例是将编辑器的内容渲染为另一种格式，比如 markdown，这在您想要将其发送到基于 markdown 的 API 时非常有用。静态渲染器的构建方式使得输出可以是您想要的任何内容，只要您提供正确的映射即可。

但是什么使得它是静态的呢？静态渲染器不需要浏览器、DOM 甚至编辑器实例来渲染内容。它是一个纯 JavaScript 函数，接受文档（作为 JSON 或 Prosemirror Node 实例）并返回目标格式。

## 从 JSON 生成 HTML 字符串

给定一个 JSON 文档，`renderToHTMLString` 函数将返回一个表示 JSON 内容的 HTML 字符串。该函数接受三个参数：JSON 文档、扩展列表和一个选项对象。

```js
import StarterKit from '@tiptap/starter-kit'
import { renderToHTMLString } from '@tiptap/static-renderer/pm/html-string'

renderToHTMLString({
  extensions: [StarterKit], // 使用您的扩展
  content: {
    type: 'doc',
    content: [
      {
        type: 'paragraph',
        content: [
          {
            type: 'text',
            text: '你好，世界！',
          },
        ],
      },
    ],
  },
})
// 返回: '<p>你好，世界！</p>'
```

> **Interactive demo:** [StaticRenderHTML](https://embed.tiptap.dev/preview/GuideContent/StaticRenderHTML)

### generateHTML API

```ts
function renderToHTMLString(options: {
  extensions: Extension[]
  content: ProsemirrorNode | JSONContent
  staticEditorOptions?: { textDirection?: 'ltr' | 'rtl' | 'auto' }
  options?: TiptapHTMLStaticRendererOptions
}): string
```

- `extensions`：用于渲染内容的 Tiptap 扩展数组。
- `content`：要渲染的内容。可以是 Prosemirror Node 实例或 Prosemirror 文档的 JSON 表示。
- `staticEditorOptions`：可选的编辑器级选项，会影响渲染结果。目前支持 `textDirection`（`'ltr' | 'rtl' | 'auto'`），它与 `Editor` 上的 `textDirection` 编辑器选项一致，因此输出会包含预期的 `dir` 属性。
- `options`：包含附加选项的对象。
- `options.nodeMapping`：将 Prosemirror 节点映射为 HTML 字符串的对象。
- `options.markMapping`：将 Prosemirror 标记映射为 HTML 字符串的对象。
- `options.unhandledNode`：当遇到未处理的节点时调用的函数。
- `options.unhandledMark`：当遇到未处理的标记时调用的函数。

## 从 JSON 生成 Markdown

给定一个 JSON 文档，`renderToMarkdown` 函数将返回一个表示 JSON 内容的 markdown 字符串。该函数接受三个参数：JSON 文档、扩展列表和一个选项对象。

> **Warning:**
>
> 此包不验证 markdown 输出，存在多种 markdown 风格，此
> 包不强制执行任何一个。确保 markdown 输出是
> 有效的责任在于您。

```js
import StarterKit from '@tiptap/starter-kit'
import { renderToMarkdown } from '@tiptap/static-renderer/pm/markdown'

renderToMarkdown({
  extensions: [StarterKit], // 使用您的扩展
  content: {
    type: 'doc',
    content: [
      {
        type: 'paragraph',
        content: [
          {
            type: 'text',
            text: '你好，世界！',
          },
        ],
      },
    ],
  },
})
// 返回: '你好，世界！'
```

### generateMarkdown API

```ts
function renderToMarkdown(options: {
  extensions: Extension[]
  content: ProsemirrorNode | JSONContent
  staticEditorOptions?: { textDirection?: 'ltr' | 'rtl' | 'auto' }
  options?: TiptapMarkdownStaticRendererOptions
}): string
```

- `extensions`：用于渲染内容的 Tiptap 扩展数组。
- `content`：要渲染的内容。可以是 Prosemirror Node 实例或 Prosemirror 文档的 JSON 表示。
- `staticEditorOptions`：可选的编辑器级选项，会影响渲染结果。目前支持 `textDirection`（`'ltr' | 'rtl' | 'auto'`），它与 `Editor` 上的 `textDirection` 编辑器选项一致，因此输出会包含预期的 `dir` 属性。
- `options`：包含附加选项的对象。
- `options.nodeMapping`：将 Prosemirror 节点映射为 markdown 字符串的对象。
- `options.markMapping`：将 Prosemirror 标记映射为 markdown 字符串的对象。
- `options.unhandledNode`：当遇到未处理的节点时调用的函数。
- `options.unhandledMark`：当遇到未处理的标记时调用的函数。

## 从 JSON 生成 React 组件

给定一个 JSON 文档，`renderToReactElement` 函数将返回一个表示 JSON 内容的 React 组件。该函数接受三个参数：JSON 文档、扩展列表和一个选项对象。

```js
import StarterKit from '@tiptap/starter-kit'
import { renderToReactElement } from '@tiptap/static-renderer/pm/react'

renderToReactElement({
  extensions: [StarterKit], // 使用您的扩展
  content: {
    type: 'doc',
    content: [
      {
        type: 'paragraph',
        content: [
          {
            type: 'text',
            text: '你好，世界！',
          },
        ],
      },
    ],
  },
})
// 返回一个 react 节点，评估后将等同于: '<p>你好，世界！</p>' 而不需要 Tiptap 编辑器实例
```

> **Interactive demo:** [StaticRenderReact](https://embed.tiptap.dev/preview/GuideContent/StaticRenderReact)

### generateReactElement API

```ts
function renderToReactElement(options: {
  extensions: Extension[]
  content: ProsemirrorNode | JSONContent
  staticEditorOptions?: { textDirection?: 'ltr' | 'rtl' | 'auto' }
  options?: TiptapReactStaticRendererOptions
}): ReactElement
```

- `extensions`：用于渲染内容的 Tiptap 扩展数组。
- `content`：要渲染的内容。可以是 Prosemirror Node 实例或 Prosemirror 文档的 JSON 表示。
- `staticEditorOptions`：可选的编辑器级选项，会影响渲染结果。目前支持 `textDirection`（`'ltr' | 'rtl' | 'auto'`），它与 `Editor` 上的 `textDirection` 编辑器选项一致，因此输出会包含预期的 `dir` 属性。
- `options`：包含附加选项的对象。
- `options.nodeMapping`：将 Prosemirror 节点映射为 React 组件的对象。
- `options.markMapping`：将 Prosemirror 标记映射为 React 组件的对象。
- `options.unhandledNode`：当遇到未处理的节点时调用的函数。
- `options.unhandledMark`：当遇到未处理的标记时调用的函数。

### React 节点视图

静态渲染器不自动支持节点视图，因此您需要为每个希望作为节点视图渲染的节点类型提供映射。以下是如何将节点视图渲染为 React 组件的示例：

```js

import { Node } from '@tiptap/core'
import { ReactNodeViewRenderer } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import { renderToReactElement } from '@tiptap/static-renderer/pm/react'

// 此组件没有 NodeViewContent，因此不渲染其子级的富文本内容
function MyCustomComponentWithoutContent() {
  const [count, setCount] = React.useState(200)

  return (
    <div className='custom-component-without-content' onClick={() => setCount(a => a + 1)}>
      {count} 这是一个 react 组件！
    </div>
  )
}

const CustomNodeExtensionWithoutContent = Node.create({
  name: 'customNodeExtensionWithoutContent',
  atom: true,
  renderHTML() {
    return ['div', { class: 'my-custom-component-without-content' }] as const
  },
  addNodeView() {
    return ReactNodeViewRenderer(MyCustomComponentWithoutContent)
  },
})

renderToReactElement({
  extensions: [StarterKit, CustomNodeExtensionWithoutContent],
  options: {
    nodeMapping: {
      // 使用预期的节点视图 React 组件渲染自定义节点
      customNodeExtensionWithoutContent: MyCustomComponentWithoutContent,
    },
  },
  content: {
    type: 'doc',
    content: [
      {
        type: 'customNodeExtensionWithoutContent',
      },
    ],
  },
})
// 返回: <div class="my-custom-component-without-content">200 这是一个 react 组件！</div>
```

但是，如果您希望渲染节点视图的富文本内容，该如何操作？您可以通过将 `NodeViewContent` 组件作为节点视图组件的子组件提供来实现：

```js
import { Node } from '@tiptap/core'
import {
  NodeViewContent,
  ReactNodeViewContentProvider,
  ReactNodeViewRenderer
} from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import { renderToReactElement } from '@tiptap/static-renderer/pm/react'

const CustomNodeExtensionWithContent = Node.create({
  name: 'customNodeExtensionWithContent',
  content: 'text*',
  group: 'block',
  renderHTML() {
    return ['div', { class: 'my-custom-component-with-content' }, 0] as const
  },
  addNodeView() {
    return ReactNodeViewRenderer(MyCustomComponentWithContent)
  },
})

function MyCustomComponentWithContent() {
  return (
    <div className="custom-component-with-content">
      具有内容的自定义组件！
      <NodeViewContent />
    </div>
  )
}

renderToReactElement({
  extensions: [StarterKit, CustomNodeExtensionWithContent],
  options: {
    nodeMapping: {
      customNodeExtensionWithContent: ({ children }) => {
        // 为了将内容传递到 NodeViewContent 组件中，我们需要使用 ReactNodeViewContentProvider 包装自定义组件
        return (
          <ReactNodeViewContentProvider content={children}>
            <MyCustomComponentWithContent />
          </ReactNodeViewContentProvider>
        )
      },
    },
  },
  content: {
    type: 'doc',
    content: [
      {
        type: 'customNodeExtensionWithContent',
        // 富文本内容
        content: [
          {
            type: 'text',
            text: '你好，世界！',
          },
        ],
      },
    ],
  },
})

// 返回: <div class="custom-component-with-content">具有内容的自定义组件！<div data-node-view-content="" style="white-space:pre-wrap">你好，世界！</div></div>
// 注意: NodeViewContent 组件渲染为带有属性 data-node-view-content 的 div，富文本内容渲染在其中
```

> **Interactive demo:** [StaticRenderingAdvanced](https://embed.tiptap.dev/preview/Examples/StaticRenderingAdvanced)

## 限制与解决方法

静态渲染器会构建 schema 并运行每个扩展的 `renderHTML`，但不会实例化 `Editor`。因此：

- `addProseMirrorPlugins`、`onCreate`、`onUpdate` 和事务钩子不会运行。
- 通过这些机制分配属性的扩展——例如 `UniqueID`（`data-id`）和 `TableOfContents`（`id`、`data-toc-id`）——不会自动填充这些属性。

对于这些情况，请在渲染之前预处理 JSON 文档：

```ts
import { generateUniqueIds } from '@tiptap/extension-unique-id'
import { generateTocIds } from '@tiptap/extension-table-of-contents'
import { renderToHTMLString } from '@tiptap/static-renderer/pm/html-string'

let doc = sourceJson
doc = generateUniqueIds(doc, extensions) // 如果使用 UniqueID
doc = generateTocIds(doc, extensions)    // 如果使用 TableOfContents

const html = renderToHTMLString({
  content: doc,
  extensions,
  staticEditorOptions: { textDirection: 'auto' }, // 与 EditorOptions 的子集一致
})
```

会影响输出的编辑器级选项可以通过 `staticEditorOptions` 对象传入（目前是 `textDirection`）。其他依赖运行时视图或事务流的编辑器选项不在适用范围内。

## 共享选项

`renderToHTMLString`、`renderToMarkdown` 和 `renderToReactElement` 函数接受一个选项对象作为参数。可以使用该对象通过提供自定义节点和标记映射，或处理未处理的节点和标记，来自定义渲染器的输出。

```js
import StarterKit from '@tiptap/starter-kit'
import {
  renderToHTMLString,
  serializeChildrenToHTMLString,
} from '@tiptap/static-renderer/pm/html-string'

renderToHTMLString({
  extensions: [StarterKit], // 使用您的扩展
  content: {
    type: 'doc',
    content: [
      {
        type: 'paragraph',
        content: [
          {
            type: 'text',
            text: '你好，世界！',
          },
        ],
      },
    ],
  },
  options: {
    // 自定义节点映射
    nodeMapping: {
      paragraph({ children }) {
        return `<div class="custom-paragraph">${serializeChildrenToHTMLString(children)}</div>`
      },
    },
    // 自定义标记映射
    markMapping: {
      bold({ children }) {
        return `<strong>${serializeChildrenToHTMLString(children)}</strong>`
      },
    },
    // 处理未处理的节点
    unhandledNode: ({ node }) => {
      return `[未知节点 ${node.type.name}]`
    },
    // 处理未处理的标记
    unhandledMark: ({ mark }) => {
      return `[未知标记 ${mark.type.name}]`
    },
  },
})
```

## 技术细节

### 命名空间导入

为了减少应用程序中的捆绑大小，静态渲染器被拆分为三个独立的包：`@tiptap/static-renderer/pm/html-string`、`@tiptap/static-renderer/pm/markdown` 和 `@tiptap/static-renderer/pm/react`。这样，您只需导入所需的静态渲染器部分。如果您希望获得更大的灵活性，可以使用 `@tiptap/static-renderer` 导入整个静态渲染器包。

```ts
// 仅 HTML 渲染器
import { renderToHTMLString } from '@tiptap/static-renderer/pm/html-string'

// 仅 markdown 渲染器
import { renderToMarkdown } from '@tiptap/static-renderer/pm/markdown'

// 仅 React 渲染器
import { renderToReactElement } from '@tiptap/static-renderer/pm/react'

// 整个静态渲染器
import { renderToHTMLString, renderToMarkdown, renderToReactElement } from '@tiptap/static-renderer'
```

`json` 命名空间下的包也可以用于静态渲染，无需依赖任何 ProseMirror 运行时。但这些包无法自动将 Prosemirror 节点和标记映射到目标格式。您需要为这些包中的每个节点和标记提供自定义映射。

```ts
// 仅 HTML 渲染器
import { renderJSONContentToString } from '@tiptap/static-renderer/json/html'

// 仅 React 渲染器
import { renderJSONContentToReactElement } from '@tiptap/static-renderer/json/react'
```

这些包与 `pm` 命名空间具有相同的 API，但是：

- 需要您为每个节点和标记提供自定义映射
- 不需要 `extensions`，因为它们不依赖于 prosemirror 包

### 自定义映射

静态渲染器使用 Prosemirror 节点和标记到目标格式的默认映射。通过在选项对象中提供自定义映射，可以覆盖这些映射。这使您能够根据需要自定义渲染器的输出。

要将自定义节点和标记转换为目标格式，您应提供一个映射函数，该函数以 `node` 或 `mark` 对象作为参数，并返回适当的目标格式元素。如果遇到未处理的节点或标记，则可以提供一个函数，该函数将与未处理的节点或标记作为参数调用。

### 这是如何工作的？

`json` 命名空间中的静态渲染器包遍历 JSON 内容，并为每个节点和标记调用适当的映射函数。`renderJSONContentToString` 函数返回一个表示 JSON 内容的字符串，而 `renderJSONContentToReactElement` 函数返回表示 JSON 内容的 React 元素。

`pm` 命名空间中的静态渲染器包扩展了 `json` 命名空间中的包，利用 Tiptap 扩展的 `renderHTML` 方法生成 Prosemirror 节点/标记到目标格式的默认映射。这些可以通过在选项中提供自定义映射完全覆盖。

## 源代码

[packages/static-renderer/](https://github.com/ueberdosis/tiptap/blob/main/packages/static-renderer/)
