@ofjs/usvg
v0.1.1
Published
Runtime <symbol>/<use> SVG sprite for Vue + a Vite plugin that bakes an external sprite.
Readme
@ofjs/usvg
Runtime <symbol>/<use> SVG sprite for Vue. Render icons through one shared,
deduplicated sprite — same icon used N times = one <symbol>. Works with
unplugin-icons components, any Vue
component that renders a single <svg>, or a raw ?raw SVG string.
Optional Vite plugin bakes an external sprite.svg (browser-cached, empty
DOM) and skips the runtime work entirely in production.
bun add @ofjs/usvgQuick start
<script setup lang="ts">
import { Usvg, usvg, usvgCache } from "@ofjs/usvg";
import IconDota from "~icons/simple-icons/dota2";
import checkRaw from "~icons/lucide/check?raw";
</script>
<template>
<!-- component form -->
<Usvg :as="IconDota" class="size-5 text-sky-500" />
<!-- slot form (class forwarded from the inner icon) -->
<Usvg><IconDota class="size-5" /></Usvg>
<!-- ?raw string -->
<Usvg :as="checkRaw" class="size-4" />
<!-- size prop: 1 / 2 / 4 numbers -->
<Usvg :as="IconDota" size="32" />
</template>// h() / render-function form (e.g. a TanStack column cell)
usvg(IconDota, { class: "size-4 text-red-600" });
usvg(checkRaw, { class: "size-4" });
// optional prewarm (module scope) → usvg() in a hot render is a pure cache hit
usvgCache(IconDota, checkRaw);<Usvg> without importing it
Register it globally (the only thing the Vue plugin does):
// main.ts
import { UsvgSprite } from "@ofjs/usvg";
app.use(UsvgSprite);
// now <Usvg> works in any template, no importUsvgSprite options: name (component tag, default Usvg), id (sprite
element id), prefix, inject (headFirst/headLast/bodyFirst/bodyLast),
strokeOverride (true→currentColor / color / false), spriteUrl.
The Vue plugin is optional — the runtime works standalone (import
Usvgor useusvg()). It only registers the global component.
Vite plugin — external sprite (recommended for prod)
// vite.config.ts
import UsvgPlugin from "@ofjs/usvg/vite";
export default defineConfig({
plugins: [Icons({ compiler: "vue3" }), UsvgPlugin()],
});- build: scans every
import … from '~icons/…'(nothing missed — ternaries, static maps, dynamic:asall still import the icon), bakesdist/usvg-sprite.svg, and<use>points at it. Aname → idmanifest is injected into the HTML so the runtime never renders a component just to get its id. - dev (default): the sprite stays in the DOM (full HMR).
UsvgPlugin({ dev: true })serves the external file in dev too. - Zero wiring: sprite URL + prefix + stroke are injected via
define, so the runtime auto-matches the build. Nothing to add inmain.ts.
UsvgPlugin options: fileName, prefix, strokeOverride, failOnError, dev.
Also exports UsvgResolver() for unplugin-vue-components (auto-import <Usvg>
without app.use).
SSR
The registry is string-based, so it works on the server. Inject the sprite into the HTML after render:
import { usvgRenderSSR } from "@ofjs/usvg";
html = html.replace("</body>", `${usvgRenderSSR()}</body>`);On the server pass icons as ?raw strings (a component needs a DOM to read its
markup).
API
| Export | From | What |
| ----------------------- | ---------------------- | -------------------------------- |
| Usvg | @ofjs/usvg | component (:as / slot, size) |
| usvg(icon, props?) | @ofjs/usvg | <use> vnode (h form) |
| usvgCache(...icons) | @ofjs/usvg | prewarm |
| UsvgSprite | @ofjs/usvg or /vue | Vue app plugin (global <Usvg>) |
| UsvgPlugin() | @ofjs/usvg/vite | Vite plugin (external sprite) |
| UsvgResolver() | @ofjs/usvg/vite | unplugin-vue-components resolver |
| usvgRenderSSR() | @ofjs/usvg | sprite markup for SSR |
| resolve / sizeAttrs | @ofjs/usvg | advanced |
Ids are content hashes (canonicalized), so an icon resolves to the same <symbol>
whether it comes from a component render, a ?raw string, or the build — the
runtime <use href> always matches the baked sprite.
Scripts
bun run build # emit dist/ (JS + d.ts)
bun run typecheck # vue-tsc over the package
bun run test:build # build the test app (test/) → emits usvg-sprite.svg
bun run test:id # canonicalize / id-consistency check
bun run test:e2e # rendered-component id == build manifest id