<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daannnyyyy</title>
    <description>The latest articles on DEV Community by Daannnyyyy (@dan_f8d850ce77481b).</description>
    <link>https://dev.to/dan_f8d850ce77481b</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4146100%2Fef18dc47-6a7e-4f02-93de-a148e1b189ab.png</url>
      <title>DEV Community: Daannnyyyy</title>
      <link>https://dev.to/dan_f8d850ce77481b</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dan_f8d850ce77481b"/>
    <language>en</language>
    <item>
      <title>How DevToolbox auto-discovers browser tools with Vite import.meta.glob</title>
      <dc:creator>Daannnyyyy</dc:creator>
      <pubDate>Mon, 28 Sep 2026 00:11:32 +0000</pubDate>
      <link>https://dev.to/dan_f8d850ce77481b/how-devtoolbox-auto-discovers-browser-tools-with-vite-importmetaglob-3d6o</link>
      <guid>https://dev.to/dan_f8d850ce77481b/how-devtoolbox-auto-discovers-browser-tools-with-vite-importmetaglob-3d6o</guid>
      <description>&lt;p&gt;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 &lt;strong&gt;many tools&lt;/strong&gt;, each owned by a different contributor, without turning &lt;code&gt;main.ts&lt;/code&gt; into a switch statement that everyone fights over.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Daannnyyyy/devtoolbox" rel="noopener noreferrer"&gt;DevToolbox&lt;/a&gt; is a small TypeScript + Vite app that treats each utility as an independent module. The live build runs at &lt;a href="https://daannnyyyy.github.io/devtoolbox/" rel="noopener noreferrer"&gt;daannnyyyy.github.io/devtoolbox&lt;/a&gt;. Tool inputs are processed locally in the browser and are not sent to an application backend.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The problem: shared apps, independent tools
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The goal for DevToolbox:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One folder per tool&lt;/strong&gt; under &lt;code&gt;src/tools/&amp;lt;id&amp;gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No manual registration&lt;/strong&gt; for the common case&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pure logic&lt;/strong&gt; that can be unit-tested without the DOM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A thin UI mount&lt;/strong&gt; that owns only that tools panel&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The tool contract
&lt;/h2&gt;

&lt;p&gt;Each tool exports a &lt;code&gt;ToolDefinition&lt;/code&gt; from &lt;code&gt;index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ToolDefinition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;h2&gt;
  
  
  Auto-discovery with &lt;code&gt;import.meta.glob&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The registry does not import tools by name. It asks Vite for every matching module at build time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../tools/*/index.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;eager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/_template/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([,&lt;/span&gt; &lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The real file also validates &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;mount&lt;/code&gt; before accepting a definition.)&lt;/p&gt;

&lt;p&gt;Because the glob is &lt;strong&gt;eager&lt;/strong&gt;, 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 &lt;code&gt;import.meta.glob&lt;/code&gt; without changing the folder layout.&lt;/p&gt;

&lt;p&gt;Skipping &lt;code&gt;_template&lt;/code&gt; means the starter folder never appears as a fake tool in the sidebar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a tool as a contributor
&lt;/h2&gt;

&lt;p&gt;The intended path is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy &lt;code&gt;src/tools/_template/&lt;/code&gt; to &lt;code&gt;src/tools/your-tool-id/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Implement pure functions + tests&lt;/li&gt;
&lt;li&gt;Export a valid &lt;code&gt;ToolDefinition&lt;/code&gt; from &lt;code&gt;index.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Open a PR  the registry picks the new folder up automatically&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h2&gt;
  
  
  Browser-local processing
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That design choice has trade-offs worth stating clearly:&lt;/p&gt;

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

&lt;p&gt;Theme preference is stored in &lt;code&gt;localStorage&lt;/code&gt; on the device. That is the only intentional client persistence in the seed app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests and CI
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;ESLint&lt;/li&gt;
&lt;li&gt;Prettier &lt;code&gt;--check&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tsc --noEmit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Vitest&lt;/li&gt;
&lt;li&gt;Production build&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  Why this shape works for a utility kit
&lt;/h2&gt;

&lt;p&gt;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 + &lt;code&gt;import.meta.glob&lt;/code&gt; hits a useful middle:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Demo: &lt;a href="https://daannnyyyy.github.io/devtoolbox/" rel="noopener noreferrer"&gt;https://daannnyyyy.github.io/devtoolbox/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/Daannnyyyy/devtoolbox" rel="noopener noreferrer"&gt;https://github.com/Daannnyyyy/devtoolbox&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>showdev</category>
      <category>typescript</category>
      <category>vite</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
