{"content":"Making Icon Sets Easy With Web Origami\n\nOver the years, I’ve used different icon sets on my blog. Right now I use Heroicons.\n\nThe recommended way to use them is to copy/paste the source from the website directly into your HTML. It’s a pretty straightforward process:\n\n * Go to the website\n * Search for the icon you want\n * Hover it\n * Click to “Copy SVG”\n * Go back to your IDE and paste it\n\nIf you’re using React or Vue, there are also npm packages you can install so you can import the icons as components.\n\nBut I’m not using either of those frameworks, so I need the raw SVGs and there’s no npm i for those so I have to manually grab the ones I want.\n\nIn the past, my approach has been to copy the SVGs into individual files in my project, like:\n\nsrc/\n icons/\n home.svg\n about.svg\n search.svg\n\nThen I have a “component” for reading those icons from disk which I use in my template files to inline the SVGs in my HTML. For example:\n\n// Some page template file\nimport { Icon } from './Icon.js'\nconst template = `
${Icon('search.svg')} Search
`\n\n// Icon.js\nimport fs from 'fs'\nimport path from 'path'\nconst __dirname = /* Do the stuff to properly resolve the file path */;\nexport const Icon = (name) => fs.readFileSync(\n path.join(__dirname, 'icons', name),\n 'utf8'\n).toString();\n\nIt’s fine. It works. It’s a lot of node boilerplate to read files from disk.\n\nBut changing icons is a bit of a pain. I have to find new SVGs, overwrite my existing ones, re-commit them to source control, etc.\n\nI suppose it would be nice if I could just npm i heroicons and get the raw SVGs installed into my node_modules folder and then I could read those. But that has its own set of trade-offs. For example:\n\n * Names are different between icon packs, so when you switch, names don’t match. For example, an icon might be named search in one pack and magnifying-glass in another. So changing sets requires going through all your templates and updating references.\n * Icon packs are often quite large and you only need a subset. npm i icon-pack might install hundreds or even thousands of icons I don’t need.\n\nSo the project’s npm packages don’t provide the raw SVGs. The website does, but I want a more programatic way to easily grab the icons I want.\n\nHow can I do this?\n\nENTER ORIGAMI\n\nI’m using Web Origami for my blog which makes it easy to map icons I use in my templates to Heroicons hosted on Github. It doesn’t require an npm install or a git submodule add. Here’s an snippet of my file:\n\n{\n home: https://raw.githubusercontent.com/tailwindlabs/heroicons/refs/heads/master/optimized/24/outline/home.svg,\n about: https://raw.githubusercontent.com/tailwindlabs/heroicons/refs/heads/master/optimized/24/outline/question-mark-circle.svg,\n search: https://raw.githubusercontent.com/tailwindlabs/heroicons/refs/heads/master/optimized/24/outline/magnifying-glass.svg\n}\n\nAs you can see, I name my icon (e.g. search) and then I point it to the SVG as hosted on Github via the Heroicons repo. Origami takes care of fetching the icons over the network and caching them in-memory.\n\nBeautiful, isn’t it? It kind of reminds me of import maps where you can map a bare module specifier to a URL (and Deno’s semi-abandoned HTTP imports which were beautiful in their own right).\n\nHOW IT WORKS\n\nOrigami makes file paths first-class citizens of the language — even “remote” file paths — so it’s very simple to create a single file that maps your icon names in a codebase to someone else’s icon names from a set, whether those are being installed on disk via npm or fetched over the internet.\n\nTo simplify my example earlier, I can have a file like icons.ori:\n\n{\n home.svg: https://example.com/path/to/home.svg\n about.svg: https://example.com/path/to/information-circle.svg\n search.svg: https://example.com/path/to/magnifying-glass.svg\n}\n\nThen I can reference those icons in my templates like this:\n\n
${icons.ori/home.svg} Search
\n\nEasy-peasy! And when I want to change icons, I simply update the entries in icons.ori to point somewhere else — at a remote or local path.\n\nAnd if you really want to go the extra mile, you can use Origami’s caching feature:\n\nTree.cache(\n {\n home.svg: https://raw.github.com/path/to/home.svg\n about.svg: https://raw.github.com/path/to/information-circle.svg\n search.svg: https://raw.github.com/path/to/magnifying-glass.svg\n },\n Origami.projectRoot()/cache\n)\n\nRather than just caching the files in memory, this will cache them to a local folder like this:\n\ncache/\n home.svg\n about.svg\n search.svg\n\nWhich is really cool because now when I run my site locally I have a folder of SVG files cached locally that I can look at and explore (useful for debugging, etc.)\n\nThis makes vendoring really easy if I want to put these in my project under source control. Just run the file once and boom, they’re on disk!\n\nThere’s something really appealing to me about this. I think it’s because it feels very “webby” — akin to the same reasons I liked HTTP imports in Deno. You declare your dependencies with URLs, then they’re fetched over the network and become available to the rest of your code. No package manager middleman introducing extra complexity like versioning, transitive dependencies, install bloat, etc.\n\nWhat’s cool about Origami is that handling icons like this isn’t a “feature” of the language. It’s an outcome of the expressiveness of the language. In some frameworks, this kind of problem would require a special feature (that’s why you have special npm packages for implementations of Heroicons in frameworks like react and vue). But because of the way Origami is crafted as a tool, it sort of pushes you towards crafting solutions in the same manner as you would with web-based technologies (HTML/CSS/JS). It helps you speak “web platform” rather than some other abstraction on top of it. I like that.\n\n----------------------------------------\n\nReply via: Email · Mastodon · Bluesky","contentType":"text/plain;utf-8","attachments":[],"quotePin":""}