在浏览器中使用 Webpack 5 打包 PDFKit:官方示例的完整配置指南
2026/9/24 16:23:06 网站建设 项目流程
  • 后端
  • 文档

【免费下载链接】pdfkit

A JavaScript PDF generation library for Node and the browser

项目地址:https://gitcode.com/gh_mirrors/pd/pdfkit
点击查看免费下载

本文基于 PDFKit 仓库中官方自带的examples/webpack示例,系统讲解如何在浏览器端用 Webpack 5 将 PDFKit 及其字体、图片资源打包为一个可运行的 PDF 生成应用。读完本文,你将掌握 PDFKit 浏览器打包的全部关键配置:如何处理 Node 原生模块(buffer、stream、zlib、util、assert)、如何把二进制资源内联为 base64、如何按需懒加载大文件、如何注册标准字体与自定义 TTF 字体,以及如何在浏览器中实时生成并预览 PDF。

示例项目概览

PDFKit 的仓库中提供了两个浏览器端打包示例:examples/browserify(使用 Browserify)和examples/webpack(使用 Webpack)。本文聚焦后者,其完整目录结构如下:

examples/webpack/ ├── package.json # 依赖与 dev/prod 构建脚本 ├── webpack.config.js # 核心打包配置 └── src/ ├── index.html # 演示页面:代码编辑器 + PDF 预览 iframe ├── index.js # 入口,含示例 PDF 生成代码 ├── assets.js # 内联字体与图片的注册/导出 ├── pdfkitHelpers.js # 收集 PDF 输出流并转成 data URL ├── httpHelpers.js # 基于 XMLHttpRequest 的懒加载工具 ├── static-assets/ # 被打包为 base64 内联的资源(fonts/、images/) └── lazy-assets/ # 以独立 URL 输出的懒加载资源(test.jpeg)

该示例实现的是一个“浏览器在线 PDF 演示页”:左侧是 Ace 代码编辑器,右侧 iframe 实时渲染 PDF。示例程序覆盖了 PDFKit 的核心能力:文本排版(多栏、两端对齐、缩进、省略号)、矢量图形(三角形、圆形、SVG path)、doc.addPage()多页文档、标准字体与自定义 Roboto 字体、图片插入,以及懒加载图片的兜底处理。

运行示例

示例的依赖与脚本定义在 examples/webpack/package.json:

{ "dependencies": { "assert": "^2.1.0", "brace": "^0.11.1", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", "pdfkit": "^0.15.0", "process": "^0.11.10", "readable-stream": "^4.5.2", "util": "^0.12.5" }, "devDependencies": { "html-webpack-plugin": "^5.6.0", "transform-loader": "^0.2.4", "webpack": "^5.91.0", "webpack-cli": "^5.1.4" }, "scripts": { "dev": "webpack --mode development", "prod": "webpack --mode production" } }

运行步骤:

# 在 examples/webpack 目录下安装依赖 npm install # 开发模式构建(输出未压缩的 bundle,便于调试) npm run dev # 生产模式构建(压缩产物,适合部署) npm run prod

构建产物由HtmlWebpackPlugin注入到由 examples/webpack/src/index.html 生成的页面中,直接用浏览器打开即可看到编辑器与 PDF 预览。注意该示例声明依赖pdfkit: ^0.15.0,而仓库根目录的 package.json 当前版本为0.20.1,两者 API 兼容,本文描述的核心机制对两个版本均成立。

Webpack 5 核心打包配置

webpack.config.js 是整个示例的灵魂,只有 47 行,却解决了浏览器端运行 PDFKit 的三大难题。下面逐段拆解。

1. 忽略 crypto 模块,显著缩小体积

PDFKit 的 Node 端实现依赖 Node 内置crypto用于 PDF 加密(如 AES-128),但在纯浏览器场景下,通过PDFDocument构造时若不传入密码参数,则完全不需要加密功能。示例通过resolve.fallbackcrypto显式置为false,让 Webpack 在遇到require('crypto')时直接提供一个空模块,而不是报错或打包庞大的 polyfill:

resolve: { fallback: { // crypto module is not necessary at browser crypto: false } }

依据:PDFKit 仓库中的加密实现位于 lib/security.js(AES/RC4 算法位于 lib/crypto/)。源码层面,crypto仅在启用加密时才被使用,因此浏览器端可以安全地忽略它。仓库的build-standalone脚本(见 package.json 的browserify --standalone PDFDocument --ignore crypto)也采用了完全相同的策略。

2. 为 Node 原生模块提供浏览器 polyfill

PDFKit 在浏览器环境通过lib/fs/browser.jslib/stream/browser.jslib/zlib/browser.js等替代 Node 实现(映射关系见 package.json 的imports字段),但部分依赖链仍会引用 Node 内置模块。示例为这些模块逐一提供了经过验证的浏览器替代品:

resolve: { symlinks: false, fallback: { crypto: false, buffer: require.resolve('buffer/'), stream: require.resolve('readable-stream'), zlib: require.resolve('browserify-zlib'), util: require.resolve('util/'), assert: require.resolve('assert/') } }

各 polyfill 的作用与对应依赖:

原生模块polyfill 包作用
bufferbuffer提供Buffer全局能力,PDF 输出流与 base64 转换依赖它
streamreadable-stream提供流式接口,PDFDocument本身就是可读流(doc.on('data')
zlibbrowserify-zlib提供 FlateDecode 压缩,PDF 内容流压缩的核心
utilutil工具函数 polyfill
assertassert断言模块 polyfill

同时,示例用webpack.ProvidePlugin自动注入Bufferprocess,避免在每个文件中手动import

plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, 'src/index.html') }), new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'], process: 'process/browser' }) ]

resolve.symlinks: false则确保 Webpack 在解析node_modules中的链接依赖时,不会因符号链接导致重复打包或路径混乱(尤其在 monorepo 或 yarn PnP 环境下)。

3. 静态资源内联为 base64、懒加载资源输出为 URL

这是示例中最具实战价值的部分——用两条 Webpack 5 内置 asset 规则,将不同目录的二进制资源区分处理:

module: { rules: [ // bundle and load binary files inside static-assets folder as base64 { test: /src[/\\]static-assets/, type: 'asset/inline', generator: { dataUrl: content => { return content.toString('base64'); } } }, // load binary files inside lazy-assets folder as an URL { test: /src[/\\]lazy-assets/, type: 'asset/resource' } ] }
  • type: 'asset/inline'(Webpack 5 内置规则):匹配src/static-assets下的所有文件(TTF 字体、PNG 图片),打包时直接以 base64 Data URL 形式内联进 JS bundle,运行时无需任何网络请求;
  • type: 'asset/resource':匹配src/lazy-assets下的文件,构建时输出为独立文件,import得到的是该文件的 URL,只有在运行时请求该 URL 才会下载数据。

知识链接:type: 'asset/inline'/'asset/resource'是 Webpack 5 取代旧版url-loader/file-loader的内置资源模块(Asset Modules),无需额外安装 loader。dataUrl生成器中的content为 Buffer,content.toString('base64')得到纯 base64 字符串;而asset/inline默认的 Data URL 带data:...;base64,前缀,因此 assets.js 中为图片手动拼接了前缀:

export const images = { bee: `data:image/png;base64,${bee}` };

4. linebreak 与 fontkit 的二进制数据

README 中提到的“convert binary files used by linebreak and fontkit to base64”指的是 PDFKit 的两个关键依赖:

  • linebreak:用于文本自动换行断行判定,需要 Unicode 断行数据;
  • fontkit:负责 TTF/OTF 字体解析、字形测量与子集化。

这些依赖内部引用的数据文件同样会经过上述 asset 规则或源码内的 base64 处理,确保在无 Nodefs的浏览器环境中也能读取。这一点在 package.json 的依赖列表(fontkit: ^2.0.4linebreak: ^1.1.0)中可以印证。

浏览器端字体与资源管理:assets.js

assets.js 展示了浏览器端注册字体的标准姿势:

import { registerStdFonts } from 'pdfkit'; import Courier from 'pdfkit/standard-fonts/Courier'; import CourierBold from 'pdfkit/standard-fonts/CourierBold'; import Helvetica from 'pdfkit/standard-fonts/Helvetica'; // webpack is configured to load files in static-assets as base64 import robotoRegular from './static-assets/fonts/Roboto-Regular.ttf'; import bee from './static-assets/images/bee.png'; // is good practice to register only required fonts to avoid the bundle size increase too much registerStdFonts(Courier, CourierBold, Helvetica); const toBytes = base64 => Uint8Array.from(atob(base64), char => char.charCodeAt(0)); export const fonts = { Roboto: toBytes(robotoRegular) }; export const images = { bee: `data:image/png;base64,${bee}` };

要点解析:

  1. registerStdFonts按需注册标准字体pdfkit/standard-fonts/*是 PDFKit 通过 package.json 的exports字段暴露的子路径(如./standard-fonts/Courier),每个字体都是可独立 import 的模块。示例只注册CourierCourierBoldHelvetica三种,源码注释明确指出这是“good practice”——只注册必要的字体,避免 bundle 体积失控。注册后可像 Node 端一样直接使用doc.font('Courier')doc.font('Courier-Bold')
  2. 自定义 TTF 字体转字节数组:Webpack 内联后的robotoRegular是 base64 字符串,toBytesatob解码为二进制字符串再转成Uint8ArrayPDFDocument.registerFont(name, src)在浏览器端接受这种字节数组,随后doc.font('Roboto')即可使用;
  3. 资源统一出口fontsimages作为单一对象导出,后续代码可以整体注入到编辑器执行环境。

浏览器端 PDF 流收集:pdfkitHelpers.js

在 Node 端,doc.pipe(fs.createWriteStream(...))即可落盘;在浏览器端没有fs,示例通过 pdfkitHelpers.js 收集 PDFDocument 输出流并转换为可在 iframe 中预览的 Data URL:

export const waitForData = async doc => { return new Promise((resolve, reject) => { const buffers = []; doc.on('data', buffers.push.bind(buffers)); doc.on('end', async () => { const pdfBuffer = Buffer.concat(buffers); const pdfBase64 = pdfBuffer.toString('base64'); resolve(`data:application/pdf;base64,${pdfBase64}`); }); doc.on('error', reject); }); };

其原理是:PDFDocument是一个可读流,doc.on('data')会持续收到 PDF 字节块(Buffer),doc.on('end')doc.end()之后触发,此时将所有块Buffer.concat合并为完整 PDF,再转 base64 拼接成data:application/pdf;base64,...URL。doc.on('error')用于异常透传。这个工具函数的使用有个关键约束——必须在调用doc.end()之前调用(源码注释明确提示 “waitForData must be called before call to doc.end()”),否则会漏掉输出事件。这一点在 index.js 的示例代码中也有体现:

// waitForData must be called before call to doc.end() waitForData(doc) .then(dataUrl => { iframe.src = dataUrl; }) .catch(error => { console.log(error); }); doc.end();

懒加载资源:httpHelpers.js 与运行时兜底

对于不希望随首屏 bundle 一起加载的大文件(如示例中的 test.jpeg),httpHelpers.js 提供了一个基于XMLHttpRequest的异步获取工具:

export function fetchFile(fileURL, { type = 'arraybuffer' } = {}) { return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); request.open('GET', fileURL, true); request.responseType = type; request.onload = function(e) { if (request.status === 200) { resolve(request.response); } else { reject(createFetchError(fileURL, request.statusText)); } }; request.onerror = error => reject(createFetchError(fileURL, error)); request.send(); }); }

入口 index.js 中展示了完整的“懒加载 + 兜底”模式:

// testImage is an URL import testImageURL from './lazy-assets/test.jpeg'; import { fonts, images } from './assets.js'; fetchFile(testImageURL) .then(testImageData => { images.test = testImageData; }) .catch(error => { console.error(error); });

随后在生成 PDF 时尝试插入这张图,若资源尚未加载完成则捕获异常并在 PDF 中输出提示文字:

try { doc.image(images.test); } catch (error) { doc.moveDown().text(`${error}`); doc.text('Image not loaded. Try again later.'); }

这是浏览器端 PDF 生成的典型异步时序问题处理范式:资源加载是异步的,而 PDF 绘制是同步的,因此需要try/catch与后续重试机制来保证文档生成流程不被未就绪的资源中断。

实时编辑演示页:index.js 的完整 PDF 示例

入口 index.js 本身就是一个可运行的 PDFKit 用法全集,编辑器中的初始代码覆盖了以下 API(可直接复制到 Node 端运行):

  • 自定义字体注册与文本绘制doc.registerFont('Roboto', fonts.Roboto)doc.font('Roboto').fontSize(25).text(...)
  • 矢量图形doc.moveTo(100, 150).lineTo(100, 250).lineTo(200, 250).fill('#FF3300')绘制填充三角形,doc.circle(280, 200, 50).fill('#6600FF')绘制圆形,doc.scale(0.6).translate(470, 130).path('M 250,75 L 323,301 ...').fill('red', 'even-odd')绘制 SVG path;
  • 多栏排版doc.text(lorem, { width: 412, align: 'justify', indent: 30, columns: 2, height: 300, ellipsis: true })实现两栏、两端对齐、首行缩进与溢出省略号;
  • 多页与图片doc.addPage()doc.image(images.bee)doc.font('Courier-Bold')切换标准字体;
  • 实时重执行:Ace 编辑器内容变化时,通过new Function('PDFDocument', 'lorem', 'waitForData', 'iframe', 'fonts', 'images', code)重新执行代码,实现“改代码即出 PDF”的交互。

注意事项:bundle 体积权衡

README 的 Caveats 部分对这套方案给出了重要的工程提醒:

The strategy to bundle binary files and standard fonts inlines them in source code, increasing the bundle size significantly.

将二进制文件与标准字体内联进源码,会显著增大 bundle 体积。因此示例给出了三条平衡策略:

  1. 只注册必要的标准字体registerStdFonts只传用到的字体),避免 14 种内置字体全量打包;
  2. 大文件走lazy-assets按需加载,通过asset/resource输出为独立文件,运行时才请求;
  3. 忽略不必要的crypto依赖,直接降低基础体积。

在实际项目中,你可以根据字体使用频率进一步细分:高频小资源内联、低频大资源懒加载、标准字体按需注册,从而在“开箱即用”与“体积可控”之间找到平衡。

小结

PDFKit 本身是双端(Node + Browser)的 PDF 生成库,其浏览器端打包的难点不在 PDFKit 自身 API,而在于:Node 原生模块的 polyfill、二进制字体/图片资源的处理策略、以及异步资源的时序管理。examples/webpack这个官方示例以极简的配置给出了完整的参考答案:

  • resolve.fallback+ProvidePlugin解决 Node 模块依赖(webpack.config.js);
  • asset/inlineasset/resource两条规则区分“立即内联”与“懒加载”资源;
  • registerStdFonts按需注册标准字体(assets.js);
  • waitForData完成浏览器端 PDF 流收集与 iframe 预览(pdfkitHelpers.js);
  • fetchFile+try/catch处理懒加载图片的异步时序(httpHelpers.js)。

这套配置可直接作为你在自己的 Webpack 5 项目中集成 PDFKit 的起点,相关配置细节均可对照仓库源码进一步验证与调整。

  • 后端
  • 文档

【免费下载链接】pdfkit

A JavaScript PDF generation library for Node and the browser

项目地址:https://gitcode.com/gh_mirrors/pd/pdfkit
点击查看免费下载
上一篇:GlosSI:让Steam控制器在任何游戏和程序中都能使用的终极方案
下一篇:GlosSI:为Windows游戏解锁系统级Steam控制器支持的终极方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询