From ea162406020c1c6273bb75345fad3b51a9836dde Mon Sep 17 00:00:00 2001 From: Jake Lazaroff Date: Fri, 5 Jan 2024 19:53:03 -0500 Subject: [PATCH] Generate a static SVG sprite sheet --- astro/generate-a-static-svg-sprite-sheed.md | 77 +++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 astro/generate-a-static-svg-sprite-sheed.md diff --git a/astro/generate-a-static-svg-sprite-sheed.md b/astro/generate-a-static-svg-sprite-sheed.md new file mode 100644 index 0000000..b1b22fa --- /dev/null +++ b/astro/generate-a-static-svg-sprite-sheed.md @@ -0,0 +1,77 @@ +# Generate a static SVG sprite sheet + +Since learning about [SVG sprite sheets](/svg/create-an-svg-sprite-sheet.md), I've used them rather than SVGs inlined in HTML. + +Generally, my process looks something like this: + +1. Put all my SVGs in a folder. +2. Run [`svg-sprite`](https://www.npmjs.com/package/svg-sprite) on the command line to combine them into a single SVG sprite. +3. Include them in HTML with the `` tag. + +I usually run it out of band, immediately before actually building the website. My build command ends up looking something like `pnpm svg && pnpm build` (where those are both `package.json` scripts that build the sprite sheet and the rest of the site). + +No longer! At least for Astro sites. Arne Bahlo has a great tutorial on [statically generating open graph images using Astro API routes](https://arne.me/articles/static-og-images-in-astro), and I realized that I use do the same technique to generate SVG sprite sheets. + +The key insight is that in static mode, [API routes](https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes) get rendered to static files. So this roughly the same workflow, except `svg-sprite` gets called programmatically instead of on the command line. + +Enough introduction! Let's get to it. + +First, install `svg-sprite` (and `@types/svg-sprite` if you're using TypeScript). + +Then, add this `icons.svg.js` file to your `pages` directory: + +```js +import type { APIRoute } from "astro"; +import SVGSpriter from "svg-sprite"; +import { readdir, readFile } from "node:fs/promises"; +import { resolve } from "node:path"; + +interface Result { + symbol: { sprite: { contents: Buffer } }; +} + +const ICON_DIR = "./assets/icons"; + +export const GET: APIRoute = async function GET() { + // create an `svg-sprite` instance + const spriter = new SVGSpriter({ mode: { symbol: true } }); + + // add all the svgs + for (const svg of await readdir(ICON_DIR)) { + const path = resolve(ICON_DIR, svg); + spriter.add(path, svg, await readFile(path).then(file => file.toString())); + } + + // compile the svgs into a sprite sheet + const { result } = (await spriter.compileAsync()) as { result: Result }; + + // respond with the compiled svg + const svg = result.symbol.sprite.contents; + return new Response(svg, { headers: { "content-type": "image/svg+xml" } }); +}; +``` + +Every time there's a request to `/icons.svg`, that will read all the SVGs in `/assets/icons`, compile them to a sprite sheet and respond with it. (That sounds inefficient, but remember that it'll get compiled to a static file.) + +To actually use the resulting sprite, I have this Astro component: + +```astro +--- +interface Props { + icon: string; + size?: number; +} + +const { icon, size = 16 } = Astro.props; +--- + + + + +``` + +The `icon` prop is equal to whatever the file name of the original SVG was, minus the extension. So you'd use it like this: + +```astro + +```