Vite import.meta.glob 实现浏览器工具自动发现:DevToolbox 的插件化架构
译注:本文翻译自 dev.to 文章《How DevToolbox auto-discovers browser tools with Vite import.meta.glob》,作者 dan_f8d850ce77481b,原文链接见文末。DevToolbox 是一个基于 TypeScript + Vite 的开发者工具集合,演示地址为 daannnyyyy.github.io/devtoolbox,源码托管于 GitHub。工具输入均在浏览器本地处理,不发送至应用后端。
日常开发中常用的工具——JSON 格式化、Base64 编解码、时间戳转换、URL 解析——单独做一个 HTML 页面并不难。但当你想集成大量工具、且每个工具由不同贡献者维护时,问题就来了:main.ts 很容易变成一个所有人都在争抢修改的巨型 switch 语句。
DevToolbox 的做法是把每个工具视为独立模块。本文梳理其架构:工具契约、基于 Vite import.meta.glob 的注册表、静态前端如何与隐私诉求契合,以及 CI 对新工具的要求。
问题:共享应用与独立工具的矛盾
如果每新增一个工具都要修改中心路由或侧边栏列表,贡献者就会在同一批文件上产生冲突。评审者也无法一眼看出某个 PR 是单纯新增工具,还是悄悄改动了外壳逻辑。
DevToolbox 的目标是:
- 每个工具一个文件夹,位于
src/tools/<id>/ - 常见场景下无需手动注册
- 纯逻辑可脱离 DOM 进行单元测试
- 轻量 UI 挂载,只负责该工具自己的面板
工具契约
每个工具在 index.ts 中导出一个 ToolDefinition:
export interface ToolDefinition {
id: string;
name: string;
description: string;
category: string;
keywords?: string[];
mount: (container: HTMLElement) => void | (() => void);
}mount 接收一个空容器并构建工具 UI,当用户离开时可返回一个清理函数。元数据(id、name、category 等)驱动侧边栏和 hash 路由(如 #/json-formatter)。
负责数据转换的逻辑放在同级模块中(例如 logic.ts),这样 Vitest 无需挂载 UI 即可测试编码/解码路径。
用 import.meta.glob 自动发现
注册表并不按名称导入工具,而是在构建时向 Vite 请求所有匹配的模块:
const modules = import.meta.glob('../tools/*/index.ts', { eager: true });
const tools = Object.entries(modules)
.filter(([path]) => !path.includes('/_template/'))
.map(([, mod]) => mod.default ?? mod.tool)
.filter(Boolean)
.sort((a, b) => a.name.localeCompare(b.name));(实际文件在接受定义前还会校验 id 和 mount。)
由于 glob 使用了 eager 模式,生产包会预先包含所有被发现的工具。这让运行时保持简单:对于一个小型工具集,无需维护异步 import map。如果目录规模变大,可以切换到惰性 import.meta.glob,而无需改变文件夹结构。
跳过 _template 意味着模板文件夹永远不会作为假工具出现在侧边栏中。
贡献者如何新增工具
预期流程是:
- 将
src/tools/_template/复制为src/tools/your-tool-id/ - 实现纯函数与测试
- 从
index.ts导出合法的ToolDefinition - 提交 PR——注册表会自动识别新文件夹
无需编辑任何中心 switch。共享样式和小型 DOM 辅助函数位于 src/core/,让工具在视觉上保持一致,同时互不依赖。
浏览器本地处理
DevToolbox 是一个静态站点(Vite + GitHub Pages),工具本身没有应用 API。当你把 JSON 或 JWT 形式的字符串粘贴进工具时,处理发生在页面 JavaScript 中。
这一设计取舍值得明确说明:
- 适用场景: 编码、格式化、解析,以及在可用时使用 Web Crypto 进行哈希
- 并不等于: 浏览器不会通过扩展、截图或网络栈本身泄露数据——只是说明本应用不会把工具输入 POST 到我们的后端(因为根本没有后端)
主题偏好存储在设备的 localStorage 中,这是种子应用中唯一有意为之的客户端持久化。
测试与 CI
每个种子工具都为其纯逻辑提供了 Vitest 覆盖(正常路径 + 边界情况)。GitHub Actions 在 main 分支和 PR 上运行:
- ESLint
- Prettier
--check tsc --noEmit- Vitest
- 生产构建
对于非管理员贡献者,分支保护要求合并前通过 CI 的 build 任务。质量标准记录在仓库中(CONTRIBUTING.md、docs/quality-bar.md):PR 范围明确、逻辑所在之处有测试、不夹带无关改动。
为什么这种形态适合工具集
插件注册表对三个工具来说过于笨重,对完整 IDE 来说又不够用。对于一个希望逐个工具成长的浏览器工具箱,文件夹即工具 + import.meta.glob 恰好落在有用的中间地带:
- 贡献者拥有一个目录
- 外壳保持小巧(布局、路由、注册表、样式)
- 评审者可以把 PR 读作“这个工具的逻辑和 UI 是否符合契约?”
作者表示,欢迎就 eager 发现是否是正确的默认值、还是更倾向于从第一天起就使用惰性加载工具提供反馈。