Created
July 15, 2024 08:54
-
-
Save antoinelin/e324f4b58f3dce7616151f94901c485c to your computer and use it in GitHub Desktop.
Create and use SVG sprite in React apps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from "fs-extra"; | |
import path from "path"; | |
import prettier from "prettier"; | |
import SVGSpriter from "svg-sprite"; | |
import { optimize } from "svgo"; | |
const SOURCE_PATH = path.resolve(__dirname, "../icons"); | |
const TYPES_OUTPUT_PATH = path.resolve(__dirname, "../components/Icon/Icon.types.ts"); | |
const SPRITE_CONTENT_OUTPUT_PATH = path.resolve(__dirname, "../components/Icon/sprite.ts"); | |
const optimizeSvg = (svgContent: string): string => { | |
const { data } = optimize(svgContent, { | |
floatPrecision: 2, | |
multipass: true, | |
}); | |
return data; | |
}; | |
const createSprite = async (iconPaths: string[]) => { | |
const spriter = new SVGSpriter({ | |
mode: { | |
symbol: {}, | |
}, | |
}); | |
for (const iconPath of iconPaths) { | |
const content = fs.readFileSync(iconPath, "utf-8"); | |
const optimizedContent = optimizeSvg(content); | |
spriter.add(path.basename(iconPath), null, optimizedContent); | |
} | |
const { result } = await spriter.compileAsync(); | |
const sprite: { contents: Buffer } = result.symbol.sprite; | |
return sprite.contents.toString(); | |
}; | |
const generateTypesFileContent = async (iconPaths: string[]) => { | |
const fileContent = await prettier.format( | |
` | |
export type IconName = ${iconPaths | |
.map((iconPath) => `'${path.basename(iconPath).replace(".svg", "")}'`) | |
.join(" | ")} | |
`, | |
{ | |
parser: "typescript", | |
printWidth: 100, | |
}, | |
); | |
return fileContent; | |
}; | |
const generateSpriteFileContent = async (iconPaths: string[]) => { | |
const sprite = await createSprite(iconPaths); | |
const symbols = sprite.replace(/^<\?xml .*\?>|<svg.*?>|<\/svg>$/g, ""); | |
const fileContent = await prettier.format( | |
` | |
export const sprite = ${JSON.stringify(symbols)} | |
`, | |
{ | |
parser: "typescript", | |
printWidth: 100, | |
}, | |
); | |
return fileContent; | |
}; | |
const getIconList = async (iconsFolderPath: string) => { | |
const iconList = await fs.readdir(iconsFolderPath); | |
return iconList.map((iconPath) => path.resolve(iconsFolderPath, iconPath)); | |
}; | |
const main = async () => { | |
const iconList = await getIconList(SOURCE_PATH); | |
const spriteFileContent = await generateSpriteFileContent(iconList); | |
const typesFileContent = await generateTypesFileContent(iconList); | |
await Promise.all([ | |
fs.writeFile(SPRITE_CONTENT_OUTPUT_PATH, spriteFileContent, "utf-8"), // write sprite file | |
fs.writeFile(TYPES_OUTPUT_PATH, typesFileContent, "utf-8"), // write types file | |
]); | |
console.log(`Successfully generated\n ${SPRITE_CONTENT_OUTPUT_PATH}\n ${TYPES_OUTPUT_PATH}`); | |
}; | |
main(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { IconName } from "./Icon.types"; | |
import { sprite } from "./sprite"; | |
export type IconProps = { | |
name: IconName; | |
title?: string; | |
label?: string; | |
className?: string; | |
style?: React.CSSProperties; | |
}; | |
export const Icon: React.FC<IconProps> = ({ name, title, label, style, className }) => { | |
return ( | |
<svg className={className} aria-label={label} style={style}> | |
{title && <title>{title}</title>} | |
<use xlinkHref={`#${name}`} /> | |
</svg> | |
); | |
}; | |
Icon.displayName = "Icon"; | |
export const Sprite: React.FC = () => { | |
return ( | |
<svg | |
aria-hidden="true" | |
width={0} | |
height={0} | |
style={{ | |
position: "absolute", | |
}} | |
dangerouslySetInnerHTML={{ | |
__html: sprite, | |
}} | |
/> | |
); | |
}; | |
Sprite.displayName = "Sprite"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
<Sprite />
in your layout component to load it in every pages (onlayout.tsx
for Next.js app for ex.)<Icon />
everywhere you need to display an iconE.g;
This script generates both sprite file + type file to get the Icon component fully typed, adding autocompletion to
name
prop on<Icon />
component.Don't forget to replace paths to your own directories: