@neodx/svg
Advanced tools
Comparing version
import * as esbuild from 'esbuild'; | ||
import { S as SvgPluginParams } from './_internal/unplugin-b0f65603.js'; | ||
import { S as SvgPluginParams } from './_internal/unplugin-bae8a85b.js'; | ||
@@ -4,0 +4,0 @@ declare const _default: (options: SvgPluginParams) => esbuild.Plugin; |
@@ -1,9 +0,9 @@ | ||
import { C as CreateSpriteBuilderParams, S as SpriteBuilder } from './_internal/create-sprite-builder-f7c30e64.js'; | ||
export { c as createSpriteBuilder } from './_internal/create-sprite-builder-f7c30e64.js'; | ||
import { C as CreateSpriteBuilderParams, S as SpriteBuilder } from './_internal/create-sprite-builder-e2c64c5e.js'; | ||
export { c as createSpriteBuilder } from './_internal/create-sprite-builder-e2c64c5e.js'; | ||
import * as chokidar from 'chokidar'; | ||
import { S as SvgSpritePluginHooks, a as SvgSpritePlugin } from './_internal/types-fe41d676.js'; | ||
export { C as Context, d as GeneratedSprite, G as GeneratedSprites, c as SpriteGroup, b as SpriteGroupsMap, e as SvgFile, f as SvgFileMeta, g as SvgNode } from './_internal/types-fe41d676.js'; | ||
export { i as plugins } from './_internal/index-5157eb75.js'; | ||
import { S as SvgSpritePluginHooks, a as SvgSpritePlugin } from './_internal/types-ce4301f9.js'; | ||
export { C as Context, d as GeneratedSprite, G as GeneratedSprites, c as SpriteGroup, b as SpriteGroupsMap, e as SvgFile, f as SvgFileMeta, g as SvgNode } from './_internal/types-ce4301f9.js'; | ||
export { i as plugins } from './_internal/index-76f02c2a.js'; | ||
interface GenerateParams extends CreateSpriteBuilderParams { | ||
interface BuildSpritesParams extends CreateSpriteBuilderParams { | ||
/** | ||
@@ -20,6 +20,6 @@ * Globs to icons files | ||
/** | ||
* Scan files by input globs, parse them and generate sprites. | ||
* Scan files by input globs, parse them, and generate sprites. | ||
* Accepts prepared config and vfs instance. | ||
*/ | ||
declare function buildSprites({ vfs, input, logger, keepTreeChanges, ...builderParams }: GenerateParams): Promise<void>; | ||
declare function buildSprites({ vfs, input, logger, keepTreeChanges, ...builderParams }: BuildSpritesParams): Promise<void>; | ||
@@ -35,2 +35,2 @@ interface CreateWatcherParams { | ||
export { CreateSpriteBuilderParams, CreateWatcherParams, GenerateParams, SpriteBuilder, SvgSpritePlugin, SvgSpritePluginHooks, buildSprites, createPlugin, createWatcher }; | ||
export { type BuildSpritesParams, CreateSpriteBuilderParams, type CreateWatcherParams, SpriteBuilder, SvgSpritePlugin, SvgSpritePluginHooks, buildSprites, createPlugin, createWatcher }; |
{ | ||
"name": "@neodx/svg", | ||
"packageManager": "yarn@3.2.0", | ||
"version": "0.5.1", | ||
"version": "0.6.0", | ||
"description": "Supercharge your icons ⚡️", | ||
@@ -49,30 +49,30 @@ "author": { | ||
"files": [ | ||
"_internal/build-sprites-79c0b006.mjs", | ||
"_internal/build-sprites-79c0b006.mjs.map", | ||
"_internal/build-sprites-f7266982.cjs", | ||
"_internal/build-sprites-f7266982.cjs.map", | ||
"_internal/create-sprite-builder-250912b5.cjs", | ||
"_internal/create-sprite-builder-250912b5.cjs.map", | ||
"_internal/create-sprite-builder-8a4c69f6.mjs", | ||
"_internal/create-sprite-builder-8a4c69f6.mjs.map", | ||
"_internal/create-sprite-builder-f7c30e64.d.ts", | ||
"_internal/build-sprites-53a600c2.mjs", | ||
"_internal/build-sprites-53a600c2.mjs.map", | ||
"_internal/build-sprites-5e33685e.cjs", | ||
"_internal/build-sprites-5e33685e.cjs.map", | ||
"_internal/create-sprite-builder-456ffd2f.cjs", | ||
"_internal/create-sprite-builder-456ffd2f.cjs.map", | ||
"_internal/create-sprite-builder-51201aa0.mjs", | ||
"_internal/create-sprite-builder-51201aa0.mjs.map", | ||
"_internal/create-sprite-builder-e2c64c5e.d.ts", | ||
"_internal/create-watcher-cbead848.mjs", | ||
"_internal/create-watcher-cbead848.mjs.map", | ||
"_internal/create-watcher-e04060c3.cjs", | ||
"_internal/create-watcher-e04060c3.cjs.map", | ||
"_internal/index-198e8e0d.cjs", | ||
"_internal/index-198e8e0d.cjs.map", | ||
"_internal/index-5157eb75.d.ts", | ||
"_internal/index-ccef0d17.mjs", | ||
"_internal/index-ccef0d17.mjs.map", | ||
"_internal/svgo-401e044a.cjs", | ||
"_internal/svgo-401e044a.cjs.map", | ||
"_internal/svgo-aa2fc7f4.mjs", | ||
"_internal/svgo-aa2fc7f4.mjs.map", | ||
"_internal/types-fe41d676.d.ts", | ||
"_internal/unplugin-58642a2c.cjs", | ||
"_internal/unplugin-58642a2c.cjs.map", | ||
"_internal/unplugin-b0f65603.d.ts", | ||
"_internal/unplugin-cb9d332a.mjs", | ||
"_internal/unplugin-cb9d332a.mjs.map", | ||
"_internal/create-watcher-f8681281.cjs", | ||
"_internal/create-watcher-f8681281.cjs.map", | ||
"_internal/index-55c40f60.mjs", | ||
"_internal/index-55c40f60.mjs.map", | ||
"_internal/index-76f02c2a.d.ts", | ||
"_internal/index-88e6636e.cjs", | ||
"_internal/index-88e6636e.cjs.map", | ||
"_internal/svgo-0c02f7ce.cjs", | ||
"_internal/svgo-0c02f7ce.cjs.map", | ||
"_internal/svgo-de218e6a.mjs", | ||
"_internal/svgo-de218e6a.mjs.map", | ||
"_internal/types-ce4301f9.d.ts", | ||
"_internal/unplugin-8e071ca2.mjs", | ||
"_internal/unplugin-8e071ca2.mjs.map", | ||
"_internal/unplugin-aa79f5b2.cjs", | ||
"_internal/unplugin-aa79f5b2.cjs.map", | ||
"_internal/unplugin-bae8a85b.d.ts", | ||
"cli.cjs", | ||
@@ -131,26 +131,34 @@ "cli.cjs.map", | ||
"dependencies": { | ||
"@neodx/fs": "^0.0.9", | ||
"@neodx/log": "^0.2.1", | ||
"@neodx/std": "^0.1.5", | ||
"@neodx/vfs": "^0.1.9", | ||
"chokidar": "^3.5.3", | ||
"@neodx/fs": "0.0.10", | ||
"@neodx/log": "0.3.0", | ||
"@neodx/std": "0.2.0", | ||
"@neodx/vfs": "0.1.10", | ||
"chokidar": "3.5.3", | ||
"colord": "2.9.3", | ||
"commander": "11.0.0", | ||
"pathe": "^1.1.1", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"svgo": "^3.0.2", | ||
"svgson": "^5.3.1", | ||
"unplugin": "^1.4.0", | ||
"zod": "^3.21.4" | ||
"pathe": "1.1.1", | ||
"react": "18.2.0", | ||
"react-dom": "18.2.0", | ||
"svgo": "3.0.2", | ||
"svgson": "5.3.1", | ||
"unplugin": "1.5.0", | ||
"zod": "3.22.4" | ||
}, | ||
"devDependencies": { | ||
"@neodx/autobuild": "*", | ||
"@swc/helpers": "^0.5.1", | ||
"@types/react": "^18.2.15", | ||
"@types/react-dom": "^18.2.7", | ||
"@types/svgo": "^3.0.0", | ||
"clsx": "^2.0.0", | ||
"prettier": "^2.8.8" | ||
"@swc/helpers": "0.5.3", | ||
"@types/react": "18.2.25", | ||
"@types/react-dom": "18.2.11", | ||
"@types/svgo": "3.0.0", | ||
"clsx": "2.0.0", | ||
"prettier": "2.8.8" | ||
}, | ||
"peerDependencies": { | ||
"prettier": "*" | ||
}, | ||
"peerDependenciesMeta": { | ||
"prettier": { | ||
"optional": true | ||
} | ||
}, | ||
"exports": { | ||
@@ -157,0 +165,0 @@ ".": { |
@@ -1,2 +0,2 @@ | ||
export { G as GroupPluginOptions, L as LegacyTypescriptPluginOptions, S as SvgoPluginOptions, f as fixViewBox, g as groupSprites, l as legacyTypescript, s as setId, a as svgo } from '../_internal/index-5157eb75.js'; | ||
export { A as AnyColorInput, h as ColorPropertyReplacementInput, i as ColorReplacementInputConfig, R as ResetColorsPluginParams, r as resetColors } from '../_internal/types-fe41d676.js'; | ||
export { G as GroupPluginOptions, L as LegacyTypescriptPluginOptions, f as fixViewBox, g as groupSprites, l as legacyTypescript, s as setId } from '../_internal/index-76f02c2a.js'; | ||
export { A as AnyColorInput, i as ColorPropertyReplacementInput, j as ColorReplacementInputConfig, R as ResetColorsPluginParams, h as SvgoPluginParams, r as resetColors, s as svgo } from '../_internal/types-ce4301f9.js'; |
700
README.md
# @neodx/svg | ||
<div align="left"> | ||
<a href="https://www.npmjs.com/package/@neodx/svg"> | ||
<img src="https://img.shields.io/npm/v/@neodx/svg.svg" alt="npm" /> | ||
</a> | ||
<img src="https://img.shields.io/npm/l/@neodx/svg.svg" alt="license"/> | ||
</div> | ||
Supercharge your icons ⚡️ | ||
## Motivation | ||
> We're working on the new documentation, please, visit [neodx.pages.dev](https://neodx.pages.dev/svg) to see the latest version. | ||
Sprites are the most effective way to work with your SVG icons, | ||
but for some reason developers (vision from a React world) prefer | ||
mostly bloated and ineffective, "compile" SVG to react component with inlined SVG content. | ||
Of course, we can use some external tools like https://svgsprit.es/ or some npm libraries, | ||
but that's not serious (if you know any alternatives, let me know, and I'll add links), developers need DX. | ||
In a ridiculous, but incredibly popular way, we don't have other solutions with the same DX. | ||
Just think about it a little, you need to "compile" SVG, to embed your secondary static content in JSX | ||
and get a lot of unwanted issues: additional source code, extra build time, extra bundle size, | ||
the user's browser will parse and evaluate your **static SVG** as JS code, | ||
you can never cache it, WOOF, etc., etc. | ||
And yes, developers keep using this insanity because even an incredibly inefficient solution with a good DX | ||
is better than a super-efficient, but unusable setup with semi-manual generators. | ||
That's why we're here! 🥳 | ||
- TypeScript support out of box - generated types and [information about your sprites](#-content-based-hashes-and-runtime-metadata-generation) | ||
- [Built-in integrated plugins](#integrate-with-your-bundler) for all major bundlers: `vite`, `webpack`, `rollup`, `esbuild`, etc. | ||
- Optional grouping by folders | ||
- Optimization with svgo | ||
- Optional [grouping by folders](https://neodx.pages.dev/svg/group-and-hash.html) | ||
- Optimization with [svgo](https://neodx.pages.dev/svg/api/plugins/svgo.html) | ||
- [Automatically reset colors](#-automatically-reset-colors) | ||
- Powerful files selection | ||
@@ -44,13 +31,8 @@ ## Installation and usage | ||
### CLI (Not recommended) | ||
We're highly recommended to start with our ["Getting started" guide](https://neodx.pages.dev/svg/). | ||
Currently, we don't recommend using CLI mode because it's not flexible enough and requires extra setup | ||
if you want to use it - see [CLI](#cli) section and [CLI Options API](#cli-options). | ||
### Integrate with your bundler | ||
```shell | ||
yarn sprite --help | ||
``` | ||
> For better understanding and to access the latest version, please visit [our documentation](https://neodx.pages.dev/svg/setup/). | ||
### Integrate with your bundler | ||
Our plugins are built upon [unplugin](https://github.com/unjs/unplugin) | ||
@@ -68,8 +50,3 @@ and provide a consistent interface and working principle across all multiple bundlers and frameworks. | ||
root: 'assets', | ||
group: true, | ||
output: 'public', | ||
metadata: 'src/shared/ui/icon/sprite.gen.ts', | ||
resetColors: { | ||
replaceUnknown: 'currentColor' | ||
} | ||
output: 'public' | ||
}) | ||
@@ -81,74 +58,9 @@ ] | ||
It will search for all SVG files in `assets` folder, group them by folders, optimize them with `svgo`, | ||
reset all colors to `currentColor` | ||
and generate sprites in `public` folder with TS definitions in `src/shared/ui/icon/sprite.gen.ts`. | ||
reset all colors to `currentColor` and generate sprites in `public` folder. | ||
For more details, see our [Step-by-step guide](#step-by-step). | ||
For more details, see our [Step-by-step guide](https://neodx.pages.dev/svg/). | ||
> **Note**: If you receive the error `TS2307: Cannot find module '@neodx/svg/vite' or its corresponding type declarations`, | ||
> it's a common issue that will be fixed in one of the next versions. | ||
> | ||
> To resolve this issue at present, change `moduleResolution` to `Bundler` in your `tsconfig.json`. | ||
Another plugins: | ||
<details> | ||
<summary>Webpack</summary> | ||
```typescript | ||
import svg from '@neodx/svg/webpack'; | ||
export default { | ||
plugins: [ | ||
svg({ | ||
root: 'assets', | ||
output: 'public', | ||
metadata: 'src/shared/ui/icon/sprite.gen.ts' | ||
}) | ||
] | ||
}; | ||
``` | ||
</details> | ||
<details> | ||
<summary>Rollup</summary> | ||
```typescript | ||
import svg from '@neodx/svg/rollup'; | ||
export default { | ||
plugins: [ | ||
svg({ | ||
root: 'assets', | ||
output: 'public', | ||
metadata: 'src/shared/ui/icon/sprite.gen.ts' | ||
}) | ||
] | ||
}; | ||
``` | ||
</details> | ||
<details> | ||
<summary>ESBuild</summary> | ||
```typescript | ||
import svg from '@neodx/svg/esbuild'; | ||
export default { | ||
plugins: [ | ||
svg({ | ||
root: 'assets', | ||
output: 'public', | ||
metadata: 'src/shared/ui/icon/sprite.gen.ts' | ||
}) | ||
] | ||
}; | ||
``` | ||
</details> | ||
## Features | ||
### 🆕 Automatically reset colors | ||
### 🆕 [Automatically reset colors](https://neodx.pages.dev/svg/colors-reset.html) | ||
@@ -171,3 +83,3 @@ Automate your icons and forget about colors management issues. | ||
To solve these issues, we're introducing a `resetColors` option: | ||
To solve these issues, we're providing a powerful color reset mechanism (`resetColors` option, enabled by default): | ||
@@ -179,109 +91,6 @@ - Automatically detects all colors in all forms (thx [colord](https://github.com/omgovich/colord)) from SVG content | ||
###### Features preview | ||
> Check out [our documentation](https://neodx.pages.dev/svg/colors-reset.html) for more details. | ||
<details> | ||
<summary>Disable colors reset</summary> | ||
### 🆕 [Content-based hashes and runtime metadata generation](https://neodx.pages.dev/svg/group-and-hash.html) | ||
```typescript | ||
svg({ | ||
// disable colors reset | ||
resetColors: false | ||
}); | ||
``` | ||
</details> | ||
<details> | ||
<summary>Filter colors and icons</summary> | ||
```typescript | ||
svg({ | ||
resetColors: { | ||
// global files filter (default - all files) | ||
exclude: ['path/to/icon.svg', /[a-z]*-colored\.svg$/], | ||
include: ['path/to/other-icon.svg' /* ... */], | ||
// keep specific colors untouched | ||
keep: ['white', '#eee'], | ||
// all colors except white and #eee will be replaced with currentColor | ||
replaceUnknown: 'currentColor' | ||
} | ||
}); | ||
``` | ||
</details> | ||
<details> | ||
<summary>Replace specific colors</summary> | ||
> Without `replaceUnknown` option, all unspecified colors will be kept as is. | ||
```typescript | ||
svg({ | ||
resetColors: { | ||
// if you want to replace specific colors only with currentColor, you can simply pass it as a string or array | ||
replace: ['white', '#eee'], | ||
// when you need to replace colors with a concrete color, you can pass an object with `from` and `to` properties | ||
replace: { | ||
from: [legacyBrandColor, legacyBrandColor2], | ||
to: brandColor | ||
}, | ||
// you can also pass an array of objects | ||
replace: [ | ||
{ | ||
from: [legacyBrandColor, legacyBrandColor2], | ||
to: brandColor | ||
}, | ||
{ | ||
from: ['white', '#eee'], | ||
to: 'currentColor' | ||
} | ||
] | ||
} | ||
}); | ||
``` | ||
</details> | ||
###### Complex example | ||
- Replace white color in all flags with `currentColor` | ||
- For all icons except flags, logos and colored icons: | ||
- Keep brand colors untouched | ||
- Replace known accent colors with `var(--icon-color)` | ||
- Replace known secondary colors with `var(--icon-bg)` | ||
- Replace all other colors with `currentColor` | ||
```typescript | ||
svg({ | ||
resetColors: [ | ||
{ | ||
include: /^flags/, | ||
replace: { | ||
from: 'white', | ||
to: 'currentColor' | ||
} | ||
}, | ||
{ | ||
keep: myTheme.brandColors, | ||
exclude: [/^flags/, /^logos/, /-colored\.svg$/], | ||
replace: [ | ||
{ | ||
from: myTheme.accentIconColors, | ||
to: 'var(--icon-color)' | ||
}, | ||
{ | ||
from: myTheme.secondaryIconColors, | ||
to: 'var(--icon-bg)' | ||
} | ||
], | ||
// if you want to replace colors in specific properties only, you can pass an array of them | ||
properties: ['fill', 'stroke'], | ||
replaceUnknown: 'currentColor' | ||
} | ||
] | ||
}); | ||
``` | ||
### 🆕 Content-based hashes and runtime metadata generation | ||
> **Note:** If you used `definitions` or `experimentalRuntime` options before, you need to update your configuration, see [Migration guide](#move-from-definitions-and-experimentalruntime-options-to-metadata-api). | ||
@@ -297,4 +106,3 @@ | ||
But this is not very good for caching, | ||
because if you change any of the SVG files, | ||
But this is not very good for caching, because if you change any of the SVG files, | ||
the sprite filename won't be updated, which could result in an infinite cache. | ||
@@ -314,82 +122,46 @@ | ||
svg({ | ||
root: 'assets', | ||
output: 'public/sprites', | ||
fileName: '{name}.{hash:8}.svg', | ||
metadata: { | ||
path: 'src/shared/ui/icon/sprite.gen.ts', | ||
path: 'src/sprite.gen.ts', | ||
runtime: { | ||
size: true, | ||
viewBox: true | ||
// generate runtime metadata (path and other information) for each sprite | ||
size: true, // will add `width` and `height` properties | ||
viewBox: true // will add `viewBox` property | ||
} | ||
} | ||
// ... | ||
}) | ||
// ... | ||
] | ||
// ... | ||
}); | ||
``` | ||
Your output will be: | ||
In the result, you will get the following sprites in your output: | ||
```diff | ||
public/ | ||
+ sprite-foo.12abc678.svg | ||
+ sprite-bar.87654def.svg | ||
/ | ||
├── assets | ||
│ ├── common | ||
│ │ ├── left.svg | ||
│ │ └── right.svg | ||
│ └── actions | ||
│ └── close.svg | ||
├── public | ||
+ └── sprites | ||
+ ├── common.12ghS6Uj.svg | ||
+ └── actions.1A34ks78.svg | ||
└── src | ||
+ └── sprite.gen.ts | ||
``` | ||
With the following metadata in `src/shared/ui/icon/sprite.gen.ts`: | ||
To learn how to use it, | ||
check out [our "Writing an Icon component" guide](https://neodx.pages.dev/svg/group-and-hash.html) or detailed basic tutorials: | ||
```typescript | ||
export interface SpritesMap { | ||
'sprite-foo': 'first' | 'second'; | ||
'sprite-bar': ' /* ... */ '; | ||
} | ||
export const SPRITES_META = { | ||
'sprite-foo': { | ||
filePath: 'sprite-foo.12abc678.svg', | ||
items: { | ||
first: { | ||
// all items will have `viewBox`, `width` and `height` properties | ||
viewBox: '0 0 48 48', | ||
width: 48, | ||
height: 48 | ||
}, | ||
second: { | ||
/* ... */ | ||
} | ||
} | ||
}, | ||
'sprite-bar': { | ||
filePath: 'sprite-bar.87654def.svg', | ||
items: { | ||
/* ... */ | ||
} | ||
} | ||
}; | ||
``` | ||
- [Group and hash sprites](https://neodx.pages.dev/svg/group-and-hash.html) | ||
- [Generate metadata](https://neodx.pages.dev/svg/metadata.html) | ||
And updates of `Icon` component will be like this: | ||
## Step-by-step | ||
> This example is based on implementation from [Building Icon component with TailwindCSS](#building-icon-component-with-tailwindcss-see-example) recipe and our [Vite application example (link to GH repo)](https://github.com/secundant/neodx/tree/main/examples/svg-vite) | ||
It's a simplified tutorial, for detailed one check our ["Getting started" guide](https://neodx.pages.dev/svg/). | ||
```diff | ||
+ import { SPRITES_META, type SpritesMap } from './sprite.gen'; | ||
+ export function Icon({ name, /* ... */ }) { | ||
const [spriteName, iconName] = name.split('/'); | ||
+ const { filePath, items } = SPRITES_META[spriteName]; | ||
+ const { viewBox } = items[iconName]; | ||
return ( | ||
<svg | ||
+ viewBox={viewBox} | ||
// ... | ||
> | ||
+ <use href={`/${filePath}#${iconName}`} /> | ||
</svg> | ||
); | ||
} | ||
``` | ||
## Step-by-step | ||
Our example stack details: | ||
@@ -405,9 +177,11 @@ | ||
```diff | ||
assets/ | ||
common/ | ||
add.svg | ||
close.svg | ||
other/ | ||
cut.svg | ||
search.svg | ||
/ | ||
├── assets | ||
│ ├── common | ||
│ │ ├── left.svg | ||
| | ... other icons | ||
│ │ └── right.svg | ||
│ └── actions | ||
│ ... other icons | ||
│ └── close.svg | ||
``` | ||
@@ -419,8 +193,6 @@ | ||
Firstly, we adopt configuration from [Integrate with your bundler](#integrate-with-your-bundler) section: | ||
```typescript | ||
import { defineConfig } from 'vite'; | ||
import svg from '@neodx/svg/vite'; | ||
import react from '@vitejs/plugin-react'; | ||
import { defineConfig } from 'vite'; | ||
import tsconfigPaths from 'vite-tsconfig-paths'; | ||
@@ -435,9 +207,4 @@ | ||
group: true, | ||
output: 'public', | ||
metadata: { | ||
path: 'src/shared/ui/icon/sprite.gen.ts' | ||
}, | ||
resetColors: { | ||
replaceUnknown: 'currentColor' | ||
} | ||
output: 'public/sprites', | ||
metadata: 'src/shared/ui/icon/sprite.gen.ts' | ||
}) | ||
@@ -448,246 +215,33 @@ ] | ||
<details> | ||
<summary>If you decided to use CLI mode:</summary> | ||
Let's run `sprite` with some additional options: | ||
```bash | ||
yarn sprite --group --root assets -o public/sprite -d src/shared/ui/icon/sprite.gen.ts --reset-unknown-colors | ||
``` | ||
In details: | ||
- The `--group` option group icons by folders (`common` and `other`) | ||
- The `--root` option sets `assets` as a base path for icons (you can try to remove it and see the difference) | ||
- The `-o` option sets `public/sprite` as a base path for generated sprites (it's default value, but let's keep it for now) | ||
- The `-d` option generates TS definitions file with sprite meta information | ||
</details> | ||
Now let's run `vite` (or `vite build`) and see what we have: | ||
```diff | ||
... | ||
shared/ | ||
ui/ | ||
icon/ | ||
+ sprite.gen.ts | ||
public/ | ||
+ sprite/ | ||
+ common.svg | ||
+ other.svg | ||
/ | ||
├── assets | ||
│ ├── common | ||
│ │ ├── left.svg | ||
│ │ └── right.svg | ||
│ └── actions | ||
│ └── close.svg | ||
├── public | ||
+ └── sprites | ||
+ ├── common.svg | ||
+ └── actions.svg | ||
└── src | ||
└── shared | ||
└── ui | ||
└── icon | ||
+ └── sprite.gen.ts | ||
``` | ||
For each folder in `assets`, a separate sprite is created, along with a TS definitions file containing metadata about all icons. | ||
Now you could visit our ["Writing an Icon component" guide](https://neodx.pages.dev/svg/writing-icon-component.html) to learn how to use it. | ||
### Look at generated TS definitions | ||
## Guides | ||
```ts | ||
export interface SpritesMap { | ||
common: 'close' | 'favourite'; | ||
format: 'align-left' | 'tag'; | ||
} | ||
- [Getting started](https://neodx.pages.dev/svg) | ||
- [Group and hash sprites](https://neodx.pages.dev/svg/group-and-hash.html) | ||
- [Generate metadata](https://neodx.pages.dev/svg/metadata.html) | ||
- [Writing an Icon component](https://neodx.pages.dev/svg/writing-icon-component.html) | ||
- [Working with multicolored icons](https://neodx.pages.dev/svg/multicolored.html) | ||
export const SPRITES_META: { [K in keyof SpritesMap]: SpritesMap[K][] } = { | ||
common: ['close', 'favourite'], | ||
format: ['align-left', 'tag'] | ||
}; | ||
``` | ||
As you can see, we have a map of all sprites and meta information about them. | ||
Now we can use it in our code - for type checking, autocomplete, and other cool stuff. | ||
### Create your Icon component | ||
> It's a **simple** implementation, you can see a real one in the "Recipes" section | ||
```tsx | ||
// shared/ui/icon/icon.tsx | ||
import { SpritesMap } from './sprite.gen'; | ||
export interface IconProps<Group extends keyof SpritesMap> { | ||
name: SpritesMap[Group]; | ||
type?: Group; | ||
} | ||
export function Icon<Group extends keyof SpritesMap = 'common'>({ type, name }: IconProps<Group>) { | ||
return ( | ||
<svg className="icon"> | ||
<use href={`/public/sprite/${type}.svg#${name}`}></use> | ||
</svg> | ||
); | ||
} | ||
``` | ||
### Enjoy 👏 | ||
```tsx | ||
import { Icon, TextField } from '@/shared/ui'; | ||
export function SomeFeature() { | ||
return ( | ||
<div className="space-y-4"> | ||
<TextField name="a" startNode={<Icon name="add" />} /> | ||
<TextField name="b" startNode={<Icon name="close" />} /> | ||
<TextField name="c" startNode={<Icon type="other" name="search" />} /> | ||
</div> | ||
); | ||
} | ||
``` | ||
## CLI | ||
> **Warning:** | ||
> While the CLI mode is currently available, | ||
> it's not the recommended method of use and might be removed in future major versions. | ||
> | ||
> Now we're providing built-it bundlers integration, please, use [our plugin](#integrate-with-your-bundler) instead. | ||
To get started, you can try the CLI mode even without any configuration, just run `sprite` command: | ||
```shell | ||
yarn sprite | ||
``` | ||
This command searches for all SVG files, excluding those in the `public/sprites` folder and generate sprites in `public/sprites`. | ||
By default, it creates a single sprite containing all icons without any grouping or TS definitions. However, this can be customized. See [CLI options](#cli-options) for more information | ||
## Recipes | ||
### Building Icon component with TailwindCSS ([see example](./examples/react)) | ||
#### Add base `icon` class | ||
```css | ||
/* shared/ui/index.css */ | ||
@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; | ||
@layer components { | ||
/* | ||
Our base class for all icons, includes: | ||
- `fill-current` - fill icon with current text color, so we can use `color` to change it | ||
- `w-[1em] h-[1em]` - set icon size to 1em, so we can use `font-size` to scale it | ||
- `box-content` - it's up to you, I choose it to keep icon size fixed | ||
*/ | ||
.icon { | ||
@apply select-none fill-current w-[1em] h-[1em] inline-block text-inherit box-content; | ||
} | ||
} | ||
``` | ||
#### Add `Icon` component | ||
```tsx | ||
// shared/ui/icon/icon.tsx | ||
import clsx from 'clsx'; | ||
import { SVGProps } from 'react'; | ||
import { SpritesMap } from './sprite.gen'; | ||
// Merging all icons as `SPRITE_NAME/SPRITE_ICON_NAME` | ||
export type IconName = { | ||
[Key in keyof SpritesMap]: `${Key}/${SpritesMap[Key]}`; | ||
}[keyof SpritesMap]; | ||
export interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'name' | 'type'> { | ||
name: IconName; | ||
} | ||
export function Icon({ name, className, viewBox, ...props }: IconProps) { | ||
const [spriteName, iconName] = name.split('/'); | ||
return ( | ||
<svg | ||
// We recommend to use specific component class for avoid collisions with other styles and simple override it | ||
className={clsx('icon', className)} | ||
viewBox={viewBox} | ||
focusable="false" | ||
aria-hidden | ||
{...props} | ||
> | ||
{/* For example, "/common.svg#favourite". Change base path if you don't store sprites under the root. */} | ||
<use href={`/${spriteName}.svg#${iconName}`} /> | ||
</svg> | ||
); | ||
} | ||
``` | ||
#### Usage | ||
```tsx | ||
import { Icon } from '@/shared/ui'; | ||
export function SomeFeature() { | ||
return ( | ||
<div className="space-y-4"> | ||
<Icon name="common/add" /> | ||
<Icon name="common/close" className="text-red-500" /> | ||
<Icon name="text/bold" className="text-lg" /> | ||
<Icon name="actions/delete" className="p-2 rounded-md bg-stone-300" /> | ||
</div> | ||
); | ||
} | ||
``` | ||
### Multiple colors | ||
Let's imagine that we have a really different icons with next requirements: | ||
- We have some known list of the accent colors, and we want to specify them in our CSS | ||
- All other colors should be inherited from the parent (for example, `currentColor`) | ||
#### Configure `resetColors` option | ||
```typescript | ||
import svg from '@neodx/svg/vite'; | ||
svg({ | ||
// ... | ||
resetColors: { | ||
// 1. Define known accent colors | ||
replace: { | ||
from: ['#6C707E', '#A8ADBD', '#818594'], | ||
to: 'var(--icon-accent-color)' | ||
}, | ||
// 2. Replace all other colors with `currentColor` | ||
replaceUnknown: 'currentColor' | ||
} | ||
}); | ||
``` | ||
#### Add CSS variables | ||
```css | ||
/* shared/ui/index.css */ | ||
@layer base { | ||
:root { | ||
/* make default accent color */ | ||
--icon-primary-color: #6c707e; | ||
} | ||
} | ||
``` | ||
#### Usage | ||
Dirty but works 🫢 | ||
Probably, you can find a better solution 🫠 | ||
```tsx | ||
import { Icon } from '@/shared/ui'; | ||
export function SomeFeature() { | ||
return ( | ||
<Icon | ||
name="common/add" | ||
className="text-red-800 [--icon-primary-color:theme(colors.green.800)]" | ||
/> | ||
); | ||
} | ||
``` | ||
## Migrations | ||
@@ -714,90 +268,2 @@ | ||
## API | ||
### Node.JS API | ||
```typescript | ||
import { buildSprites } from '@neodx/svg'; | ||
import { createVfs } from '@neodx/vfs'; | ||
await buildSprites({ | ||
vfs: createVfs(process.cwd()), | ||
root: 'assets', | ||
input: '**/*.svg', | ||
output: 'public', | ||
definition: 'src/shared/ui/icon/sprite.gen.ts' | ||
// ... options (see below) | ||
}); | ||
``` | ||
#### Options | ||
```typescript | ||
interface Options { | ||
/** | ||
* Root folder for inputs, useful for correct groups naming | ||
* @default process.cwd() | ||
*/ | ||
root?: string; | ||
/** | ||
* Path to generated sprite/sprites folder | ||
* @default public | ||
*/ | ||
output?: string; | ||
/** | ||
* Logger instance (or object with any compatible interface) | ||
* @see `@neodx/log` | ||
* @default built-in logger | ||
*/ | ||
logger?: LoggerMethods<'info' | 'debug' | 'error'>; | ||
/** | ||
* Should we group icons? | ||
* @default false | ||
*/ | ||
group?: boolean; | ||
/** | ||
* Template of sprite file name | ||
* @example {name}.svg | ||
* @example sprite-{name}.svg | ||
* @example {name}-{hash}.svg | ||
* @example {name}-{hash:8}.svg | ||
* @default {name}.svg | ||
*/ | ||
fileName?: string; | ||
/** | ||
* Should we optimize icons? | ||
*/ | ||
optimize?: boolean; | ||
/** | ||
* Configures metadata generation | ||
* @example "src/sprites/meta.ts" | ||
* @example { path: "meta.ts", runtime: false } // will generate only types | ||
* @example { path: "meta.ts", types: 'TypeName', runtime: 'InfoName' } // will generate "interface TypeName" types and "const InfoName" runtime metadata | ||
* @example { path: "meta.ts", runtime: { size: true, viewBox: true } } // will generate runtime metadata with size and viewBox | ||
*/ | ||
metadata?: MetadataPluginParams; | ||
/** | ||
* Reset colors config | ||
*/ | ||
resetColors?: ResetColorsPluginParams; | ||
} | ||
``` | ||
### CLI Options | ||
| option | default | description | | ||
| -------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | ||
| `-i`, `--input` | `"**/*.svg"` | Glob paths to icons files (output path will be automatically excluded) | | ||
| `-o`, `--output` | `"public/sprites"` | Base path to generated sprite/sprites folder | | ||
| `-d`, `--definitions` | Not provided (**disabled**) | Path to generated TS file with sprite meta | | ||
| `--root` | `"."` (same as the current dir) | Base path to your assets, useful for correct groups names<br/>**careful:** `--input` should be relative to `--root` | | ||
| `--group` | `false` | Should we group icons by folders? | | ||
| `--dry-run` | `false` | Print proposal of generated file paths without actually generating it | | ||
| `--optimize` | `true` | Should we optimize SVG with [svgo](https://github.com/svg/svgo)? | | ||
| `--reset-color-values` | `"#000,#000000"` | An array of colors to replace as `currentColor` | | ||
| `--reset-unknown-colors` | `false` | Should we set `currentColor` for all colors not defined in `--reset-color-values`, or for all colors if this option isn't provided? | | ||
| `--reset-color-properties` | `"fill,stroke"` | An array of SVG properties that will be replaced with `currentColor` if they're present | | ||
> **Note:** `--reset-color-values` and `--reset-color-properties` are strings with comma-separated values, don't forget to wrap them with quotes: | ||
> | ||
> `sprite ... --reset-color-values "#000,#000000,#fff"` | ||
- [API Reference](https://neodx.pages.dev/svg/api/) |
@@ -1,2 +0,2 @@ | ||
import { S as SvgPluginParams } from './_internal/unplugin-b0f65603.js'; | ||
import { S as SvgPluginParams } from './_internal/unplugin-bae8a85b.js'; | ||
@@ -3,0 +3,0 @@ declare const _default: (options: SvgPluginParams) => WebpackPluginInstance; |
@@ -1,2 +0,2 @@ | ||
import { S as SvgPluginParams } from './_internal/unplugin-b0f65603.js'; | ||
import { S as SvgPluginParams } from './_internal/unplugin-bae8a85b.js'; | ||
@@ -3,0 +3,0 @@ declare const _default: (options: SvgPluginParams) => RspackPluginInstance; |
import * as vite from 'vite'; | ||
import { S as SvgPluginParams } from './_internal/unplugin-b0f65603.js'; | ||
import { S as SvgPluginParams } from './_internal/unplugin-bae8a85b.js'; | ||
@@ -4,0 +4,0 @@ declare const _default: (options: SvgPluginParams) => vite.Plugin | vite.Plugin[]; |
@@ -1,2 +0,2 @@ | ||
import { S as SvgPluginParams } from './_internal/unplugin-b0f65603.js'; | ||
import { S as SvgPluginParams } from './_internal/unplugin-bae8a85b.js'; | ||
@@ -3,0 +3,0 @@ declare const _default: (options: SvgPluginParams) => WebpackPluginInstance; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 4 instances in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 2 instances in 1 package
565
1.8%169346
-6.64%15
7.14%258
-67.42%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated