scripts/generate.js reads SVG folders and produces the per-icon .svelte components and barrel files that consumers import from. This page walks through what it does, line by line.
What it produces
For each SETS entry, the script writes:
src/lib/<set>/
icons/
Acorn.svelte
Airplane.svelte
...
index.ts - One
.sveltecomponent per icon, named by PascalCasing the stem fromnameFromFile. - One
index.tsbarrel that exports every icon under its component name.
The SETS config
const SETS = [
{
name: 'phosphor',
variants: {
regular: 'static/svg/phosphor-regular',
light: 'static/svg/phosphor-light',
fill: 'static/svg/phosphor-fill'
},
defaultVariant: 'regular',
nameFromFile: (filename, variant) =>
filename.replace('.svg', '').replace(`-${variant}`, ''),
stripSuffix: { fill: '-fill', light: '' }
},
{
name: 'remix',
variants: {
fill: 'static/svg/remixicons-fill',
line: 'static/svg/remixicons-line'
},
defaultVariant: 'line',
nameFromFile: (filename, variant) =>
filename.replace('.svg', '').replace(`-${variant}`, ''),
stripSuffix: {}
},
{
name: 'flowbite',
variants: {
outline: 'static/svg/flowbite-outline',
solid: 'static/svg/flowbite-solid'
},
defaultVariant: 'outline',
nameFromFile: (filename) => filename.replace('.svg', ''),
stripSuffix: {}
},
{
name: 'hero',
variants: {
outline: 'static/svg/heroicons-outline',
solid: 'static/svg/heroicons-solid'
},
defaultVariant: 'outline',
nameFromFile: (filename) => filename.replace('.svg', ''),
stripSuffix: {}
},
{
name: 'ion',
variants: {
outline: 'static/svg/ionicons-outline',
filled: 'static/svg/ionicons-filled'
},
defaultVariant: 'outline',
nameFromFile: (filename, variant) =>
filename.replace('.svg', '').replace(`-${variant}`, ''),
stripSuffix: {}
}
]; Each entry tells the script:
- What to call the resulting library (
name→ subpath and folder). - Which folders contain the source SVGs (
variants). - What to default to (
defaultVariant). - How to derive the icon’s name from its filename (
nameFromFile). - How to clean up variant suffixes (
stripSuffix).
The pipeline
1. Read & match
For each set, the script reads every SVG file from each variant directory. It uses nameFromFile to compute the icon’s stem, then groups files by stem. An icon needs a file for at least one variant to be generated - it doesn’t need to ship in all three.
2. Strip the wrapper
Each SVG comes out of the source folders looking roughly like:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none"/>
<path d="…" stroke="currentColor" …/>
</svg> The script:
- Removes the outer
<svg …>and closing</svg>. - Removes the bounding
<rect width="…" height="…" fill="none"/>. - Normalizes hardcoded colors (
stroke="black",fill="black") tocurrentColor. - Wraps the remaining inner markup in
<g fill="…">— using the SVG’s ownfillattribute if present, orcurrentColorif absent — so fill-type icons (Phosphor fill, Ion filled, etc.) whose paths have nofillattribute inherit the icon’s color instead of rendering as SVG-default black.
The result is the svg string the component embeds. See AnimatedIcon for why inner-only is what the engine expects.
3. Write per-icon components
For each icon:
<!-- src/lib/phosphor/icons/Acorn.svelte (auto-generated) -->
<script lang="ts">
import AnimatedIcon from '../../core/AnimatedIcon.svelte';
const VARIANTS: Record<string, string> = {
regular: `<g fill="currentColor"><path d="…" fill="none" stroke="currentColor" …/></g>`,
light: `<g fill="currentColor"><path d="…" stroke-width="12"/></g>`,
fill: `<g fill="currentColor"><path d="…"/></g>`
};
interface Props {
variant?: string;
[key: string]: unknown;
}
let { variant = 'regular', ...rest }: Props = $props();
</script>
<AnimatedIcon svg={VARIANTS[variant]} {...rest} /> Each generated file:
- Embeds every variant as a raw string in a
VARIANTS: Record<string, string>constant, wrapped in<g fill="currentColor">so all paths inherit the icon’s color. - Forwards everything else (
...rest) toAnimatedIcon, so all the props documented in Usage work without re-declaring them.
4. Write the barrel
// src/lib/phosphor/index.ts (one per library)
export { default as Acorn } from './icons/Acorn.svelte';
export { default as Airplane } from './icons/Airplane.svelte';
// ... one line per icon in the library The barrel is what consumers get when they import { Gear } from 'svelte-animated-icon/phosphor'. With named imports, bundlers only follow the lines they actually use. See Tree Shaking.
Running it
node scripts/generate.js There are no flags or arguments. The script is idempotent - re-running it overwrites the same files with no leftover state.
When to regenerate
- After adding or removing SVGs in a variant folder.
- After upgrading the source icon set (Phosphor, Remix, etc.) to a new version.
- After adding a new library - see Adding an Icon Library.
- After changing anything in
SETS.
Commit the generated files. Consumers install the published package and don’t run this script themselves.
Gotchas
- Don’t hand-edit generated files. Your changes are wiped on the next run. Customize at the consumer side instead - see Custom SVG Icons.
nameFromFilemust be deterministic. Two different filenames producing the same component name will collide silently.- Empty folders. A variant folder with no SVGs is silently skipped - make sure the path is right if you expect icons to appear.
- Non-256 viewBoxes. The script doesn’t normalize viewBox; it preserves whatever the source SVG had. Icons with a different viewBox will render correctly but templates may scale oddly. Normalize upstream.