将编辑器 CSS 导出为 DOCX 样式

Available in Start planBetav0.16.0

Beta 功能

本页上的 API 和属性映射正在逐步稳定,但在正式可用之前仍可能继续 调整。如果你依赖此功能,请固定精确的包版本,并在采用之前阅读 不会起作用的内容 一节。

这会做什么

从 Tiptap 导出到 DOCX 时,通常会把文档的 节点和标记属性 写入到每个段落、文本片段和单元格的内联样式中。结果会得到一个 DOCX 文件,其中每个元素都携带自己的一份 fontSizecolor 和间距:冗长、重复、视觉上相同,但结构上并不理想。

此功能让你把编辑器的 样式表 作为唯一可信来源。你可以提供 CSS 规则(显式提供,或让我们从浏览器中提取),导出时我们会把这些规则编译成 DOCX 样式定义:也就是 Word 作为自身可信来源所使用的命名条目。导出的 DOCX 会拥有一个完整的样式目录,Word 用户只需编辑一次,就能改变所有使用该样式的段落外观。

导入时的 CSS 注入 配合使用后,你就能为文档的 样式层 建立一条往返路径,而与 内容层 分离。

第二个示例演示浏览器提取(browser-extraction)变体:

安装

npm i @tiptap-pro/extension-export-docx@^0.16.0

工作原理

三个步骤:

  1. 收集 CSS。 导出器会从两个可能来源读取你的样式(你可以任意选择其一或同时使用两者):
    • 你在 configure() 中传入的显式 styles 对象
    • 浏览器的实时样式表(如果 extractFromDocument: true),作用域限定在你的编辑器根节点(editor root)
  2. 编译每个选择器。 编译器在一个固定映射(import 侧相同的 16 个选择器)中查找每个选择器,把每个 CSS 属性转换为它的 DOCX 等效项,并丢弃那些无法映射的属性。
  3. 合并到样式目录(styles catalog)。 编译后的 DOCX 样式片段会合并进导出选项的样式目录,然后交给 DOCX 写入器(writer)。
import { ExportDocx } from '@tiptap-pro/extension-export-docx'

ExportDocx.configure({
  cssStyles: {
    styles: {
      h1: { fontSize: '32px', fontWeight: 'bold', color: '#1a1a1a' },
      p:  { fontSize: '16px', lineHeight: 1.5, marginTop: '0pt', marginBottom: '8pt' },
      blockquote: { fontStyle: 'italic', color: '#555' },
    },
    // 可选:也从实时浏览器样式表中提取规则
    extractFromDocument: true,
    // 可选:用于 rem/em 解析的基准
    baseFontSize: 16,
  },
})

extractFromDocument: true 时,导出器会遍历 document.styleSheets,找到在你的编辑器根节点作用域下匹配这 16-selector 映射的规则,并将它们与任何显式 styles 合并。逐属性而言,后者/显式样式优先(wins per-property)。

适用项

选择器(16): 与导入侧相同。

p, h1h6, blockquote, ul li, ol li, strong, em, u, s, a, code

CSS 属性。 每个选择器都可以包含以下任意子集。未列出的属性在导出时会跳过,并在导出过程中输出 console.warn

  • fontSize: 转换为 half-points(DOCX w:sz
  • color: 映射到 DOCX w:color(十六进制)
  • fontFamily: 映射到 DOCX w:rFonts
  • fontWeight: bold 切换 w:b
  • fontStyle: italic 切换 w:i
  • textDecoration: underlinew:uline-throughw:strike
  • backgroundColor: 映射到 DOCX w:shd 填充
  • letterSpacing: 导出时支持(导入时不提取)
  • textAlign: 映射到 DOCX w:jcjustify → DOCX both
  • marginTop / marginBottom: 映射到 w:spacingbefore / after(单位为 twips)
  • lineHeight: 无单位 → 自动行距规则(比例);Npt → 精确值;Npx → 转换

单位转换。 编译器接受 pxptrememremem 会基于 baseFontSize(默认 16)进行解析。其他单位(%vhch 等)不被识别,相关属性会被跳过。

列表合并。 ul liol li 都映射到 DOCX ListParagraph 样式。如果两者都定义了,会合并它们的属性;逐属性而言,后面的条目胜出。

不支持的内容

这些是有意的限制

这些不是 bug。这些是功能的明确、文档化边界。如果你的工作流依赖下面任意一项,此功能就不是合适的工具。

  • 跨源样式表。 浏览器安全机制会阻止访问任何来自不同源的样式表的 .cssRulesextractFromDocument 会静默跳过这些样式表。如果你的主题托管在 CDN 上,请改为通过 styles 显式传入规则。
  • 伪类、伪元素、媒体查询。 a:hoverp::first-line@media (min-width: …):这些都不会映射到 DOCX,会被丢弃。
  • CSS 变量和 calc() 编译器读取的是声明值,而不是计算后的值,因此 var(--brand)calc(100% - 2rem) 无法解析。请在传入样式之前先解析它们。
  • 任意选择器。 这 16 个选择器就是全部支持范围。像 .prose particle h1 这样的后代选择器,只有在去除作用域前缀后能与映射精确匹配时才会生效;否则会被丢弃。
  • 层叠与优先级。 编译器会把每个选择器压平成一组单一属性。如果同一个选择器在不同优先级下存在冲突规则,只有编译结果会进入 DOCX。层叠关系不会被保留。
  • 计算后的样式继承。 浏览器提取器直接读取 document.styleSheets,不会遍历 DOM,也不会计算继承值。如果某个属性在浏览器中是继承得到的,但未在目标选择器上显式声明,它就不会被导出。

你可以预期什么

  • 一个包含正确 styles.xml 的 DOCX 文件,Word 用户可以在其中集中编辑样式。
  • 每个匹配的选择器恰好对应一个 DOCX 样式条目,并且只包含成功映射的属性。其余内容要么被显式 styles 覆盖合并,要么在导出时伴随 console.warn 被跳过。
  • 确定性的行为:给定相同输入(styles 对象和 DOM 样式表),导出结果可以逐字节比较。
  • 安全的服务端使用:如果 document 未定义(Node、SSR),extractFromDocument 不会产生任何作用。不会抛出错误,也不会注入任何内容。

你不应该预期什么

  • 与导入侧完全往返一致。 某些 CSS → DOCX 的转换会丢失信息(例如:line-height: 1.5 → 自动行距规则,但重新导入该 DOCX 时会把 line-height 重新输出为无单位倍数;如果 Word 对存储值做了标准化,它可能不会精确等于 1.5)。
  • 内联标记属性能够在往返后保留。 导出会接受每个选择器上的全部 12 个属性,包括 strongemusacode。导入时对应的 CSS 注入 会把这些内联标记选择器过滤到只保留它们的身份属性(例如 strong 只保留 fontWeightem 只保留 fontStylecode 只保留 fontFamily/color/fontSize/backgroundColor)。为了避免通过优先级覆盖父级块样式,你设置的超出这些范围的内容在重新导入时都会被丢弃。要实现往返,例如 strong 上的颜色,请改为设置在父选择器(ph1)上。
  • 支持媒体查询感知的导出。 DOCX 不支持媒体查询。如果你的响应式 CSS 在移动端和桌面端不同,那么你得到的是调用 configure() 时匹配到的规则(通常是桌面端)。
  • 与浏览器渲染完全一致的视觉效果。 DOCX 的渲染引擎(Word、LibreOffice、Google Docs)对样式的解释与浏览器不同。请预期行距、字距和段落间距会有细微差异。
  • 自动主题转换。 颜色、字体和字号都是 1:1 映射。Word 的“主题”(强调色、字体搭配)不是。如果你想使用 Word 主题,需要在导出选项中直接进行设置。

配置参考

ExportDocx.configure({
  cssStyles: {
    // 二选一:选择器 → CSS 声明的显式对象
    styles?: {
      p?: { fontSize?: string; color?: string; /* … */ },
      h1?: { /* … */ },
      // …任意一个 16 个选择器
    },

    // 或:从浏览器的实时样式表中提取
    extractFromDocument?: boolean,  // 默认:false

    // 用于解析 rem / em 的基准大小
    baseFontSize?: number,  // 默认:16
  },
})

相关