news 2026/10/10 14:38:20

50 行代码让 Open File Viewer 支持新文件格式:PreviewPlugin 插件协议深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
50 行代码让 Open File Viewer 支持新文件格式:PreviewPlugin 插件协议深度解析
  • 前端
  • 音视频

【免费下载链接】open-file-viewer

A framework-agnostic embedded file viewer for vanilla JavaScript, React, Vue and Svelte. Put PDF, Office, images, media, archives, email, drawings, 3D, GIS and engineering files inside one stable container.

项目地址:https://gitcode.com/gh_mirrors/op/open-file-viewer
点击查看免费下载

Open File Viewer 是一个跨框架的文件预览 SDK,把 PDF、Office、图片、音视频、压缩包、邮件、图纸、3D、GIS 等格式放进同一个可控容器,同时支持原生 JS、React、Vue 和 Svelte。它的核心设计是插件化:每种文件格式由一个独立插件负责,而插件要遵守的协议——PreviewPlugin——只有 3 个成员。读懂这个协议,你只需要 50 行左右代码,就能让自己的私有格式或业务文件格式也进入统一的预览容器 🧩。

为什么用插件架构:一个容器,N 种格式

大多数业务系统最终都需要附件预览:合同、表格、图纸、压缩包、邮件……如果每种格式都写一套预览逻辑,容器、加载态、错误态、工具栏、多文件队列这些能力就得重复实现。

Open File Viewer 的做法是把这些公共能力全部收敛到 viewer.ts 里的createViewer()容器中,插件只负责回答两个问题:

  1. 这个文件是不是我的?(match)
  2. 怎么把它渲染进容器?(render)

内置插件与格式的对应关系如下:

类别插件代表格式
图片imagePlugin()jpg、png、webp、avif、svg、heic
视频 / 音频videoPlugin()、audioPlugin()mp4、webm、mp3、flac
文本 / 代码textPlugin()txt、md、json、js、py、sql
PDF / 电子书pdfPlugin()、epubPlugin()、xpsPlugin()pdf、epub、xps
Office / OFDofficePlugin()、ofdPlugin()docx、xlsx、pptx、ofd
工程 / 专业格式cadPlugin()、model3dPlugin()、gisPlugin()、drawingPlugin()、xmindPlugin()dxf、dwg、glb、kml、drawio、xmind
压缩包 / 邮件archivePlugin()、emailPlugin()zip、rar、eml
资产识别assetPlugin()psd、sqlite、wasm、parquet

完整插件列表见 index.ts 的导出,每个插件都遵循同一套协议,行为因此可以互相替换、裁剪和扩展。

拆解 PreviewPlugin 接口:只有 3 个成员

整个插件协议的定义在 types.ts,短到可以一眼看完:

export interface PreviewPlugin { name: string; match: (file: PreviewFile) => boolean | Promise<boolean>; render: (ctx: PreviewContext) => Promise<PreviewInstance> | PreviewInstance; }

逐个理解这三个成员:

name:插件的唯一标识

用于日志与调试。内置插件分别叫"image"、"fallback"等。注意一个特殊约定:名为"fallback"的插件在支持性检测中有终止性含义,自定义插件不要占用这个名字。

match:判断文件是否归你管

入参是一个已经归一化好的PreviewFile对象(定义见 types.ts),包含source、name、extension、mimeType、size等字段。文件归一化逻辑由 detect.ts 的normalizeFile()完成:它会根据扩展名自动补全 MIME 类型,所以插件里可以直接读file.extension和file.mimeType。

一个典型的匹配逻辑可以看 image.ts 的实现——先看 MIME 是否以image/开头,再兜底查扩展名白名单,同时排除dxf这类"名字叫图像但其实不是"的格式。

match允许返回Promise<boolean>,因此你也可以在匹配阶段做异步探测(比如读文件头几个字节判断魔数)。

render:真正干活的渲染函数

render接收一个PreviewContext,返回一个PreviewInstance(可以是同步对象,也可以是 Promise)。前者决定"往哪画",后者决定"容器如何回收你"。

渲染上下文 PreviewContext:容器给了你什么

render收到的ctx对象定义在 types.ts,它把容器侧的所有能力一次性交到你手上:

字段作用
ctx.host/ctx.viewport预览宿主与内容视口(你的渲染目标)
ctx.file当前文件的归一化信息PreviewFile
ctx.size视口当前宽高PreviewSize
ctx.options完整的预览配置(fit、fallback、zoom、messages 等)
ctx.toolbar工具栏上下文,可读取文件、索引、缩放状态
ctx.signalAbortSignal:本次渲染被新请求取代或容器销毁时触发中止
ctx.setLoading/ctx.setError控制容器的加载态与错误展示

容器还会自动帮你做脏活累活:多文件队列切换(next()/previous())、响应式尺寸监听、工具栏按钮的启用/禁用、主题与国际化文案、打印前的preparePrint流程——这些全部发生在 viewer.ts 的renderFile()里,插件完全不需要关心。

返回值 PreviewInstance:一份生命周期契约

render必须返回一个PreviewInstance对象(定义见 types.ts),它只有 1 个必填成员 + 4 个可选能力:

export interface PreviewInstance { resize?: (size: PreviewSize) => void; // 容器尺寸变化时响应 goToPage?: (page: number) => boolean; // 分页预览跳转(1 基) command?: (command: PreviewCommand) => void | boolean; // 响应缩放/旋转指令 canCommand?: (command: PreviewCommand) => boolean; // 声明哪些指令可用 preparePrint?: () => void | Promise<void>; // 打印前准备好懒加载内容 destroy: () => void; // 必填:清理一切副作用 }
  • resize(size):窗口或布局变化时容器会调用它,Canvas/WebGL 类插件必须实现。
  • command/canCommand:容器内置了滚轮、捏合手势缩放(见 viewer.ts 的手势安装逻辑),如果你的预览支持zoom-in、rotate-right等指令,实现这两个方法后工具栏和手势会自动联动。
  • destroy():这是硬性约定。切文件、销毁容器时容器都会调用它,你要在这里移除 DOM、断开事件监听、撤销 object URL、停止 Worker、释放 Canvas/WebGL 资源。

插件匹配机制:顺序、兜底与预检查

容器如何从一堆插件里选出渲染者?逻辑在 viewer.ts 的findPlugin(),非常直白:按你传入数组的顺序依次调用match(),第一个返回true的插件胜出。

两个关键细节:

  1. fallbackPlugin()自动垫底。渲染时容器会自动在你的插件列表末尾追加 fallback 插件,它的match()永远返回true。未命中任何原生插件的文件会走到这里,展示"格式不支持 + 下载"面板(或你通过renderFallback提供的自定义渲染),保证任何文件都不会白屏。
  2. 顺序即优先级。csv同时能被textPlugin()和officePlugin()命中——想让表格走电子表格样式,就把officePlugin()放在前面。

如果你在挂载预览器之前就想判断"这个文件能不能预览",可以用 support.ts 导出的isPreviewSupported()。它复用同一套match()顺序做纯检查,不挂载任何 DOM、不调用render(),且fallbackPlugin()命中不算"原生支持"——非常适合做"可在线预览 / 请下载"这类 UI 分流。

实战:50 行代码接入一个新格式

假设业务里有一种.ticket格式的工单文件,想在预览容器里展示。参照 README.md 中的 Plugin Development 章节,完整实现如下:

import type { PreviewPlugin } from "@open-file-viewer/core"; export function ticketPlugin(): PreviewPlugin { return { name: "ticket", match(file) { return file.extension === "ticket"; }, async render(ctx) { const panel = document.createElement("div"); panel.className = "ticket-preview"; panel.textContent = `工单:${ctx.file.name}`; ctx.viewport.append(panel); return { resize(size) { // 视口尺寸变化时重排,例如缩放画布 }, destroy() { panel.remove(); } }; } }; }

然后在创建预览器时把它插进插件列表的合适位置(fallbackPlugin()永远放最后):

const viewer = createViewer({ container: "#viewer", file: ticketFile, fileName: "TK-2026-001.ticket", width: "100%", height: "70vh", toolbar: true, plugins: [imagePlugin(), textPlugin(), ticketPlugin(), pdfPlugin({ workerSrc }), fallbackPlugin()] });

就这么简单:match圈定管辖范围,render画进ctx.viewport,返回的对象负责自清理——整个流程不超过 50 行。React / Vue / Svelte 侧只需把同一个插件数组传给各自适配器组件(如<FileViewer plugins={plugins} />),四端行为完全一致,示例见 examples/ 下的 vanilla、react、vue、svelte 四个工程。

如果格式更复杂(比如私有二进制),协议同样够用:在render里fetch你后端的转换接口、拿回 SVG/PNG 或 PDF 再渲染即可;容器已经替你处理了加载中、错误、多文件切换与资源销毁的时机。

插件开发清单:5 条铁律

来自 README.md 的 Plugin constraints,也是核心源码实际执行的约定:

  • ✅只往ctx.viewport里渲染,不要动ctx.host或外部 DOM
  • ✅默认不打开新窗口,一切展示留在容器内
  • ✅需要响应尺寸变化就实现resize(size)
  • ✅实现destroy():清理事件、object URL、定时器、Canvas/WebGL 等所有副作用
  • ✅尊重ctx.signal:切换文件时本次渲染会被中止,长任务应在关键节点检查signal.aborted

相关源码速查

文件说明
types.tsPreviewContext、PreviewInstance、PreviewPlugin三大接口定义
viewer.tsrenderFile():加载态、插件选择、实例挂载与错误处理
viewer.tsfindPlugin():按顺序match(),fallback 垫底
detect.tsnormalizeFile():扩展名 → MIME 归一化
support.tsisPreviewSupported():挂载前预判支持性
fallback.ts兜底插件:不支持格式的下载面板
lite.ts轻量入口:仅含运行时 + 图片 + PDF + fallback
plugins/全部内置格式插件实现,是最好的活教材

想深入时,建议直接打开 image.ts 或 cad.ts 这类内置插件对照协议读一遍——它们分别代表了"最简单"和"最复杂"两端的插件写法。协议本身保持如此精简,正是 Open File Viewer 能让任何新格式以最小成本融入统一预览容器的原因。

  • 前端
  • 音视频

【免费下载链接】open-file-viewer

A framework-agnostic embedded file viewer for vanilla JavaScript, React, Vue and Svelte. Put PDF, Office, images, media, archives, email, drawings, 3D, GIS and engineering files inside one stable container.

项目地址:https://gitcode.com/gh_mirrors/op/open-file-viewer
点击查看免费下载

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 14:36:11

UE4传送门完整落地指南:缓存工程、蓝图坐标变换与碰撞触发

简介&#xff1a;针对UE4蓝图开发者的传送门专题案例包&#xff0c;面向中初级游戏开发者及蓝图学习者&#xff0c;覆盖从基础触发器到复杂度较高的场景切换、网络同步等应用场景。压缩包共232个文件&#xff0c;大小约75.68MB&#xff0c;包含124个uasset蓝图与资产、10个umap…

作者头像 李华
网站建设 2026/10/10 14:36:10

SVM手写数字识别全流程:从数据预处理到参数调优实战

简介&#xff1a;这是一份基于支持向量机&#xff08;SVM&#xff09;的手写数字识别完整资源&#xff0c;面向计算机视觉与机器学习初学者&#xff0c;适用于课程设计、毕业设计及算法对比学习。资源以MNIST公开手写数字数据集为对象&#xff0c;完整覆盖六万张训练图片与一万…

作者头像 李华
网站建设 2026/10/10 14:35:33

火星月球陨石坑检测数据集:VOC与YOLO双格式目标检测实战指南

简介&#xff1a;面向目标检测与行星遥感研究的一份小型数据集&#xff0c;包含132张火星、月球表面陨石坑jpg图片&#xff0c;配套VOC格式xml与YOLO格式txt标注文件&#xff0c;共标注1044个keng类矩形框。使用labelImg工具按统一规则画框标注&#xff0c;类别一致、坐标信息完…

作者头像 李华
网站建设 2026/10/10 14:34:29

Spring DataSource原理剖析:连接池、自动配置与多数据源实战

1. 全局视角&#xff1a;为什么弄懂 DataSource 才算真正理解 Spring 的数据库原理直接说吧&#xff0c;Spring 的数据库原理这座大厦里&#xff0c;DataSource 就是地基中的地基。不管是 JdbcTemplate、MyBatis 还是 JPA&#xff0c;底层全部要跟数据库建立连接&#xff0c;而…

作者头像 李华
网站建设 2026/10/10 14:30:54

C++ Qt词法分析器课设:NFA/DFA状态图可视化与完整实现

简介&#xff1a;一套面向编译原理课程与期末课设场景的C/Qt词法分析器工程包&#xff0c;适合正在学习词法分析、自动机理论与GUI开发的学生参考。工具将源代码拆分为标记&#xff08;token&#xff09;&#xff0c;覆盖关键字、标识符、数字、运算符等常见规则&#xff0c;并…

作者头像 李华
网站建设 2026/10/10 14:30:14

Go并发面试题详解:两个goroutine交替打印1到100的三种解法

1. 题目拆解&#xff1a;面试官到底在考什么“两个 goroutine 轮流打印 1 到 100&#xff0c;一个打印奇数&#xff0c;一个打印偶数”——这道题在 Go 面试中出现的频率&#xff0c;高到几乎可以跟“反转链表”并列。我第一次在面试中被问到的时候&#xff0c;脑子里全是 chan…

作者头像 李华