html-pdf、pdfkit 和 puppeteer 代表了三种不同的 PDF 生成路径。html-pdf 是早期的 HTML 转 PDF 方案,但已停止维护;pdfkit 是一个底层的 PDF 绘制库,适合通过代码生成结构化文档;puppeteer 则通过控制无头浏览器来渲染页面,是目前 HTML 转 PDF 的主流标准。
在 Node.js 后端生成 PDF 文件是常见需求,无论是发票、报表还是导出功能。html-pdf、pdfkit 和 puppeteer 是三个经常被提及的库,但它们的底层原理和适用场景完全不同。本文将深入对比它们的技术细节,帮助你做出正确的架构决策。
在选择库之前,必须了解其维护状态。这直接影响项目的长期安全性。
html-pdf 已处于废弃状态。
// html-pdf: 已废弃的 API 示例
const pdf = require('html-pdf');
// 不推荐:依赖旧的 PhantomJS 二进制文件
pdf.create('<h1>Hello</h1>').toFile('./file.pdf', (err, res) => {
if (err) return console.log(err);
console.log(res.filename);
});
pdfkit 维护良好,专注于底层绘制。
// pdfkit: 稳定的 API 示例
const PDFDocument = require('pdfkit');
const doc = new PDFDocument();
// 推荐:直接绘制矢量内容
doc.text('Hello', 100, 100);
doc.save('./file.pdf');
puppeteer 由 Chrome 团队维护,更新频繁。
// puppeteer: 现代标准的 API 示例
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 推荐:使用现代无头浏览器渲染
await page.setContent('<h1>Hello</h1>');
await page.pdf({ path: './file.pdf' });
await browser.close();
})();
这三个库生成 PDF 的方式截然不同,决定了它们能处理什么样的内容。
html-pdf 使用旧的渲染引擎。
// html-pdf: 渲染简单 HTML
pdf.create('<div style="float:left">Content</div>').toFile('./out.pdf');
pdfkit 直接操作 PDF 指令。
// pdfkit: 手动绘制坐标
doc.rect(100, 100, 200, 50); // 画矩形
doc.text('Invoice', 110, 115); // 写文字
puppeteer 使用完整的 Chrome 引擎。
@media print。// puppeteer: 渲染完整页面
await page.goto('https://example.com/report', { waitUntil: 'networkidle0' });
await page.pdf({ format: 'A4', printBackground: true });
前端开发者最关心的是 CSS 支持程度。这决定了你能否复用现有的前端代码。
html-pdf 支持有限的 CSS。
/* html-pdf: 仅支持旧式 CSS */
.box { float: left; width: 50%; } /* 可行 */
.box { display: flex; } /* 可能失效 */
pdfkit 不支持 CSS。
fontSize、fillColor)。// pdfkit: 通过 API 设置样式
doc.fontSize(20).fillColor('blue').text('Title');
puppeteer 支持完整的 CSS。
print 媒体查询来隐藏按钮或导航栏。/* puppeteer: 支持完整 CSS */
.container { display: grid; grid-template-columns: 1fr 1fr; }
@media print { .no-print { display: none; } }
服务器资源是有限的,不同的库对内存和 CPU 的要求不同。
html-pdf 占用中等,但启动慢。
// html-pdf: 高并发下不稳定
// 无内置并发控制,需自行管理队列
pdfkit 占用最低,速度最快。
// pdfkit: 适合高并发
// 直接在当前进程运行,无额外开销
puppeteer 占用最高,启动较重。
--no-sandbox 优化容器环境。// puppeteer: 需控制并发
const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
// 建议配合 p-limit 控制同时打开的页面数
| 特性 | html-pdf | pdfkit | puppeteer |
|---|---|---|---|
| 维护状态 | ❌ 已废弃 | ✅ 活跃 | ✅ 活跃 |
| 渲染引擎 | PhantomJS (旧) | 无 (矢量绘制) | Chrome/Chromium |
| HTML 支持 | 基础 HTML | 不支持 | 完整支持 |
| CSS 支持 | 有限 (旧标准) | 不支持 | 完整 (现代标准) |
| 资源占用 | 中 | 低 | 高 |
| 适用场景 | 旧项目维护 | 发票、证书、图表 | 报表、网页导出 |
html-pdf 是历史遗留产物 — 除非你被锁定在旧系统中,否则请立即停止使用。它的安全风险和兼容性问题是不可接受的。
pdfkit 是数据驱动文档的最佳选择 — 如果你需要生成发票、收据或带有动态图表的证书,且不需要 HTML 布局,它的性能和控制力是最好的。
puppeteer 是 Web 技术栈的自然延伸 — 如果你的团队熟悉 HTML/CSS,且需要生成复杂的报表或导出用户看到的页面,它是唯一正确的选择。虽然资源占用较高,但可以通过集群管理来优化。
核心原则:不要为了生成 PDF 而重新学习一套布局系统。如果能用 HTML/CSS 解决,就用 puppeteer;如果需要精细的矢量控制,就用 pdfkit。
当你需要将现有的 HTML/CSS 页面转换为 PDF 时选择它。它能完美支持现代 CSS 布局、字体和打印媒体查询,是生成报表的最佳方案。
不建议在任何新项目中使用。该库依赖已废弃的 PhantomJS 引擎,存在安全漏洞且不再维护。如果维护旧代码,应尽快迁移至 puppeteer。
当你需要从零开始绘制 PDF 元素(如动态发票、图表、证书)时选择它。它不依赖浏览器引擎,适合生成由数据驱动的矢量图形文档。
Puppeteer is a JavaScript library which provides a high-level API to control Chrome or Firefox over the DevTools Protocol or WebDriver BiDi. Puppeteer runs in the headless (no visible UI) by default
npm i puppeteer # Downloads compatible Chrome during installation.
npm i puppeteer-core # Alternatively, install as a library, without downloading Chrome.
:::note
Modern package managers (including npm (see the RFC), pnpm, Yarn, Bun, and Deno) block dependency install scripts by default. If the install script is blocked, Puppeteer will not download the browser during installation, leading to runtime errors.
You can manually download the required browsers after installation by running:
npx puppeteer browsers install
Alternatively, you can configure your package manager to allow the install script to run (for example, with npm, by adding "puppeteer" to "allowScripts" in your package.json).
:::
Install chrome-devtools-mcp,
a Puppeteer-based MCP server for browser automation and debugging.
Puppeteer also supports the experimental WebMCP API.
import puppeteer from 'puppeteer';
// Or import puppeteer from 'puppeteer-core';
// Launch the browser and open a new blank page.
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Navigate the page to a URL.
await page.goto('https://developer.chrome.com/');
// Set the screen size.
await page.setViewport({width: 1080, height: 1024});
// Open the search menu using the keyboard.
await page.keyboard.press('/');
// Type into search box using accessible input name.
await page.locator('::-p-aria(Search)').fill('automate beyond recorder');
// Wait and click on first result.
await page.locator('.devsite-result-item-link').click();
// Locate the full title with a unique string.
const textSelector = await page
.locator('::-p-text(Customize and automate)')
.waitHandle();
const fullTitle = await textSelector?.evaluate(el => el.textContent);
// Print the full title.
console.log('The title of this blog post is "%s".', fullTitle);
await browser.close();