JavaScript PDF 库对比 2026:7 个库在同一套浏览器基准里的实测
JavaScript PDF 库对比 2026:7 个库在同一套浏览器基准里的实测
用 jsPDF 导出一张发票,半小时能跑通。麻烦在后面:过几个月,同一个项目要读客户上传的 PDF、要印中文、要把 Word 模板转成 PDF,当初为第一个需求挑的库,往往只覆盖了后来的一个角。
本文用同一套浏览器测试工程,把七个库跑了一遍:pdf-lib、jsPDF、PDFKit、PDF.js、@react-pdf/renderer、pdfmake 和 Spire.PDF for JavaScript。下面给出的是实测耗时、输出体积、字体行为与转换结果,最后再按项目形态给出选型建议。
需要说明的是,Spire.PDF for JavaScript 是 E-iceblue 的商业产品,本文由 E-iceblue 发布,测试也由发布方完成。版本、夹具和计时方法写在文末的“测试说明”一节,结果里对我们不利的部分同样保留。
快速导航
- 如何选择 JavaScript PDF 库
- 七个库各自擅长什么
- 单一库在什么地方停下来
- Spire.PDF for JavaScript 补上了什么
- 一个包覆盖 Office 到 PDF 的转换
- 性能与加载成本
- 中文排版与字体处理
- PDF/A 与授权
- 为你的项目选对方案
- 常见问题
- 测试说明
1. 如何选择 JavaScript PDF 库
先看项目要做几件事。只做一件,选对应的专用库就够;要做两件以上,或者涉及格式之间的转换,考量方式会变。
| 任务 | 本次测试中的合适选择 | 依据 |
|---|---|---|
| 代码生成 PDF,追求速度 | jsPDF、PDFKit | jsPDF 的 10 页中位耗时最低,3.9 ms |
| 生成带表格、可自动分页的长文档 | pdfmake | 声明式布局,表格支持完整 |
| 用 React 组件生成 PDF | @react-pdf/renderer | 组件式 API,底层是 PDFKit |
| 读取文本、展示 PDF | PDF.js | 解析核心、展示 API 与现成阅读器 |
| 修改已有 PDF | pdf-lib | 直接编辑现成文件 |
| 格式转换,或把上面几项合成一个包 | Spire.PDF for JavaScript | 本次实测跑通 12 种输出格式 |
前五行的活儿,对应的开源库都做得很好,不需要额外的东西。最后一行针对的是另一种情况:一个项目同时需要上面几项,或者要在格式之间来回转,这时账算起来不一样。第 3 节讲单库的边界,第 4 节讲最后一行为什么存在。
范围说明: 本文只涵盖六个主流开源库和一个商业 SDK,并未穷举 JavaScript 生态里的所有 PDF 工具。
2. 七个库各自擅长什么
搜“javascript pdf 库”或“js pdf 库”的人,要的东西其实分两类:一类是把数据变成 PDF,另一类是从现成 PDF 里取出内容或改它的结构。下面七个库沿着这条线分工,每个条目依次给出定位、实测数据和能力边界。
pdf-lib
pdf-lib 既能从零创建,也能修改已有的 PDF,所以常被当成“JavaScript PDF 编辑器”的默认答案。它读我们那份 10 页夹具的页数只用了 8 ms,但没有文本提取 API,读到这里为止。
1 | import { PDFDocument, StandardFonts } from 'pdf-lib' |
这段代码新建一页 A4,写一行字,返回字节流。适合 PDF 已经存在、你只需要加一页、盖一行字或填几个值的场景。
要留意的是维护节奏:最新版本 1.17.1 发布于 2021 年 11 月 6 日,此后没有新版本。社区 fork 的 @cantoo/pdf-lib 在继续维护,对长期项目来说,选哪个版本值得明确写进依赖说明。
jsPDF
jsPDF 在浏览器里用命令式调用生成文档,一行一行往下画。
1 | import { jsPDF } from 'jspdf' |
它适合几行代码就要出一张回执、一页报表的轻量导出。它只能创建,不能打开并编辑现成的 PDF,这也是 pdf-lib 与 jsPDF 最常见的分界。
compress 选项有一个坑,中文文档尤其要注意:开启后文件体积骤降,但中文字体会丢失,整页变成拉丁乱码。第 7 节有实测数据。
PDFKit
PDFKit 在 Node 端生成文档,配合打包工具也能在浏览器里跑。浏览器构建需要手动注册标准字体,不注册会直接报错。
1 | import PDFDocument, { registerStdFonts } from 'pdfkit' |
它提供的是一套绘图 API,不是 HTML/CSS 渲染引擎,网页不能直接丢进去。这种控制力适合版式固定的文档,比如标签、证书、对账单。它不能读取已有 PDF。
PDF.js
PDF.js 由三层组成:解析核心、展示 API,以及一个可以直接用的阅读器。要做 PDF 阅读和文本提取,它是开源侧的事实标准。
1 | import * as pdfjsLib from 'pdfjs-dist' |
它返回结构化的文本项和页面数据,不是原始字节,所以文本提取和页面渲染都能做。它不能从零创建 PDF;编辑能力限于 saveDocument 覆盖的范围,比如按页选取、注释改动,称不上通用编辑器。
@react-pdf/renderer
用 React 组件描述文档结构,再由这个库渲染成 PDF。
1 | import { Document, Page, Text, pdf } from '@react-pdf/renderer' |
底层是 PDFKit,所以在浏览器里会遇到同一个标准字体未注册的问题。适合已经在用 React 描述界面、希望发票或报表复用组件的团队。它只负责产出,读取或修改现成文件需要另找库。
pdfmake
pdfmake 接收一份声明式的文档定义,负责布局、表格和自动分页。
1 | import pdfMake from 'pdfmake/build/pdfmake' |
六个开源库里,它处理长表格类输出最省心,因为你只描述内容,分页交给它决定。需要注意 0.3 之后 getBuffer 返回 Promise,沿用老的回调写法会一直等不到结果。
Spire.PDF for JavaScript
Spire.PDF for JavaScript 是这里唯一不按单项分工的一个。
前面六个各守一块,下一节会说明这样做的代价。这个库用一套 API 覆盖同样的四件事:生成、文本提取、页面操作、渲染成图片,另外还有十二种输出格式,其他六个库完全没有。它的边界我们也测了,写在下面:它不是阅读器组件;生产环境需要商业授权。代码示例和完整的转换结果在它自己那一节。
3. 单一库在什么地方停下来
六个开源库加起来,没有一个能同时做到生成、解析、修改和渲染。每个库守着一块,这是它们各自做得好的原因,也是组合使用时成本上升的原因。
拿一个听起来很普通的需求举例:读客户给的 DOCX 模板,填入订单数据,生成 PDF,再加密码保护。六个库里没有一个能打开 Word 文件,所以得另找一个包处理 DOCX,通常放在服务端;版式交给一个生成库;加密再交给第三个包。三个包,两次交接。
交接本身不是大事,麻烦的是交接处:每一段都要校验 ArrayBuffer,每个包对字体和编码各有各的规矩。真正让维护成本堆起来的是胶水代码,而团队往往在需求第一次跨过边界时才意识到它,比如老板说“顺便也导一份 Word 出来”。
同一个词在不同场景里还指不同的事。有人说“渲染 PDF”,可能是指用代码画出一份文档,也可能是指把现成文件显示给用户看,还可能是指把某一页转成图片。PDF.js 做第二件和第三件,生成类库做第一件,六个库里没有一个能全包。把项目需要的是哪一件写清楚,候选名单立刻少一半。
4. Spire.PDF for JavaScript 补上了什么
Spire.PDF for JavaScript 以 WebAssembly 形式跑在浏览器里,文档在客户端处理,不需要来回传服务器。本文通过 spire.office 包测试,版本 11.7.0。
它的 API 覆盖合并、拆分、压缩、页面操作、表单域、注释、书签、附件和图层,完整清单见 Spire.PDF for JavaScript 教程。我们这一节测的是转换:把一份 2 页的中文 PDF(13.1 KB)转成 FileFormat 枚举里能转的格式。
| 输出 | 体积 |
|---|---|
| DOCX | 5,132,598 B |
| XLSX | 9,594 B |
| PPTX | 27,827 B |
| HTML | 89,687 B |
| Markdown | 558 B |
| XPS | 24,202 B |
| OFD | 17,281 B |
| PCL | 47,272 B |
| PostScript | 87,159 B |
| PNG(第 1 页,184 ms) | 19,789 B |
| PDF/A-1b | 57,581 B |
| SVG(每页一个文件) | 44,777 B |
十二种格式全部转换成功。SVG 导出按页拆分文件,命名形如 output_1.svg、output_2.svg。DOCX 之所以有 5.1 MB,是中文被一并带进了 Word 文件,英文文档不会这样。
1 | import * as spire from '/spire.pdf.js' |
参数一律用对象字面量传入,改成位置参数会抛 Ambiguous call 错误。做格式转换还要多一步,因为 WebAssembly 环境里没有系统字体:
1 | // 不注册字体目录会报 Cannot found font |
调用之前需要把字体文件放进虚拟文件系统的 /Library/Fonts/ 目录。
因为整条链路在一个 WebAssembly 包里,一次操作可以从打开 PDF、修改内容到另存为别的格式,中途不把字节交给第二个库。对需要在浏览器里处理用户上传文件的团队来说,这一点有实际价值:文件不必离开客户端。
代价有三项,都是实测出来的。首次加载 1,900.7 ms(另一次冷加载 2,756 ms),主要花在 CPU 上:运行时需要下载约 19 MB 的 WebAssembly 压缩包,用 JSZip 解压后才开始工作。其中两个包分别是 Spire.Common.Wasm(10.1 MB)和 Spire.Pdf.Wasm(8.6 MB)。JS 胶水层原始约 1.3 MB,gzip 之后约 117 KB;两个 Wasm 包本身已经是 deflate 压缩,再压没有收益。
5. 一个包覆盖 Office 到 PDF 的转换
六个开源库的起点都是 PDF。Word、Excel、PowerPoint 不在它们的范围内,所以上一节那个模板流程才需要额外的包。
Spire.Office for JavaScript 是一个 npm 包体系,包含 Word、Excel、PowerPoint、PDF 四个入口。每个入口有自己的 WebAssembly 模块,底下共用一个公共模块。Word 转 PDF、Excel 转 PDF、PowerPoint 转 PDF 都在浏览器里完成,不需要服务端;反向的 PDF 转 Word、Excel、PowerPoint、HTML 也走同一套体系。
具体写法可以看在 React 中使用 JavaScript 将 Word 转换为 PDF。
这一节有一个前提要说清楚:本次基准只测了 PDF 操作,Office 到 PDF 的转换没有测,所以文中没有这条路径的速度和保真度数据。准备依赖它的团队,应该拿自己的 Word、Excel 文件先跑一遍,版面复杂度对转换耗时的影响远大于文件大小。能确定的是结构层面的变化:原本需要三个包、两次交接的模板流程,可以写成对一个包体系的调用,交接环节消失。
6. 性能与加载成本
七个库在同一套夹具上跑,10 页英文文档全部在 100 ms 内完成。jsPDF 最快,中位 3.9 ms;Spire 90.7 ms。这个差距可以测出来,但放进一次点击触发的导出里,用户感觉不到。
| 库 | 首次运行 | 中位(10 页) | 输出体积 |
|---|---|---|---|
| jsPDF | 12.1 ms | 3.9 ms | 6,609 B |
| PDFKit | 45.7 ms | 6.8 ms | 6,194 B |
| pdf-lib | 86.1 ms | 9.2 ms | 3,123 B |
| pdfmake | 75.6 ms | 25.4 ms | 16,823 B |
| React-PDF | 125.5 ms | 36.3 ms | 7,395 B |
| Spire | 486.0 ms | 90.7 ms | 7,723 B |
首次运行那一列值得单独看,它包含一次性初始化,接近用户点下按钮后实际等待的时间。开源库在 12.1 到 125.5 ms 之间,Spire 是 486.0 ms。重复调用之后,各家都稳定下来。
真正的开销在加载。Spire 首次导入 1,900.7 ms,其他库在 13 到 211 ms 之间。
| 库 | 首次导入 |
|---|---|
| jsPDF | 13.0 ms |
| React-PDF | 53.2 ms |
| PDFKit | 119.2 ms |
| pdf-lib | 191.7 ms |
| pdfmake | 197.0 ms |
| PDF.js | 211.2 ms |
| Spire.PDF for JavaScript | 1,900.7 ms |
这两个数字要分开算。生成耗时按文档计,加载耗时按访问计。一次会话里导出几百份文件的工具,1.9 秒只付一次;落地页只导一份文件,这 1.9 秒就全算在那一份上。
如果 PDF 相关工作发生在首屏渲染期间,这个延迟是实打实的。如果转换发生在点击之后,把模块提前加载或按需加载,用户基本看不到。
输出体积更多取决于设置。插入同一张 800×800 PNG,jsPDF 默认输出 1,923,288 B,开启 compress 后是 32,044 B;pdf-lib 和 PDFKit 分别用 128.1 ms 和 189.6 ms 完成,输出 26,846 B 和 27,993 B。
读取是另一组基准,测试对象是 10 页 PDF:
| 库 | 读取页数 | 提取文本 |
|---|---|---|
| pdf-lib | 8 ms | 不支持 |
| Spire | 94 ms | 284 ms |
| PDF.js | 200 ms | 190 ms |
耗时可以比,字符数不能比。Spire 配置为保留空白,返回 1,381 个字符;PDF.js 按文本项拼接,返回 521 个。两者的口径不同。
7. 中文排版与字体处理
六个开源库都需要你自备一份中文字体文件,本次用的是 SimHei,9.7 MB,TrueType 格式。字体给到位之后,六个库都能正确嵌入并渲染,我们用 PyMuPDF 渲染产物逐一确认过。
| 库 | 中位(1 页中文) | 输出体积 |
|---|---|---|
| jsPDF | 1.1 ms(首次 479.8 ms) | 3,160 B |
| React-PDF | 7.3 ms | 11,286 B |
| pdfmake | 10.7 ms | 5,323 B |
| PDFKit | 11.3 ms | 10,743 B |
| pdf-lib | 32.7 ms | 9,693 B |
| Spire(嵌入 TTF) | 169.3 ms | 12,289 B |
9.7 MB 的字体文件本身也要算进首屏成本,页面得传输或缓存它,建议按需加载。jsPDF 那 1.1 ms 是字体注册之后的重复调用,首次包含注册用了 479.8 ms,不能当成速度优势来读。
真正的坑在 jsPDF 的 compress。中文页面默认设置下是 184,355 B,渲染正常;开启 compress: true 后文件缩到 3,168 B,但中文字符一个不剩,整页渲染成拉丁乱码。字体在字体表里直接消失,我们用渲染截图确认过,不是提取环节的误差。中文文档不要开压缩,出图之后检查一遍页面。纯图片页压缩是安全的,1,923,288 B 降到 32,044 B,所以这是个按文档类型决定的事。
Spire 提供了内置 CJK 字族,不需要任何字体文件。因为字体不嵌入,输出体积是我们见过最小的,2,262 B。覆盖率取决于选哪个字族:SinoTypeSongLight 在我们这份样本上一个字不缺,MonotypeHeiMedium 和 MonotypeSungLight 会漏掉「报」「数」两个字。上线前拿自己的字符集验一遍,或者干脆嵌入 TTF,本次测试里这样渲染完全正确。
8. PDF/A 与授权
PDF/A 是 PDF 的归档格式,很多档案与合规流程会明确要求。这类文件在阅读器里看着正常,实际可能不达标,所以我们没有用眼睛判断,而是用了校验器。归档输出用 veraPDF 1.30.3 验证:Spire 的 PDF/A-1b 和 PDF/A-2b 都通过。PDFKit 按其文档提供的 subset: 'PDF/A-1b' 选项调用,被判定五项违规:字体未嵌入(6.3.4)、缺少元数据项(6.7.2)、Info 字典与 XMP 元数据不一致(6.7.3)。未转换的 Spire 源文件同样在 1b 上失败,说明校验器能把合格与不合格分开。其他库没有测 PDF/A。
Spire.PDF for JavaScript 分为几个版本。免费版见 Free Spire.PDF for JavaScript,覆盖 PDF 生成与转换,不带水印。本次基准用的是评估版,输出带水印和页数上限;评估期间可以申请免费临时授权解除限制,正式上线则需要商业授权。对一个商业 SDK 来说,这是常规安排。
评估的实际做法是挑三份自己的文件:最长的一份、含中文或其他非拉丁文字的一份、必须转成 PDF/A 或 Word 的一份。跑的时候申请临时授权,文档长度就不再是变量,然后拿去和原本打算用的开源库对比,加载时间和输出结果都要看。
9. 为你的项目选对方案
怎么选,取决于项目要做几件事。
只做一件:选专用库。
- 发票、回执,速度优先:jsPDF 或 PDFKit,中文场景要单独验字体。
- 表格密集的长报表:pdfmake。
- React 项目:@react-pdf/renderer。
- 编辑已有 PDF:pdf-lib,同时留意它 2021 年之后没有发过新版。
- 阅读与展示:PDF.js。
- 归档输出且必须过校验:Spire.PDF for JavaScript。它的 PDF/A-1b、PDF/A-2b 通过了 veraPDF,PDFKit 在同样条件下有五项违规。
- 中文输出但不想带字体文件:Spire 的内置 CJK 字族。SinoTypeSongLight 在我们的样本上完整,而六个开源库都需要你自行分发字体文件,本次测试里是 9.7 MB。
两件以上,或者涉及格式转换:账要重算。 一个项目要读 DOCX 模板、生成 PDF、再把结果转成别的格式,就需要上面两三个库,外加一个处理 Word 的包;每次交接都要校验 ArrayBuffer,字体处理也要重配一遍。这正是 Spire.PDF for JavaScript 面向的场景:一套 API 覆盖前面那四件事,外加十二种输出格式,全程跑在浏览器里,不需要服务端。
代价我们也如实报了:首次加载 1.9 秒,运行时要在打开第一份文档之前解压约 19 MB 的 WebAssembly。把加载提前到用户点击之前,这个时间就看不出来。免费版覆盖生成与转换,不带水印;评估期间申请临时授权,就能拿自己的文件做对比。
10. 常见问题
浏览器里能渲染 PDF 吗?
用 PDF.js,它包含展示 API 和现成阅读器。我们没有跨库对比渲染像素。Spire.PDF for JavaScript 能把一页转成 PNG(本次第 1 页用时 184 ms),但它不是阅读器组件;需要在浏览器内查看和编辑,对应的是 Spire.OfficeJS。
哪个库最适合中文?
六个开源库在提供字体文件之后都能正常工作,jsPDF 开 compress: true 的情况除外。Spire 的内置 CJK 字族不需要字体文件,SinoTypeSongLight 覆盖了我们完整的样本,Monotype 两个字族会漏掉个别常用字。
需要处理 Word 或 Excel 文件怎么办?
六个开源库的起点都是 PDF。Spire.Office for JavaScript 在同一个包体系里覆盖 Word、Excel 和 PowerPoint。
打包体积会成问题吗?
开源库初始化在 13 到 211 ms 之间。Spire 首次加载 1.9 秒,文件合计约 19 MB,主要部分是 WebAssembly 压缩包。首屏敏感的项目建议按需加载。
生产环境需要授权吗?
免费版覆盖 PDF 生成与转换,不带水印。评估版为了保护输出会加带水印和页数上限,评估期间可用临时授权解除。生产环境需要商业授权。
11. 测试说明
披露: Spire.PDF for JavaScript 是 E-iceblue 的产品,本次基准测试由发布方完成。测试工程、夹具和脚本都可复现,开源库在完全相同的条件下测试。
- 环境: Windows 11 Pro(10.0.26300),Chromium 内核浏览器,Node v26.12.1,npm 12.0.1。
- 版本: pdf-lib 1.17.1、jsPDF 4.2.1、PDFKit 0.20.2、PDF.js 6.5.438、@react-pdf/renderer 4.9.0、pdfmake 0.3.14、spire.office 11.7.0。
- 夹具: 运行时用 canvas 生成的 800×800 PNG,以及 10 页文本文档;中文测试统一使用 SimHei。
- 计时: 每项预热 1 次、正式测量 5 次,报告中位数,另记首次耗时。
- 正确性: 字体与渲染结果用 PyMuPDF 核验,PDF/A 文件用 veraPDF 1.30.3 校验。
- 未覆盖: 跨库的渲染像素对比、超过 10 页的文档、Spire 与 PDFKit 之外的 PDF/A 输出。换一台机器,耗时会有差异,库之间的比例是更有参考价值的信号。











