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 发布,测试也由发布方完成。版本、夹具和计时方法写在文末的“测试说明”一节,结果里对我们不利的部分同样保留。

快速导航


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
2
3
4
5
6
7
import { PDFDocument, StandardFonts } from 'pdf-lib'

const doc = await PDFDocument.create()
const page = doc.addPage([595.28, 841.89])
const font = await doc.embedFont(StandardFonts.Helvetica)
page.drawText('Hello', { x: 40, y: 800, size: 12, font })
const bytes = await doc.save()

这段代码新建一页 A4,写一行字,返回字节流。适合 PDF 已经存在、你只需要加一页、盖一行字或填几个值的场景。

要留意的是维护节奏:最新版本 1.17.1 发布于 2021 年 11 月 6 日,此后没有新版本。社区 fork 的 @cantoo/pdf-lib 在继续维护,对长期项目来说,选哪个版本值得明确写进依赖说明。

jsPDF

jsPDF 在浏览器里用命令式调用生成文档,一行一行往下画。

1
2
3
4
5
import { jsPDF } from 'jspdf'

const doc = new jsPDF()
doc.text('Hello', 10, 10)
const buffer = doc.output('arraybuffer')

它适合几行代码就要出一张回执、一页报表的轻量导出。它只能创建,不能打开并编辑现成的 PDF,这也是 pdf-lib 与 jsPDF 最常见的分界。

compress 选项有一个坑,中文文档尤其要注意:开启后文件体积骤降,但中文字体会丢失,整页变成拉丁乱码。第 7 节有实测数据。

PDFKit

PDFKit 在 Node 端生成文档,配合打包工具也能在浏览器里跑。浏览器构建需要手动注册标准字体,不注册会直接报错。

1
2
3
4
5
6
import PDFDocument, { registerStdFonts } from 'pdfkit'
import Helvetica from 'pdfkit/standard-fonts/Helvetica'
registerStdFonts(Helvetica)

const doc = new PDFDocument()
doc.font('Helvetica').text('Hello')

它提供的是一套绘图 API,不是 HTML/CSS 渲染引擎,网页不能直接丢进去。这种控制力适合版式固定的文档,比如标签、证书、对账单。它不能读取已有 PDF。

PDF.js

PDF.js 由三层组成:解析核心、展示 API,以及一个可以直接用的阅读器。要做 PDF 阅读和文本提取,它是开源侧的事实标准。

1
2
3
4
5
import * as pdfjsLib from 'pdfjs-dist'

const doc = await pdfjsLib.getDocument({ data: bytes }).promise
const page = await doc.getPage(1)
const content = await page.getTextContent()

它返回结构化的文本项和页面数据,不是原始字节,所以文本提取和页面渲染都能做。它不能从零创建 PDF;编辑能力限于 saveDocument 覆盖的范围,比如按页选取、注释改动,称不上通用编辑器。

@react-pdf/renderer

用 React 组件描述文档结构,再由这个库渲染成 PDF。

1
2
3
4
5
import { Document, Page, Text, pdf } from '@react-pdf/renderer'

const blob = await pdf(
<Document><Page><Text>Hello</Text></Page></Document>
).toBlob()

底层是 PDFKit,所以在浏览器里会遇到同一个标准字体未注册的问题。适合已经在用 React 描述界面、希望发票或报表复用组件的团队。它只负责产出,读取或修改现成文件需要另找库。

pdfmake

pdfmake 接收一份声明式的文档定义,负责布局、表格和自动分页。

1
2
3
4
5
import pdfMake from 'pdfmake/build/pdfmake'
import pdfFonts from 'pdfmake/build/vfs_fonts'
pdfMake.vfs = pdfFonts.vfs

const buffer = await pdfMake.createPdf({ content: ['Hello'] }).getBuffer()

六个开源库里,它处理长表格类输出最省心,因为你只描述内容,分页交给它决定。需要注意 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
2
3
4
5
6
7
8
import * as spire from '/spire.pdf.js'
const { PdfDocument, PdfFont, PdfFontFamily, PdfBrushes } = spire.spirepdf

const doc = new PdfDocument()
const page = doc.Pages.Add()
const font = new PdfFont({ fontFamily: PdfFontFamily.Helvetica, size: 12 })
page.Canvas.DrawString({ s: 'Hello', font, brush: PdfBrushes.get_Black(), x: 40, y: 60 })
doc.SaveToFile({ fileName: 'out.pdf' })

参数一律用对象字面量传入,改成位置参数会抛 Ambiguous call 错误。做格式转换还要多一步,因为 WebAssembly 环境里没有系统字体:

1
2
3
4
5
6
// 不注册字体目录会报 Cannot found font
PdfDocument.LoadCustomFontFolder('/Library/Fonts')

const doc = new PdfDocument()
doc.LoadFromFile({ fileName: 'in.pdf' })
doc.SaveToFile({ fileName: 'out.docx', fileFormat: spire.spirepdf.FileFormat.DOCX })

调用之前需要把字体文件放进虚拟文件系统的 /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 相关工作发生在首屏渲染期间,这个延迟是实打实的。如果转换发生在点击之后,把模块提前加载或按需加载,用户基本看不到。

生成 10 页文档的中位耗时与各库首次导入耗时:jsPDF 3.9 ms 与 13 ms,PDFKit 6.8 ms 与 119.2 ms,Spire.PDF 90.7 ms 与 1900.7 ms

输出体积更多取决于设置。插入同一张 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,所以这是个按文档类型决定的事。

同一段中文分别由 pdf-lib 嵌入 SimHei、jsPDF 默认设置、jsPDF 开启 compress 输出,第三种变成拉丁乱码

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 输出。换一台机器,耗时会有差异,库之间的比例是更有参考价值的信号。

资料来源