Skip to content

Commit

Permalink
Generate a static SVG sprite sheet
Browse files Browse the repository at this point in the history
  • Loading branch information
jakelazaroff committed Jan 6, 2024
1 parent 506b97e commit ea16240
Showing 1 changed file with 77 additions and 0 deletions.
77 changes: 77 additions & 0 deletions astro/generate-a-static-svg-sprite-sheed.md
Original file line number Diff line number Diff line change
@@ -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 `<use>` 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;
---
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size}>
<use href={`/icons.svg#${icon}`}></use>
</svg>
```

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
<Icon icon="heart" />
```

0 comments on commit ea16240

Please sign in to comment.