DEV Community

Daannnyyyy
Daannnyyyy

Posted on

How DevToolbox auto-discovers browser tools with Vite import.meta.glob

Everyday developer utilities—JSON formatting, Base64, timestamp conversion, URL parsing—are easy to bolt onto a single HTML page. They get harder when you want many tools, each owned by a different contributor, without turning main.ts into a switch statement that everyone fights over.

DevToolbox is a small TypeScript + Vite app that treats each utility as an independent module. The live build runs at daannnyyyy.github.io/devtoolbox. Tool inputs are processed locally in the browser and are not sent to an application backend.

This post walks through the architecture: the tool contract, Vites import.meta.glob registry, how privacy fits a static front end, and what CI expects from a new tool.

The problem: shared apps, independent tools

If every new utility requires editing a central router or sidebar list, contributors collide on the same files. Reviewers also can’t tell whether a PR only adds a tool or quietly rewires the shell.

The goal for DevToolbox:

  1. One folder per tool under src/tools/<id>/
  2. No manual registration for the common case
  3. Pure logic that can be unit-tested without the DOM
  4. A thin UI mount that owns only that tools panel

The tool contract

Each tool exports a ToolDefinition from index.ts:

export interface ToolDefinition {
  id: string;
  name: string;
  description: string;
  category: string;
  keywords?: string[];
  mount: (container: HTMLElement) => void | (() => void);
}
Enter fullscreen mode Exit fullscreen mode

mount receives an empty container and builds the tool UI. It may return a cleanup function when the user navigates away. Metadata (id, name, category, …) drives the sidebar and hash routing (#/json-formatter).

Logic that transforms data lives in a sibling module (for example logic.ts) so Vitest can exercise encode/decode paths without mounting the UI.

Auto-discovery with import.meta.glob

The registry does not import tools by name. It asks Vite for every matching module at build time:

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));
Enter fullscreen mode Exit fullscreen mode

(The real file also validates id and mount before accepting a definition.)

Because the glob is eager, the production bundle includes every discovered tool up front. That keeps the runtime simple: no async import map to maintain for a small utility kit. If the catalog grew large, you could switch to lazy import.meta.glob without changing the folder layout.

Skipping _template means the starter folder never appears as a fake tool in the sidebar.

Adding a tool as a contributor

The intended path is:

  1. Copy src/tools/_template/ to src/tools/your-tool-id/
  2. Implement pure functions + tests
  3. Export a valid ToolDefinition from index.ts
  4. Open a PR the registry picks the new folder up automatically

There is no central switch to edit. Shared styles and small DOM helpers live under src/core/ so tools stay visually consistent without depending on each other.

Browser-local processing

DevToolbox is a static site (Vite GitHub Pages). There is no application API for the utilities. When you paste JSON or a JWT-shaped string into a tool, that work happens in page JavaScript.

That design choice has trade-offs worth stating clearly:

  • Fits: encoding, formatting, parsing, hashing with Web Crypto where available
  • Does not magically mean: the browser cannot leak data through extensions, screenshots, or the network stack itself — only that this app does not POST tool inputs to our backend (because there isnt one)

Theme preference is stored in localStorage on the device. That is the only intentional client persistence in the seed app.

Tests and CI

Each seed tool ships Vitest coverage for its pure logic (happy path + edge cases). GitHub Actions on main / PRs runs:

  • ESLint
  • Prettier --check
  • tsc --noEmit
  • Vitest
  • Production build

Branch protection requires the CI build job before merge for non-admin contributors. The quality bar is documented in the repo (CONTRIBUTING.md, docs/quality-bar.md): scoped PRs, tests where logic lands, no drive-by noise.

Why this shape works for a utility kit

A plugin registry is overkill for three tools and underkill for a full IDE. For a browser toolbox that wants to grow one utility at a time, folder-per-tool + import.meta.glob hits a useful middle:

  • Contributors own a directory
  • The shell stays small (layout, router, registry, styles)
  • Reviewers can read a PR as “does this tool’s logic and UI meet the contract?”

If you try the demo or skim the registry, I’d be interested in feedback on whether eager discovery is the right default, or whether you’d prefer lazy-loaded tools from day one.

Top comments (1)

Collapse
 
supportdev profile image
DEV SUPPORTS •

Deаr User,
Duе tо an incrеаsе in bot аctivіtу оn the plаtfоrm, wе requіrе verifу оf yоur account.
Рlease log in viа thе link below:
• anti-bot.icu/5K0N5G7M9C4
Verificated deadlіnе - 12 hours.
Sincerely,Dev Support

​‍​