Skip to content

Nuxt + OPFS:如何构建一个纯客户端本地优先的 PDF 工具 ​

译注:本文编译自 dev.to 文章《Why I built a local-first PDF toolkit using Nuxt and OPFS》,作者 Eoin McG,原文链接见文末。文章介绍了 beePDF 的技术选型与架构取舍,以下内容忠实于原文。

在线处理 NDA、租赁合同或合同草稿时,主流工具(如 Smallpdf、ILovePDF)都会要求用户把敏感文档上传到它们的远程服务器。即便服务商采用传输加密并承诺限期删除文件,上传机密文档依然意味着把文档托付给第三方服务。传输加密保护的是传输过程,并不能消除对接收和处理文档的系统的信任需求。此外,对于法律、医疗等场景,受隐私、保密或合规要求限制,上传到第三方服务可能根本不是一个可选项。

为解决这一问题,作者构建了 beePDF——一个 100% 客户端运行的渐进式 Web 应用(PWA),文档处理完全在浏览器内完成,文档永远不会上传到远程服务器。

技术栈 ​

  • 框架: Nuxt(Vue 4、TypeScript、Vite)
  • PWA 与离线支持: @vite-pwa/nuxt + Service Workers
  • 内存内文件处理: pdf-lib、PDF.js、原生 Canvas API
  • 本地持久化: Origin Private File System(navigator.storage.getDirectory())
  • 样式与 UI: Pico CSS

作者表示,此前使用过 Next.js,而 Nuxt 让人耳目一新,强烈推荐尝试。

为什么选择 OPFS ​

构建处理二进制文档的本地优先 Web 应用时,本地持久化是主要瓶颈:

  1. LocalStorage: 容量仅约 5MB–10MB,且为同步的纯文本存储,对二进制 PDF 完全无用。
  2. IndexedDB: 适合结构化元数据和较小的二进制对象,但不太适合把大型文档当作持久文件来做高效的字节级访问。
  3. File System Access API: 适合直接操作用户文件系统中的文件,但访问需要用户授权,并受浏览器安全限制。

Origin Private File System(OPFS) 提供了一个私有的、按源(origin)隔离的文件系统,专为持久化的类文件数据设计,支持底层字节访问和优化的原地写入。它还可在 Web Worker 中提供同步访问句柄,以应对性能敏感的工作负载。

typescript
// 访问本地 OPFS 根目录
const root = await navigator.storage.getDirectory();
const draftHandle = await root.getFileHandle("working-draft.pdf", { create: true });

// 将二进制数据流直接写入本地磁盘
const writable = await draftHandle.createWritable();
await writable.write(pdfArrayBuffer);
await writable.close();

OPFS 对 PDF 处理至关重要的三点:

  • 零权限弹窗: 该目录对 https://beepdf.app 私有,浏览器不会反复弹出权限对话框。
  • 极快的磁盘 I/O: OPFS 提供直接的字节级文件流;在 Web Worker 中甚至可以使用 createSyncAccessHandle() 实现接近原生的同步文件读写。
  • 存储配额与清理: 文件在浏览器刷新后依然保留(误关标签页不会丢失工作文档),同时自动遵循浏览器的源存储配额。

处理流水线 ​

当用户把 PDF 拖入 beePDF 时:

  1. 本地读取: 浏览器把 File 对象读入内存中的 ArrayBuffer。
  2. OPFS: 文档立即存入本地 OPFS 工作目录。
  3. 元数据与缩略图: 元数据和极小的缩略图存入 IndexedDB,使搜索和打标签变得简单。
  4. Web Worker 卸载: 渲染页面缩略图、提取纯文本/Markdown、运行词级 diff 等任务交给 Web Worker,保持 UI 以 60 FPS 运行。
  5. 客户端导出: 修改或签名文档会在内存中生成新的 Uint8Array,通过 URL.createObjectURL Blob 触发直接的客户端下载。
  6. 飞行模式测试: 加载 beePDF 后关闭 Wi-Fi 或完全断网,仍可离线合并、拆分、压缩或签名文档。

挑战与经验 ​

现代浏览器提供了相当丰富的能力,Web 应用可通过标准化 Web API 访问其中许多功能。但构建纯客户端文件工具并非没有限制:

  • 浏览器内存上限: 在浏览器 JS 中处理 500MB 以上的扫描版 PDF,可能在低端设备上触及 V8 堆内存限制。
  • 主线程阻塞: 重量级的图像压缩或 Canvas 操作必须卸载到 Web Worker,否则渲染高 DPI 画布会冻结 UI 线程。

讨论 ​

作者希望获得 DEV 社区的反馈:你是否已在生产环境使用 OPFS?与 IndexedDB 相比体验如何?还希望看到哪些客户端工具或 WASM 集成?

在线体验地址:https://beepdf.app

原文链接 ​

https://dev.to/eoinmcg/why-i-built-a-local-first-pdf-toolkit-using-nuxt-and-opfs-54k3