← dompdf.js Studio

dompdf.js 与 jsPDF、html2canvas 共存指南

很多项目在引入 dompdf.js 之前,已经用 jsPDF、html2canvas 或其他 PDF 库实现了导出功能。新库能不能和旧库共存、会不会冲突、要不要一次性迁移,是团队最关心的问题。答案是明确的:dompdf.js 可以与现有 PDF 库在同一项目中共存,两者定位不同——jsPDF 是底层绘图库,html2canvas 解决的是截图转图片,而 dompdf.js 是完整的 DOM 转矢量 PDF 引擎,各自擅长不同的场景,可以按需路由而不是互相替代。本文从各库的定位差异讲起,分析共存时可能遇到的命名空间与构建冲突,给出按场景路由到不同库的代码示例,讨论按需加载与包体积控制,再给出从旧库渐进迁移到 dompdf.js 的路线图,最后总结共存场景的常见问题与最佳实践。读完你就能判断自己的项目该共存还是迁移,并且知道每一步怎么做风险最低,让多库并存成为可控的工程决策,而不是技术债。

各库定位差异:谁适合什么场景

jsPDF 是底层 PDF 生成库,通过 API 逐行绘制文本、线条与图片,适合高度定制化的输出,但把 HTML 转 PDF 需要自己解析布局,工作量巨大;html2canvas 把 DOM 截图成 canvas 再转图片,实现简单但输出是位图,文字不可选中、放大模糊、体积大。dompdf.js 直接渲染 DOM 快照为矢量 PDF,文字可选中、体积小、支持自动分页,三者优势互补。

场景选择可以这样划分:动态数据驱动的表格、报表、合同,用 dompdf.js 最合适,矢量输出和分页能力正好匹配;需要对页面像素级复刻的场景,html2canvas 截图方案仍有价值,比如带复杂视觉效果的营销页快照;需要程序化绘制图表或自定义坐标排版时,jsPDF 的底层 API 更灵活。明确场景边界,共存才有意义。

共存的核心原则是单一职责:一个导出入口背后只有一个引擎干活,不要让两个库同时渲染同一份文档。按功能模块划分归属——旧功能保持旧引擎,新功能默认用 dompdf.js,通过统一的导出服务层做路由,业务代码不直接依赖具体库,后续切换引擎时只改路由配置,业务层零改动,这是多库共存最稳妥的架构。

共存会不会冲突:命名空间与构建风险

dompdf.js 是纯前端库,不依赖 jsPDF 或 html2canvas,两者也没有全局变量冲突:jsPDF 暴露全局 jsPDF,html2canvas 暴露 html2canvas,dompdf.js 以 ES Module 方式导出 DomPDF 类,在模块化项目里互不干扰。真正需要警惕的是打包与版本问题:多个库都依赖同一份底层工具库时,版本不一致可能引发重复打包或行为差异。

用 CDN 方式引入多个库时,要注意加载顺序与全局命名空间被覆盖的风险:如果两个库都往 window 上挂同名字段,后加载的会覆盖先加载的。解决方法是优先使用 ES Module 或打包器管理依赖,让每个库的命名空间隔离在模块作用域内;必须用全局脚本时,加载后立即把引用保存到自己的命名空间变量,避免后续脚本覆盖。

构建层面的风险主要是体积与 tree-shaking:dompdf.js 包含 WASM 内核,打包后体积比纯 JS 库大,如果项目同时保留三个库,首屏体积会明显上升。建议把 PDF 相关代码全部放进动态 import,用户点击导出时才加载对应引擎,首屏不承担任何 PDF 库的成本,这是多库共存最重要的工程手段,直接决定体验与性能。

代码示例:按场景路由到不同库

路由的核心是让业务代码只面对一个 exportPdf 函数,引擎选择收敛在函数内部:默认 auto 模式根据浏览器能力自动选择 dompdf.js 或旧方案,也可以由调用方显式指定 engine。这样新功能用上新引擎的同时,旧功能的行为完全不变,回归风险被隔离在路由层,团队可以放心并行推进。

示例里 html2canvas 与 jsPDF 用了动态 import,只有走到位图分支时才下载这两个库,dompdf.js 主路径不会为它们付出首屏成本。反过来,如果项目以旧库为主、dompdf.js 只是补充,就把 dompdf.js 放进动态 import,原则是一样的:按需加载,谁干活谁进包,避免三个库的体积同时压在用户身上。

路由之外,建议在导出服务层统一记录引擎使用分布:多少导出走了 dompdf.js、多少走了旧方案、旧方案的失败率如何。这份数据是后续迁移决策的依据——当 dompdf.js 覆盖了绝大多数场景且旧方案只剩零星调用时,就可以安排旧库下线,迁移从拍脑袋变成数据驱动。

import { DomPDF } from 'dompdf.js';
// 项目里已有的 jsPDF 与 html2canvas

function supportsWasm() {
  return typeof WebAssembly !== 'undefined';
}

async function exportPdf(source, { engine = 'auto' } = {}) {
  if (engine === 'dompdf' || (engine === 'auto' && supportsWasm())) {
    // 矢量导出:报表、合同、多页文档
    const pdf = new DomPDF();
    pdf.addPage(source, { format: 'A4' });
    pdf.save('document.pdf');
    return;
  }
  // 位图兜底:旧浏览器或像素级复刻场景
  const { default: html2canvas } = await import('html2canvas');
  const { jsPDF } = await import('jspdf');
  const canvas = await html2canvas(source);
  const img = canvas.toDataURL('image/png');
  const doc = new jsPDF();
  doc.addImage(img, 'PNG', 0, 0, 210, 297);
  doc.save('document.pdf');
}

按需加载与包体积控制

多库共存的成本主要在体积,按需加载是唯一正确的解法。把每个 PDF 库封装成独立的动态 import 模块,页面初始化时不加载任何 PDF 代码,用户点击导出时才按路由加载对应引擎。首屏体积与导出功能彻底解耦,导出功能再重也不影响页面打开速度,这是对用户体验最友好的共存形态。

体积控制还可以细化到资源层:dompdf.js 的 wasm 文件、字体文件都走独立请求,配合浏览器缓存可以做到首次加载后零重复下载。给 wasm 与字体资源设置合理的缓存策略和版本化文件名,升级库时只更新受影响的文件,用户的缓存命中率保持高位,重复导出的加载时间趋近于零。

打包层面留意 tree-shaking 的边界:动态 import 的模块如果内部有副作用代码,可能被完整打进主包,检查构建产物的分包结果,确认 PDF 相关代码确实落在独立 chunk 里。还可以用构建工具的分析插件看每个 chunk 的体积构成,出现异常膨胀时能快速定位是哪个库的哪部分代码混进了主包,及时修正。

渐进式迁移:从 jsPDF 迁移到 dompdf.js

如果目标是最终迁移到 dompdf.js,推荐渐进式路线而非一刀切:第一阶段共存,新旧功能并行,各自独立验证;第二阶段在新功能中默认使用 dompdf.js,旧功能保持原引擎;第三阶段把高频旧功能逐个切换到 dompdf.js,每次切换前用同一份输入对比两份输出,确认分页、字体、图片渲染符合预期;第四阶段下线旧库。

对比验证是迁移的核心环节:对每个要迁移的功能,准备一批代表性输入——不同长度的文本、含图片的页面、中英文混排的文档,分别用新旧引擎生成 PDF,逐页对比页数、文本内容与关键样式。差异要记录成清单,区分是必须修复的问题还是可接受的细节差异,避免迁移过程中质量标准的漂移。

迁移期间保持双引擎可切换:路由层保留 engine 参数,线上出问题时可以一键切回旧引擎,把风险控制在分钟级。迁移完成后也不要急着删旧库代码,保留一个版本的切换开关,观察线上数据稳定后再彻底清理。渐进式迁移的每一步都可回退、可验证,团队压力小,成功率远高于周末大重构。

共存常见问题与最佳实践

常见问题一:两个库都生成 PDF,导出文件名或触发方式互相干扰。解决:统一走导出服务层,文件名、触发按钮、下载逻辑全部收敛到一处,业务代码不直接调用任何库。常见问题二:打包后体积翻倍。解决:动态 import 全部 PDF 库,确认分包正确。常见问题三:升级某个库后另一个库行为变化。解决:锁定依赖版本,升级时跑一遍全量导出回归用例。

最佳实践小结:单一职责,一个导出入口一个引擎;统一路由,业务代码不直接依赖库;按需加载,谁干活谁进包;版本锁定,升级前先回归;数据驱动,用导出埋点决定迁移节奏。这五条原则让多库并存从混乱的技术债变成清晰的工程结构,团队任何成员接手都能快速理解导出链路的全貌。

最后,把共存方案写进项目的技术文档:为什么引入 dompdf.js、路由规则是什么、每个引擎的适用场景与已知限制、迁移路线图。文档是共存架构的防腐剂,人员流动后新成员不必重新摸索,决策依据也不会丢失。多库共存不是永久的,但清晰的文档让它在存续期间始终可控、可维护、可退出。

⚡ 现场演示(点击生成 PDF)

下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:

Hello from dompdf.js!

这是由 dompdf.js 渲染的示例 PDF 内容。