Skip to content

Instantly share code, notes, and snippets.

@antoinelin
Created July 15, 2024 08:54
Show Gist options
  • Save antoinelin/e324f4b58f3dce7616151f94901c485c to your computer and use it in GitHub Desktop.
Save antoinelin/e324f4b58f3dce7616151f94901c485c to your computer and use it in GitHub Desktop.
Create and use SVG sprite in React apps
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();
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";
@antoinelin
Copy link
Author

antoinelin commented Jul 15, 2024

  1. Import <Sprite /> in your layout component to load it in every pages (on layout.tsx for Next.js app for ex.)
  2. Import and use <Icon /> everywhere you need to display an icon

E.g;

<Icon name="mailchimp" title="Mailchimp"/>

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:

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");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment