vite-plugin-svg-symbols
v1.2.0
Published
Vite plugin for creating SVG symbol sprite
Maintainers
Readme
vite-plugin-svg-symbols
Vite plugin for creating SVG symbol sprite 🧞♂️
Features
- Create a single sprite from SVG files
- Use a sprite as a static file for JS bundle optimization
- The production build includes only the icons that are actually used
- Support mass import or by one
- Compatible with modern frameworks: Vue / Svelte / React / Solid / Preact
Navigation
Getting Started
1. Install using npm:
npm i vite-plugin-svg-symbols -D2. Add to your vite.config.ts:
import { defineConfig } from 'vite';
import { fileURLToPath, URL } from 'node:url';
import useSvgSymbols from 'vite-plugin-svg-symbols'; // ← [1] import plugin
export default defineConfig({
plugins: [
useSvgSymbols(), // ← [2] use plugin
],
resolve: {
alias: {
'@icons': fileURLToPath(new URL('./src/icons', import.meta.url)), // ← [3] add alias
},
},
});3. Add to your vite-env.d.ts or env.d.ts:
/// <reference types="vite-plugin-svg-symbols/client" />[!TIP] If your project doesn't include an
env.d.tsfile, you can specify the type reference intsconfig.app.json:"compilerOptions": { "types": ["vite-plugin-svg-symbols/client"] }
4. Finally, add the vpss command before type checking in package.json:
[!IMPORTANT] The
vpsscommand generates types for icons to avoid errors such as:
error TS2614: Module 'svg:symbols@icons' has no exported member ...
"scripts": {
"build": "vpss && tsc -b && vite build"
}Another option is to run type checking after the build process; since types are generated during the build, no extra command is required:
"scripts": {
"build": "vite build && tsc -b"
}Usage
For example, you have src/icons/cat.svg and src/icons/dog.svg and @icons alias in vite.config.ts
Plain import
import dogIcon from '@icons/dog.svg?symbol'; // import icon as symbol hrefMass import
import { dogIcon, catIcon } from 'svg:symbols@icons'; // import two icons as symbol hrefsIcon packages
You can add alias for library, for example @lucide (lucide):
// vite.config.ts
alias: {
'@lucide': fileURLToPath(new URL('./node_modules/lucide-static/icons/', import.meta.url)),
},Then import icons from lucide package:
import bananaIcon from '@lucide/banana.svg?symbol';import { appleIcon, bananaIcon } from 'svg:symbols@lucide';Import names
By default, icon names are defined in camel case with the Icon suffix:
banana.svg → bananaIcon
a-arrow-up.svg → aArrowUpIcon
arrow-down-a-z.svg → arrowDownAZIcon
arrow-down-1-0.svg → arrowDown10Icon
// subdir
home/foo.svg → homeFooIcon
// when same names
arrow-down-0-1.svg → arrowDown01Icon
arrow-down-01.svg → arrowDown01Icon2 (because arrowDown01Icon already exist)Examples
When you have src/icons/cat.svg and src/icons/dog.svg and @icons alias in vite.config.ts
Vue
Plain import:
<script setup lang="ts">
import dogIcon from '@icons/dog.svg?symbol';
</script>
<template>
<svg aria-hidden="true">
<use :href="dogIcon" /> // use dogIcon from script
</svg>
<svg aria-hidden="true">
<use href="@icons/cat.svg?symbol" /> // or import icon directly
</svg>
<!-- Don't do this -->
<svg aria-hidden="true">
<use :href="'@icons/cat.svg?symbol'" /> // ⚠️ dynamic path not working
</svg>
<!-- :( -->
</template>Mass import:
<script setup lang="ts">
import { dogIcon, catIcon } from 'svg:symbols@icons'; // import two icons
</script>
<template>
<svg aria-hidden="true">
<use :href="dogIcon" />
</svg>
<svg aria-hidden="true">
<use :href="catIcon" />
</svg>
</template>You can create a very simple component BaseIcon.vue:
<script setup lang="ts">
defineProps<{ src: string }>()
</script>
<template>
<svg aria-hidden="true">
<use :href="src" />
</svg>
</template><script setup lang="ts">
import { dogIcon, catIcon } from 'svg:symbols@icons';
import BaseIcon from '@/components/BaseIcon.vue';
</script>
<template>
<BaseIcon :src="dogIcon" />
<BaseIcon :src="catIcon" />
</template>Svelte
Plain import:
<script lang="ts">
import dogIcon from '@icons/dog.svg?symbol';
</script>
<main>
<svg aria-hidden="true">
<use href={dogIcon} />
</svg>
</main>Mass import:
<script lang="ts">
import { dogIcon, catIcon } from 'svg:symbols@icons'; // import two icons
</script>
<main>
<svg aria-hidden="true">
<use href={dogIcon} />
</svg>
<svg aria-hidden="true">
<use href={catIcon} />
</svg>
</main>You can create a very simple component BaseIcon.svelte:
<script lang="ts">
export let src = '';
</script>
<svg aria-hidden="true">
<use href={src} />
</svg><script lang="ts">
import { dogIcon, catIcon } from 'svg:symbols@icons';
import BaseIcon from './BaseIcon.svelte';
</script>
<main>
<BaseIcon src={dogIcon} />
<BaseIcon src={catIcon} />
</main>React & Solid & Preact
Plain import:
import dogIcon from '@icons/dog.svg?symbol'
function App() {
return (
<>
<svg aria-hidden="true">
<use href={dogIcon} />
</svg>
</>
)
}
export default AppMass import:
import { dogIcon, catIcon } from 'svg:symbols@icons' // import two icons
function App() {
return (
<>
<svg aria-hidden="true">
<use href={dogIcon} />
</svg>
<svg aria-hidden="true">
<use href={catIcon} />
</svg>
</>
)
}
export default AppYou can create a very simple component BaseIcon.tsx:
function BaseIcon({ src = '' }) {
return (
<svg aria-hidden="true">
<use href={src} />
</svg>
)
}
export default BaseIconimport { dogIcon, catIcon } from 'svg:symbols@icons' // import two icons
import BaseIcon from './BaseIcon'
function App() {
return (
<>
<BaseIcon src={dogIcon} />
<BaseIcon src={catIcon} />
</>
)
}
export default AppOptions
- Inject: shouldInjectToHtml | injectAttrs
- Transform: Icon | IconId | Sprite | Symbol | ImportName | ImportComment
type SymbolPayload = {
symbolId: string
symbolBody: string
symbolViewBox: string
svgBody: string
svgPath: string
svgAttrs: Record<string, string>
}
type SvgSymbolsPluginOptions = {
fileName?: string
shouldInjectToHtml?: boolean
injectAttrs?: Record<string, string | boolean | undefined>
transformIcon?: (iconCode: string, iconPath: string) => string | Promise<string>
transformIconId?: (iconId: string, iconPath: string) => string | Promise<string>
transformSprite?: (body: string) => string | Promise<string>
transformSymbol?: (symbolCode: string, payload: SymbolPayload) => string | Promise<string>
transformImportName?: (name: string, iconPath: string) => string | Promise<string>
transformImportComment?: (comment: string, iconPath: string) => string | Promise<string>
}fileName
{
fileName: 'my-sprite-name-[hash]', // default 'sprite-[hash]'
}shouldInheritAttrs
{
shouldInheritAttrs: [], // List of SVG attributes to inherit in <symbol>
}
// default: [
// 'fill',
// 'stroke',
// 'stroke-dasharray',
// 'stroke-dashoffset',
// 'stroke-linecap',
// 'stroke-linejoin',
// 'stroke-miterlimit',
// 'stroke-opacity',
// 'stroke-width',
// ];<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="8" cy="8" r="8"></circle>
</svg>
<!-- Become -->
<symbol viewBox="0 0 16 16" id="some-id">
<g fill="none" stroke="currentColor" stroke-width="2">
<circle cx="8" cy="8" r="8"></circle>
</g>
</symbol>shouldInjectToHtml
{
shouldInjectToHtml: true, // When set to true, injects the sprite into index.html
}injectAttrs
{
injectAttrs: { ... }, // Defines sprite attributes when injected into index.html
}
// default:
// {
// 'aria-hidden': 'true',
// style: 'position:absolute;top:0;left:0;width:1px;height:1px;visibility:hidden;opacity:0;',
// }transformIcon
// Callback function to transform an icon body, for example, to optimize it with svgo:
const { optimize } = require('svgo');
const svgoOptions = {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeComments: true,
},
},
},
],
};{
transformIcon: (iconCode, iconPath) => {
const optimizationResult = optimize(iconCode, svgoOptions);
return optimizationResult.data;
},
}transformIconId
// Callback function to transform an icon id, for example, when you have:
// src/icons/cat.svg
{
transformIconId: (iconId, iconPath) => {
console.log(iconId); // 'icons-cat'
console.log(iconPath); // '/Users/werty1001/Desktop/app/src/icons/cat.svg'
return iconId;
},
}transformSprite
// Callback function to transform the sprite body, for example, to prepend <defs>:
{
transformSprite: (body) => {
return '<defs>...</defs>'.concat(body);
},
}transformSymbol
// Callback function to transform the sprite symbol, for example, when you have:
// src/icons/cat.svg
{
transformSymbol: (symbolCode, payload) => {
console.log(symbolCode); // '<symbol ...'
console.log(payload);
// {
// symbolId: 'icons-cat',
// symbolBody: '<path ...',
// symbolViewBox: '0 0 24 24',
// svgBody: '<path ...',
// svgPath: '/Users/werty1001/Desktop/app/src/icons/cat.svg',
// svgAttrs: { viewBox: '0 0 24 24', ... }
// }
return symbolCode;
},
}transformImportName
// Callback function to transform import name, for example, when you have:
// src/icons/cat.svg
{
transformImportName: (name, iconPath) => {
console.log(name); // 'catIcon'
console.log(iconPath); // '/Users/werty1001/Desktop/app/src/icons/cat.svg'
return name;
},
}
// import { catIcon } from 'svg:symbols@icons';transformImportComment
// Callback function to transform import comment,
// for example, when you have lucide package:
import { basename } from 'path';
const lucideComment = `/**
* Lucide
* @see https://lucide.dev/icons/[name]
*/`;{
// Replace the default comment to enable IDEs to show
// tooltips with a link to the icon at https://lucide.dev
transformImportComment: (comment, iconPath) => {
if (iconPath.includes('lucide-static')) {
return lucideComment.replace('[name]', basename(iconPath, '.svg'));
}
return comment;
},
}