@neodx/svg
Advanced tools
Comparing version
@@ -1,39 +0,14 @@ | ||
import { Command } from 'commander'; | ||
import { LoggerMethods } from '@neodx/log'; | ||
import { VFS } from '@neodx/vfs'; | ||
import { C as CreateSpriteBuilderParams, S as SpriteBuilder } from './_internal/create-sprite-builder-f5d1f843.js'; | ||
export { c as createSpriteBuilder } from './_internal/create-sprite-builder-f5d1f843.js'; | ||
import * as chokidar from 'chokidar'; | ||
import { S as SvgSpritePluginHooks, a as SvgSpritePlugin } from './_internal/reset-colors-3b2e91d7.js'; | ||
export { C as Context, c as SpriteGroup, b as SpriteGroupsMap, d as SvgFile, e as SvgNode } from './_internal/reset-colors-3b2e91d7.js'; | ||
export { i as plugins } from './_internal/index-1523c0b0.js'; | ||
declare function createCli(cwd?: string): Command; | ||
interface GenerateParams { | ||
vfs: VFS; | ||
interface GenerateParams extends CreateSpriteBuilderParams { | ||
/** | ||
* Logger instance | ||
*/ | ||
logger?: LoggerMethods<'info' | 'debug'>; | ||
/** | ||
* Globs to icons files | ||
*/ | ||
input: string[]; | ||
input: string | string[]; | ||
/** | ||
* Path to generated sprite/sprites folder | ||
*/ | ||
output: string; | ||
/** | ||
* Root folder for inputs, useful for correct groups naming | ||
*/ | ||
root?: string; | ||
/** | ||
* Should we group icons? | ||
* @default false | ||
*/ | ||
group?: boolean; | ||
/** | ||
* Template of sprite file name | ||
* @example {name}.svg | ||
* @example sprite-{name}.svg | ||
*/ | ||
fileName?: string; | ||
optimize?: boolean; | ||
definitions?: string; | ||
/** | ||
* Keep tree changes after generation even if dry-run mode is enabled | ||
@@ -43,4 +18,2 @@ * Useful for testing (for example, to check what EXACTLY was changed) | ||
keepTreeChanges?: boolean; | ||
resetColorValues?: string[]; | ||
resetColorProperties?: string[]; | ||
} | ||
@@ -51,38 +24,13 @@ /** | ||
*/ | ||
declare function generateSvgSprites({ vfs, input, logger, group: enableGroup, root, optimize, definitions, resetColorValues, resetColorProperties, output, fileName, keepTreeChanges }: GenerateParams): Promise<void>; | ||
declare function buildSprites({ vfs, logger, input, keepTreeChanges, ...builderParams }: GenerateParams): Promise<void>; | ||
interface Context { | ||
vfs: VFS; | ||
interface CreateWatcherParams { | ||
builder: SpriteBuilder; | ||
root: string; | ||
input: string | string[]; | ||
} | ||
interface SvgSpritePlugin extends Partial<SvgSpritePluginHooks> { | ||
name: string; | ||
} | ||
interface SvgSpritePluginHooks { | ||
afterWrite(groups: SpriteGroupsMap, context: Context): unknown | Promise<unknown>; | ||
afterWriteGroup(group: SpriteGroup, context: Context): unknown | Promise<unknown>; | ||
resolveEntriesMap(groups: SpriteGroupsMap, context: Context): SpriteGroupsMap; | ||
transformNode(node: SvgFile): SvgNode; | ||
transformSourceContent(path: string, content: string): string | Promise<string>; | ||
transformOutputEntryContent(content: string): string | Promise<string>; | ||
} | ||
type SpriteGroupsMap = Map<string, SpriteGroup>; | ||
interface SpriteGroup { | ||
name: string; | ||
files: SvgFile[]; | ||
} | ||
interface SvgFile { | ||
node: SvgNode; | ||
path: string; | ||
name: string; | ||
} | ||
interface SvgNode { | ||
name: string; | ||
type: string; | ||
value: string; | ||
children: SvgNode[]; | ||
attributes: Record<string, string>; | ||
} | ||
declare function createWatcher({ root, input, builder }: CreateWatcherParams): chokidar.FSWatcher; | ||
declare function createPlugin(name: string, hooks: Partial<SvgSpritePluginHooks>): SvgSpritePlugin; | ||
export { GenerateParams, SvgFile, SvgNode, SvgSpritePlugin, SvgSpritePluginHooks, createCli, createPlugin, generateSvgSprites }; | ||
export { CreateSpriteBuilderParams, CreateWatcherParams, GenerateParams, SpriteBuilder, SvgSpritePlugin, SvgSpritePluginHooks, buildSprites, createPlugin, createWatcher }; |
{ | ||
"name": "@neodx/svg", | ||
"packageManager": "yarn@3.2.0", | ||
"version": "0.1.9", | ||
"version": "0.2.0", | ||
"description": "Supercharge your icons ⚡️", | ||
@@ -31,3 +31,12 @@ "author": { | ||
], | ||
"source": "src/index.ts", | ||
"source": [ | ||
"src/index.ts", | ||
"src/cli.ts", | ||
"src/esbuild.ts", | ||
"src/rollup.ts", | ||
"src/rspack.ts", | ||
"src/vite.ts", | ||
"src/webpack.ts", | ||
"src/plugins/index.ts" | ||
], | ||
"type": "module", | ||
@@ -54,7 +63,10 @@ "main": "./dist/index.cjs", | ||
"dependencies": { | ||
"@neodx/fs": "*", | ||
"@neodx/log": "*", | ||
"@neodx/std": "*", | ||
"@neodx/vfs": "*", | ||
"@neodx/fs": "^0.0.8", | ||
"@neodx/log": "^0.1.7", | ||
"@neodx/std": "^0.1.4", | ||
"@neodx/vfs": "^0.1.7", | ||
"chokidar": "^3.5.3", | ||
"colord": "2.9.3", | ||
"commander": "10.0.1", | ||
"pathe": "^1.1.0", | ||
"react": "^18.2.0", | ||
@@ -64,2 +76,3 @@ "react-dom": "^18.2.0", | ||
"svgson": "^5.2.1", | ||
"unplugin": "^1.3.1", | ||
"zod": "^3.21.4" | ||
@@ -81,4 +94,39 @@ }, | ||
"require": "./dist/index.cjs" | ||
}, | ||
"./cli": { | ||
"types": "./dist/cli.d.ts", | ||
"import": "./dist/cli.mjs", | ||
"require": "./dist/cli.cjs" | ||
}, | ||
"./plugins": { | ||
"types": "./dist/plugins/index.d.ts", | ||
"import": "./dist/plugins/index.mjs", | ||
"require": "./dist/plugins/index.cjs" | ||
}, | ||
"./esbuild": { | ||
"types": "./dist/esbuild.d.ts", | ||
"require": "./dist/esbuild.cjs", | ||
"default": "./dist/esbuild.mjs" | ||
}, | ||
"./rollup": { | ||
"types": "./dist/rollup.d.ts", | ||
"require": "./dist/rollup.cjs", | ||
"default": "./dist/rollup.mjs" | ||
}, | ||
"./rspack": { | ||
"types": "./dist/rspack.d.ts", | ||
"require": "./dist/rspack.cjs", | ||
"default": "./dist/rspack.mjs" | ||
}, | ||
"./vite": { | ||
"types": "./dist/vite.d.ts", | ||
"require": "./dist/vite.cjs", | ||
"default": "./dist/vite.mjs" | ||
}, | ||
"./webpack": { | ||
"types": "./dist/webpack.d.ts", | ||
"require": "./dist/webpack.cjs", | ||
"default": "./dist/webpack.mjs" | ||
} | ||
} | ||
} |
330
README.md
@@ -27,5 +27,6 @@ # @neodx/svg | ||
- TypeScript support out of box - generated types and information about your sprites | ||
- Built-in plugins for all major bundlers: `vite`, `webpack`, `rollup`, `esbuild`, etc. | ||
- Optional grouping by folders | ||
- Optimization with svgo | ||
- Built-in colors reset | ||
- Flexible colors reset | ||
- Powerful files selection | ||
@@ -44,4 +45,11 @@ | ||
You can try it even without any configuration, just run `sprite` command: | ||
### CLI Mode | ||
> **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](#plugins) instead. | ||
To get started, you can try the CLI mode even without any configuration, just run `sprite` command: | ||
```shell | ||
@@ -51,36 +59,164 @@ yarn sprite | ||
It will search for all SVG files except files in `public/sprites` folder and generate sprites in `public/sprites`. | ||
This command searches for all SVG files, excluding those in the `public/sprites` folder and generate sprites in `public/sprites`. | ||
In default mode, it will be the single sprite with all icons without grouping and TS definitions, but you can customize it, see below. | ||
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 | ||
## Options | ||
### Plugins | ||
| 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-color-properties` | `"fill,stroke"` | An array of SVG properties that will be replaced with `currentColor` if they're present | | ||
Our plugins provide a consistent interface and working principle across all major bundlers. | ||
> **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"` | ||
For instance, here's an example of `vite` plugin with some options: | ||
```typescript | ||
import svg from '@neodx/svg/vite'; | ||
export default defineConfig({ | ||
plugins: [ | ||
svg({ | ||
root: 'assets', | ||
group: true, | ||
output: 'public', | ||
definitions: 'src/shared/ui/icon/sprite.h.ts', | ||
resetColors: { | ||
replaceUnknown: 'currentColor' | ||
} | ||
}) | ||
] | ||
}); | ||
``` | ||
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.h.ts`. | ||
For more details see our [Step-by-step guide](#step-by-step). | ||
Another plugins: | ||
<details> | ||
<summary>Webpack</summary> | ||
```typescript | ||
import svg from '@neodx/svg/webpack'; | ||
export default { | ||
plugins: [ | ||
svg({ | ||
root: 'assets', | ||
output: 'public', | ||
definition: 'src/shared/ui/icon/sprite.h.ts' | ||
}) | ||
] | ||
}; | ||
``` | ||
</details> | ||
<details> | ||
<summary>Rollup</summary> | ||
```typescript | ||
import svg from '@neodx/svg/rollup'; | ||
export default { | ||
plugins: [ | ||
svg({ | ||
root: 'assets', | ||
output: 'public', | ||
definition: 'src/shared/ui/icon/sprite.h.ts' | ||
}) | ||
] | ||
}; | ||
``` | ||
</details> | ||
<details> | ||
<summary>ESBuild</summary> | ||
```typescript | ||
import svg from '@neodx/svg/esbuild'; | ||
export default { | ||
plugins: [ | ||
svg({ | ||
root: 'assets', | ||
output: 'public', | ||
definition: 'src/shared/ui/icon/sprite.h.ts' | ||
}) | ||
] | ||
}; | ||
``` | ||
</details> | ||
## Step-by-step | ||
Our example stack details: | ||
- `vite` | ||
- `react` | ||
- `typescript` | ||
- `tailwindcss` | ||
We'll be working with the following icons in our project: | ||
```diff | ||
assets/ | ||
common/ | ||
add.svg | ||
close.svg | ||
other/ | ||
cut.svg | ||
search.svg | ||
``` | ||
We want to generate separate sprites for each folder and use them in our React components. | ||
### Build icons | ||
Firstly, we adopt configuration from [Plugins](#plugins) section: | ||
```typescript | ||
import svg from '@neodx/svg/vite'; | ||
import react from '@vitejs/plugin-react'; | ||
import { defineConfig } from 'vite'; | ||
import tsconfigPaths from 'vite-tsconfig-paths'; | ||
export default defineConfig({ | ||
plugins: [ | ||
react(), | ||
tsconfigPaths(), | ||
svg({ | ||
root: 'assets', | ||
group: true, | ||
output: 'public', | ||
definitions: 'src/shared/ui/icon/sprite.h.ts', | ||
resetColors: { | ||
replaceUnknown: 'currentColor' | ||
} | ||
}) | ||
] | ||
}); | ||
``` | ||
<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.h.ts | ||
yarn sprite --group --root assets -o public/sprite -d src/shared/ui/icon/sprite.h.ts --reset-unknown-colors | ||
``` | ||
As a result, we will get: | ||
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 | ||
@@ -96,18 +232,6 @@ ... | ||
+ other.svg | ||
assets/ | ||
common/ | ||
add.svg | ||
close.svg | ||
other/ | ||
cut.svg | ||
search.svg | ||
``` | ||
In details: | ||
For each folder in `assets`, a separate sprite is created, along with a TS definitions file containing metadata about all icons. | ||
- with `--group` option we will group icons by folders (`common` and `other`) | ||
- with `--root` option we will use `assets` as a base path for icons (you can try to remove it and see the difference) | ||
- with `-o` option we will use `public/sprite` as a base path for generated sprites (it's default value, but let's keep it for now) | ||
- with `-d` option we will generate TS definitions file with sprite meta | ||
### Look at generated TS definitions | ||
@@ -171,4 +295,27 @@ | ||
### Real Icon component ([see example](./examples/react)) | ||
### 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 | ||
@@ -180,3 +327,4 @@ // shared/ui/icon/icon.tsx | ||
export type SpriteKey = { | ||
// Merging all icons as `SPRITE_NAME/SPRITE_ICON_NAME` | ||
export type IconName = { | ||
[Key in keyof SpritesMap]: `${Key}/${SpritesMap[Key]}`; | ||
@@ -186,3 +334,3 @@ }[keyof SpritesMap]; | ||
export interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'name' | 'type'> { | ||
name: SpriteKey; | ||
name: IconName; | ||
} | ||
@@ -195,6 +343,4 @@ | ||
<svg | ||
className={clsx( | ||
'select-none fill-current w-[1em] h-[1em] inline-block text-inherit', | ||
className | ||
)} | ||
// We recommend to use specific component class for avoid collisions with other styles and simple override it | ||
className={clsx('icon', className)} | ||
viewBox={viewBox} | ||
@@ -205,3 +351,4 @@ focusable="false" | ||
> | ||
<use xlinkHref={`/path/to/sprites/${spriteName}.svg#${iconName}`} /> | ||
{/* For example, "/common.svg#favourite". Change base path if you don't store sprites under the root. */} | ||
<use xlinkHref={`/${spriteName}.svg#${iconName}`} /> | ||
</svg> | ||
@@ -221,4 +368,5 @@ ); | ||
<Icon name="common/add" /> | ||
<Icon name="common/close" /> | ||
<Icon name="other/search" /> | ||
<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> | ||
@@ -228,1 +376,95 @@ ); | ||
``` | ||
### 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)]" | ||
/> | ||
); | ||
} | ||
``` | ||
## 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.h.ts' | ||
}); | ||
``` | ||
### 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"` |
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 2 instances in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 4 instances 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 1 instance in 1 package
128565
77.03%67
737.5%389
170.14%0
-100%460
111.01%14
40%12
300%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated