html-pdf vs html2pdf.js vs jspdf vs pdfmake
Web 应用中从 HTML 和程序化内容生成 PDF 的客户端方案对比
html-pdfhtml2pdf.jsjspdfpdfmake类似的npm包:

Web 应用中从 HTML 和程序化内容生成 PDF 的客户端方案对比

html-pdfhtml2pdf.jsjsPDFpdfmake 是用于在 Web 应用中生成 PDF 文档的 JavaScript 库,但它们在架构、能力和运行环境上差异显著。html-pdf 是一个仅限 Node.js 的包,使用 PhantomJS 将 HTML 转换为 PDF,目前已官方弃用html2pdf.js 在浏览器中运行,利用 html2canvas 和 jsPDF 将 DOM 元素捕获为 PDF。jsPDF 是一个底层 PDF 创建库,支持浏览器和 Node.js 环境,允许通过代码绘制文本、图像和图形,但不直接渲染 HTML。pdfmake 则采用声明式的文档定义方式生成 PDF,对布局、表格和样式有强大支持,同样兼容浏览器和 Node.js。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
html-pdf03,612-4685 年前MIT
html2pdf.js04,83610.9 MB4933 个月前MIT
jspdf031,21930.2 MB1121 个月前MIT
pdfmake012,25815.3 MB2351 个月前MIT

PDF 生成库对比:html-pdf、html2pdf.js、jsPDF 与 pdfmake

在 Web 应用中生成 PDF 是常见需求 —— 无论是生成发票、报表还是可打印摘要。但选择哪个库,取决于你的输入源(HTML 还是结构化数据)、运行环境(浏览器还是 Node.js)以及对输出质量的要求。下面我们从工程实践角度深入分析。

⚠️ html-pdf:已弃用,仅限 Node.js

html-pdf 曾是一个流行的 Node.js 包,通过 PhantomJS 渲染 HTML 生成 PDF。但该包现已在 npm 和 GitHub 上被官方标记为弃用,作者明确建议迁移到 Puppeteer 等现代库 。PhantomJS 本身早已停止维护,这意味着无法支持现代 CSS(如 Flexbox、Grid),且存在未修复的安全漏洞 。

只能在 Node.js 中运行,无法用于浏览器环境。因此,新项目绝不应使用 html-pdf

// html-pdf:已弃用的用法(仅 Node.js)
const pdf = require('html-pdf');
const html = '<h1>Hello World</h1>';
pdf.create(html).toFile('./output.pdf', (err, res) => {
  if (err) return console.log(err);
  console.log(res.filename);
});

💡 迁移建议:Node.js 环境下使用 Puppeteer 或 Playwright 进行现代、可靠的 HTML 到 PDF 转换。

🖼️ html2pdf.js:浏览器端的 HTML 快照

html2pdf.js 完全在浏览器中运行,旨在将任意 DOM 元素转换为 PDF。其内部结合了两个库 :

  • html2canvas:将 HTML 元素渲染为 <canvas>(将文本和布局光栅化为图像)
  • jsPDF:将 canvas 转换为 PDF

这意味着输出是基于像素的图像,而非矢量文本。放大后可能模糊,文件体积也较大。但它的优势是使用极其简单,能快速捕获 UI 的视觉状态。

// html2pdf.js:将 DOM 元素转为 PDF
import html2pdf from 'html2pdf.js';

const element = document.getElementById('invoice');
html2pdf().from(element).save();

你可以通过配置设置边距、文件名或页面尺寸:

html2pdf()
  .set({ margin: 10, filename: 'report.pdf', image: { type: 'jpeg', quality: 0.95 } })
  .from(element)
  .save();

✅ 适用场景:需要“导出为 PDF”按钮,且希望结果与浏览器中显示一致。
❌ 不适用场景:需要可搜索文本、小文件体积或精确排版。

✍️ jsPDF:底层 PDF 构建器

jsPDF 提供通过代码绘制 PDF 的底层 API。它不理解 HTML 或 CSS —— 你需要使用坐标手动定位文本、图像和图形。这赋予你完全控制权,但也意味着布局逻辑需自行实现。

它支持浏览器和 Node.js(在 Node 中需配合 canvas 或图像插件),并生成矢量文本、可搜索的 PDF

// jsPDF:通过代码创建 PDF
import { jsPDF } from 'jspdf';

const doc = new jsPDF();
doc.setFontSize(22);
doc.text('Hello world!', 20, 20);
doc.save('output.pdf');

插入图片需要预先加载:

const img = new Image();
img.src = '/logo.png';
img.onload = () => {
  doc.addImage(img, 'PNG', 15, 40, 180, 160);
  doc.save('with-image.pdf');
};

对于表格或分页,需借助 jspdf-autotable 等插件。否则,所有布局都需手动计算。

✅ 适用场景:生成简单、数据驱动的文档(如快递单、证书),且能通过代码定义布局。
❌ 不适用场景:需要复现复杂 HTML 布局或自动分页。

📐 pdfmake:声明式文档定义

pdfmake 使用类似 JSON 的文档定义来描述 PDF 结构。你将内容定义为对象数组(文本、表格、图像、列),pdfmake 会自动处理布局、分页、页眉页脚和样式 。

它支持矢量文本带行合并的表格分页页眉页脚自定义字体。输出文件干净、可搜索且体积小 。

// pdfmake:声明式 PDF
import pdfMake from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';
pdfMake.vfs = pdfFonts.pdfMake.vfs;

const docDefinition = {
  content: [
    { text: '发票', style: 'header' },
    { text: '客户:张三' },
    {
      table: {
        body: [
          ['商品', '价格'],
          ['小部件', '¥10'],
          ['小配件', '¥20']
        ]
      }
    }
  ],
  styles: {
    header: { fontSize: 18, bold: true }
  }
};

pdfMake.createPdf(docDefinition).download('invoice.pdf');

它还支持动态页眉页脚:

const docDefinition = {
  header: () => '我的公司',
  footer: (currentPage, pageCount) => `第 ${currentPage} 页,共 ${pageCount} 页`,
  content: [/* ... */]
};

✅ 适用场景:需要生成专业、可打印的文档,包含表格、一致样式和自动分页。
❌ 不适用场景:输入源是无法转换为结构化数据的原始 HTML。

🔁 输入源:HTML vs. 结构化数据

这是核心架构差异:

输入类型渲染方式
html-pdfHTML (Node.js)PhantomJS (已弃用)
html2pdf.jsDOM 元素光栅化为图像
jsPDF代码(图元)矢量绘制
pdfmakeJSON 定义矢量布局引擎

如果你的内容是浏览器中的 HTML,且需要视觉快照 → 选 html2pdf.js
如果你有结构化数据(如 API 返回),需要生成高质量 PDF → 选 pdfmake
如果你需要像素级控制坐标 → 选 jsPDF

🌐 运行环境

  • 仅浏览器html2pdf.js(设计如此)
  • 浏览器 + Node.jsjsPDFpdfmake(Node.js 中需少量配置)
  • 仅 Node.js(已弃用)html-pdf

注意:jsPDFpdfmake 虽支持 Node.js,但它们不渲染 HTML —— 它们从代码或定义生成 PDF。

📄 质量与文件体积

  • html2pdf.js 生成较大文件,因为内容以图像形式嵌入。
  • jsPDFpdfmake 生成小体积、文本可搜索的 PDF
  • html-pdf 的输出质量依赖 PhantomJS,缺乏现代渲染能力。

🧩 总结:如何选择

场景推荐库
将屏幕上的 DOM 转为 PDF(如“导出为 PDF”按钮)html2pdf.js
从 JSON 数据生成发票、合同或报表pdfmake
创建简单、代码定义的 PDF(如标签、证书)jsPDF
Node.js 中 HTML 转 PDF(新项目)不要用这些 —— 用 Puppeteer
Node.js 中 HTML 转 PDF(旧项目)html-pdf 迁移

🚫 关于 html-pdf 的最后提醒

html-pdf 不应在任何新项目中使用。它对 PhantomJS 的依赖使其过时、不安全且与现代 Web 标准不兼容。如需服务端 HTML 转 PDF,请使用基于 Chromium 的方案如 Puppeteer。对于客户端需求,根据输入源在 html2pdf.js(视觉快照)和 pdfmake/jsPDF(结构化高质量文档)之间选择。

如何选择: html-pdf vs html2pdf.js vs jspdf vs pdfmake

  • html-pdf:

    不要在新项目中使用 html-pdf —— 该包已在 npm 和 GitHub 上被官方标记为弃用,且依赖已停止维护的 PhantomJS 。它仅适用于 Node.js,无法用于客户端 PDF 生成。现有项目应迁移到 Puppeteer 等现代替代方案。

  • html2pdf.js:

    当需要将现有 HTML 内容(如报表、发票或仪表盘)直接在浏览器中转为 PDF 且希望保留视觉样式时,选择 html2pdf.js 。它适合“一键导出”场景,但需注意其内部使用 html2canvas 进行光栅化,可能导致文本不可搜索、文件体积大或复杂 CSS 渲染不准确。

  • jspdf:

    当需要对 PDF 内容进行精细控制,并通过代码而非 HTML 生成文档时,选择 jsPDF 。它适用于生成标签、证书或简单表单等场景,但缺乏原生 HTML/CSS 支持,复杂布局(如表格)需依赖插件或手动实现。

  • pdfmake:

    当需要基于结构化数据生成包含复杂布局(如表格、页眉页脚、多栏)的专业级 PDF 时,选择 pdfmake 。它使用声明式 API,自动处理分页和样式,输出为矢量文本,文件小且可搜索,适合发票、合同等业务文档。

html-pdf的README

node-html-pdf

HTML to PDF converter that uses phantomjs

image
Example Business Card
-> and its Source file

Example Receipt

Changelog

Have a look at the releases page: https://github.com/marcbachmann/node-html-pdf/releases

Installation

Install the html-pdf utility via npm:

$ npm install -g html-pdf

Command-line example

$ html-pdf test/businesscard.html businesscard.pdf

Code example

var fs = require('fs');
var pdf = require('html-pdf');
var html = fs.readFileSync('./test/businesscard.html', 'utf8');
var options = { format: 'Letter' };

pdf.create(html, options).toFile('./businesscard.pdf', function(err, res) {
  if (err) return console.log(err);
  console.log(res); // { filename: '/app/businesscard.pdf' }
});

API

var pdf = require('html-pdf');
pdf.create(html).toFile([filepath, ]function(err, res){
  console.log(res.filename);
});

pdf.create(html).toStream(function(err, stream){
  stream.pipe(fs.createWriteStream('./foo.pdf'));
});

pdf.create(html).toBuffer(function(err, buffer){
  console.log('This is a buffer:', Buffer.isBuffer(buffer));
});


// for backwards compatibility
// alias to pdf.create(html[, options]).toBuffer(callback)
pdf.create(html [, options], function(err, buffer){});

Footers and Headers

html-pdf can read the header or footer either out of the footer and header config object or out of the html source. You can either set a default header & footer or overwrite that by appending a page number (1 based index) to the id="pageHeader" attribute of a html tag.

You can use any combination of those tags. The library tries to find any element, that contains the pageHeader or pageFooter id prefix.

<div id="pageHeader">Default header</div>
<div id="pageHeader-first">Header on first page</div>
<div id="pageHeader-2">Header on second page</div>
<div id="pageHeader-3">Header on third page</div>
<div id="pageHeader-last">Header on last page</div>
...
<div id="pageFooter">Default footer</div>
<div id="pageFooter-first">Footer on first page</div>
<div id="pageFooter-2">Footer on second page</div>
<div id="pageFooter-last">Footer on last page</div>

Options

config = {

  // Export options
  "directory": "/tmp",       // The directory the file gets written into if not using .toFile(filename, callback). default: '/tmp'

  // Papersize Options: http://phantomjs.org/api/webpage/property/paper-size.html
  "height": "10.5in",        // allowed units: mm, cm, in, px
  "width": "8in",            // allowed units: mm, cm, in, px
  - or -
  "format": "Letter",        // allowed units: A3, A4, A5, Legal, Letter, Tabloid
  "orientation": "portrait", // portrait or landscape

  // Page options
  "border": "0",             // default is 0, units: mm, cm, in, px
  - or -
  "border": {
    "top": "2in",            // default is 0, units: mm, cm, in, px
    "right": "1in",
    "bottom": "2in",
    "left": "1.5in"
  },

  paginationOffset: 1,       // Override the initial pagination number
  "header": {
    "height": "45mm",
    "contents": '<div style="text-align: center;">Author: Marc Bachmann</div>'
  },
  "footer": {
    "height": "28mm",
    "contents": {
      first: 'Cover page',
      2: 'Second page', // Any page number is working. 1-based index
      default: '<span style="color: #444;">{{page}}</span>/<span>{{pages}}</span>', // fallback value
      last: 'Last Page'
    }
  },


  // Rendering options
  "base": "file:///home/www/your-asset-path/", // Base path that's used to load files (images, css, js) when they aren't referenced using a host

  // Zooming option, can be used to scale images if `options.type` is not pdf
  "zoomFactor": "1", // default is 1

  // File options
  "type": "pdf",           // allowed file types: png, jpeg, pdf
  "quality": "75",         // only used for types png & jpeg

  // Script options
  "phantomPath": "./node_modules/phantomjs/bin/phantomjs", // PhantomJS binary which should get downloaded automatically
  "phantomArgs": [], // array of strings used as phantomjs args e.g. ["--ignore-ssl-errors=yes"]
  "localUrlAccess": false, // Prevent local file:// access by passing '--local-url-access=false' to phantomjs
                           // For security reasons you should keep the default value if you render arbritary html/js.
  "script": '/url',        // Absolute path to a custom phantomjs script, use the file in lib/scripts as example
  "timeout": 30000,        // Timeout that will cancel phantomjs, in milliseconds

  // Time we should wait after window load
  // accepted values are 'manual', some delay in milliseconds or undefined to wait for a render event
  "renderDelay": 1000,

  // HTTP Headers that are used for requests
  "httpHeaders": {
    // e.g.
    "Authorization": "Bearer ACEFAD8C-4B4D-4042-AB30-6C735F5BAC8B"
  },

  // To run Node application as Windows service
  "childProcessOptions": {
    "detached": true
  }

  // HTTP Cookies that are used for requests
  "httpCookies": [
    // e.g.
    {
      "name": "Valid-Cookie-Name", // required
      "value": "Valid-Cookie-Value", // required
      "domain": "localhost",
      "path": "/foo", // required
      "httponly": true,
      "secure": false,
      "expires": (new Date()).getTime() + (1000 * 60 * 60) // e.g. expires in 1 hour
    }
  ]

}

The full options object gets converted to JSON and will get passed to the phantomjs script as third argument.
There are more options concerning the paperSize, header & footer options inside the phantomjs script.