← dompdf.js Studio

dompdf.js 报错排查手册

把 HTML 转成 PDF 的功能上线后,最消耗开发时间的往往不是功能本身,而是各种报错排查:有的错误直接抛出异常,有的则静默产出空白页或乱码文档,让用户一头雾水。dompdf.js 作为纯前端方案,链路比传统服务端生成更长——主线程采集 DOM 快照、Web Worker 整理渲染数据、Rust/WASM 模块写出 PDF 字节流、浏览器以 Blob 返回结果,任何一个环节出问题,外在表现都不同。好在这条链路的每一段都有规律可循:模块加载失败、WASM 初始化异常、addPage 参数错误、字体与图片资源加载失败、分页布局异常,各自对应不同的根因和排查路径。本文按错误类型组织成一份可直接对照的排查手册,先讲基本方法论,再逐个拆解高频报错场景,给出具体错误信息、根因分析和可复制的解决方案,最后附一张速查表。无论你是第一次接入还是线上出了问题,都能按图索骥、快速定位,让 PDF 生成功能稳定可靠。

排查报错的基本流程

遇到报错先别急着改代码,第一步是定位错误发生的阶段。打开浏览器控制台,把错误信息完整读一遍:同步抛出的 TypeError 通常来自 addPage 的参数处理,Promise 拒绝多半发生在异步渲染阶段,而网络面板里的红色请求则指向资源加载问题。错误信息的堆栈能告诉我们它来自主线程还是 Worker,这一步就能把排查范围缩小一大半。

第二步是做最小复现:把业务代码剥离,只保留一段最简单的 HTML 和一次 addPage 调用,在独立页面里复现问题。如果最小用例正常,说明问题出在业务侧——模板里的异常标签、不规范的 CSS 或某个特殊字符;如果最小用例也报错,才需要怀疑库本身或运行环境。最小复现还能让问题更容易向社区提问,别人一眼就能看懂并给出建议。

第三步是记录环境信息:浏览器及版本、操作系统、设备类型、网络环境、dompdf.js 版本、wasm 文件是否可达。同一份代码在 Chrome 正常、在旧版 Safari 报错,是非常常见的情况,环境信息不齐,排查往往事倍功半。把这些信息连同错误堆栈一起整理,再对照下文各场景逐个排除,多数问题几分钟内就能定位。

模块加载与 WASM 初始化错误

最常见的启动期报错是模块找不到:npm 安装失败、包名写错、CDN 路径拼错,都会在 import 阶段抛出 Cannot find module 或 404。先确认 node_modules 里存在 dompdf.js 且版本与文档一致,再检查构建工具的打包配置是否把 wasm 文件正确复制到了产物目录。用 CDN 时注意引入 dist 下的完整产物,路径以包内实际文件名为准。

WASM 初始化失败是第二类高频问题,典型表现是控制台出现 TypeError: Cannot read properties of undefined 或 WebAssembly.instantiate 相关错误。根因通常是服务器没有把 .wasm 文件按 application/wasm 的 MIME 类型返回,或者请求被跨域策略拦截。打开网络面板确认 wasm 请求的状态码和响应头,必要时在服务器配置 MIME 映射,或把静态资源与页面部署在同源下,问题即可解决。

某些部署环境还会遇到 SharedArrayBuffer 相关提示,这是浏览器对跨源隔离环境的限制,dompdf.js 的核心路径并不依赖它,遇到时先确认是否误开了相关配置。另外,开发服务器与生产环境行为不一致时,优先对比两边的响应头与缓存策略,wasm 文件被错误缓存成旧版本,也会导致初始化异常,清缓存后重试往往立竿见影。

代码示例:统一错误处理与重试

把生成逻辑统一封装成函数,是排查线上问题的第一步:所有错误都会经过同一处日志出口,带上错误名、消息和自定义上下文,后续按时间线回溯就非常方便。同步的 try/catch 能覆盖 addPage 与 save 调用本身的异常,如果 save 返回 Promise,记得补上 .catch 或 await,避免未处理的 Promise 拒绝悄悄消失在控制台之外。

重试只适合偶发的资源类错误,比如 wasm 文件第一次加载超时、字体请求被瞬时网络抖动打断。对语法错误、参数错误这类确定性失败,重试毫无意义,还会拖慢用户操作,所以代码里用错误消息的关键词做了一次分类,只有命中 wasm、fetch、network 等关键词才进入重试队列,其他错误立即上报。

生产环境建议把错误信息上报到监控平台,并附带浏览器 UA、页面 URL、dompdf.js 版本和错误堆栈。有了统一出口和结构化日志,线上反馈的每个报错都能快速归类到本文下面的场景中,修复时也有据可依,而不是靠用户截图猜问题。

import { DomPDF } from 'dompdf.js';

function generatePdf(html, options = {}) {
  try {
    const pdf = new DomPDF();
    pdf.addPage(html, { format: 'A4', ...options });
    pdf.save('output.pdf');
    return { ok: true };
  } catch (err) {
    console.error('[dompdf.js] 生成失败:', err && err.name, err && err.message);
    return { ok: false, error: err };
  }
}

// 对偶发的资源类错误做有限重试
async function generateWithRetry(html, retries = 2) {
  for (let i = 0; i <= retries; i++) {
    const result = generatePdf(html);
    if (result.ok) return result;
    if (!/wasm|fetch|network|init/i.test(String(result.error && result.error.message))) {
      return result; // 非资源类错误不重试,直接暴露
    }
    await new Promise((r) => setTimeout(r, 500 * (i + 1)));
  }
  return { ok: false, error: new Error('重试次数用尽') };
}

addPage 参数与空白 PDF 问题

空白 PDF 是反馈量最大的问题之一,根因往往在输入侧。第一种情况是传给 addPage 的 HTML 是空字符串或只包含空白字符,生成器自然产出空白页;第二种情况是传入的是 DOM 元素,但元素被 display: none 隐藏、不在文档流中或位于视口之外,快照采集时拿不到任何内容,结果同样是空白。

第三种情况是内容其实存在,但渲染时被布局因素挤掉了:容器宽度与纸张宽度不匹配导致内容溢出页面边界,A4 在 96 DPI 下对应的推荐宽度是 794px,容器过宽时内容会被截断到页面外。还有元素依赖滚动位置或动态插入的内容,采集快照的瞬间尚未就绪,也会表现为缺内容,而不是完全空白。

排查时先给生成函数加一段诊断输出:打印传入 HTML 的长度、元素是否可见、getBoundingClientRect 的尺寸,一次调用就能确认输入侧是否正常。修复手段包括显式校验空内容并提示用户、把待导出容器固定为可见状态、设置与纸张匹配的宽度,以及在调用 addPage 前 await document.fonts.ready 和图片加载完成,确保快照时刻的内容是完整的。

字体、图片与资源加载异常

字体加载失败通常不抛异常,而是静默回退:PDF 里中文变成默认字形、英文变成系统字体,观感与网页完全不同。根因一般是字体文件 404、跨域被拦截或加载时序问题——addPage 执行时字体还没下载完。解决方案是在生成前等待 document.fonts.ready,并用 document.fonts.check 验证关键字体族可用,网络面板里确认字体请求状态码为 200。

图片异常的表现更直观:图片缺失显示为空白占位、跨域图片无法绘制、超大图片拖慢甚至拖垮生成。dompdf.js 渲染真实 DOM 快照,跨域图片如果服务器没返回正确的 CORS 头,浏览器层面就无法正常读取像素数据。生产环境建议图片与页面同源,或为静态资源配置 Access-Control-Allow-Origin,同时为图片加加载与解码完成等待,保证快照时图片已渲染。

离线或弱网环境下,资源请求可能整体超时,表现为生成卡在某个进度不动或最终报错。可以在调用前预取关键资源、为 fetch 设置超时与重试,并把生成按钮设计成可重复点击,配合进度反馈让用户知道任务仍在进行。资源类问题的共同排查入口是网络面板:状态码、耗时、响应头三列信息,能覆盖绝大多数加载异常。

分页布局异常与速查表

分页异常常见四类:内容被拦腰截断、页数比预期多或少、页眉页脚错位、某些区块被拆散到两页。前三类大多与容器宽度、页面边距和内容高度有关,先检查待导出容器宽度是否与纸张匹配,再检查元素是否有固定高度或 overflow 属性干扰了分页计算;第四类可以用 divisionDisable 让区块尽量保持完整,或用 pageBreak 在指定元素前强制分页。

速查表:报错 Cannot find module——检查安装与打包;WASM 相关 TypeError——检查 MIME 与跨域;空白 PDF——检查输入为空、元素隐藏、宽度溢出;字体不对——等待 document.fonts.ready 并验证 check();图片空白——检查 CORS 与加载时序;页数异常——检查容器宽度与分页选项;卡住无响应——检查资源超时与内存占用,必要时拆分大文档分段生成。

最后提醒一句:把上面的排查要点沉淀成团队内部的故障手册,配合统一封装与结构化日志,多数问题在新人接手时也能独立定位。dompdf.js 的报错并不可怕,可怕的是没有排查路径地乱试。按本文的顺序走一遍,从输入校验到资源检查再到布局参数,绝大部分线上问题都能在十分钟内解决,剩下的也能带着完整信息快速定位。

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

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

Hello from dompdf.js!

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