UNPKG

vite-plugin-svg-symbols

Version:
518 lines (456 loc) 12.9 kB
# vite-plugin-svg-symbols [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&color=0071e3&labelColor=1d1d1f)](https://raw.githubusercontent.com/werty1001/vite-plugin-svg-symbols/main/LICENSE) [![](https://img.shields.io/npm/v/vite-plugin-svg-symbols?style=flat-square&color=0071e3&labelColor=1d1d1f)](https://www.npmjs.com/package/vite-plugin-svg-symbols) [![](https://img.shields.io/npm/dm/vite-plugin-svg-symbols?style=flat-square&color=0071e3&labelColor=1d1d1f)](https://www.npmjs.com/package/vite-plugin-svg-symbols) ![](https://img.shields.io/github/stars/werty1001/vite-plugin-svg-symbols?style=flat-square&color=0071e3&labelColor=1d1d1f) > 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](#vue) / [Svelte](#svelte) / [React](#react--solid--preact) / [Solid](#react--solid--preact) / [Preact](#react--solid--preact) ### Navigation * [Getting Started](#getting-started) * [Usage](#usage) * [Examples](#examples) * [Options](#options) <p align="center"> <a href="#vite-plugin-svg-symbols"><img align="center" src="https://werty1001.github.io/sep.svg" alt=""></a> </p> ## Getting Started **1.** Install using npm: ```bash npm i vite-plugin-svg-symbols -D ``` **2.** Add to your `vite.config.ts`: ```js 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`: ```ts /// <reference types="vite-plugin-svg-symbols/client" /> ``` > [!TIP] > If your project doesn't include an `env.d.ts` file, you can specify the type reference in `tsconfig.app.json`: > ```json > "compilerOptions": { > "types": ["vite-plugin-svg-symbols/client"] > } > ``` **4.** Finally, add the `vpss` command **before** type checking in `package.json`: > [!IMPORTANT] > **The `vpss` command generates types for icons to avoid errors such as:** > > `error TS2614: Module 'svg:symbols@icons' has no exported member ...` ```json "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: ```json "scripts": { "build": "vite build && tsc -b" } ``` <p align="center"> <a href="#vite-plugin-svg-symbols"><img align="center" src="https://werty1001.github.io/sep.svg" alt=""></a> </p> ## Usage For example, you have `src/icons/cat.svg` and `src/icons/dog.svg` and **@icons** alias in `vite.config.ts` #### Plain import ```js import dogIcon from '@icons/dog.svg?symbol'; // import icon as symbol href ``` #### Mass import ```js import { dogIcon, catIcon } from 'svg:symbols@icons'; // import two icons as symbol hrefs ``` #### Icon packages You can add alias for library, for example **@lucide** ([lucide](https://www.npmjs.com/package/lucide-static)): ```js // vite.config.ts alias: { '@lucide': fileURLToPath(new URL('./node_modules/lucide-static/icons/', import.meta.url)), }, ``` Then import icons from **lucide** package: ```js import bananaIcon from '@lucide/banana.svg?symbol'; ``` ```js import { appleIcon, bananaIcon } from 'svg:symbols@lucide'; ``` #### Import names By default, icon names are defined in camel case with the `Icon` suffix: ```js 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) ``` <p align="center"> <a href="#vite-plugin-svg-symbols"><img align="center" src="https://werty1001.github.io/sep.svg" alt=""></a> </p> ## Examples When you have `src/icons/cat.svg` and `src/icons/dog.svg` and **@icons** alias in `vite.config.ts` > [Vue](#vue) / [Svelte](#svelte) / [React](#react--solid--preact) / [Solid](#react--solid--preact) / [Preact](#react--solid--preact) ### Vue Plain import: ```vue <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: ```vue <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**: ```vue <script setup lang="ts"> defineProps<{ src: string }>() </script> <template> <svg aria-hidden="true"> <use :href="src" /> </svg> </template> ``` ```vue <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> ``` <p align="center"> <a href="#vite-plugin-svg-symbols"><img align="center" src="https://werty1001.github.io/sep.svg" alt=""></a> </p> ### Svelte Plain import: ```svelte <script lang="ts"> import dogIcon from '@icons/dog.svg?symbol'; </script> <main> <svg aria-hidden="true"> <use href={dogIcon} /> </svg> </main> ``` Mass import: ```svelte <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**: ```svelte <script lang="ts"> export let src = ''; </script> <svg aria-hidden="true"> <use href={src} /> </svg> ``` ```svelte <script lang="ts"> import { dogIcon, catIcon } from 'svg:symbols@icons'; import BaseIcon from './BaseIcon.svelte'; </script> <main> <BaseIcon src={dogIcon} /> <BaseIcon src={catIcon} /> </main> ``` <p align="center"> <a href="#vite-plugin-svg-symbols"><img align="center" src="https://werty1001.github.io/sep.svg" alt=""></a> </p> ### React & Solid & Preact Plain import: ```tsx import dogIcon from '@icons/dog.svg?symbol' function App() { return ( <> <svg aria-hidden="true"> <use href={dogIcon} /> </svg> </> ) } export default App ``` Mass import: ```tsx 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 App ``` You can create a very simple component **BaseIcon.tsx**: ```tsx function BaseIcon({ src = '' }) { return ( <svg aria-hidden="true"> <use href={src} /> </svg> ) } export default BaseIcon ``` ```tsx import { dogIcon, catIcon } from 'svg:symbols@icons' // import two icons import BaseIcon from './BaseIcon' function App() { return ( <> <BaseIcon src={dogIcon} /> <BaseIcon src={catIcon} /> </> ) } export default App ``` <p align="center"> <a href="#vite-plugin-svg-symbols"><img align="center" src="https://werty1001.github.io/sep.svg" alt=""></a> </p> ## Options * Inject: [shouldInjectToHtml](#shouldinjecttohtml) | [injectAttrs](#injectattrs) * Transform: [Icon](#transformicon) | [IconId](#transformiconid) | [Sprite](#transformsprite) | [Symbol](#transformsymbol) | [ImportName](#transformimportname) | [ImportComment](#transformimportcomment) ```ts 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 ```js { fileName: 'my-sprite-name-[hash]', // default 'sprite-[hash]' } ``` #### shouldInheritAttrs ```js { 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', // ]; ``` ```html <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 ```js { shouldInjectToHtml: true, // When set to true, injects the sprite into index.html } ``` #### injectAttrs ```js { 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 ```js // 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, }, }, }, ], }; ``` ```js { transformIcon: (iconCode, iconPath) => { const optimizationResult = optimize(iconCode, svgoOptions); return optimizationResult.data; }, } ``` #### transformIconId ```js // 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 ```js // Callback function to transform the sprite body, for example, to prepend <defs>: { transformSprite: (body) => { return '<defs>...</defs>'.concat(body); }, } ``` #### transformSymbol ```js // 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 ```js // 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 ```js // 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] */`; ``` ```js { // 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; }, } ``` <p align="center"> <a href="#vite-plugin-svg-symbols"><img align="center" src="https://werty1001.github.io/sep.svg" alt=""></a> </p>