Forme - 用 JSX 生成真正面向分页的 PDF
很多 Web 应用最后都会遇到同一个问题:页面看起来很好,但导出的 PDF 很难稳定。浏览器打印依赖 CSS page break,遇到动态数据、长表格、多语言字体或页眉页脚时容易出边界;传统模板引擎又常常牺牲组件化和开发体验。对于发票、订单、审计报告、证书、内部审批单这类文档,“差不多能打印”通常不够。
今天推荐的 danmolitor/forme 正是围绕这个问题设计的。它让开发者用 React/JSX 写 PDF 文档,但底层不是把 HTML 丢给 Chrome 打印,而是使用 Rust 编写并编译到 WASM 的分页布局引擎。GitHub 当前显示项目约有 130 stars、7 forks,主要语言是 Rust,许可为 MIT。仓库创建于 2026 年 2 月 17 日,最近仍在 2026 年 5 月 28 日更新;GitHub Releases 中最新版本为 v0.10.3。
项目概览
| 属性 | 详情 |
|---|---|
| 仓库 | danmolitor/forme |
| Stars | 约 130 |
| Forks | 7 |
| 主要语言 | Rust |
| 许可 | MIT |
| 创建时间 | 2026 年 2 月 17 日 |
| 最近更新 | 2026 年 5 月 28 日 |
| 最新 release | v0.10.3 |
不是浏览器打印,而是分页布局引擎
Forme 的核心取舍很清楚:它不是把网页截图或打印成 PDF,而是把 PDF 当成一个分页文档来排版。README 里强调的几个点都围绕这个方向展开:内容会流入页面,分页应该发生在正确的位置,表格和文本不应该因为跨页而被截断。
这和很多常见方案不一样。Puppeteer 这类方案的优势是复用浏览器渲染能力,但分页仍然要和 CSS 打交道;react-pdf 这类方案能用组件思维写文档,但一些复杂排版能力可能要靠绕路。Forme 的思路更接近“为 PDF 重新设计一套组件和布局系统”,让分页、表格、字体和文档元数据成为一等能力。
它的基本写法接近 React:
import { Document, Page, View, Text } from '@formepdf/react';
import { renderDocument } from '@formepdf/core';
const pdf = await renderDocument(
<Document>
<Page size="Letter" margin={36}>
<Text style={{ fontSize: 24, fontWeight: 'bold' }}>
Invoice #2026-001
</Text>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text>Consulting</Text>
<Text>$800.00</Text>
</View>
</Page>
</Document>
);
输出是 PDF 字节,可以保存、返回给接口、放进邮件附件,或者在浏览器端生成后下载。
为什么分页是重点
PDF 生成最麻烦的地方通常不是第一页,而是数据变长之后。订单行项目超过一页,表格表头要重复;说明文字不能在页底只剩一行;右对齐金额不能因为宽度计算出错而跑出页面;一个网格行如果放不下,最好整行移动到下一页,而不是每个单元格各自分页。
Forme 在 README 和 release notes 里反复围绕这些细节迭代。例如它支持 widow/orphan control,避免段落在页底或页顶留下孤行;表格可以跨页处理,表头能在每页重复;CSS Grid 和 flex wrap 也考虑了跨页行为。最新的 v0.10.3 还是一个文本布局修复版本,专门修掉了 flex row 中固定宽度文本在右对齐时可能被推出页面的问题。
这说明项目关注的不是“能不能输出 PDF”这种最低门槛,而是动态文档在真实业务数据下是否稳定。对于账单、合同、报表这类文件,这种细节往往决定能不能进生产。
React 组件,但不是网页 DOM
Forme 提供的组件包括 Document、Page、View、Text、Image、Table、Svg、QrCode、Barcode、Canvas、图表组件、表单组件,以及重复页眉页脚用的 Fixed。这让模板可以拆成组件,而不是把一大段 HTML 或字符串拼接在一起。
但它不是浏览器 DOM 的完整替代。样式写法借鉴 CSS 和 React Native 风格,目标是覆盖 PDF 文档常用的布局与视觉需求。README 里列出的能力包括 flex、grid、绝对定位、边框圆角、渐变背景、文本溢出、图片、链接、书签、页码变量和 Tailwind class 转换。
这种边界很重要。如果你想把现有网页原样导出,浏览器打印方案可能更直接;如果你想维护一套结构化、可测试、可版本化的 PDF 模板,Forme 的组件模型会更合适。
字体、语言和可访问性
PDF 文档经常涉及字体和语言,尤其是客户名称、国际地址、多语言条款或右到左文本。Forme 的 README 提到几个值得注意的能力:使用 rustybuzz 做 OpenType shaping,支持 ligature、kerning 和 contextual forms;支持自动断字,并内置多种语言的 hyphenation;支持 BiDi 文本;也支持字体注册、字体 fallback 和子集嵌入。
它还提供文档语言、PDF/UA accessibility、PDF/A archival 等能力。对普通发票来说这些可能不是第一天就用到的功能,但对政府、金融、医疗或长期归档场景很关键。很多 PDF 工具在早期只关心视觉输出,等到需要可访问性、可归档或字体合规时才发现架构不够用;Forme 从 README 看已经把这些能力放进了设计范围。
开发体验:dev server 和 VS Code 插件
PDF 模板难调试的原因之一是反馈慢。改一行模板、重新生成、打开文件、放大定位问题,这个循环很容易消耗时间。Forme 提供 forme dev,可以开启实时预览和 debug overlay,并允许点击元素查看 computed styles。项目还提供 VS Code 扩展,支持 PDF 预览、组件树、高亮和 inspector 面板。
这对模板维护很实用。PDF 问题经常不是 JavaScript 报错,而是“为什么这个文本在第二页右侧消失了”“为什么这个表格列宽不对”。能直接看到元素边界、盒模型和源位置,比只看最终 PDF 更容易定位。
多运行环境和多语言入口
Forme 不是只给 Node.js 服务端用。README 中给出了浏览器端用法,可以通过 @formepdf/core/browser 在客户端生成 PDF,适配 Vite、Next.js、Remix 或其他能处理 WASM 的 bundler。Releases 也列出 npm、Rust crate、Python package、Go package、Docker image、VS Code extension 等不同消费方式。
这让它适合几种不同部署形态:
- 后端 API 按请求生成 PDF;
- 前端在浏览器里直接生成客户侧文件;
- 内部工具用 CLI 批量生成文档;
- Worker 或 edge 环境中做轻量文档渲染;
- 其他语言服务通过 SDK 或容器调用同一套引擎。
需要注意的是,越多运行环境也意味着集成细节越多。v0.10.1 就修复过 Cloudflare Workers 导入 WASM 相关的问题。好处是项目 release notes 写得很具体,能看出作者在真实打磨这些边界。
和 Puppeteer、react-pdf 的取舍
Forme README 里直接把自己和 react-pdf、Puppeteer 做了对比。它的优势主要集中在分页、表格跨页、文本 shaping、断字、可访问性和 PDF 专有能力上。Rust/WASM 内核也让它避开了“为生成 PDF 启动一个浏览器”的成本。
但这不代表它适合所有场景。如果你已经有一套复杂 HTML 页面,而且 PDF 只是“把当前页面打印出来”,Puppeteer 仍然可能更省事。如果你需要完整复用浏览器 CSS、Canvas、第三方图表库和网页交互状态,Forme 的组件体系反而需要迁移成本。
它更适合从一开始就把 PDF 当作独立输出物维护的项目:模板清晰、数据结构稳定、分页质量重要、希望代码审查能看懂文档结构,而不是在浏览器打印细节里反复调参。
适合什么团队
我会把 Forme 推荐给几类团队:
- SaaS 产品需要稳定生成发票、报价单、合同或审计报告;
- 内部系统有很多动态表格和多页报表;
- 团队熟悉 React,希望用组件方式维护 PDF 模板;
- 现有 Puppeteer 打印方案在分页、字体或性能上已经变得脆弱;
- 需要浏览器端或 edge 端生成 PDF,但又不想依赖完整浏览器;
- 对 PDF/A、PDF/UA、书签、链接、二维码、条码等文档能力有明确需求。
如果只是偶尔导出一个简单页面,Forme 可能显得偏重。但如果 PDF 是产品的一部分,而不是附带功能,它的方向很值得关注。
需要注意的边界
Forme 仍然是一个年轻项目。仓库创建时间很近,API 和周边包还在快速迭代,最近几个 release 也包含布局和打包修复。生产采用前,应该用自己的真实模板做回归测试,而不是只跑 README 示例。
另外,它的能力边界不是“支持所有 CSS”。它支持很多 CSS 风格属性和 Tailwind 转换,但 PDF 组件树和浏览器 DOM 是不同模型。迁移已有网页模板时,最好重新设计文档结构,而不是期待一比一搬运。
最后,PDF 正确性最好通过视觉测试或快照测试来保护。v0.10.3 的 release notes 提到过一种情况:PDF bytes 是稳定的,但右对齐文本在视觉上跑出了页面。对生产文档来说,只测字节或文件存在并不够,关键页面需要真实渲染检查。
总结
danmolitor/forme 是一个定位很清楚的 PDF 生成项目:用 React/JSX 保留开发体验,用 Rust/WASM 负责分页布局,把表格、字体、文本 shaping、浏览器端生成和可访问性这些 PDF 真实难点放到核心设计里。
它还很年轻,但方向比“把 HTML 打印成 PDF”更适合严肃文档系统。如果你的应用里 PDF 是用户会反复下载、归档、审计和转发的正式文件,Forme 值得放进技术选型清单。