This commit is contained in:
2026-07-14 16:11:39 +03:00
parent 1280eb6fcd
commit ab7042001a
126 changed files with 5670 additions and 4120 deletions

View File

@@ -50,7 +50,7 @@
- подготовка нейтрального compiled artifact;
- protocol `ModeAdapter`/`OutputPlan`;
- проверка output paths;
- atomic writer, symlink protection и ownership state;
- staged directory writer и symlink protection;
- logger и базовые result types.
Core не генерирует JavaScript, declarations, manifest source, CSS или framework-specific exports. Изменение общего compiler может ожидаемо изменить SVG всех modes; изменение generated source должно быть локально одному adapter.
@@ -61,13 +61,11 @@ Core не генерирует JavaScript, declarations, manifest source, CSS и
Runtime генерируется как ESM JavaScript. Типизация добавляется отдельными `.d.ts`; TypeScript/TSX не используется как runtime output.
Core writer владеет корневым `.gitignore` и `.svg-sprite/state.json`. State не является manifest спрайта: он хранит owner mode, contract version и список управляемых файлов.
Core writer полностью владеет каталогом `.svg-sprite` и при каждой генерации заменяет его через временный каталог с rollback при ошибке. Корневым `.gitignore` writer владеет, когда exact-mode adapter запрашивает его через `OutputPlan`. Bare `standalone` не создаёт `.gitignore`; остальные modes создают.
Sprite-level asset, icon data, manifest и facade лежат непосредственно в `.svg-sprite/`. Framework runtime группируется отдельно: React adapters используют `.svg-sprite/react/`, будущие framework adapters получают собственный framework-каталог.
Sprite-level asset, icon data, manifest и facade лежат непосредственно в `.svg-sprite/`. `standalone@vite` и `standalone@webpack` генерируют нативный icon Web Component внутри своего facade; bare `standalone` остаётся без JavaScript runtime. Framework runtime группируется отдельно: React adapters используют `.svg-sprite/react/`, будущие framework adapters получают собственный framework-каталог.
Каждый adapter имеет собственный `contractVersion`. При изменении его generated-контракта повышается только версия этого adapter.
Adapter возвращает файлы в памяти. Только core writer проверяет paths и markers, обновляет output и записывает `state.json` последним.
Adapter возвращает файлы в памяти. Только core writer проверяет paths, полностью заменяет `.svg-sprite` и обновляет управляемый `.gitignore`.
## Зависимости
@@ -98,5 +96,4 @@ mode A -X-> shared output codegen
1. Изменяйте только его каталог и mode-neutral protocol, если это действительно необходимо.
2. Не переносите output-логику в core ради устранения дублирования.
3. Не меняйте generated-контракты других adapters автоматически.
4. Повышайте `contractVersion` только изменённого adapter.
5. Проверяйте отсутствие cross-mode imports.
4. Проверяйте отсутствие cross-mode imports.

View File

@@ -1,6 +1,6 @@
# Иконки без лишней цены для приложения
`@gromlab/svg-sprites` превращает SVG проекта в кешируемую, типизированную систему иконок для React и Next.js. В коде остаются простые компоненты, а приложение получает преимущества спрайтов без сложной инфраструктуры.
`@gromlab/svg-sprites` превращает SVG проекта в кешируемую, типизированную систему иконок для vanilla-приложений, React и Next.js. В коде остаются простые компоненты, а приложение получает преимущества спрайтов без сложной инфраструктуры.
1. **AI-friendly из коробки**
@@ -42,9 +42,9 @@
`SpriteViewer` рендерит все спрайты проекта в одном месте и показывает, какие иконки вошли в каждый набор и как они выглядят. Для каждой иконки видны созданные CSS-переменные и их fallback-цвета. Значения можно менять прямо в Viewer и сразу наблюдать результат. Здесь же доступны готовые примеры подключения через React, `<svg><use>`, `<img>` и CSS.
11. **React и Next.js**
11. **Standalone, React и Next.js**
Пакет генерирует типизированные React-компоненты и поддерживает Vite, Webpack 5, Next.js App Router и Pages Router с Turbopack или Webpack.
Для vanilla-приложений с Vite или Webpack пакет генерирует нативный типизированный Web Component без runtime-зависимостей. Для React и Next.js создаётся React-компонент с поддержкой Vite, Webpack 5, App Router, Pages Router и Turbopack.
12. **Чистый Git**
@@ -52,4 +52,4 @@
13. **В production только иконки**
`@gromlab/svg-sprites` выполняет всю работу на этапе генерации и остаётся в `devDependencies`. Compiler и CLI не попадают в клиентское приложение: после сборки остаются только локальный типизированный компонент и внешний SVG-файл.
Генератор можно запускать через `npx`, не добавляя package в проект. Локальная установка нужна только для необязательного Viewer, типизации config через package или программного API. Compiler и CLI не попадают в клиентское приложение: после сборки остаются только локальный типизированный компонент и внешний SVG-файл.

View File

@@ -6,7 +6,7 @@
`@gromlab/svg-sprites` is an SVG sprite generator for modern web applications. It combines selected SVG icons into one or more external, cacheable sprites and prepares them for use in the UI.
For React and Next.js, the package generates typed components and external SVG assets with support for Vite, Webpack 5, and Turbopack.
For vanilla applications using Vite/Webpack, the package generates a native typed Web Component; for React and Next.js, it generates a React component. In every case, the SVG remains a separate cacheable asset.
## An SVG sprite as simple as a regular SVG icon
@@ -24,6 +24,20 @@ The component accepts familiar SVG attributes: dimensions, `color`, `className`,
You do not have to work with the sprite directly in your application. Use it like a regular SVG icon while benefiting from a single component, autocomplete, and TypeScript validation for every name.
In `standalone@vite` and `standalone@webpack`, the same approach works without React:
```ts
import { defineAppIconElement } from './app-icons'
defineAppIconElement()
```
```html
<app-icon icon="search" style="font-size: 24px"></app-icon>
```
Bare `standalone` remains minimal and generates only an SVG asset and JSON manifest, with no JavaScript runtime.
## AI-friendly out of the box
`@gromlab/svg-sprites` is designed to work with AI agents from the start. Add the ready-made skill and ask an agent to configure, migrate, or troubleshoot the package without lengthy instructions or manual documentation research.
@@ -36,12 +50,15 @@ You do not have to work with the sprite directly in your application. Use it lik
The main example uses the Next.js App Router and Turbopack.
### 1. Install the package
### 1. Generate without installing the package
```bash
npm install --save-dev @gromlab/svg-sprites
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites --help
```
`npx` downloads the CLI temporarily. It does not add `@gromlab/svg-sprites` to
`package.json`, and the generated production runtime does not import the package.
### 2. Specify the icons you need
SVG files can remain in your project's existing structure:
@@ -61,17 +78,15 @@ Create the sprite configuration:
```ts
// src/ui/app-icons/svg-sprite.config.ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
export default {
mode: 'next@app/turbopack',
name: 'app',
inputFiles: [
input: [
'../../assets/icons/search.svg',
'../../assets/icons/settings.svg',
'../../features/profile/user.svg',
],
})
}
```
### 3. Add generation
@@ -79,7 +94,7 @@ export default defineSpriteConfig({
```json
{
"scripts": {
"sprites": "svg-sprites src/ui/app-icons/svg-sprite.config.ts",
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/app-icons/svg-sprite.config.ts",
"predev": "npm run sprites",
"prebuild": "npm run sprites"
}
@@ -233,13 +248,13 @@ The package generates low-level standalone sprites for static HTML, Vite, and We
## Clean Git history
The generator creates a local `.gitignore` that excludes generated files and keeps them from cluttering project history, pull requests, and the codebase.
Bundler and framework modes create a local `.gitignore` that excludes generated files and keeps them from cluttering project history, pull requests, and the codebase. Bare `standalone` leaves the repository policy to the application.
The repository contains the source SVG files, configuration, and `.gitignore` rule, while sprites, components, and types are regenerated locally and in CI through `prebuild`.
In bundler and framework modes, the repository contains the source SVG files, configuration, and `.gitignore` rule, while sprites, components, and types are regenerated locally and in CI through `prebuild`.
## Only icons in production
`@gromlab/svg-sprites` does its main work during generation and remains in `devDependencies`.
Generation can run entirely through `npx`, without adding the package to the project. Install it as a development dependency only when you need the Viewer, config types, or the programmatic API.
Production components use only local generated code, styles, and the external SVG file. The compiler and CLI are not bundled into the client application, while `SpriteViewer` is imported separately only where a debug page is needed.
@@ -249,15 +264,21 @@ This README introduces the project's capabilities and demonstrates the primary u
### Quick start
- [Next.js App Router](docs/en/next-app.md)
- [Next.js Pages Router](docs/en/next-pages.md)
- [React + Vite](docs/en/react-vite.md)
- [React + Webpack 5](docs/en/react-webpack.md)
- [Bare standalone](docs/en/guides/standalone.md)
- [Standalone + Vite](docs/en/guides/standalone-vite.md)
- [Standalone + Webpack 5](docs/en/guides/standalone-webpack.md)
- [React + Vite](docs/en/guides/react-vite.md)
- [React + Webpack 5](docs/en/guides/react-webpack.md)
- [Next.js App Router + Turbopack](docs/en/guides/next-app-turbopack.md)
- [Next.js App Router + Webpack](docs/en/guides/next-app-webpack.md)
- [Next.js Pages Router + Turbopack](docs/en/guides/next-pages-turbopack.md)
- [Next.js Pages Router + Webpack](docs/en/guides/next-pages-webpack.md)
### Technical resources
- [Technical reference](docs/en/reference.md)
- [Programmatic API](docs/en/programmatic-api.md)
- [Documentation index](docs/en/README.md)
- [Technical reference](docs/en/reference/technical.md)
- [Programmatic API](docs/en/reference/programmatic-api.md)
## License

View File

@@ -6,7 +6,7 @@
`@gromlab/svg-sprites` — генератор SVG-спрайтов для современных веб-приложений. Он собирает выбранные SVG-иконки в один или несколько внешних кешируемых спрайтов и подготавливает их для использования в интерфейсе.
Для React и Next.js пакет создаёт типизированные компоненты и внешние SVG assets, поддерживая Vite, Webpack 5 и Turbopack.
Для vanilla-приложений с Vite/Webpack пакет создаёт нативный типизированный Web Component, а для React и Next.js — React-компонент. SVG во всех случаях остаётся отдельным кешируемым asset.
## SVG-спрайт так же прост, как обычная SVG-иконка
@@ -24,6 +24,20 @@
В приложении не приходится работать со спрайтом напрямую. Вы используете его так же, как обычную SVG-иконку, но получаете один компонент, автокомплит и TypeScript-проверку всех имён.
В `standalone@vite` и `standalone@webpack` тот же подход доступен без React:
```ts
import { defineAppIconElement } from './app-icons'
defineAppIconElement()
```
```html
<app-icon icon="search" style="font-size: 24px"></app-icon>
```
Bare `standalone` остаётся минимальным и генерирует только SVG asset и JSON manifest без JavaScript runtime.
## AI-friendly из коробки
`@gromlab/svg-sprites` сразу рассчитан на работу с AI-агентами. Подключите готовый skill и поручите агенту настройку, миграцию или диагностику без длинных инструкций и ручного изучения документации.
@@ -36,12 +50,15 @@
Основной пример использует Next.js App Router и Turbopack.
### 1. Установите пакет
### 1. Генерируйте без установки пакета
```bash
npm install --save-dev @gromlab/svg-sprites
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites --help
```
`npx` временно скачивает CLI, не добавляет `@gromlab/svg-sprites` в
`package.json`, а generated production runtime не импортирует package.
### 2. Укажите нужные иконки
SVG могут оставаться в существующей структуре проекта:
@@ -61,17 +78,15 @@ src/
```ts
// src/ui/app-icons/svg-sprite.config.ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
export default {
mode: 'next@app/turbopack',
name: 'app',
inputFiles: [
input: [
'../../assets/icons/search.svg',
'../../assets/icons/settings.svg',
'../../features/profile/user.svg',
],
})
}
```
### 3. Добавьте генерацию
@@ -79,7 +94,7 @@ export default defineSpriteConfig({
```json
{
"scripts": {
"sprites": "svg-sprites src/ui/app-icons/svg-sprite.config.ts",
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/app-icons/svg-sprite.config.ts",
"predev": "npm run sprites",
"prebuild": "npm run sprites"
}
@@ -233,13 +248,13 @@ Bare standalone подключает Viewer через browser script и HTML el
## Чистый Git
Генератор создаёт локальный `.gitignore`, который исключает generated-файлы и не позволяет им засорять историю, pull requests и код проекта.
Bundler и framework modes создают локальный `.gitignore`, который исключает generated-файлы и не позволяет им засорять историю, pull requests и код проекта. Bare `standalone` оставляет политику репозитория приложению.
В репозитории остаются исходные SVG, конфигурация и правило `.gitignore`, а локально и в CI спрайты, компоненты и типы заново создаются через `prebuild`.
В bundler и framework modes в репозитории остаются исходные SVG, конфигурация и правило `.gitignore`, а локально и в CI спрайты, компоненты и типы заново создаются через `prebuild`.
## В production только иконки
`@gromlab/svg-sprites` выполняет основную работу на этапе генерации и остаётся в `devDependencies`.
Генерация полностью работает через `npx`, без добавления package в проект. Устанавливайте его как development dependency, только если нужны Viewer, типы конфига или программный API.
Production-компоненты используют только локальный generated-код, стили и внешний SVG-файл. Compiler и CLI не попадают в клиентское приложение, а `SpriteViewer` подключается отдельно только там, где нужна debug-страница.
@@ -249,15 +264,21 @@ README знакомит с возможностями проекта и пока
### Быстрый старт
- [Next.js App Router](docs/ru/next-app.md)
- [Next.js Pages Router](docs/ru/next-pages.md)
- [React + Vite](docs/ru/react-vite.md)
- [React + Webpack 5](docs/ru/react-webpack.md)
- [Bare standalone](docs/ru/guides/standalone.md)
- [Standalone + Vite](docs/ru/guides/standalone-vite.md)
- [Standalone + Webpack 5](docs/ru/guides/standalone-webpack.md)
- [React + Vite](docs/ru/guides/react-vite.md)
- [React + Webpack 5](docs/ru/guides/react-webpack.md)
- [Next.js App Router + Turbopack](docs/ru/guides/next-app-turbopack.md)
- [Next.js App Router + Webpack](docs/ru/guides/next-app-webpack.md)
- [Next.js Pages Router + Turbopack](docs/ru/guides/next-pages-turbopack.md)
- [Next.js Pages Router + Webpack](docs/ru/guides/next-pages-webpack.md)
### Технические материалы
- [Технический справочник](docs/ru/reference.md)
- [Программный API](docs/ru/programmatic-api.md)
- [Индекс документации](docs/ru/README.md)
- [Технический справочник](docs/ru/reference/technical.md)
- [Программный API](docs/ru/reference/programmatic-api.md)
## Лицензия

29
docs/en/README.md Normal file
View File

@@ -0,0 +1,29 @@
# Documentation
Choose one exact mode guide for setup. The guides are standalone documents and
can also be used unchanged by AI skills.
## Quick Start Guides
| Project | Exact mode | Guide |
|---|---|---|
| Static HTML or custom publishing | `standalone` | [Bare standalone](guides/standalone.md) |
| Vanilla + Vite | `standalone@vite` | [Standalone + Vite](guides/standalone-vite.md) |
| Vanilla + Webpack 5 | `standalone@webpack` | [Standalone + Webpack](guides/standalone-webpack.md) |
| React + Vite | `react@vite` | [React + Vite](guides/react-vite.md) |
| React + Webpack 5 | `react@webpack` | [React + Webpack](guides/react-webpack.md) |
| Next.js App Router + Turbopack | `next@app/turbopack` | [App Router + Turbopack](guides/next-app-turbopack.md) |
| Next.js App Router + Webpack | `next@app/webpack` | [App Router + Webpack](guides/next-app-webpack.md) |
| Next.js Pages Router + Turbopack | `next@pages/turbopack` | [Pages Router + Turbopack](guides/next-pages-turbopack.md) |
| Next.js Pages Router + Webpack | `next@pages/webpack` | [Pages Router + Webpack](guides/next-pages-webpack.md) |
Every guide follows the same order:
1. Generate the sprite through `npx` without adding the package to the project.
2. Optionally install and connect the debug Viewer.
3. Optionally type the config through the package or a local copy-paste type.
## Reference
- [Technical reference](reference/technical.md)
- [Programmatic API](reference/programmatic-api.md)

11
docs/en/guides/README.md Normal file
View File

@@ -0,0 +1,11 @@
# Quick Start Guides
- `standalone`: [bare standalone](standalone.md)
- `standalone@vite`: [standalone with Vite](standalone-vite.md)
- `standalone@webpack`: [standalone with Webpack](standalone-webpack.md)
- `react@vite`: [React with Vite](react-vite.md)
- `react@webpack`: [React with Webpack](react-webpack.md)
- `next@app/turbopack`: [App Router with Turbopack](next-app-turbopack.md)
- `next@app/webpack`: [App Router with Webpack](next-app-webpack.md)
- `next@pages/turbopack`: [Pages Router with Turbopack](next-pages-turbopack.md)
- `next@pages/webpack`: [Pages Router with Webpack](next-pages-webpack.md)

View File

@@ -0,0 +1,153 @@
# Next.js App Router Turbopack SVG Sprite Quick Start
This guide targets the exact mode key `next@app/turbopack`: a generated typed React icon for the Next.js App Router and Turbopack.
## 1. Generate the sprite
No package installation and no `package.json` dependency are needed. `npx` downloads the CLI temporarily, and generated runtime does not import `@gromlab/svg-sprites`.
Keep the config and source icons together:
```text
src/ui/icons/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Use a plain default object export with no package import:
```ts
// src/ui/icons/svg-sprite.config.ts
export default {
mode: 'next@app/turbopack',
name: 'icons',
}
```
When `input` is omitted, SVG files are read from `./icons` relative to the config. A `.js` config with a default export and a `.json` config are also supported. Generate directly with:
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts
```
Generate once per invocation and keep the exact Turbopack flags on both commands:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts",
"dev": "npm run sprites && next dev --turbopack",
"build": "npm run sprites && next build --turbopack"
}
}
```
Do not add `predev` or `prebuild` hooks to these scripts; that would run generation twice. In CI, replace `latest` with an exact package version.
Generation creates a local `.gitignore`; commit that file once, but do not commit `.svg-sprite/`. Generated declarations are self-contained and do not require the package.
### Production usage
The generated icon has no `'use client'` directive and is Server Component-compatible. Import it directly in an App Router page or layout:
```tsx
// src/app/page.tsx
import {
IconsIcon,
iconsIconNames,
} from '../ui/icons/.svg-sprite/index.js'
export default function Page() {
return (
<main>
<IconsIcon icon="folder" width={24} height={24} aria-label="Files" />
<p>{iconsIconNames.length} icons available</p>
</main>
)
}
```
Turbopack resolves the generated `new URL('../sprite.svg', import.meta.url)` and CSS Module, emitting a separate SVG asset. Keep the mode and the `--turbopack` dev/build flags aligned.
## 2. Debug and preview
This section is optional. Only users who need the Viewer or icon previews should install:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Viewer is interactive, so place the React bridge in a separate Client Component:
```tsx
// src/app/icon-debug/IconsViewer.tsx
'use client'
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('../../ui/icons/.svg-sprite/svg-sprite.manifest.js'),
]
export function IconsViewer() {
return <SpriteViewer sources={sources} title="Project icons" />
}
```
Render it from the route's Server Component:
```tsx
// src/app/icon-debug/page.tsx
import { IconsViewer } from './IconsViewer'
export default function IconDebugPage() {
return <IconsViewer />
}
```
Keep the route internal or development-only. Viewer is not part of the production icon runtime.
## 3. Type the config
Choose one of these two paths.
### With a local package installation
After installing the package locally, use the helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/turbopack',
name: 'icons',
})
```
You can alternatively import `type SpriteConfig` and apply `satisfies SpriteConfig`.
### Without the package
Copy a mode-specific type directly into the config:
```ts
type LocalSpriteConfig = {
mode: 'next@app/turbopack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'next@app/turbopack',
name: 'icons',
} satisfies LocalSpriteConfig
```

View File

@@ -0,0 +1,153 @@
# Next.js App Router Webpack SVG Sprite Quick Start
This guide targets the exact mode key `next@app/webpack`: a generated typed React icon for the Next.js App Router and Webpack 5.
## 1. Generate the sprite
No package installation and no `package.json` dependency are needed. `npx` downloads the CLI temporarily, and generated runtime does not import `@gromlab/svg-sprites`.
Keep the config adjacent to its source icons:
```text
src/ui/icons/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Use a plain default object export with no package import:
```ts
// src/ui/icons/svg-sprite.config.ts
export default {
mode: 'next@app/webpack',
name: 'icons',
}
```
When `input` is omitted, SVG files are read from `./icons` relative to the config. A `.js` config with a default export and a `.json` config are also supported. Generate directly with:
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts
```
Generate once per invocation and keep the exact Webpack flags on both commands:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts",
"dev": "npm run sprites && next dev --webpack",
"build": "npm run sprites && next build --webpack"
}
}
```
Do not add `predev` or `prebuild` hooks to these scripts; that would run generation twice. In CI, replace `latest` with an exact package version.
Generation creates a local `.gitignore`; commit that file once, but do not commit `.svg-sprite/`. Generated declarations are self-contained and do not require the package.
### Production usage
The generated icon has no `'use client'` directive and is Server Component-compatible. Import it directly in an App Router page or layout:
```tsx
// src/app/page.tsx
import {
IconsIcon,
iconsIconNames,
} from '../ui/icons/.svg-sprite/index.js'
export default function Page() {
return (
<main>
<IconsIcon icon="folder" width={24} height={24} aria-label="Files" />
<p>{iconsIconNames.length} icons available</p>
</main>
)
}
```
Webpack resolves the generated `new URL('../sprite.svg', import.meta.url)` and CSS Module, emitting a separate SVG asset. Keep the mode and the `--webpack` dev/build flags aligned. If custom Next.js webpack rules process SVG through SVGR, exclude `.svg-sprite/sprite.svg` from those rules.
## 2. Debug and preview
This section is optional. Only users who need the Viewer or icon previews should install:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Viewer is interactive, so place the React bridge in a separate Client Component:
```tsx
// src/app/icon-debug/IconsViewer.tsx
'use client'
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('../../ui/icons/.svg-sprite/svg-sprite.manifest.js'),
]
export function IconsViewer() {
return <SpriteViewer sources={sources} title="Project icons" />
}
```
Render it from the route's Server Component:
```tsx
// src/app/icon-debug/page.tsx
import { IconsViewer } from './IconsViewer'
export default function IconDebugPage() {
return <IconsViewer />
}
```
Keep the route internal or development-only. Viewer is not part of the production icon runtime.
## 3. Type the config
Choose one of these two paths.
### With a local package installation
After installing the package locally, use the helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/webpack',
name: 'icons',
})
```
You can alternatively import `type SpriteConfig` and apply `satisfies SpriteConfig`.
### Without the package
Copy a mode-specific type directly into the config:
```ts
type LocalSpriteConfig = {
mode: 'next@app/webpack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'next@app/webpack',
name: 'icons',
} satisfies LocalSpriteConfig
```

View File

@@ -0,0 +1,140 @@
# Next.js Pages Router Turbopack SVG Sprite Quick Start
This guide targets the exact mode key `next@pages/turbopack`: a generated typed React icon for the Next.js Pages Router and Turbopack.
## 1. Generate the sprite
No package installation and no `package.json` dependency are needed. `npx` downloads the CLI temporarily, and generated runtime does not import `@gromlab/svg-sprites`.
Keep the config and source icons together:
```text
src/ui/icons/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Use a plain default object export with no package import:
```ts
// src/ui/icons/svg-sprite.config.ts
export default {
mode: 'next@pages/turbopack',
name: 'icons',
}
```
When `input` is omitted, SVG files are read from `./icons` relative to the config. A `.js` config with a default export and a `.json` config are also supported. Generate directly with:
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts
```
Generate once per invocation and keep the exact Turbopack flags on both commands:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts",
"dev": "npm run sprites && next dev --turbopack",
"build": "npm run sprites && next build --turbopack"
}
}
```
Do not add `predev` or `prebuild` hooks to these scripts; that would run generation twice. In CI, replace `latest` with an exact package version.
Generation creates a local `.gitignore`; commit that file once, but do not commit `.svg-sprite/`. Generated declarations are self-contained and do not require the package.
### Production usage
Import the generated component and icon-name list into a Pages Router page:
```tsx
// src/pages/index.tsx
import {
IconsIcon,
iconsIconNames,
} from '../ui/icons/.svg-sprite/index.js'
export default function HomePage() {
return (
<main>
<IconsIcon icon="folder" width={24} height={24} aria-label="Files" />
<p>{iconsIconNames.length} icons available</p>
</main>
)
}
```
The component works with SSR, SSG, and client-side navigation. Turbopack resolves the generated SVG URL and CSS Module and emits a separate asset. Keep the mode and the `--turbopack` dev/build flags aligned.
## 2. Debug and preview
This section is optional. Only users who need the Viewer or icon previews should install:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Pages Router does not require an App Router Client Component boundary. Use the React bridge directly in a page with a static loader array:
```tsx
// src/pages/icon-debug.tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('../ui/icons/.svg-sprite/svg-sprite.manifest.js'),
]
export default function IconDebugPage() {
return <SpriteViewer sources={sources} title="Project icons" />
}
```
Keep the page internal or development-only. Viewer is not part of the production icon runtime.
## 3. Type the config
Choose one of these two paths.
### With a local package installation
After installing the package locally, use the helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@pages/turbopack',
name: 'icons',
})
```
You can alternatively import `type SpriteConfig` and apply `satisfies SpriteConfig`.
### Without the package
Copy a mode-specific type directly into the config:
```ts
type LocalSpriteConfig = {
mode: 'next@pages/turbopack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'next@pages/turbopack',
name: 'icons',
} satisfies LocalSpriteConfig
```

View File

@@ -0,0 +1,140 @@
# Next.js Pages Router Webpack SVG Sprite Quick Start
This guide targets the exact mode key `next@pages/webpack`: a generated typed React icon for the Next.js Pages Router and Webpack 5.
## 1. Generate the sprite
No package installation and no `package.json` dependency are needed. `npx` downloads the CLI temporarily, and generated runtime does not import `@gromlab/svg-sprites`.
Keep the config adjacent to its source icons:
```text
src/ui/icons/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Use a plain default object export with no package import:
```ts
// src/ui/icons/svg-sprite.config.ts
export default {
mode: 'next@pages/webpack',
name: 'icons',
}
```
When `input` is omitted, SVG files are read from `./icons` relative to the config. A `.js` config with a default export and a `.json` config are also supported. Generate directly with:
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts
```
Generate once per invocation and keep the exact Webpack flags on both commands:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts",
"dev": "npm run sprites && next dev --webpack",
"build": "npm run sprites && next build --webpack"
}
}
```
Do not add `predev` or `prebuild` hooks to these scripts; that would run generation twice. In CI, replace `latest` with an exact package version.
Generation creates a local `.gitignore`; commit that file once, but do not commit `.svg-sprite/`. Generated declarations are self-contained and do not require the package.
### Production usage
Import the generated component and icon-name list into a Pages Router page:
```tsx
// src/pages/index.tsx
import {
IconsIcon,
iconsIconNames,
} from '../ui/icons/.svg-sprite/index.js'
export default function HomePage() {
return (
<main>
<IconsIcon icon="folder" width={24} height={24} aria-label="Files" />
<p>{iconsIconNames.length} icons available</p>
</main>
)
}
```
The component works with SSR, SSG, and client-side navigation. Webpack resolves the generated SVG URL and CSS Module and emits a separate asset. Keep the mode and the `--webpack` dev/build flags aligned. If custom Next.js webpack rules process SVG through SVGR, exclude `.svg-sprite/sprite.svg` from those rules.
## 2. Debug and preview
This section is optional. Only users who need the Viewer or icon previews should install:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Pages Router does not require an App Router Client Component boundary. Use the React bridge directly in a page with a static loader array:
```tsx
// src/pages/icon-debug.tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('../ui/icons/.svg-sprite/svg-sprite.manifest.js'),
]
export default function IconDebugPage() {
return <SpriteViewer sources={sources} title="Project icons" />
}
```
Keep the page internal or development-only. Viewer is not part of the production icon runtime.
## 3. Type the config
Choose one of these two paths.
### With a local package installation
After installing the package locally, use the helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@pages/webpack',
name: 'icons',
})
```
You can alternatively import `type SpriteConfig` and apply `satisfies SpriteConfig`.
### Without the package
Copy a mode-specific type directly into the config:
```ts
type LocalSpriteConfig = {
mode: 'next@pages/webpack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'next@pages/webpack',
name: 'icons',
} satisfies LocalSpriteConfig
```

View File

@@ -0,0 +1,148 @@
# React Vite SVG Sprite Quick Start
This guide targets the exact mode key `react@vite`: a generated typed React component with a Vite-managed SVG asset.
## 1. Generate the sprite
No package installation and no `package.json` dependency are needed. `npx` downloads the CLI temporarily, and generated runtime does not import `@gromlab/svg-sprites`.
Keep the config and source icons together:
```text
src/ui/icons/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Use a plain default object export with no package import:
```ts
// src/ui/icons/svg-sprite.config.ts
export default {
mode: 'react@vite',
name: 'icons',
}
```
When `input` is omitted, SVG files are read from `./icons` relative to the config. A `.js` config with a default export and a `.json` config are also supported. Generate directly with:
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts
```
Use the exact Vite dev/build commands and generate once per invocation:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts",
"dev": "npm run sprites && vite",
"build": "npm run sprites && tsc --noEmit && vite build"
}
}
```
Do not add `predev` or `prebuild` hooks to these scripts; that would run generation twice. In CI, replace `latest` with an exact package version.
Generation creates a local `.gitignore`; commit that file once, but do not commit `.svg-sprite/`. Generated declarations are self-contained and do not require the package.
### Production usage
Import the generated component and icon-name list directly:
```tsx
// src/App.tsx
import {
IconsIcon,
iconsIconNames,
} from './ui/icons/.svg-sprite/index.js'
export function App() {
return (
<main>
<IconsIcon icon="check" width={24} height={24} aria-label="Complete" />
<small>{iconsIconNames.length} icons available</small>
</main>
)
}
```
`icon` is a generated union of SVG file names. Vite automatically handles the generated CSS Module and the `sprite.svg?no-inline` import, emitting the sprite as a separate asset. If your own TypeScript source imports Vite query assets, include Vite's ambient types:
```json
{
"compilerOptions": {
"types": ["vite/client"]
}
}
```
## 2. Debug and preview
This section is optional. Only users who need the Viewer or icon previews should install:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Use the React `SpriteViewer` bridge with a static loader array. Keep every `import()` path a string literal:
```tsx
// src/IconsDebugPage.tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('./ui/icons/.svg-sprite/svg-sprite.manifest.js'),
]
export function IconsDebugPage() {
return <SpriteViewer sources={sources} title="Project icons" />
}
```
Keep this component on a debug route or in an internal tool. Viewer is not part of the production icon runtime.
## 3. Type the config
Choose one of these two paths.
### With a local package installation
After installing the package locally, use the helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@vite',
name: 'icons',
})
```
You can alternatively import `type SpriteConfig` and apply `satisfies SpriteConfig`.
### Without the package
Copy a mode-specific type directly into the config:
```ts
type LocalSpriteConfig = {
mode: 'react@vite'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'react@vite',
name: 'icons',
} satisfies LocalSpriteConfig
```

View File

@@ -0,0 +1,156 @@
# React Webpack SVG Sprite Quick Start
This guide targets the exact mode key `react@webpack`: a generated typed React component using Webpack 5 Asset Modules and CSS Modules.
## 1. Generate the sprite
No package installation and no `package.json` dependency are needed. `npx` downloads the CLI temporarily, and generated runtime does not import `@gromlab/svg-sprites`.
Keep the config adjacent to its source icons:
```text
src/ui/icons/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Use a plain default object export with no package import:
```ts
// src/ui/icons/svg-sprite.config.ts
export default {
mode: 'react@webpack',
name: 'icons',
}
```
When `input` is omitted, SVG files are read from `./icons` relative to the config. A `.js` config with a default export and a `.json` config are also supported. Generate directly with:
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts
```
Use the exact Webpack 5 flags and generate once per invocation:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts",
"dev": "npm run sprites && webpack serve --mode development",
"build": "npm run sprites && webpack --mode production"
}
}
```
Do not add `predev` or `prebuild` hooks to these scripts; that would run generation twice. In CI, replace `latest` with an exact package version.
Generation creates a local `.gitignore`; commit that file once, but do not commit `.svg-sprite/`. Generated declarations are self-contained and do not require the package.
### Production usage
Import the generated component and icon-name list directly:
```tsx
// src/App.tsx
import {
IconsIcon,
iconsIconNames,
} from './ui/icons/.svg-sprite/index.js'
export function App() {
return (
<main>
<IconsIcon icon="folder" width={24} height={24} aria-label="Files" />
<small>{iconsIconNames.length} icons available</small>
</main>
)
}
```
The generated component uses `new URL('../sprite.svg', import.meta.url)`, which Webpack 5 processes through Asset Modules and emits as a separate SVG asset. Exclude `.svg-sprite/sprite.svg` from SVG component or SVGR rules so they do not intercept that URL dependency.
The generated component also imports `react-component.module.css`. Configure `.module.css` through `css-loader` with modules enabled, plus `style-loader` or `MiniCssExtractPlugin`:
```js
// webpack.config.js (relevant rule)
export default {
module: {
rules: [
{
test: /\.module\.css$/i,
use: ['style-loader', { loader: 'css-loader', options: { modules: true } }],
},
],
},
}
```
## 2. Debug and preview
This section is optional. Only users who need the Viewer or icon previews should install:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Use the React `SpriteViewer` bridge with a static loader array. Keep every `import()` path a string literal so Webpack can create the chunk:
```tsx
// src/IconsDebugPage.tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('./ui/icons/.svg-sprite/svg-sprite.manifest.js'),
]
export function IconsDebugPage() {
return <SpriteViewer sources={sources} title="Project icons" />
}
```
Keep this component on a debug route or in an internal tool. Viewer is not part of the production icon runtime.
## 3. Type the config
Choose one of these two paths.
### With a local package installation
After installing the package locally, use the helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@webpack',
name: 'icons',
})
```
You can alternatively import `type SpriteConfig` and apply `satisfies SpriteConfig`.
### Without the package
Copy a mode-specific type directly into the config:
```ts
type LocalSpriteConfig = {
mode: 'react@webpack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'react@webpack',
name: 'icons',
} satisfies LocalSpriteConfig
```

View File

@@ -0,0 +1,156 @@
# Standalone Vite SVG Sprite Quick Start
This guide targets the exact mode key `standalone@vite`: a native generated Web Component with Vite-managed SVG assets.
## 1. Generate the sprite
No package installation and no `package.json` dependency are needed. `npx` downloads the CLI temporarily, and generated runtime does not import `@gromlab/svg-sprites`.
Keep the config and source icons together:
```text
src/ui/icons/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Use a plain default object export with no package import:
```ts
// src/ui/icons/svg-sprite.config.ts
export default {
mode: 'standalone@vite',
name: 'icons',
}
```
When `input` is omitted, SVG files are read from `./icons` relative to the config. A `.js` config with a default export and a `.json` config are also supported. Generate once directly with:
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts
```
Use the exact Vite dev/build commands and generate once per invocation:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts",
"dev": "npm run sprites && vite",
"build": "npm run sprites && vite build"
}
}
```
Do not add `predev` or `prebuild` hooks to these scripts; that would run generation twice. In CI, replace `latest` with an exact package version.
Generation creates a local `.gitignore`; commit that file once, but do not commit `.svg-sprite/`. The generated JavaScript and declarations live together, and the declarations are self-contained: they do not require `@gromlab/svg-sprites`.
### Production usage
Register the generated element once, then use `<icons-icon>`:
```ts
// src/main.ts
import {
defineIconsIconElement,
iconsIconNames,
} from './ui/icons/.svg-sprite/index.js'
defineIconsIconElement()
console.log('Available icons:', iconsIconNames)
```
```html
<icons-icon icon="check" role="img" aria-label="Complete"></icons-icon>
```
The host is `1em` by `1em`, so `font-size` controls its default size. Transformed colors use `currentColor` and custom properties such as `--icon-color-1`:
```css
icons-icon {
font-size: 24px;
color: #2563eb;
--icon-color-2: #dbeafe;
}
```
Vite handles the generated `sprite.svg?no-inline` import automatically and emits a separate asset. If your own TypeScript source imports Vite query assets, include Vite's ambient types:
```json
{
"compilerOptions": {
"types": ["vite/client"]
}
}
```
## 2. Debug and preview
This section is optional. Only users who need the Viewer or icon previews should install:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Register the Viewer element, import its type, and assign the generated JavaScript manifest to `sources`:
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
import spriteManifest from './ui/icons/.svg-sprite/svg-sprite.manifest.js'
document.querySelector<HTMLDivElement>('#debug')!.innerHTML = `
<gromlab-sprite-viewer viewer-title="Project icons"></gromlab-sprite-viewer>
`
const viewer = document.querySelector<SpriteViewerElement>('gromlab-sprite-viewer')!
viewer.sources = [spriteManifest]
```
Keep this code on a debug route or in an internal tool. Viewer is not part of the production icon runtime.
## 3. Type the config
Choose one of these two paths.
### With a local package installation
After installing the package locally, use the helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'standalone@vite',
name: 'icons',
})
```
You can alternatively import `type SpriteConfig` and apply `satisfies SpriteConfig`.
### Without the package
Copy a mode-specific type directly into the config:
```ts
type LocalSpriteConfig = {
mode: 'standalone@vite'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'standalone@vite',
name: 'icons',
} satisfies LocalSpriteConfig
```

View File

@@ -0,0 +1,143 @@
# Standalone Webpack SVG Sprite Quick Start
This guide targets the exact mode key `standalone@webpack`: a native generated Web Component using Webpack 5 Asset Modules.
## 1. Generate the sprite
No package installation and no `package.json` dependency are needed. `npx` downloads the CLI temporarily, and generated runtime does not import `@gromlab/svg-sprites`.
Keep the config adjacent to its source icons:
```text
src/ui/icons/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Use a plain default object export with no package import:
```ts
// src/ui/icons/svg-sprite.config.ts
export default {
mode: 'standalone@webpack',
name: 'icons',
}
```
When `input` is omitted, SVG files are read from `./icons` relative to the config. A `.js` config with a default export and a `.json` config are also supported. Generate directly with:
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts
```
Use the exact Webpack 5 flags and generate once per invocation:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.ts",
"dev": "npm run sprites && webpack serve --mode development",
"build": "npm run sprites && webpack --mode production"
}
}
```
Do not add `predev` or `prebuild` hooks to these scripts; that would run generation twice. In CI, replace `latest` with an exact package version.
Generation creates a local `.gitignore`; commit that file once, but do not commit `.svg-sprite/`. Generated declarations are self-contained and do not require the package.
### Production usage
Register the generated element once, then use `<icons-icon>`:
```ts
// src/main.ts
import {
defineIconsIconElement,
iconsIconNames,
} from './ui/icons/.svg-sprite/index.js'
defineIconsIconElement()
console.log('Available icons:', iconsIconNames)
```
```html
<icons-icon
icon="check"
role="img"
aria-label="Complete"
style="font-size:24px;color:#16a34a;--icon-color-1:#16a34a"
></icons-icon>
```
The generated facade uses `new URL('./sprite.svg', import.meta.url)`, which Webpack 5 processes through Asset Modules and emits as a separate asset. If the project has SVG-to-component or SVGR rules, exclude `.svg-sprite/sprite.svg` from them so they do not intercept this URL dependency. Check `output.publicPath` if the emitted URL is wrong.
## 2. Debug and preview
This section is optional. Only users who need the Viewer or icon previews should install:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Register the Viewer element, import its type, and assign the generated JavaScript manifest to `sources`:
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
import spriteManifest from './ui/icons/.svg-sprite/svg-sprite.manifest.js'
document.querySelector<HTMLDivElement>('#debug')!.innerHTML = `
<gromlab-sprite-viewer viewer-title="Project icons"></gromlab-sprite-viewer>
`
const viewer = document.querySelector<SpriteViewerElement>('gromlab-sprite-viewer')!
viewer.sources = [spriteManifest]
```
Keep this code on a debug route or in an internal tool. Viewer is not part of the production icon runtime.
## 3. Type the config
Choose one of these two paths.
### With a local package installation
After installing the package locally, use the helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'standalone@webpack',
name: 'icons',
})
```
You can alternatively import `type SpriteConfig` and apply `satisfies SpriteConfig`.
### Without the package
Copy a mode-specific type directly into the config:
```ts
type LocalSpriteConfig = {
mode: 'standalone@webpack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'standalone@webpack',
name: 'icons',
} satisfies LocalSpriteConfig
```

View File

@@ -0,0 +1,171 @@
# Standalone SVG Sprite Quick Start
This guide targets the exact mode key `standalone`: static HTML or a custom asset pipeline with no generated JavaScript facade.
## 1. Generate the sprite
No package installation and no `package.json` dependency are needed. `npx` downloads the CLI temporarily, and generated runtime does not import `@gromlab/svg-sprites`; bare `standalone` does not generate a JavaScript runtime at all.
Keep the config next to its source icons:
```text
src/ui/icons/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.json
```
Create a JSON config:
```json
{
"mode": "standalone",
"name": "icons"
}
```
When `input` is omitted, the generator reads `./icons` next to the config.
To include SVG files from nested directories, set one glob pattern:
```json
{
"mode": "standalone",
"name": "icons",
"input": "../assets/icons/**/*.svg"
}
```
An array can mix folders, individual SVG files, and glob patterns:
```json
{
"mode": "standalone",
"name": "icons",
"input": [
"../assets/icons",
"../features/profile/user.svg",
"../features/admin/*.svg"
]
}
```
All relative paths start at the config directory. Pass the config path explicitly:
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.json
```
For repeatable local commands, the same temporary CLI can be used from a script:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/icons/svg-sprite.config.json"
}
}
```
Run that script once from your existing dev/build pipeline. If you use a `predev` or `prebuild` hook instead, do not also call `npm run sprites` inside the corresponding script. Bare `standalone` has no bundler-specific dev or build flag. In CI, replace `latest` with an exact package version.
Bare `standalone` does not create or modify `.gitignore`; the application decides whether `.svg-sprite/` is committed or ignored. It emits no declarations, facade, or component. Generated declarations in typed modes are self-contained and do not require the package.
### Production usage
The application owns the public URL, versioning, and cache policy. Copy both generated assets into the deploy output:
```bash
cp src/ui/icons/.svg-sprite/sprite.svg public/assets/icons.svg
cp src/ui/icons/.svg-sprite/svg-sprite.manifest.json public/assets/icons.manifest.json
```
Use the published URL manually:
```html
<svg width="24" height="24" role="img" aria-label="Complete">
<use href="/assets/icons.svg#check"></use>
</svg>
```
Safe icon names normally match their fragment IDs. Names containing spaces or other SVG-ID-unsafe characters receive a generated ID; read that icon's `id` from `icons.manifest.json` instead of constructing the fragment yourself.
## 2. Debug and preview
This section is optional. Only install the package locally if you need the debug Viewer or an icon preview:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Without a bundler, self-host the browser bundle by copying it from `node_modules` into the deploy output:
```bash
cp node_modules/@gromlab/svg-sprites/dist/viewer-element.js public/debug/viewer-element.js
```
Then provide both public asset URLs through HTML attributes:
```html
<script type="module" src="/debug/viewer-element.js"></script>
<gromlab-sprite-viewer
viewer-title="Project icons"
manifest-url="/assets/icons.manifest.json"
sprite-url="/assets/icons.svg"
></gromlab-sprite-viewer>
```
For a quick preview, a pinned CDN file can replace the self-hosted script:
```html
<script
type="module"
src="https://unpkg.com/@gromlab/svg-sprites@1.1.5/dist/viewer-element.js"
></script>
```
Self-host the file for controlled environments and pin the CDN version if you use the alternative. Viewer is optional tooling, not part of the production icon runtime.
## 3. Type the config
This guide uses a JSON config, which works without TypeScript types. If config autocomplete is needed, replace `svg-sprite.config.json` with `svg-sprite.config.ts` and choose one of these approaches.
### With a local package installation
After installing the package locally, use the helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'standalone',
name: 'icons',
})
```
You can alternatively import `type SpriteConfig` and apply `satisfies SpriteConfig`.
### Without the package
Keep a narrow local type directly in the config:
```ts
type LocalSpriteConfig = {
mode: 'standalone'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'standalone',
name: 'icons',
} satisfies LocalSpriteConfig
```

View File

@@ -1,113 +1,4 @@
# Next.js App Router
# Next.js App Router Guides Moved
[← Back to home](../../README.md)
Two explicit modes are supported:
| Bundler | Mode key |
|---|---|
| Turbopack | `next@app/turbopack` |
| Webpack 5 | `next@app/webpack` |
## 1. Install the package
```bash
npm install --save-dev @gromlab/svg-sprites
```
## 2. Create a sprite module
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
├── index.ts
└── svg-sprite.config.ts
```
```ts
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/turbopack',
name: 'file-manager',
description: 'File manager icons',
})
```
The root barrel is application-owned:
```ts
// src/ui/file-manager/svg-sprite/index.ts
export * from './.svg-sprite'
```
## 3. Add generation
For Turbopack:
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager"
}
}
```
For Webpack, replace the mode key with `next@app/webpack`.
Run the first generation before importing the generated module:
```bash
npm run sprite:file-manager
```
## 4. Use it in a Server Component
The generated component does not contain `'use client'`, so it can be imported directly into `page.tsx` or `layout.tsx`:
```tsx
import { FileManagerIcon } from '@/ui/file-manager/svg-sprite'
export default function Page() {
return (
<main>
<FileManagerIcon icon="folder" width={24} height={24} />
</main>
)
}
```
Next.js emits a separate SVG asset with a content hash. The same generated code is used during SSR and in the browser, with no URL mismatch.
## 5. Add SpriteViewer
The viewer is interactive, so it requires a separate Client Component boundary:
```tsx
'use client'
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('@/ui/file-manager/svg-sprite/.svg-sprite/svg-sprite.manifest.js'),
]
export default function SpritesPage() {
return <SpriteViewer sources={sources} />
}
```
## Verify the bundler
Run the project's build script configured for the selected bundler:
```bash
npm run build
```
The build script and the generator mode key must target the same bundler.
- [App Router + Turbopack](guides/next-app-turbopack.md)
- [App Router + Webpack](guides/next-app-webpack.md)

View File

@@ -1,105 +1,4 @@
# Next.js Pages Router
# Next.js Pages Router Guides Moved
[← Back to home](../../README.md)
Two explicit modes are supported:
| Bundler | Mode key |
|---|---|
| Turbopack | `next@pages/turbopack` |
| Webpack 5 | `next@pages/webpack` |
## 1. Install the package
```bash
npm install --save-dev @gromlab/svg-sprites
```
## 2. Create a sprite module
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
├── index.ts
└── svg-sprite.config.ts
```
```ts
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@pages/webpack',
name: 'file-manager',
description: 'File manager icons',
})
```
The root barrel is application-owned:
```ts
// src/ui/file-manager/svg-sprite/index.ts
export * from './.svg-sprite'
```
## 3. Add generation
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager"
}
}
```
For Turbopack, replace the mode key with `next@pages/turbopack`.
Run the first generation before importing the generated module:
```bash
npm run sprite:file-manager
```
## 4. Use it on a page
```tsx
import { FileManagerIcon } from '@/ui/file-manager/svg-sprite'
export default function FilesPage() {
return <FileManagerIcon icon="folder" width={24} height={24} />
}
export function getServerSideProps() {
return { props: {} }
}
```
The component works the same way with SSR, SSG, and client-side navigation. Next.js emits a separate SVG asset with a content hash.
## 5. Add SpriteViewer
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('@/ui/file-manager/svg-sprite/.svg-sprite/svg-sprite.manifest.js'),
]
export default function SpritesPage() {
return <SpriteViewer sources={sources} />
}
```
## Verify the bundler
Run the project's build script configured for the selected bundler:
```bash
npm run build
```
The build script and the generator mode key must target the same bundler.
- [Pages Router + Turbopack](guides/next-pages-turbopack.md)
- [Pages Router + Webpack](guides/next-pages-webpack.md)

View File

@@ -1,147 +1,3 @@
# Programmatic API
# Programmatic API Moved
[← Back to home](../../README.md)
The package is ESM-only and provides one Node.js generation API. The framework-neutral Viewer is available from `@gromlab/svg-sprites/viewer`, its auto-register entry from `@gromlab/svg-sprites/viewer/element`, and the React bridge from `@gromlab/svg-sprites/react`.
## `generateSprite`
```ts
import { generateSprite } from '@gromlab/svg-sprites'
const result = await generateSprite(
'src/ui/file-manager/svg-sprite/svg-sprite.config.ts',
)
```
For static standalone mode, use `result.spritePath` in a build script to publish the
SVG under an application URL:
```ts
import { copyFile } from 'node:fs/promises'
const result = await generateSprite('src/sprite/svg-sprite.config.ts', {
mode: 'standalone',
})
await copyFile(result.spritePath, 'dist/app-icons/sprite.svg')
```
`spritePath` is a filesystem path, not a browser URL. A deployment-neutral JSON
manifest is available through `result.manifestPath` and is copied independently.
The first argument accepts the full path to an explicitly selected `.ts`, `.js`, or `.json` config file with any name. Passing a directory enables config-less mode and uses that directory as the sprite module root.
The second argument contains optional overrides and always takes precedence over the config:
```ts
await generateSprite('src/ui/file-manager/svg-sprite/custom-config.json', {
mode: 'react@webpack',
name: 'documents',
inputFolder: './assets',
inputFiles: ['../../shared/search.svg'],
transform: {
addTransition: false,
},
generatedNotice: false,
})
```
Configuration is resolved in this order:
```text
defaults → config → API overrides
```
For fully programmatic generation, pass a directory and provide the required settings as overrides:
```ts
await generateSprite('src/ui/file-manager/svg-sprite', {
mode: 'react@vite',
name: 'file-manager',
inputFiles: [
'../../shared/search.svg',
'../../shared/settings.svg',
],
})
```
## Configuration
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@vite',
name: 'file-manager',
description: 'File manager icons',
inputFolder: './icons',
inputFiles: ['../../shared/check.svg'],
transform: {
removeSize: true,
replaceColors: true,
addTransition: true,
},
generatedNotice: true,
})
```
`defineSpriteConfig` is an identity helper for TypeScript autocomplete. JavaScript can export the same object with `export default`, while JSON contains the object directly.
## Specialized wrappers
The specialized functions are available as wrappers around `generateSprite`:
```ts
import { generateNextSprite, generateReactSprite } from '@gromlab/svg-sprites'
await generateReactSprite('path/to/config.ts', 'vite')
await generateNextSprite('path/to/config.ts', {
router: 'app',
bundler: 'turbopack',
})
```
An explicitly supplied target overrides `mode` from the file. Prefer `generateSprite` in new code.
## Config API
```ts
import {
loadSpriteConfig,
resolveSpriteConfig,
validateSpriteConfig,
} from '@gromlab/svg-sprites'
```
- `loadSpriteConfig(file)` loads an explicitly selected `.ts`, `.js`, or `.json` file.
- `validateSpriteConfig(value)` performs runtime validation.
- `resolveSpriteConfig(root, config, overrides)` merges values, applies defaults, and resolves paths relative to `root`.
## Low-level compiler
```ts
import {
compileSprite,
compileSpriteContent,
createShapeTransform,
} from '@gromlab/svg-sprites'
```
These functions are intended for custom orchestration. Standard generation should use `generateSprite`.
## Viewer runtime
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
```
The browser entry registers `<gromlab-sprite-viewer>`. Bare standalone can also load the self-contained `dist/viewer-element.js` without a bundler.
The React bridge keeps the component API:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
```
`SpriteViewer` accepts generated manifests, remote standalone sources, lazy loaders, or an `import.meta.glob` result. The React entry contains `'use client'` and is intended for debug tools; production components are imported from local sprite modules.
The canonical document is now the [programmatic API](reference/programmatic-api.md).

View File

@@ -1,125 +1,3 @@
# React + Vite
# React + Vite Guide Moved
[← Back to home](../../README.md)
A quick guide to installing and using SVG sprites in a React and Vite project.
The result is a typed React component and a separate cacheable SVG asset.
## 1. Install the package
```bash
npm install --save-dev @gromlab/svg-sprites
```
## 2. Create the sprite directory
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
├── index.ts
└── svg-sprite.config.ts
```
Place the source SVG files in `icons/`.
## 3. Add the configuration
```ts
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@vite',
name: 'file-manager',
description: 'File manager icons',
})
```
The root barrel is application-owned and explicitly re-exports the generated API:
```ts
// src/ui/file-manager/svg-sprite/index.ts
export * from './.svg-sprite'
```
By default, SVG files are loaded from `./icons`. You can add shared icons from other directories through `inputFiles`: the directory and file list are combined into a single sprite.
The complete list of options is available under [Unified configuration](reference.md#unified-configuration).
## 4. Add generation to package.json
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
Generated files are excluded from Git, so generation must run before `dev`, `build`, and `typecheck`.
First run:
```bash
npm run sprite:file-manager
```
## 5. Use the component
The name `file-manager` is converted to `FileManagerIcon`:
```tsx
import { FileManagerIcon } from './svg-sprite'
export const OpenFolderButton = () => (
<button type="button">
<FileManagerIcon icon="folder" width={24} height={24} />
Open
</button>
)
```
TypeScript checks the `icon` value against the file names:
```tsx
<FileManagerIcon icon="check" /> // valid
<FileManagerIcon icon="unknown" /> // TypeScript error
```
Types, rendering methods, and color controls are described in the [technical reference](reference.md#react-component-and-typescript).
Vite emits the sprite as a separate file named like `assets/sprite-<hash>.svg`. SVG path data is not included in JavaScript.
## 6. Add a debug page
After integrating the icons, you can display all React sprites with `SpriteViewer`:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
import type { SpriteManifestModule } from '@gromlab/svg-sprites/react'
const sources = import.meta.glob<SpriteManifestModule>(
'/src/**/svg-sprite/.svg-sprite/svg-sprite.manifest.js',
)
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} title="Project icons" />
)
```
Vite automatically finds the generated manifest for each React sprite. The `import.meta.glob` pattern must be a string literal, and generation must run before Vite starts.
Only include the Viewer on a debug route or in an internal tool.
## Troubleshooting
- Missing `.svg-sprite/index.js`: run `npm run sprite:file-manager`.
- The Viewer cannot find the sprite: check the glob path to `.svg-sprite/svg-sprite.manifest.js`.
- `Refusing to overwrite a user file` error: there is a user file at a generated path.
- The icon does not change color: use `color` or `--icon-color-N`.
The canonical guide is now [React + Vite](guides/react-vite.md).

View File

@@ -1,129 +1,3 @@
# React + Webpack 5
# React + Webpack Guide Moved
[← Back to home](../../README.md)
A quick guide to installing and using SVG sprites in a React and Webpack 5 project.
The result is a typed React component and a separate SVG asset emitted through Webpack Asset Modules.
## 1. Install the package
```bash
npm install --save-dev @gromlab/svg-sprites
```
## 2. Create the sprite directory
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
├── index.ts
└── svg-sprite.config.ts
```
Place the source SVG files in `icons/`.
## 3. Add the configuration
```ts
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@webpack',
name: 'file-manager',
description: 'File manager icons',
})
```
The root barrel is application-owned and explicitly re-exports the generated API:
```ts
// src/ui/file-manager/svg-sprite/index.ts
export * from './.svg-sprite'
```
By default, SVG files are loaded from `./icons`. You can add shared icons from other directories through `inputFiles`: the directory and file list are combined into a single sprite.
The complete list of options is available under [Unified configuration](reference.md#unified-configuration).
## 4. Add generation to package.json
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
Generated files are excluded from Git, so generation must run before `dev`, `build`, and `typecheck`.
First run:
```bash
npm run sprite:file-manager
```
## 5. Use the component
```tsx
import { FileManagerIcon } from './svg-sprite'
export const OpenFolderButton = () => (
<button type="button">
<FileManagerIcon icon="folder" width={24} height={24} />
Open
</button>
)
```
TypeScript checks the `icon` value against the file names:
```tsx
<FileManagerIcon icon="folder" /> // valid
<FileManagerIcon icon="missing" /> // TypeScript error
```
Types, rendering methods, and color controls are described in the [technical reference](reference.md#react-component-and-typescript).
Webpack processes the generated `new URL('./sprite.svg', import.meta.url)` through Asset Modules and emits a separate SVG asset.
If the project already uses a custom SVG loader, make sure it does not intercept the generated `sprite.svg` instead of Asset Modules.
The generated component imports `react/react-component.module.css`, so Webpack must process CSS Modules through `css-loader` and `style-loader` or `MiniCssExtractPlugin`. If the TypeScript project does not include a declaration for CSS Modules, add one separately.
## 6. Add a debug page
Webpack does not support Vite's `import.meta.glob` API, so provide static loaders:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('./ui/file-manager/svg-sprite/.svg-sprite/svg-sprite.manifest.js'),
() => import('./ui/navigation/svg-sprite/.svg-sprite/svg-sprite.manifest.js'),
]
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} title="Project icons" />
)
```
The paths in `import()` must be string literals. Webpack creates chunks for the manifests and associates them with the SVG assets.
Only include the Viewer on a debug route or in an internal tool.
## Troubleshooting
- Missing `.svg-sprite/index.js`: run `npm run sprite:file-manager`.
- The Viewer does not load the sprite: check the `import()` path to `.svg-sprite/svg-sprite.manifest.js`.
- Incorrect asset URL: check `output.publicPath`.
- Another loader intercepts the SVG: exclude the generated sprite from the incompatible rule.
For Next.js, use the separate mode keys described in the [App Router](next-app.md) and [Pages Router](next-pages.md) guides.
The canonical guide is now [React + Webpack](guides/react-webpack.md).

View File

@@ -1,555 +1,3 @@
# Technical reference
# Technical Reference Moved
[← Back to home](../../README.md)
Reference for the configuration, generated API, and behavior of `@gromlab/svg-sprites`. For step-by-step setup instructions, see the guide for your stack:
- [Next.js App Router](next-app.md)
- [Next.js Pages Router](next-pages.md)
- [React + Vite](react-vite.md)
- [React + Webpack 5](react-webpack.md)
## Requirements
- Node.js 18 or newer;
- the package is distributed as ESM and is loaded with `import`;
- React 18 or 19 is required for generated components and `@gromlab/svg-sprites/react`;
- for typed package exports, use TypeScript 5+ with `moduleResolution: "bundler"`, `"node16"`, or `"nodenext"`.
Install the package as a development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
## CLI and generation modes
The CLI accepts exactly one path: an explicitly selected config file or a directory for config-less generation:
```text
svg-sprites [options] <config-file-or-directory>
```
| Environment | Mode |
|---|---|
| Static HTML / custom publishing | `standalone` |
| Standalone + Vite | `standalone@vite` |
| Standalone + Webpack 5 | `standalone@webpack` |
| React + Vite | `react@vite` |
| React + Webpack 5 | `react@webpack` |
| Next.js App Router + Turbopack | `next@app/turbopack` |
| Next.js App Router + Webpack 5 | `next@app/webpack` |
| Next.js Pages Router + Turbopack | `next@pages/turbopack` |
| Next.js Pages Router + Webpack 5 | `next@pages/webpack` |
The config file may have any name and use the `.ts`, `.js`, or `.json` extension. The CLI does not discover it by convention: pass the file explicitly. The guides use `svg-sprite.config.ts` as the recommended name.
When a directory is passed, all settings come from CLI options. When a config file is passed, CLI options override the file. The full order is `defaults → config → CLI`.
Available options are `--mode`, `--name`, `--description`, `--input-folder`, repeatable `--input-file`, plus the `--remove-size`/`--no-remove-size`, `--replace-colors`/`--no-replace-colors`, `--add-transition`/`--no-add-transition`, and `--generated-notice`/`--no-generated-notice` pairs. Transform flags override individual fields, while supplying at least one `--input-file` replaces the complete config `inputFiles` array.
The mode must match the application's publishing strategy. Bare `standalone` leaves the public URL to the application; Vite and Webpack modes generate bundler-specific SVG asset integration.
## Unified configuration
Each config file defines one independent sprite.
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/turbopack',
name: 'app',
description: 'Shared application icons',
inputFolder: './local-icons',
inputFiles: [
'../../assets/icons/search.svg',
'../../assets/icons/settings.svg',
],
transform: {
removeSize: true,
replaceColors: true,
addTransition: true,
},
generatedNotice: true,
})
```
| Option | Type | Default | Purpose |
|---|---|---|---|
| `mode` | `SpriteMode` | None | Generation mode; may be supplied by CLI/API |
| `name` | `string` | Derived from the directory | Name of the sprite, component, and public types |
| `description` | `string` | None | Description for types and the debug manifest |
| `inputFolder` | `string` | `./icons` | SVG directory relative to the module root |
| `inputFiles` | `string[]` | `[]` | Paths to individual SVG files relative to the module root |
| `transform` | `TransformOptions` | All enabled | SVG preparation settings |
| `generatedNotice` | `boolean` | `true` | Full or abbreviated warning in generated files |
### Sprite name
`name` is written in kebab-case and must start with an ASCII letter:
```text
app → AppIcon
file-manager → FileManagerIcon
```
If `name` is omitted, the generator derives it from the directory. For a directory named `svg-sprite` or `svg-sprites`, the parent directory's name is used.
### Icon sources
`inputFolder` and `inputFiles` are combined into one set. This lets you keep local SVG files next to a module and add shared icons from other parts of the project without copying them.
If `inputFiles` is populated and the implicit `./icons` directory does not exist, generation uses only the file list. An explicitly configured `inputFolder` that does not exist is an error.
Only the top level of the directory is scanned. Nested directories are not traversed recursively. For a nested structure, list the exact paths through `inputFiles`.
Identical absolute paths are deduplicated. Different SVG files with the same file name are treated as a conflict because the public icon name is derived from the basename.
## Generated module
After generation, a React or Next.js sprite directory looks like this:
```text
app-icons/
├── .gitignore
├── svg-sprite.config.ts
├── index.ts # optional user-owned barrel
└── .svg-sprite/
├── state.json
├── index.js
├── index.d.ts
├── icon-data.js
├── icon-data.d.ts
├── sprite.svg
├── svg-sprite.manifest.js
├── svg-sprite.manifest.d.ts
└── react/
├── react-component.js
├── react-component.d.ts
└── react-component.module.css
```
| File | Purpose |
|---|---|
| `.svg-sprite/index.js` | Production exports for the component and runtime icon-name list |
| `.svg-sprite/index.d.ts` | Public declarations for the component, props, styles, and icon-name union |
| `.svg-sprite/svg-sprite.manifest.js` | Debug metadata and the asset URL for `SpriteViewer` |
| `.svg-sprite/sprite.svg` | Compiled SVG sprite |
| `.svg-sprite/react/react-component.js` | React component runtime without TypeScript or JSX |
| `.svg-sprite/react/react-component.d.ts` | React component props, style, and declaration |
| `.svg-sprite/react/react-component.module.css` | Styles for the React implementation |
| `.svg-sprite/icon-data.js` | Runtime icon-name list and internal IDs |
| `.svg-sprite/*.d.ts` | TypeScript declarations for the corresponding JavaScript modules |
| `.svg-sprite/state.json` | Mode, contract version, and managed file list |
Standalone contracts do not create `react/`. Bare `standalone` contains only the
runtime asset and deployment-neutral manifest data:
```text
.svg-sprite/
├── state.json
├── sprite.svg
└── svg-sprite.manifest.json
```
`standalone@vite` and `standalone@webpack` additionally create `index.*`,
`icon-data.*`, and a resolved `svg-sprite.manifest.*`.
The generator overwrites and deletes only files that contain its marker. If a user file occupies a managed path, generation fails. The root `index.ts` is user-owned; create a barrel when needed:
```ts
export * from './.svg-sprite'
```
## React component and TypeScript
A sprite with `name: 'app'` exports:
```ts
export { AppIcon, appIconNames }
export type { AppIconName, AppIconProps, AppIconStyle }
```
### Icon names
SVG file names become valid `icon` values:
```tsx
<AppIcon icon="search" />
<AppIcon icon="unknown" /> // TypeScript error
```
The runtime list contains the same values:
```ts
import { appIconNames } from '@/ui/app-icons'
// readonly ['search', 'settings', 'user']
```
Names containing spaces or other characters that are unsafe in SVG IDs remain part of the public API. For the internal fragment ID, the generator creates a stable, safe hash:
```text
folder open.svg → icon="folder open" → id="icon-<stable-hash>"
```
For these names, use the generated component or the `id` from the debug manifest instead of constructing the fragment ID manually.
### SVG attributes
By default, the component renders an `<svg>` and accepts standard SVG attributes:
```tsx
<AppIcon
icon="search"
width={24}
height={24}
color="rebeccapurple"
className="searchIcon"
aria-label="Search"
/>
```
The component does not add accessibility semantics automatically. Pass appropriate `aria-*` attributes, a `role`, or a label based on the icon's purpose.
### Wrapper
`wrapped` renders a `<span>` containing the SVG. In this mode, the remaining props apply to the `<span>`:
```tsx
<AppIcon icon="search" wrapped className="iconWrapper" />
```
### Typed CSS custom properties
`AppIconStyle` extends `CSSProperties` and supports properties in the form `--icon-color-N`:
```tsx
<AppIcon
icon="user"
style={{
'--icon-color-1': '#2563eb',
'--icon-color-2': '#dbeafe',
}}
/>
```
## Multiple sprites
Each directory with a configuration creates an independent component, types, manifest, and SVG asset:
```text
app-icons → AppIcon → shared icons
analytics-icons → AnalyticsIcon → analytics page icons
editor-icons → EditorIcon → editor icons
```
The same source SVG can be added to multiple configurations through `inputFiles`. You do not need to copy the file into each sprite directory.
For multiple sprites, add a separate CLI command for each directory or combine the commands in a shared npm script.
## Formats and rendering methods
All current modes generate the `stack` format.
| Format | `<svg><use>` | `<img>` | CSS background |
|---|---:|---:|---:|
| `stack` | Yes | Yes | Yes |
### Generated component
For React and Next.js, use the generated component. It knows the internal IDs, constructs the URL, and provides a TypeScript API:
```tsx
<AppIcon icon="search" width={24} height={24} />
```
### Manually with `<svg><use>`
How you obtain `spriteUrl` depends on the bundler.
Static HTML after the application publishes `.svg-sprite/sprite.svg`:
```html
<svg aria-hidden="true">
<use href="/assets/icons.svg#search"></use>
</svg>
```
Standalone Vite/Webpack provides generated `getIconsIconHref()` and an internal ID
map. Do not construct fragments from unsafe file names manually.
Vite:
```ts
import spriteUrl from './.svg-sprite/sprite.svg?no-inline'
```
Webpack 5, Turbopack, and Next.js:
```ts
const spriteUrl = new URL('./.svg-sprite/sprite.svg', import.meta.url).href
```
After obtaining the URL, use it in JSX:
```tsx
<svg width="24" height="24" aria-label="Search">
<use href={`${spriteUrl}#search`} />
</svg>
```
For names that are unsafe as SVG IDs, use the internal `id` from the manifest.
### With `<img>`
```tsx
<img src={`${spriteUrl}#search`} width={24} height={24} alt="Search" />
```
An SVG inside `<img>` is isolated from the page's CSS. Setting `color` or `--icon-color-N` on the outer element does not change its internal colors.
### With CSS
```css
.icon {
background: url('./.svg-sprite/sprite.svg#search') center / contain no-repeat;
}
```
For a single-color silhouette, you can use a mask:
```css
.icon {
background-color: currentColor;
mask: url('./.svg-sprite/sprite.svg#search') center / contain no-repeat;
}
```
A mask does not preserve original colors, gradients, or differences between `fill` and `stroke`.
The path in CSS is resolved relative to the CSS file itself. In these examples, the CSS file is next to `svg-sprite.config.ts`.
## Assets and caching
The generated component or standalone facade passes the SVG to the bundler as a separate asset:
- Vite uses a static import with `?no-inline`;
- Webpack 5, Turbopack, and Next.js use `new URL(..., import.meta.url)`;
- SVG path data is not serialized into generated JavaScript.
Bare `standalone` does not participate in an asset pipeline: the application copies
or publishes `sprite.svg` and owns its URL, versioning, and cache policy.
With standard asset naming, the bundler adds a content hash:
```text
/assets/sprite-<hash>.svg
```
This allows the SVG to be cached separately from JavaScript. Changing React code does not change the sprite contents, while changing icons creates a new asset version.
HTTP cache headers, CDN behavior, and `Cache-Control` are configured by the application or hosting platform. With Webpack, the final file name depends on the project's `assetModuleFilename`.
## SVG transformations
All transformations are enabled by default and can be configured independently:
| Option | Behavior |
|---|---|
| `removeSize` | Removes `width` and `height` from the root `<svg>` while preserving an existing `viewBox` |
| `replaceColors` | Replaces detected `fill` and `stroke` values with `--icon-color-N` |
| `addTransition` | Adds transitions for `fill` and `stroke` to colored elements and generated styles |
To disable an individual operation:
```ts
export default defineSpriteConfig({
mode: 'next@app/turbopack',
transform: {
removeSize: false,
replaceColors: false,
addTransition: false,
},
})
```
Source SVG files are not modified. Transformations apply only to the generated sprite contents.
## Color management
### Monochrome icons
If one color is detected, its fallback becomes `currentColor`:
```svg
stroke="var(--icon-color-1, currentColor)"
```
Set the color through a prop or CSS:
```tsx
<AppIcon icon="search" color="rebeccapurple" />
```
### Multicolor icons
Each unique color gets its own custom property with the original color as its fallback:
```svg
fill="var(--icon-color-1, #798198)"
fill="var(--icon-color-2, #ffffff)"
fill="var(--icon-color-3, #129d9d)"
```
You can override only the values you need:
```css
.icon {
--icon-color-1: #4b5563;
--icon-color-3: #14b8a6;
}
```
### Limitations
- `none`, `transparent`, `inherit`, `unset`, and `initial` are not replaced;
- colors in `fill`, `stroke`, and inline `style` attributes are handled most reliably;
- CSS classes and external stylesheets inside the SVG are not the primary transformation use case;
- `url(#...)` values may be replaced along with colors, so gradients and patterns require a separate sprite with `replaceColors: false`;
- masks, filters, and complex internal CSS rules require visual verification;
- page CSS custom properties are available through `<svg><use>`, but not inside `<img>` or a CSS background.
For a complex icon, you can disable `replaceColors` in a separate sprite configuration.
## SpriteViewer
The Viewer uses one Shadow DOM Web Component for every mode. React and future framework components are bridges to that same element, so the visuals and behavior are not duplicated.
Bare `standalone` loads the self-contained browser bundle and supplies the JSON manifest URL and the published SVG URL:
```html
<script
type="module"
src="https://unpkg.com/@gromlab/svg-sprites@<version>/dist/viewer-element.js"
></script>
<gromlab-sprite-viewer
viewer-title="Project icons"
manifest-url="/app-icons/manifest.json"
sprite-url="/app-icons/sprite.svg"
></gromlab-sprite-viewer>
```
`viewer-element.js` has no additional runtime files and can be copied with the other static assets for self-hosting.
`standalone@vite` and `standalone@webpack` register the same element through an npm entry and pass the generated JS manifest through the `sources` property:
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
import spriteManifest from './svg-sprite/.svg-sprite/svg-sprite.manifest.js'
const viewer = document.querySelector<SpriteViewerElement>('gromlab-sprite-viewer')!
viewer.sources = [spriteManifest]
```
React and Next.js keep the component API:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
```
It accepts ready-made manifests, remote standalone sources, an array of lazy loaders, or a record in the format returned by `import.meta.glob`.
Vite:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
import type { SpriteManifestModule } from '@gromlab/svg-sprites/react'
const sources = import.meta.glob<SpriteManifestModule>(
'/src/**/svg-sprite/.svg-sprite/svg-sprite.manifest.js',
)
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} title="Project icons" />
)
```
Webpack and Next.js:
```tsx
const sources = [
() => import('@/ui/app-icons/.svg-sprite/svg-sprite.manifest.js'),
() => import('@/features/analytics/icons/.svg-sprite/svg-sprite.manifest.js'),
]
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} />
)
```
The Viewer displays groups, search, `viewBox`, CSS custom properties, and fallback colors. React/Next manifests get React, SVG, IMG, and CSS tabs; standalone manifests get SVG, IMG, and CSS. You can change color values in the interface and immediately inspect the result.
### Viewer theme
By default, `colorTheme="auto"` follows `prefers-color-scheme`. You can explicitly pass `light` or `dark`:
```tsx
<SpriteViewer sources={sources} colorTheme="dark" />
```
To synchronize it with the application theme:
```tsx
<SpriteViewer
sources={sources}
colorTheme={appTheme}
onColorThemeChange={setAppTheme}
/>
```
`@gromlab/svg-sprites/react` contains `'use client'` and renders the Web Component host; its internal Shadow DOM is created after the browser runtime loads. In the Next.js App Router, place the Viewer inside a separate Client Component boundary and use it only on a debug route or in an internal tool.
## Generated files, Git, and CI
A modern sprite module creates a local `.gitignore` for:
```text
/.svg-sprite/
```
Commit the local `.gitignore` to the repository once. It excludes the other generated files, so generation must run before commands that import the sprite module:
```json
{
"scripts": {
"sprites": "svg-sprites src/ui/app-icons/svg-sprite.config.ts",
"predev": "npm run sprites",
"prebuild": "npm run sprites",
"pretypecheck": "npm run sprites"
}
}
```
CI must install development dependencies and run the generation script before building or type-checking.
If the sprite directory already contains a user-created `.gitignore` or a user-owned file inside `.svg-sprite`, the generator will not overwrite it. The root `index.ts` remains user-owned and may re-export the generated API.
## Troubleshooting
- Missing `.svg-sprite/index.js`: run the generation script before importing the generated module.
- Source not found: pass an existing config file or sprite module directory.
- Mode missing: add `mode` to the config or pass `--mode`.
- Icon missing from the type: check `inputFiles`, the `.svg` extension, and the nesting level under `inputFolder`.
- Name conflict: two different SVG files have the same basename; rename one of them.
- `Refusing to overwrite a user file`: a file without the generated marker occupies a managed path.
- The icon does not change color: use `<svg><use>` or the generated component and check `replaceColors`.
- Webpack emits an incorrect URL: check Asset Modules, `output.publicPath`, and SVG loaders.
- Static sprite returns 404: check the post-generation copy or server alias, and do not put a filesystem `spritePath` into HTML.
- The Viewer cannot find the sprite: check the path to `.svg-sprite/svg-sprite.manifest.js` and run generation before starting the application.
- Build and mode do not match: use the target that corresponds to the actual bundler.
For custom orchestration and low-level compilation, see the [Programmatic API](programmatic-api.md).
The canonical document is now the [technical reference](reference/technical.md).

View File

@@ -0,0 +1,4 @@
# Reference
- [Technical reference](technical.md)
- [Programmatic API](programmatic-api.md)

View File

@@ -0,0 +1,147 @@
# Programmatic API
[Documentation index](../README.md)
The package is ESM-only and provides one Node.js generation API. The framework-neutral Viewer is available from `@gromlab/svg-sprites/viewer`, its auto-register entry from `@gromlab/svg-sprites/viewer/element`, and the React bridge from `@gromlab/svg-sprites/react`.
## `generateSprite`
```ts
import { generateSprite } from '@gromlab/svg-sprites'
const result = await generateSprite(
'src/ui/file-manager/svg-sprite/svg-sprite.config.ts',
)
```
For static standalone mode, use `result.spritePath` in a build script to publish the
SVG under an application URL:
```ts
import { copyFile } from 'node:fs/promises'
const result = await generateSprite('src/sprite/svg-sprite.config.ts', {
mode: 'standalone',
})
await copyFile(result.spritePath, 'dist/app-icons/sprite.svg')
```
`spritePath` is a filesystem path, not a browser URL. A deployment-neutral JSON
manifest is available through `result.manifestPath` and is copied independently.
The first argument accepts the full path to an explicitly selected `.ts`, `.js`, or `.json` config file with any name. Passing a directory enables config-less mode and uses that directory as the sprite module root.
The second argument contains optional overrides and always takes precedence over the config:
```ts
await generateSprite('src/ui/file-manager/svg-sprite/custom-config.json', {
mode: 'react@webpack',
name: 'documents',
input: ['./assets', '../../shared/search.svg'],
transform: {
addTransition: false,
},
generatedNotice: false,
})
```
Configuration is resolved in this order:
```text
defaults → config → API overrides
```
For fully programmatic generation, pass a directory and provide the required settings as overrides:
```ts
await generateSprite('src/ui/file-manager/svg-sprite', {
mode: 'react@vite',
name: 'file-manager',
input: [
'../../shared/search.svg',
'../../shared/settings.svg',
],
})
```
## Configuration
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@vite',
name: 'file-manager',
description: 'File manager icons',
input: ['./icons', '../../shared/check.svg'],
transform: {
removeSize: true,
replaceColors: true,
addTransition: true,
},
generatedNotice: true,
})
```
`input` accepts one folder, SVG file, or glob pattern, or an array that combines them. When omitted, it defaults to `./icons`; relative paths start at the config directory.
`defineSpriteConfig` is an identity helper for TypeScript autocomplete. JavaScript can export the same object with `export default`, while JSON contains the object directly.
## Specialized wrappers
The specialized functions are available as wrappers around `generateSprite`:
```ts
import { generateNextSprite, generateReactSprite } from '@gromlab/svg-sprites'
await generateReactSprite('path/to/config.ts', 'vite')
await generateNextSprite('path/to/config.ts', {
router: 'app',
bundler: 'turbopack',
})
```
An explicitly supplied target overrides `mode` from the file. Prefer `generateSprite` in new code.
## Config API
```ts
import {
loadSpriteConfig,
resolveSpriteConfig,
validateSpriteConfig,
} from '@gromlab/svg-sprites'
```
- `loadSpriteConfig(file)` loads an explicitly selected `.ts`, `.js`, or `.json` file.
- `validateSpriteConfig(value)` performs runtime validation.
- `resolveSpriteConfig(root, config, overrides)` merges values, applies defaults, and resolves paths relative to `root`.
## Low-level compiler
```ts
import {
compileSprite,
compileSpriteContent,
createShapeTransform,
} from '@gromlab/svg-sprites'
```
These functions are intended for custom orchestration. Standard generation should use `generateSprite`.
## Viewer runtime
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
```
The browser entry registers `<gromlab-sprite-viewer>`. Bare standalone can also load the self-contained `dist/viewer-element.js` without a bundler.
The React bridge keeps the component API:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
```
`SpriteViewer` accepts generated manifests, remote standalone sources, lazy loaders, or an `import.meta.glob` result. The React entry contains `'use client'` and is intended for debug tools; production components are imported from local sprite modules.

View File

@@ -0,0 +1,632 @@
# Technical reference
[Documentation index](../README.md)
Reference for the configuration, generated API, and behavior of `@gromlab/svg-sprites`. For step-by-step setup instructions, see the guide for your stack:
- [Bare standalone](../guides/standalone.md)
- [Standalone + Vite](../guides/standalone-vite.md)
- [Standalone + Webpack 5](../guides/standalone-webpack.md)
- [React + Vite](../guides/react-vite.md)
- [React + Webpack 5](../guides/react-webpack.md)
- [Next.js App Router + Turbopack](../guides/next-app-turbopack.md)
- [Next.js App Router + Webpack](../guides/next-app-webpack.md)
- [Next.js Pages Router + Turbopack](../guides/next-pages-turbopack.md)
- [Next.js Pages Router + Webpack](../guides/next-pages-webpack.md)
## Requirements
- Node.js 18 or newer;
- the package is distributed as ESM and is loaded with `import`;
- React 18 or 19 is required only for React/Next generated components and `@gromlab/svg-sprites/react`;
- for typed package exports, use TypeScript 5+ with `moduleResolution: "bundler"`, `"node16"`, or `"nodenext"`.
Generation does not require a project dependency. Run the CLI through `npx`:
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites path/to/svg-sprite.config.ts
```
Install the package as a development dependency only when the project needs the
Viewer, config types, or the programmatic API:
```bash
npm install --save-dev @gromlab/svg-sprites
```
## CLI and generation modes
The CLI accepts exactly one path: an explicitly selected config file or a directory for config-less generation:
```text
svg-sprites [options] <config-file-or-directory>
```
| Environment | Mode |
|---|---|
| Static HTML / custom publishing | `standalone` |
| Standalone + Vite | `standalone@vite` |
| Standalone + Webpack 5 | `standalone@webpack` |
| React + Vite | `react@vite` |
| React + Webpack 5 | `react@webpack` |
| Next.js App Router + Turbopack | `next@app/turbopack` |
| Next.js App Router + Webpack 5 | `next@app/webpack` |
| Next.js Pages Router + Turbopack | `next@pages/turbopack` |
| Next.js Pages Router + Webpack 5 | `next@pages/webpack` |
The config file may have any name and use the `.ts`, `.js`, or `.json` extension. The CLI does not discover it by convention: pass the file explicitly. The guides use `svg-sprite.config.ts` as the recommended name.
When a directory is passed, all settings come from CLI options. When a config file is passed, CLI options override the file. The full order is `defaults → config → CLI`.
Available options are `--mode`, `--name`, `--description`, repeatable `--input <path-or-glob>`, plus the `--remove-size`/`--no-remove-size`, `--replace-colors`/`--no-replace-colors`, `--add-transition`/`--no-add-transition`, and `--generated-notice`/`--no-generated-notice` pairs. Transform flags override individual fields, while supplying at least one `--input` replaces the complete config `input` value.
Quote CLI glob patterns with single quotes so the shell does not expand them before the generator receives them:
```bash
svg-sprites --input './icons/**/*.svg' --input '!./icons/legacy/**' svg-sprite.config.ts
```
The mode must match the application's publishing strategy. Bare `standalone` leaves the public URL to the application; Vite and Webpack modes generate bundler-specific SVG asset integration.
## Unified configuration
Each config file defines one independent sprite.
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/turbopack',
name: 'app',
description: 'Shared application icons',
input: [
'./local-icons',
'../../assets/icons/*.svg',
'!../../assets/icons/deprecated-*.svg',
],
transform: {
removeSize: true,
replaceColors: true,
addTransition: true,
},
generatedNotice: true,
})
```
| Option | Type | Default | Purpose |
|---|---|---|---|
| `mode` | `SpriteMode` | None | Generation mode; may be supplied by CLI/API |
| `name` | `string` | Derived from the directory | Name of the sprite, component, and public types |
| `description` | `string` | None | Description for types and the debug manifest |
| `input` | `string \| string[]` | `./icons` | SVG folders, files, and glob patterns relative to the config directory |
| `transform` | `TransformOptions` | All enabled | SVG preparation settings |
| `generatedNotice` | `boolean` | `true` | Full or abbreviated warning in generated files |
### Sprite name
`name` is written in kebab-case and must start with an ASCII letter:
```text
app → AppIcon
file-manager → FileManagerIcon
```
If `name` is omitted, the generator derives it from the directory. For a directory named `svg-sprite` or `svg-sprites`, the parent directory's name is used.
### Icon sources
`SpriteConfig.input` is optional and has the type `string | string[]`. When it is omitted, the source is the literal `./icons` folder relative to the config directory. In config-less mode, relative paths start at the directory passed to the CLI or API.
Each positive string may be a literal folder, a literal `.svg` file, or a glob pattern. A literal folder includes only its immediate `*.svg` children. Use an explicit pattern such as `icons/**/*.svg` to traverse nested directories.
An array combines all positive sources. A pattern prefixed with `!` excludes its matches from the combined result globally, regardless of which positive source included them.
Supported glob syntax includes:
| Syntax | Meaning |
|---|---|
| `*` | Any characters within one path segment |
| `**` | Any number of nested directories |
| `?` | One character within a path segment |
| `{a,b}` | Either alternative |
| `[abc]` | One character from the set or range |
| `!pattern` | Exclude matches from the full combined input |
Every positive source or pattern must find at least one SVG, otherwise generation fails. Duplicate paths are removed and the final file list is sorted deterministically. Different SVG files with the same basename remain a conflict because the basename defines the public icon name.
## Generated module
After generation, a React or Next.js sprite directory looks like this:
```text
app-icons/
├── .gitignore
├── svg-sprite.config.ts
├── index.ts # optional user-owned barrel
└── .svg-sprite/
├── index.js
├── index.d.ts
├── icon-data.js
├── icon-data.d.ts
├── sprite.svg
├── svg-sprite.manifest.js
├── svg-sprite.manifest.d.ts
└── react/
├── react-component.js
├── react-component.d.ts
└── react-component.module.css
```
| File | Purpose |
|---|---|
| `.svg-sprite/index.js` | Mode-specific production facade and runtime icon-name list |
| `.svg-sprite/index.d.ts` | Public declarations for the facade, component, and icon-name union |
| `.svg-sprite/svg-sprite.manifest.js` | Debug metadata and the asset URL for `SpriteViewer` |
| `.svg-sprite/sprite.svg` | Compiled SVG sprite |
| `.svg-sprite/react/react-component.js` | React component runtime without TypeScript or JSX |
| `.svg-sprite/react/react-component.d.ts` | React component props, style, and declaration |
| `.svg-sprite/react/react-component.module.css` | Styles for the React implementation |
| `.svg-sprite/icon-data.js` | Runtime icon-name list and internal IDs |
| `.svg-sprite/*.d.ts` | TypeScript declarations for the corresponding JavaScript modules |
Standalone contracts do not create `react/`. Bare `standalone` contains only the
runtime asset and deployment-neutral manifest data:
```text
.svg-sprite/
├── sprite.svg
└── svg-sprite.manifest.json
```
`standalone@vite` and `standalone@webpack` additionally create `index.*`,
`icon-data.*`, and a resolved `svg-sprite.manifest.*`. Their facade contains a
native generated Web Component with no external runtime dependencies. Bare
`standalone` intentionally does not generate a JavaScript component.
The generator overwrites and deletes only files that contain its marker. If a user file occupies a managed path, generation fails. The root `index.ts` is user-owned; create a barrel when needed:
```ts
export * from './.svg-sprite'
```
## Standalone Web Component and TypeScript
In `standalone@vite` and `standalone@webpack`, a sprite with `name: 'app'`
exports the `defineAppIconElement()` registration function and the `<app-icon>`
tag:
```ts
import { defineAppIconElement } from '@/ui/app-icons'
defineAppIconElement()
```
After registration, use the element in HTML:
```html
<app-icon icon="search" aria-hidden="true"></app-icon>
<app-icon
icon="settings"
role="img"
aria-label="Settings"
></app-icon>
```
The component renders `<svg><use>` in an open Shadow DOM, selects the internal
ID and `viewBox`, and obtains the asset URL through the corresponding Vite or
Webpack mechanism. The host defaults to `1em × 1em`; set `class`, `style`,
`color`, and `--icon-color-N` with ordinary CSS.
The generated `HTMLElementTagNameMap` types the property API:
```ts
const icon = document.createElement('app-icon')
icon.icon = 'search'
icon.icon = 'unknown' // TypeScript error
```
TypeScript does not validate attribute values in plain HTML. Therefore an
unknown `icon="unknown"` is also validated at runtime: the component hides its
inner SVG and reports an error instead of creating a `#undefined` fragment.
Calling `defineAppIconElement()` repeatedly is safe for the same sprite; a
different element already registered as `<app-icon>` causes an error.
## React component and TypeScript
A sprite with `name: 'app'` exports:
```ts
export { AppIcon, appIconNames }
export type { AppIconName, AppIconProps, AppIconStyle }
```
### Icon names
SVG file names become valid `icon` values:
```tsx
<AppIcon icon="search" />
<AppIcon icon="unknown" /> // TypeScript error
```
The runtime list contains the same values:
```ts
import { appIconNames } from '@/ui/app-icons'
// readonly ['search', 'settings', 'user']
```
Names containing spaces or other characters that are unsafe in SVG IDs remain part of the public API. For the internal fragment ID, the generator creates a stable, safe hash:
```text
folder open.svg → icon="folder open" → id="icon-<stable-hash>"
```
For these names, use the generated component or the `id` from the debug manifest instead of constructing the fragment ID manually.
### SVG attributes
By default, the component renders an `<svg>` and accepts standard SVG attributes:
```tsx
<AppIcon
icon="search"
width={24}
height={24}
color="rebeccapurple"
className="searchIcon"
aria-label="Search"
/>
```
The component does not add accessibility semantics automatically. Pass appropriate `aria-*` attributes, a `role`, or a label based on the icon's purpose.
### Wrapper
`wrapped` renders a `<span>` containing the SVG. In this mode, the remaining props apply to the `<span>`:
```tsx
<AppIcon icon="search" wrapped className="iconWrapper" />
```
### Typed CSS custom properties
`AppIconStyle` extends `CSSProperties` and supports properties in the form `--icon-color-N`:
```tsx
<AppIcon
icon="user"
style={{
'--icon-color-1': '#2563eb',
'--icon-color-2': '#dbeafe',
}}
/>
```
## Multiple sprites
Each directory with a configuration creates an independent component, types, manifest, and SVG asset:
```text
app-icons → AppIcon → shared icons
analytics-icons → AnalyticsIcon → analytics page icons
editor-icons → EditorIcon → editor icons
```
The same source SVG can be added to multiple configurations through `input`. You do not need to copy the file into each sprite directory.
For multiple sprites, add a separate CLI command for each directory or combine the commands in a shared npm script.
## Formats and rendering methods
All current modes generate the `stack` format.
| Format | `<svg><use>` | `<img>` | CSS background |
|---|---:|---:|---:|
| `stack` | Yes | Yes | Yes |
### Generated component
For React and Next.js, use the generated React component. It knows the internal IDs, constructs the URL, and provides a TypeScript API:
```tsx
<AppIcon icon="search" width={24} height={24} />
```
For `standalone@vite` and `standalone@webpack`, use the generated Web Component:
```html
<app-icon icon="search" style="font-size: 24px"></app-icon>
```
### Manually with `<svg><use>`
How you obtain `spriteUrl` depends on the bundler.
Static HTML after the application publishes `.svg-sprite/sprite.svg`:
```html
<svg aria-hidden="true">
<use href="/assets/icons.svg#search"></use>
</svg>
```
Standalone Vite/Webpack provides generated `getIconsIconHref()` and an internal ID
map. Do not construct fragments from unsafe file names manually.
Vite:
```ts
import spriteUrl from './.svg-sprite/sprite.svg?no-inline'
```
Webpack 5, Turbopack, and Next.js:
```ts
const spriteUrl = new URL('./.svg-sprite/sprite.svg', import.meta.url).href
```
After obtaining the URL, use it in JSX:
```tsx
<svg width="24" height="24" aria-label="Search">
<use href={`${spriteUrl}#search`} />
</svg>
```
For names that are unsafe as SVG IDs, use the internal `id` from the manifest.
### With `<img>`
```tsx
<img src={`${spriteUrl}#search`} width={24} height={24} alt="Search" />
```
An SVG inside `<img>` is isolated from the page's CSS. Setting `color` or `--icon-color-N` on the outer element does not change its internal colors.
### With CSS
```css
.icon {
background: url('./.svg-sprite/sprite.svg#search') center / contain no-repeat;
}
```
For a single-color silhouette, you can use a mask:
```css
.icon {
background-color: currentColor;
mask: url('./.svg-sprite/sprite.svg#search') center / contain no-repeat;
}
```
A mask does not preserve original colors, gradients, or differences between `fill` and `stroke`.
The path in CSS is resolved relative to the CSS file itself. In these examples, the CSS file is next to `svg-sprite.config.ts`.
## Assets and caching
The generated component or standalone facade passes the SVG to the bundler as a separate asset:
- Vite uses a static import with `?no-inline`;
- Webpack 5, Turbopack, and Next.js use `new URL(..., import.meta.url)`;
- SVG path data is not serialized into generated JavaScript.
Bare `standalone` does not participate in an asset pipeline: the application copies
or publishes `sprite.svg` and owns its URL, versioning, and cache policy.
With standard asset naming, the bundler adds a content hash:
```text
/assets/sprite-<hash>.svg
```
This allows the SVG to be cached separately from JavaScript. Changing React code does not change the sprite contents, while changing icons creates a new asset version.
HTTP cache headers, CDN behavior, and `Cache-Control` are configured by the application or hosting platform. With Webpack, the final file name depends on the project's `assetModuleFilename`.
## SVG transformations
All transformations are enabled by default and can be configured independently:
| Option | Behavior |
|---|---|
| `removeSize` | Removes `width` and `height` from the root `<svg>` while preserving an existing `viewBox` |
| `replaceColors` | Replaces detected `fill` and `stroke` values with `--icon-color-N` |
| `addTransition` | Adds transitions for `fill` and `stroke` to colored elements and generated styles |
To disable an individual operation:
```ts
export default defineSpriteConfig({
mode: 'next@app/turbopack',
transform: {
removeSize: false,
replaceColors: false,
addTransition: false,
},
})
```
Source SVG files are not modified. Transformations apply only to the generated sprite contents.
## Color management
### Monochrome icons
If one color is detected, its fallback becomes `currentColor`:
```svg
stroke="var(--icon-color-1, currentColor)"
```
Set the color through a prop or CSS:
```tsx
<AppIcon icon="search" color="rebeccapurple" />
```
### Multicolor icons
Each unique color gets its own custom property with the original color as its fallback:
```svg
fill="var(--icon-color-1, #798198)"
fill="var(--icon-color-2, #ffffff)"
fill="var(--icon-color-3, #129d9d)"
```
You can override only the values you need:
```css
.icon {
--icon-color-1: #4b5563;
--icon-color-3: #14b8a6;
}
```
### Limitations
- `none`, `transparent`, `inherit`, `unset`, and `initial` are not replaced;
- colors in `fill`, `stroke`, and inline `style` attributes are handled most reliably;
- CSS classes and external stylesheets inside the SVG are not the primary transformation use case;
- `url(#...)` values may be replaced along with colors, so gradients and patterns require a separate sprite with `replaceColors: false`;
- masks, filters, and complex internal CSS rules require visual verification;
- page CSS custom properties are available through `<svg><use>`, but not inside `<img>` or a CSS background.
For a complex icon, you can disable `replaceColors` in a separate sprite configuration.
## SpriteViewer
The Viewer uses one Shadow DOM Web Component for every mode. React and future framework components are bridges to that same element, so the visuals and behavior are not duplicated.
Bare `standalone` loads the self-contained browser bundle and supplies the JSON manifest URL and the published SVG URL:
```html
<script
type="module"
src="https://unpkg.com/@gromlab/svg-sprites@<version>/dist/viewer-element.js"
></script>
<gromlab-sprite-viewer
viewer-title="Project icons"
manifest-url="/app-icons/manifest.json"
sprite-url="/app-icons/sprite.svg"
></gromlab-sprite-viewer>
```
`viewer-element.js` has no additional runtime files and can be copied with the other static assets for self-hosting.
`standalone@vite` and `standalone@webpack` register the same element through an npm entry and pass the generated JS manifest through the `sources` property:
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
import spriteManifest from './svg-sprite/.svg-sprite/svg-sprite.manifest.js'
const viewer = document.querySelector<SpriteViewerElement>('gromlab-sprite-viewer')!
viewer.sources = [spriteManifest]
```
React and Next.js keep the component API:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
```
It accepts ready-made manifests, remote standalone sources, an array of lazy loaders, or a record in the format returned by `import.meta.glob`.
Vite:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
import type { SpriteManifestModule } from '@gromlab/svg-sprites/react'
const sources = import.meta.glob<SpriteManifestModule>(
'/src/**/svg-sprite/.svg-sprite/svg-sprite.manifest.js',
)
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} title="Project icons" />
)
```
Webpack and Next.js:
```tsx
const sources = [
() => import('@/ui/app-icons/.svg-sprite/svg-sprite.manifest.js'),
() => import('@/features/analytics/icons/.svg-sprite/svg-sprite.manifest.js'),
]
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} />
)
```
The Viewer displays groups, search, `viewBox`, CSS custom properties, and fallback colors. React/Next manifests get React, SVG, IMG, and CSS tabs; standalone manifests get SVG, IMG, and CSS. You can change color values in the interface and immediately inspect the result.
### Viewer theme
By default, `colorTheme="auto"` follows `prefers-color-scheme`. You can explicitly pass `light` or `dark`:
```tsx
<SpriteViewer sources={sources} colorTheme="dark" />
```
To synchronize it with the application theme:
```tsx
<SpriteViewer
sources={sources}
colorTheme={appTheme}
onColorThemeChange={setAppTheme}
/>
```
`@gromlab/svg-sprites/react` contains `'use client'` and renders the Web Component host; its internal Shadow DOM is created after the browser runtime loads. In the Next.js App Router, place the Viewer inside a separate Client Component boundary and use it only on a debug route or in an internal tool.
## Generated files, Git, and CI
Every mode except bare `standalone` creates a local `.gitignore` for:
```text
/.svg-sprite/
```
Commit the local `.gitignore` to the repository once. It excludes the other generated files, so generation must run before commands that import the sprite module:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/app-icons/svg-sprite.config.ts",
"predev": "npm run sprites",
"prebuild": "npm run sprites",
"pretypecheck": "npm run sprites"
}
}
```
CI must run generation before building or type-checking. Replace `latest` with an exact version for reproducibility. A local package installation is not required unless CI also uses the Viewer, package config types, or the programmatic API.
Bare `standalone` does not create or modify `.gitignore`; the application decides whether its `.svg-sprite/` output is committed or ignored. In other modes, the generator will not overwrite a user-created `.gitignore`. It also refuses to overwrite a user-owned file inside `.svg-sprite`. The root `index.ts` remains user-owned and may re-export the generated API.
## Troubleshooting
- Missing `.svg-sprite/index.js`: run the generation script before importing the generated module.
- Source not found: pass an existing config file or sprite module directory.
- Mode missing: add `mode` to the config or pass `--mode`.
- Icon missing from the type: check `input`, the `.svg` extension, glob exclusions, and whether nested folders require `**/*.svg`.
- Name conflict: two different SVG files have the same basename; rename one of them.
- `Refusing to overwrite a user file`: a file without the generated marker occupies a managed path.
- The icon does not change color: use `<svg><use>` or the generated component and check `replaceColors`.
- Webpack emits an incorrect URL: check Asset Modules, `output.publicPath`, and SVG loaders.
- Static sprite returns 404: check the post-generation copy or server alias, and do not put a filesystem `spritePath` into HTML.
- The Viewer cannot find the sprite: check the path to `.svg-sprite/svg-sprite.manifest.js` and run generation before starting the application.
- Build and mode do not match: use the target that corresponds to the actual bundler.
For custom orchestration and low-level compilation, see the [Programmatic API](programmatic-api.md).

29
docs/ru/README.md Normal file
View File

@@ -0,0 +1,29 @@
# Документация
Для настройки выберите guide одного exact mode. Каждый guide является
самостоятельным документом и без изменений используется в AI skills.
## Гайды быстрого старта
| Проект | Exact mode | Guide |
|---|---|---|
| Static HTML или собственная публикация | `standalone` | [Bare standalone](guides/standalone.md) |
| Vanilla + Vite | `standalone@vite` | [Standalone + Vite](guides/standalone-vite.md) |
| Vanilla + Webpack 5 | `standalone@webpack` | [Standalone + Webpack](guides/standalone-webpack.md) |
| React + Vite | `react@vite` | [React + Vite](guides/react-vite.md) |
| React + Webpack 5 | `react@webpack` | [React + Webpack](guides/react-webpack.md) |
| Next.js App Router + Turbopack | `next@app/turbopack` | [App Router + Turbopack](guides/next-app-turbopack.md) |
| Next.js App Router + Webpack | `next@app/webpack` | [App Router + Webpack](guides/next-app-webpack.md) |
| Next.js Pages Router + Turbopack | `next@pages/turbopack` | [Pages Router + Turbopack](guides/next-pages-turbopack.md) |
| Next.js Pages Router + Webpack | `next@pages/webpack` | [Pages Router + Webpack](guides/next-pages-webpack.md) |
Все guides используют один порядок:
1. Генерация спрайта через `npx` без добавления package в проект.
2. Необязательное подключение Viewer для дебага и превью.
3. Необязательная типизация конфига через package или локальный copy-paste type.
## Справочники
- [Технический справочник](reference/technical.md)
- [Программный API](reference/programmatic-api.md)

11
docs/ru/guides/README.md Normal file
View File

@@ -0,0 +1,11 @@
# Гайды быстрого старта
- `standalone`: [bare standalone](standalone.md)
- `standalone@vite`: [standalone с Vite](standalone-vite.md)
- `standalone@webpack`: [standalone с Webpack](standalone-webpack.md)
- `react@vite`: [React с Vite](react-vite.md)
- `react@webpack`: [React с Webpack](react-webpack.md)
- `next@app/turbopack`: [App Router с Turbopack](next-app-turbopack.md)
- `next@app/webpack`: [App Router с Webpack](next-app-webpack.md)
- `next@pages/turbopack`: [Pages Router с Turbopack](next-pages-turbopack.md)
- `next@pages/webpack`: [Pages Router с Webpack](next-pages-webpack.md)

View File

@@ -0,0 +1,153 @@
# Next.js App Router с Turbopack
Это автономный quick start для exact mode key `next@app/turbopack`: generated `IconsIcon` совместим с Server Components и asset pipeline Turbopack.
## 1. Генерация спрайта
Главное преимущество: генератор не нужно устанавливать и добавлять в `package.json`. `npx` временно скачивает CLI, а generated production runtime не импортирует `@gromlab/svg-sprites`.
```text
src/sprite/
├── icons/
│ ├── check.svg
│ └── warning.svg
├── index.ts
└── svg-sprite.config.ts
```
Минимальный plain config:
```ts
export default {
mode: 'next@app/turbopack',
name: 'icons',
}
```
Если `input` не указан, SVG читаются из `./icons` относительно конфига. Конфиг может быть `.ts`, `.js` с `default export` или `.json`.
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts
```
В CI зафиксируйте точную версию, например `@gromlab/svg-sprites@1.1.5`. Mode и команды Next должны указывать один bundler:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts",
"dev": "npm run sprites && next dev --turbopack",
"build": "npm run sprites && next build --turbopack",
"start": "next start",
"typecheck": "npm run sprites && tsc --noEmit"
}
}
```
Не добавляйте одновременно `predev`/`prebuild` и явный `npm run sprites`. Generated `.svg-sprite` не коммитится. Generated локальный `.gitignore`, который исключает этот каталог, нужно добавить в Git один раз. Его `.d.ts` self-contained и не импортируют generator package.
```ts
// src/sprite/index.ts
export * from './.svg-sprite/index.js'
```
Generated icon не содержит `'use client'`, поэтому его можно импортировать прямо в Server Component:
```tsx
// app/page.tsx
import { IconsIcon, iconsIconNames } from '../src/sprite'
export default function Page() {
return (
<main>
<IconsIcon
icon="check"
width={24}
height={24}
aria-label="Готово"
style={{ '--icon-color-1': '#16a34a' }}
/>
<span>{iconsIconNames.length} иконок</span>
</main>
)
}
```
Turbopack обрабатывает generated `new URL('../sprite.svg', import.meta.url).href` и выпускает внешний hashed asset. Не добавляйте Client Component boundary только ради `IconsIcon`.
## 2. Дебаг и превью
Viewer необязателен и нужен только для debug/preview:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Viewer интерактивен, поэтому создайте для него отдельный Client Component:
```tsx
// app/icons-debug/sprite-viewer.tsx
'use client'
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('../../src/sprite/.svg-sprite/svg-sprite.manifest.js'),
] as const
export function AppSpriteViewer() {
return <SpriteViewer sources={sources} title="Иконки проекта" />
}
```
Server page импортирует только эту boundary:
```tsx
// app/icons-debug/page.tsx
import { AppSpriteViewer } from './sprite-viewer'
export default function IconsDebugPage() {
return <AppSpriteViewer />
}
```
Viewer не входит в production icon runtime и не нужен обычным страницам с `IconsIcon`.
## 3. Типизация конфига
После локальной установки package используйте helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/turbopack',
name: 'icons',
})
```
Возможен и type-only import `SpriteConfig` с `satisfies SpriteConfig`.
Без package добавьте локальный type в config:
```ts
type LocalSpriteConfig = {
mode: 'next@app/turbopack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'next@app/turbopack',
name: 'icons',
} satisfies LocalSpriteConfig
```
Exact literal защищает от смешивания App Router и других bundler contracts.

View File

@@ -0,0 +1,151 @@
# Next.js App Router с Webpack
Это автономный quick start для exact mode key `next@app/webpack`: generated `IconsIcon` совместим с Server Components и Webpack pipeline Next.js.
## 1. Генерация спрайта
Главное преимущество: генератор не нужно устанавливать и добавлять в `package.json`. `npx` временно скачивает CLI, а generated production runtime не импортирует `@gromlab/svg-sprites`.
```text
src/sprite/
├── icons/
│ ├── check.svg
│ └── warning.svg
├── index.ts
└── svg-sprite.config.ts
```
Минимальный plain config:
```ts
export default {
mode: 'next@app/webpack',
name: 'icons',
}
```
Если `input` не указан, SVG читаются из `./icons` относительно конфига. Также поддерживаются `.js` с `default export` и `.json`.
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts
```
В CI замените `latest` точной версией, например `@gromlab/svg-sprites@1.1.5`. Exact Next commands для этого mode:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts",
"dev": "npm run sprites && next dev --webpack",
"build": "npm run sprites && next build --webpack",
"start": "next start",
"typecheck": "npm run sprites && tsc --noEmit"
}
}
```
Не сочетайте явный `npm run sprites` с `predev` или `prebuild`. `.svg-sprite` generated и не коммитится. Generated локальный `.gitignore`, который исключает этот каталог, нужно добавить в Git один раз. Generated declarations self-contained и не зависят от `@gromlab/svg-sprites`.
```ts
// src/sprite/index.ts
export * from './.svg-sprite/index.js'
```
Production usage в Server Component:
```tsx
// app/page.tsx
import { IconsIcon, iconsIconNames } from '../src/sprite'
export default function Page() {
return (
<main>
<IconsIcon
icon="check"
width={24}
height={24}
aria-label="Готово"
style={{ '--icon-color-1': '#16a34a' }}
/>
<span>{iconsIconNames.length} иконок</span>
</main>
)
}
```
Generated component не содержит `'use client'`. Next Webpack обрабатывает `new URL('../sprite.svg', import.meta.url).href` и публикует отдельный SVG asset; не переписывайте этот URL и не переносите sprite в `public` вручную.
## 2. Дебаг и превью
Viewer необязателен. Для debug/preview установите package:
```bash
npm install --save-dev @gromlab/svg-sprites
```
App Router требует отдельную Client Component boundary для Viewer:
```tsx
// app/icons-debug/sprite-viewer.tsx
'use client'
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('../../src/sprite/.svg-sprite/svg-sprite.manifest.js'),
] as const
export function AppSpriteViewer() {
return <SpriteViewer sources={sources} title="Иконки проекта" />
}
```
```tsx
// app/icons-debug/page.tsx
import { AppSpriteViewer } from './sprite-viewer'
export default function IconsDebugPage() {
return <AppSpriteViewer />
}
```
Статический loader позволяет Webpack связать manifest и emitted SVG. Viewer не входит в production runtime `IconsIcon`.
## 3. Типизация конфига
При локально установленном package используйте helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/webpack',
name: 'icons',
})
```
Другой package-вариант: type-only import `SpriteConfig` и `satisfies SpriteConfig`.
Без package вставьте локальный type прямо в config:
```ts
type LocalSpriteConfig = {
mode: 'next@app/webpack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'next@app/webpack',
name: 'icons',
} satisfies LocalSpriteConfig
```
Локальный exact literal исключает случайную генерацию Turbopack или Pages Router output.

View File

@@ -0,0 +1,140 @@
# Next.js Pages Router с Turbopack
Это автономный quick start для exact mode key `next@pages/turbopack`: generated `IconsIcon` работает при SSR, SSG и клиентских переходах Pages Router.
## 1. Генерация спрайта
Главное преимущество: генератор не нужно устанавливать и добавлять в `package.json`. `npx` временно скачивает CLI, а generated production runtime не импортирует `@gromlab/svg-sprites`.
```text
src/sprite/
├── icons/
│ ├── check.svg
│ └── warning.svg
├── index.ts
└── svg-sprite.config.ts
```
Минимальный plain config:
```ts
export default {
mode: 'next@pages/turbopack',
name: 'icons',
}
```
Если `input` не указан, SVG читаются из `./icons` относительно конфига. Конфиг также может быть `.js` с `default export` или `.json`.
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts
```
Для CI зафиксируйте точную версию, например `@gromlab/svg-sprites@1.1.5`. Exact commands должны сохранять Turbopack и для dev, и для production build:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts",
"dev": "npm run sprites && next dev --turbopack",
"build": "npm run sprites && next build --turbopack",
"start": "next start",
"typecheck": "npm run sprites && tsc --noEmit"
}
}
```
Не добавляйте `predev`/`prebuild`, если scripts уже явно вызывают `npm run sprites`. Generated `.svg-sprite` не коммитится. Generated локальный `.gitignore`, который исключает этот каталог, нужно добавить в Git один раз. Generated `.d.ts` self-contained и не импортируют generator package.
```ts
// src/sprite/index.ts
export * from './.svg-sprite/index.js'
```
Production usage на обычной page:
```tsx
// pages/index.tsx
import { IconsIcon, iconsIconNames } from '../src/sprite'
export default function Page() {
return (
<main>
<IconsIcon
icon="check"
width={24}
height={24}
aria-label="Готово"
style={{ '--icon-color-1': '#16a34a' }}
/>
<span>{iconsIconNames.length} иконок</span>
</main>
)
}
```
Компонент одинаково работает с `getServerSideProps`, `getStaticProps` и client navigation. Turbopack разрешает generated `new URL('../sprite.svg', import.meta.url).href` в отдельный hashed asset.
## 2. Дебаг и превью
Viewer необязателен и нужен только для debug/preview:
```bash
npm install --save-dev @gromlab/svg-sprites
```
В Pages Router Viewer можно использовать прямо в page, без отдельной App Router Client Component boundary:
```tsx
// pages/icons-debug.tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('../src/sprite/.svg-sprite/svg-sprite.manifest.js'),
] as const
export default function IconsDebugPage() {
return <SpriteViewer sources={sources} title="Иконки проекта" />
}
```
Оставляйте эту page только во внутреннем debug-разделе. Viewer не входит в production icon runtime `IconsIcon`.
## 3. Типизация конфига
Если package установлен локально, используйте helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@pages/turbopack',
name: 'icons',
})
```
Альтернатива: type-only import `SpriteConfig` и объект `satisfies SpriteConfig`.
Без package добавьте copy-paste type в config:
```ts
type LocalSpriteConfig = {
mode: 'next@pages/turbopack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'next@pages/turbopack',
name: 'icons',
} satisfies LocalSpriteConfig
```
Exact literal не позволяет незаметно смешать Pages Router с App Router или Webpack output.

View File

@@ -0,0 +1,140 @@
# Next.js Pages Router с Webpack
Это автономный quick start для exact mode key `next@pages/webpack`: generated `IconsIcon` работает в Pages Router и публикует SVG через Webpack pipeline Next.js.
## 1. Генерация спрайта
Главное преимущество: генератор не нужно устанавливать и добавлять в `package.json`. `npx` временно скачивает CLI, а generated production runtime не импортирует `@gromlab/svg-sprites`.
```text
src/sprite/
├── icons/
│ ├── check.svg
│ └── warning.svg
├── index.ts
└── svg-sprite.config.ts
```
Минимальный plain config:
```ts
export default {
mode: 'next@pages/webpack',
name: 'icons',
}
```
Если `input` не указан, SVG читаются из `./icons` относительно конфига. Поддерживаются `.ts`, `.js` с `default export` и `.json` configs.
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts
```
В CI замените `latest` на точную проверенную версию, например `@gromlab/svg-sprites@1.1.5`. Exact Next commands для Webpack:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts",
"dev": "npm run sprites && next dev --webpack",
"build": "npm run sprites && next build --webpack",
"start": "next start",
"typecheck": "npm run sprites && tsc --noEmit"
}
}
```
Не дублируйте эти вызовы через `predev`, `prebuild` или `pretypecheck`. `.svg-sprite` generated и не коммитится. Generated локальный `.gitignore`, который исключает этот каталог, нужно добавить в Git один раз. Generated declarations self-contained и не требуют `@gromlab/svg-sprites`.
```ts
// src/sprite/index.ts
export * from './.svg-sprite/index.js'
```
Production usage:
```tsx
// pages/index.tsx
import { IconsIcon, iconsIconNames } from '../src/sprite'
export default function Page() {
return (
<main>
<IconsIcon
icon="check"
width={24}
height={24}
aria-label="Готово"
style={{ '--icon-color-1': '#16a34a' }}
/>
<span>{iconsIconNames.length} иконок</span>
</main>
)
}
```
Компонент поддерживает SSR, SSG и клиентские переходы. Next Webpack преобразует generated `new URL('../sprite.svg', import.meta.url).href` во внешний hashed asset; не конструируйте URL спрайта вручную.
## 2. Дебаг и превью
Viewer необязателен. Устанавливайте package только для debug/preview:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Pages Router позволяет разместить Viewer непосредственно в page без отдельной App Router boundary:
```tsx
// pages/icons-debug.tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('../src/sprite/.svg-sprite/svg-sprite.manifest.js'),
] as const
export default function IconsDebugPage() {
return <SpriteViewer sources={sources} title="Иконки проекта" />
}
```
Статический loader даёт Webpack точный manifest module и связанный SVG asset. Не импортируйте Viewer из production pages, если preview там не нужен; runtime `IconsIcon` от него независим.
## 3. Типизация конфига
После локальной установки package доступен helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@pages/webpack',
name: 'icons',
})
```
Также можно применить `satisfies SpriteConfig` с type-only импортом `SpriteConfig`.
Без package вставьте локальный type прямо в config:
```ts
type LocalSpriteConfig = {
mode: 'next@pages/webpack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'next@pages/webpack',
name: 'icons',
} satisfies LocalSpriteConfig
```
Такой exact literal выявляет ошибочный выбор App Router или Turbopack ещё при проверке config.

View File

@@ -0,0 +1,141 @@
# React-компонент для Vite
Это автономный quick start для exact mode key `react@vite`: генератор создаёт типизированный `IconsIcon`, а Vite публикует отдельный SVG asset.
## 1. Генерация спрайта
Главное преимущество: генератор не нужно устанавливать и добавлять в `package.json`. `npx` временно скачивает CLI, а generated production runtime не импортирует `@gromlab/svg-sprites`.
```text
src/sprite/
├── icons/
│ ├── check.svg
│ └── warning.svg
├── index.ts
└── svg-sprite.config.ts
```
Минимальный plain config:
```ts
export default {
mode: 'react@vite',
name: 'icons',
}
```
Если `input` не указан, SVG читаются из `./icons` относительно конфига. Вместо `.ts` можно использовать `.js` с `default export` или `.json`.
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts
```
В CI замените `latest` на точную версию, например `@gromlab/svg-sprites@1.1.5`. Exact Vite commands:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts",
"dev": "npm run sprites && vite",
"build": "npm run sprites && tsc --noEmit && vite build",
"typecheck": "npm run sprites && tsc --noEmit"
}
}
```
Не добавляйте `predev`, `prebuild` или `pretypecheck`, если соответствующие scripts уже явно запускают `npm run sprites`. Generated `.svg-sprite` не коммитится. Generated локальный `.gitignore`, который исключает этот каталог, нужно добавить в Git один раз. Generated declarations self-contained: они описывают компонент и manifest без импорта `@gromlab/svg-sprites`.
Пользовательский barrel возвращает generated API:
```ts
// src/sprite/index.ts
export * from './.svg-sprite/index.js'
```
Production usage:
```tsx
import { IconsIcon, iconsIconNames } from './sprite'
export function SaveButton() {
return (
<button type="button">
<IconsIcon
icon="check"
width={24}
height={24}
aria-hidden="true"
style={{ '--icon-color-1': '#16a34a' }}
/>
Сохранить
</button>
)
}
console.log(iconsIconNames)
```
Prop `icon` является union имён исходных файлов. Vite автоматически обрабатывает generated CSS Module и импорт `sprite.svg?no-inline`; query запрещает inline и заставляет Vite выпустить отдельный hashed SVG asset. Если TypeScript не знает Vite asset imports, добавьте `/// <reference types="vite/client" />` в `src/vite-env.d.ts`.
## 2. Дебаг и превью
Viewer необязателен и нужен только для debug/preview. Установите package отдельно:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Используйте React bridge со статическим массивом loaders:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('./sprite/.svg-sprite/svg-sprite.manifest.js'),
] as const
export function IconsDebugPage() {
return <SpriteViewer sources={sources} title="Иконки проекта" />
}
```
Строковый путь в `import()` должен указывать на generated JS manifest. Держите страницу за debug-маршрутом; `SpriteViewer` не входит в production runtime `IconsIcon`.
## 3. Типизация конфига
Если package установлен локально, используйте helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@vite',
name: 'icons',
})
```
Также можно импортировать `SpriteConfig` только как type и написать объект `satisfies SpriteConfig`.
Без package добавьте локальный type прямо в config:
```ts
type LocalSpriteConfig = {
mode: 'react@vite'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'react@vite',
name: 'icons',
} satisfies LocalSpriteConfig
```
Локальный type проверяет только этот exact mode и не создаёт runtime-зависимость.

View File

@@ -0,0 +1,141 @@
# React-компонент для Webpack 5
Это автономный quick start для exact mode key `react@webpack`: generated `IconsIcon` использует Webpack 5 Asset Modules и CSS Modules.
## 1. Генерация спрайта
Главное преимущество: генератор не нужно устанавливать и добавлять в `package.json`. `npx` временно скачивает CLI, а generated production runtime не импортирует `@gromlab/svg-sprites`.
```text
src/sprite/
├── icons/
│ ├── check.svg
│ └── warning.svg
├── index.ts
└── svg-sprite.config.ts
```
Минимальный plain config:
```ts
export default {
mode: 'react@webpack',
name: 'icons',
}
```
Если `input` не указан, SVG читаются из `./icons` относительно конфига. Поддерживаются `.ts`, `.js` с `default export` и `.json` configs.
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts
```
В CI замените `latest` на точную версию, например `@gromlab/svg-sprites@1.1.5`. Exact Webpack commands:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts",
"dev": "npm run sprites && webpack serve --mode development",
"build": "npm run sprites && webpack --mode production",
"typecheck": "npm run sprites && tsc --noEmit"
}
}
```
Не сочетайте эти явные вызовы с `predev`/`prebuild`: иначе генерация задублируется. `.svg-sprite` не коммитится. Generated локальный `.gitignore`, который исключает этот каталог, нужно добавить в Git один раз. Generated `.d.ts` self-contained и не импортируют generator package.
```ts
// src/sprite/index.ts
export * from './.svg-sprite/index.js'
```
Production usage:
```tsx
import { IconsIcon, iconsIconNames } from './sprite'
export function SaveButton() {
return (
<button type="button">
<IconsIcon
icon="check"
width={24}
height={24}
aria-hidden="true"
style={{ '--icon-color-1': '#16a34a' }}
/>
Сохранить
</button>
)
}
console.log(iconsIconNames)
```
Generated component получает URL через `new URL('../sprite.svg', import.meta.url).href`. Webpack 5 должен обработать SVG как Asset Module. Исключите generated `sprite.svg` из `@svgr/webpack`, inline/raw loaders и других общих SVG rules либо задайте для него `type: 'asset/resource'`.
Компонент импортирует `react-component.module.css`. Webpack config должен обрабатывать `*.module.css` через `css-loader` с CSS Modules и `style-loader` или `MiniCssExtractPlugin`. Для TypeScript при необходимости добавьте декларацию `declare module '*.module.css'`.
## 2. Дебаг и превью
Viewer необязателен. Устанавливайте package только для debug/preview:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Webpack не использует `import.meta.glob`; передайте статический loader:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('./sprite/.svg-sprite/svg-sprite.manifest.js'),
] as const
export function IconsDebugPage() {
return <SpriteViewer sources={sources} title="Иконки проекта" />
}
```
Webpack создаст chunk manifest и разрешит его SVG через тот же Asset Modules pipeline. Viewer держите только в debug route; production `IconsIcon` от него не зависит.
## 3. Типизация конфига
С локально установленным package доступен helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@webpack',
name: 'icons',
})
```
Эквивалентная проверка: type-only import `SpriteConfig` и `satisfies SpriteConfig`.
Без package используйте copy-paste type в config:
```ts
type LocalSpriteConfig = {
mode: 'react@webpack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'react@webpack',
name: 'icons',
} satisfies LocalSpriteConfig
```
Локальный literal не разрешит случайно выбрать Vite или Next mode.

View File

@@ -0,0 +1,151 @@
# Нативный icon Web Component с Vite
Это автономный quick start для exact mode key `standalone@vite`: generated facade регистрирует `<icons-icon>` и отдаёт SVG в asset pipeline Vite.
## 1. Генерация спрайта
Главное преимущество: генератор не нужно устанавливать и добавлять в `package.json`. `npx` временно скачивает CLI, а generated production runtime не импортирует `@gromlab/svg-sprites`.
Рекомендуемая структура держит конфиг и иконки рядом:
```text
src/sprite/
├── icons/
│ ├── check.svg
│ └── warning.svg
├── index.ts
└── svg-sprite.config.ts
```
Минимальный plain config:
```ts
export default {
mode: 'standalone@vite',
name: 'icons',
}
```
Если `input` не указан, SVG читаются из `./icons` относительно конфига. Конфиг также может быть `.js` с `default export` или `.json`.
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts
```
В CI замените `latest` на точную версию, например `@gromlab/svg-sprites@1.1.5`. Exact dev/build commands для Vite:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts",
"dev": "npm run sprites && vite",
"build": "npm run sprites && vite build",
"typecheck": "npm run sprites && tsc --noEmit"
}
}
```
Не добавляйте одновременно `predev`/`prebuild` и явный `npm run sprites` в этих scripts. `.svg-sprite` является generated-каталогом и не коммитится. Generated локальный `.gitignore`, который исключает этот каталог, нужно добавить в Git один раз. Generated `.d.ts` self-contained и не импортируют `@gromlab/svg-sprites`.
Верните facade через пользовательский barrel:
```ts
// src/sprite/index.ts
export * from './.svg-sprite/index.js'
```
Production entry регистрирует native Web Component:
```ts
import { defineIconsIconElement, iconsIconNames } from './sprite'
defineIconsIconElement()
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<icons-icon icon="check" role="img" aria-label="Готово"></icons-icon>
`
console.log(iconsIconNames)
```
Generated facade импортирует `sprite.svg?no-inline`: Vite автоматически выпускает отдельный hashed SVG asset и не превращает его в data URL. TypeScript-проекту при необходимости добавьте стандартные Vite types:
```ts
/// <reference types="vite/client" />
```
Размер по умолчанию равен `1em`, поэтому компонент удобно масштабировать через `font-size`. Цвет задаётся через `color` и generated custom properties:
```css
icons-icon {
font-size: 24px;
color: #334155;
--icon-color-2: #f59e0b;
}
```
## 2. Дебаг и превью
Viewer необязателен и нужен только для debug/preview. Установите его отдельно:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Зарегистрируйте Viewer и передайте generated JS manifest через свойство `sources`:
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
import spriteManifest from './sprite/.svg-sprite/svg-sprite.manifest.js'
document.querySelector<HTMLDivElement>('#app')!.insertAdjacentHTML(
'beforeend',
'<gromlab-sprite-viewer></gromlab-sprite-viewer>',
)
const viewer = document.querySelector<SpriteViewerElement>('gromlab-sprite-viewer')!
viewer.viewerTitle = 'Иконки проекта'
viewer.sources = [spriteManifest]
```
Расположите этот код только в debug entry или внутреннем маршруте. Viewer не входит в production runtime `<icons-icon>`.
## 3. Типизация конфига
При локально установленном package используйте helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'standalone@vite',
name: 'icons',
})
```
Альтернатива с package: `import type { SpriteConfig }` и объект `satisfies SpriteConfig`.
Без package скопируйте локальный type прямо в config:
```ts
type LocalSpriteConfig = {
mode: 'standalone@vite'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'standalone@vite',
name: 'icons',
} satisfies LocalSpriteConfig
```
Локальный type ограничивает `mode` одним exact literal и ничего не загружает во время генерации.

View File

@@ -0,0 +1,145 @@
# Нативный icon Web Component с Webpack 5
Это автономный quick start для exact mode key `standalone@webpack`: generated facade предоставляет `<icons-icon>`, а Webpack 5 публикует SVG через Asset Modules.
## 1. Генерация спрайта
Главное преимущество: генератор не нужно устанавливать и добавлять в `package.json`. `npx` временно скачивает CLI, а generated production runtime не импортирует `@gromlab/svg-sprites`.
```text
src/sprite/
├── icons/
│ ├── check.svg
│ └── warning.svg
├── index.ts
└── svg-sprite.config.ts
```
Минимальный config рядом с `icons/`:
```ts
export default {
mode: 'standalone@webpack',
name: 'icons',
}
```
Если `input` не указан, SVG читаются из `./icons` относительно конфига. Поддерживаются также `.js` с `default export` и `.json`.
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts
```
Для CI закрепите точную версию, например `@gromlab/svg-sprites@1.1.5`. Exact Webpack commands:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/sprite/svg-sprite.config.ts",
"dev": "npm run sprites && webpack serve --mode development",
"build": "npm run sprites && webpack --mode production",
"typecheck": "npm run sprites && tsc --noEmit"
}
}
```
Не дублируйте запуск через `predev`/`prebuild`, если scripts уже явно вызывают `npm run sprites`. Generated `.svg-sprite` не коммитится. Generated локальный `.gitignore`, который исключает этот каталог, нужно добавить в Git один раз. Его declarations self-contained и не требуют `@gromlab/svg-sprites`.
```ts
// src/sprite/index.ts
export * from './.svg-sprite/index.js'
```
Production usage:
```ts
import { defineIconsIconElement, iconsIconNames } from './sprite'
defineIconsIconElement()
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<icons-icon icon="check" role="img" aria-label="Готово"></icons-icon>
`
console.log(iconsIconNames)
```
Generated facade использует `new URL('./sprite.svg', import.meta.url).href`. Webpack 5 Asset Modules выпускают отдельный asset; его итоговый URL учитывает `output.publicPath` и `assetModuleFilename`.
Если проект использует `@svgr/webpack`, `svg-inline-loader`, `raw-loader` или общий SVG rule, исключите `src/sprite/.svg-sprite/sprite.svg` из этого правила. Generated SVG должен обрабатываться как `asset/resource`, а не как React-компонент или inline source.
Размер Web Component по умолчанию `1em`; управляйте им и цветами обычным CSS:
```css
icons-icon {
font-size: 24px;
color: #334155;
--icon-color-2: #f59e0b;
}
```
## 2. Дебаг и превью
Viewer необязателен. Для debug/preview установите package как dev dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Подключите element entry и generated JS manifest:
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
import spriteManifest from './sprite/.svg-sprite/svg-sprite.manifest.js'
document.querySelector<HTMLDivElement>('#app')!.insertAdjacentHTML(
'beforeend',
'<gromlab-sprite-viewer></gromlab-sprite-viewer>',
)
const viewer = document.querySelector<SpriteViewerElement>('gromlab-sprite-viewer')!
viewer.viewerTitle = 'Иконки проекта'
viewer.sources = [spriteManifest]
```
Webpack свяжет manifest с тем же emitted SVG asset. Оставляйте Viewer только в debug entry: production `<icons-icon>` от него не зависит.
## 3. Типизация конфига
После локальной установки package можно использовать helper:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'standalone@webpack',
name: 'icons',
})
```
Либо импортируйте только `SpriteConfig` как type и примените `satisfies SpriteConfig`.
Без package добавьте copy-paste type в сам config:
```ts
type LocalSpriteConfig = {
mode: 'standalone@webpack'
name?: string
description?: string
input?: string | string[]
transform?: {
removeSize?: boolean
replaceColors?: boolean
addTransition?: boolean
}
generatedNotice?: boolean
}
export default {
mode: 'standalone@webpack',
name: 'icons',
} satisfies LocalSpriteConfig
```
Этот вариант сохраняет проверку exact mode без runtime import и без записи generator package в проект.

View File

@@ -0,0 +1,108 @@
# SVG-спрайт для сайта без сборщика
Соберите SVG-иконки в один файл и используйте их на HTML-странице.
## Генерация спрайта
Устанавливать пакет в проект не нужно.
В руководстве используется следующая структура проекта:
```text
/
├── index.html
└── assets/
├── app-icons/
└── svg-icons/
├── check.svg
└── warning.svg
```
### 1. Создайте конфиг спрайта
Выберите папку для спрайта. В этом примере используется `assets/app-icons`. Создайте в ней файл `svg-sprite.config.json`:
```json
{
"mode": "standalone",
"name": "icons"
}
```
### 2. Укажите источник иконок
В `input` можно указать папку, отдельный SVG-файл или glob-шаблон. Для нескольких источников используйте массив с любой комбинацией этих значений:
```json
{
"mode": "standalone",
"name": "icons",
"input": "../svg-icons/**/*.svg"
}
```
### 3. Сгенерируйте спрайт
Передайте команде путь к конфигу:
```bash
npx --yes @gromlab/svg-sprites assets/app-icons/svg-sprite.config.json
```
Пакет соберёт иконки в каталог `.svg-sprite` рядом с конфигом:
```text
assets/app-icons/.svg-sprite/
├── sprite.svg
└── svg-sprite.manifest.json
```
- `sprite.svg` — готовый спрайт для использования на сайте.
- `svg-sprite.manifest.json` — данные об иконках для Viewer.
Каталог `.svg-sprite` создаётся автоматически и полностью заменяется при каждой генерации. Не редактируйте его содержимое вручную.
### 4. Используйте иконку
В `index.html` укажите путь к созданному `sprite.svg`. После `#` добавьте имя нужной иконки без расширения `.svg`:
```html
<svg
width="24"
height="24"
aria-label="Готово"
>
<use href="./assets/app-icons/.svg-sprite/sprite.svg#check"></use>
</svg>
```
Иконка из файла `check.svg` будет доступна как `#check`.
## Дебаг и превью
`sprite.svg` — технический файл, а не галерея иконок. При его открытии нельзя удобно просмотреть весь набор. Кроме того, градиенты, маски, фильтры и ссылки на внутренние `id` могут отображаться с артефактами.
Для визуальной проверки используйте официальный Viewer. Он показывает все иконки спрайта и помогает проверить их цвета и отображение.
Viewer необязателен и предназначен только для разработки. Устанавливать пакет через npm не нужно.
Viewer работает напрямую с файлами из `.svg-sprite`. Ничего копировать не нужно.
### Добавьте Viewer на страницу
Добавьте в `index.html` module script и укажите пути к generated manifest и спрайту:
```html
<script
type="module"
src="https://cdn.jsdelivr.net/npm/@gromlab/svg-sprites/dist/viewer-element.js"
></script>
<gromlab-sprite-viewer
viewer-title="Иконки проекта"
manifest-url="./assets/app-icons/.svg-sprite/svg-sprite.manifest.json"
sprite-url="./assets/app-icons/.svg-sprite/sprite.svg"
></gromlab-sprite-viewer>
```
Viewer можно вынести в отдельный HTML-файл в корне сайта, предназначенный только для разработки и проверки иконок.

View File

@@ -1,113 +1,4 @@
# Next.js App Router
# Guides Next.js App Router перемещены
[← Главная](../../README_RU.md)
Поддерживаются два явных режима:
| Сборщик | Mode key |
|---|---|
| Turbopack | `next@app/turbopack` |
| Webpack 5 | `next@app/webpack` |
## 1. Установите пакет
```bash
npm install --save-dev @gromlab/svg-sprites
```
## 2. Создайте sprite-модуль
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
├── index.ts
└── svg-sprite.config.ts
```
```ts
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/turbopack',
name: 'file-manager',
description: 'Иконки файлового менеджера',
})
```
Корневой barrel принадлежит приложению:
```ts
// src/ui/file-manager/svg-sprite/index.ts
export * from './.svg-sprite'
```
## 3. Добавьте генерацию
Для Turbopack:
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager"
}
}
```
Для Webpack замените mode key на `next@app/webpack`.
До импорта generated-модуля выполните первую генерацию:
```bash
npm run sprite:file-manager
```
## 4. Используйте в Server Component
Generated-компонент не содержит `'use client'`, поэтому его можно импортировать непосредственно в `page.tsx` или `layout.tsx`:
```tsx
import { FileManagerIcon } from '@/ui/file-manager/svg-sprite'
export default function Page() {
return (
<main>
<FileManagerIcon icon="folder" width={24} height={24} />
</main>
)
}
```
Next.js выпустит отдельный SVG asset с content hash. Один generated-код используется при SSR и в браузере без расхождения URL.
## 5. Добавьте SpriteViewer
Viewer интерактивен, поэтому для него нужна отдельная Client Component граница:
```tsx
'use client'
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('@/ui/file-manager/svg-sprite/.svg-sprite/svg-sprite.manifest.js'),
]
export default function SpritesPage() {
return <SpriteViewer sources={sources} />
}
```
## Проверка сборщика
Запустите build script проекта, настроенный на выбранный сборщик:
```bash
npm run build
```
Build script и mode key генератора должны указывать один и тот же сборщик.
- [App Router + Turbopack](guides/next-app-turbopack.md)
- [App Router + Webpack](guides/next-app-webpack.md)

View File

@@ -1,105 +1,4 @@
# Next.js Pages Router
# Guides Next.js Pages Router перемещены
[← Главная](../../README_RU.md)
Поддерживаются два явных режима:
| Сборщик | Mode key |
|---|---|
| Turbopack | `next@pages/turbopack` |
| Webpack 5 | `next@pages/webpack` |
## 1. Установите пакет
```bash
npm install --save-dev @gromlab/svg-sprites
```
## 2. Создайте sprite-модуль
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
├── index.ts
└── svg-sprite.config.ts
```
```ts
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@pages/webpack',
name: 'file-manager',
description: 'Иконки файлового менеджера',
})
```
Корневой barrel принадлежит приложению:
```ts
// src/ui/file-manager/svg-sprite/index.ts
export * from './.svg-sprite'
```
## 3. Добавьте генерацию
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager"
}
}
```
Для Turbopack замените mode key на `next@pages/turbopack`.
До импорта generated-модуля выполните первую генерацию:
```bash
npm run sprite:file-manager
```
## 4. Используйте на странице
```tsx
import { FileManagerIcon } from '@/ui/file-manager/svg-sprite'
export default function FilesPage() {
return <FileManagerIcon icon="folder" width={24} height={24} />
}
export function getServerSideProps() {
return { props: {} }
}
```
Компонент одинаково работает при SSR, SSG и клиентских переходах. Next.js выпускает отдельный SVG asset с content hash.
## 5. Добавьте SpriteViewer
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('@/ui/file-manager/svg-sprite/.svg-sprite/svg-sprite.manifest.js'),
]
export default function SpritesPage() {
return <SpriteViewer sources={sources} />
}
```
## Проверка сборщика
Запустите build script проекта, настроенный на выбранный сборщик:
```bash
npm run build
```
Build script и mode key генератора должны указывать один и тот же сборщик.
- [Pages Router + Turbopack](guides/next-pages-turbopack.md)
- [Pages Router + Webpack](guides/next-pages-webpack.md)

View File

@@ -1,147 +1,3 @@
# Программный API
# Программный API перемещён
[← Главная](../../README_RU.md)
Пакет распространяется как ESM и предоставляет единый Node.js API генерации. Framework-neutral Viewer находится в `@gromlab/svg-sprites/viewer`, auto-register entry — в `@gromlab/svg-sprites/viewer/element`, React bridge — в `@gromlab/svg-sprites/react`.
## `generateSprite`
```ts
import { generateSprite } from '@gromlab/svg-sprites'
const result = await generateSprite(
'src/ui/file-manager/svg-sprite/svg-sprite.config.ts',
)
```
Для static standalone mode `result.spritePath` можно использовать в build-скрипте,
чтобы опубликовать SVG по URL приложения:
```ts
import { copyFile } from 'node:fs/promises'
const result = await generateSprite('src/sprite/svg-sprite.config.ts', {
mode: 'standalone',
})
await copyFile(result.spritePath, 'dist/app-icons/sprite.svg')
```
`spritePath` является filesystem path, а не browser URL. Deployment-neutral JSON
manifest доступен через `result.manifestPath` и копируется независимо от SVG.
Первый аргумент принимает полный путь к config-файлу с любым именем и расширением `.ts`, `.js` или `.json`. Каталог вместо файла включает config-less режим: корнем sprite-модуля становится этот каталог.
Второй аргумент содержит необязательные overrides и всегда имеет приоритет над конфигом:
```ts
await generateSprite('src/ui/file-manager/svg-sprite/custom-config.json', {
mode: 'react@webpack',
name: 'documents',
inputFolder: './assets',
inputFiles: ['../../shared/search.svg'],
transform: {
addTransition: false,
},
generatedNotice: false,
})
```
Порядок разрешения настроек:
```text
defaults → config → API overrides
```
Для полностью программной генерации передайте каталог и все обязательные настройки через overrides:
```ts
await generateSprite('src/ui/file-manager/svg-sprite', {
mode: 'react@vite',
name: 'file-manager',
inputFiles: [
'../../shared/search.svg',
'../../shared/settings.svg',
],
})
```
## Конфигурация
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@vite',
name: 'file-manager',
description: 'Иконки файлового менеджера',
inputFolder: './icons',
inputFiles: ['../../shared/check.svg'],
transform: {
removeSize: true,
replaceColors: true,
addTransition: true,
},
generatedNotice: true,
})
```
`defineSpriteConfig` является identity helper для TypeScript autocomplete. JS может экспортировать тот же объект через `export default`, а JSON содержит объект непосредственно.
## Специализированные обёртки
Специализированные функции доступны как обёртки над `generateSprite`:
```ts
import { generateNextSprite, generateReactSprite } from '@gromlab/svg-sprites'
await generateReactSprite('path/to/config.ts', 'vite')
await generateNextSprite('path/to/config.ts', {
router: 'app',
bundler: 'turbopack',
})
```
Явно переданный target перекрывает `mode` из файла. Для нового кода используйте `generateSprite`.
## Config API
```ts
import {
loadSpriteConfig,
resolveSpriteConfig,
validateSpriteConfig,
} from '@gromlab/svg-sprites'
```
- `loadSpriteConfig(file)` загружает явно указанный `.ts`, `.js` или `.json` файл.
- `validateSpriteConfig(value)` выполняет runtime-валидацию объекта.
- `resolveSpriteConfig(root, config, overrides)` объединяет значения, добавляет defaults и разрешает пути относительно `root`.
## Низкоуровневый compiler
```ts
import {
compileSprite,
compileSpriteContent,
createShapeTransform,
} from '@gromlab/svg-sprites'
```
Эти функции предназначены для собственного orchestration. Стандартная генерация должна выполняться через `generateSprite`.
## Viewer runtime
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
```
Browser entry регистрирует `<gromlab-sprite-viewer>`. Bare standalone также может загрузить самостоятельный `dist/viewer-element.js` без bundler.
React bridge сохраняет компонентный API:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
```
`SpriteViewer` принимает generated manifests, remote standalone sources, lazy loaders или результат `import.meta.glob`. React entry содержит `'use client'` и предназначен для debug-инструментов; production-компоненты импортируются из локальных sprite-модулей приложения.
Canonical документ: [программный API](reference/programmatic-api.md).

View File

@@ -1,125 +1,3 @@
# React + Vite
# Guide React + Vite перемещён
[← Главная](../../README_RU.md)
Краткая инструкция по установке и использованию SVG-спрайтов в проекте на React и Vite.
В результате вы получите типизированный React-компонент и отдельный кешируемый SVG asset.
## 1. Установите пакет
```bash
npm install --save-dev @gromlab/svg-sprites
```
## 2. Создайте папку спрайта
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
├── index.ts
└── svg-sprite.config.ts
```
Поместите исходные SVG-файлы в `icons/`.
## 3. Добавьте конфиг
```ts
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@vite',
name: 'file-manager',
description: 'Иконки файлового менеджера',
})
```
Корневой barrel принадлежит приложению и явно возвращает generated API:
```ts
// src/ui/file-manager/svg-sprite/index.ts
export * from './.svg-sprite'
```
По умолчанию SVG берутся из `./icons`. Общие иконки из других папок можно добавить через `inputFiles`: папка и список объединяются в один спрайт.
Полный список опций находится в разделе [«Единая конфигурация»](reference.md#единая-конфигурация).
## 4. Добавьте генерацию в package.json
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
Generated-файлы исключаются из Git, поэтому генерация должна выполняться перед `dev`, `build` и `typecheck`.
Первый запуск:
```bash
npm run sprite:file-manager
```
## 5. Используйте компонент
Имя `file-manager` преобразуется в `FileManagerIcon`:
```tsx
import { FileManagerIcon } from './svg-sprite'
export const OpenFolderButton = () => (
<button type="button">
<FileManagerIcon icon="folder" width={24} height={24} />
Открыть
</button>
)
```
Значение `icon` проверяется TypeScript по именам файлов:
```tsx
<FileManagerIcon icon="check" /> // допустимо
<FileManagerIcon icon="unknown" /> // ошибка TypeScript
```
Типы, способы отображения и управление цветами описаны в [техническом справочнике](reference.md#react-компонент-и-typescript).
Vite выпустит спрайт отдельным файлом вида `assets/sprite-<hash>.svg`. SVG path-данные не попадут в JavaScript.
## 6. Добавьте debug-страницу
После подключения иконок можно вывести все React-спрайты через `SpriteViewer`:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
import type { SpriteManifestModule } from '@gromlab/svg-sprites/react'
const sources = import.meta.glob<SpriteManifestModule>(
'/src/**/svg-sprite/.svg-sprite/svg-sprite.manifest.js',
)
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} title="Иконки проекта" />
)
```
Vite автоматически найдёт generated manifest каждого React-спрайта. Шаблон `import.meta.glob` должен быть строковым литералом, а генерация должна выполниться до запуска Vite.
Размещайте Viewer только на debug-маршруте или во внутреннем инструменте.
## Если что-то не работает
- Нет `.svg-sprite/index.js`: запустите `npm run sprite:file-manager`.
- Viewer не видит спрайт: проверьте glob-путь к `.svg-sprite/svg-sprite.manifest.js`.
- Ошибка `Refusing to overwrite a user file`: в generated-пути находится пользовательский файл.
- Иконка не меняет цвет: используйте `color` или `--icon-color-N`.
Canonical guide: [React + Vite](guides/react-vite.md).

View File

@@ -1,129 +1,3 @@
# React + Webpack 5
# Guide React + Webpack перемещён
[← Главная](../../README_RU.md)
Краткая инструкция по установке и использованию SVG-спрайтов в проекте на React и Webpack 5.
В результате вы получите типизированный React-компонент и отдельный SVG asset через Webpack Asset Modules.
## 1. Установите пакет
```bash
npm install --save-dev @gromlab/svg-sprites
```
## 2. Создайте папку спрайта
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
├── index.ts
└── svg-sprite.config.ts
```
Поместите исходные SVG-файлы в `icons/`.
## 3. Добавьте конфиг
```ts
// src/ui/file-manager/svg-sprite/svg-sprite.config.ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@webpack',
name: 'file-manager',
description: 'Иконки файлового менеджера',
})
```
Корневой barrel принадлежит приложению и явно возвращает generated API:
```ts
// src/ui/file-manager/svg-sprite/index.ts
export * from './.svg-sprite'
```
По умолчанию SVG берутся из `./icons`. Общие иконки из других папок можно добавить через `inputFiles`: папка и список объединяются в один спрайт.
Полный список опций находится в разделе [«Единая конфигурация»](reference.md#единая-конфигурация).
## 4. Добавьте генерацию в package.json
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
Generated-файлы исключаются из Git, поэтому генерация должна выполняться перед `dev`, `build` и `typecheck`.
Первый запуск:
```bash
npm run sprite:file-manager
```
## 5. Используйте компонент
```tsx
import { FileManagerIcon } from './svg-sprite'
export const OpenFolderButton = () => (
<button type="button">
<FileManagerIcon icon="folder" width={24} height={24} />
Открыть
</button>
)
```
Значение `icon` проверяется TypeScript по именам файлов:
```tsx
<FileManagerIcon icon="folder" /> // допустимо
<FileManagerIcon icon="missing" /> // ошибка TypeScript
```
Типы, способы отображения и управление цветами описаны в [техническом справочнике](reference.md#react-компонент-и-typescript).
Webpack обработает generated `new URL('./sprite.svg', import.meta.url)` через Asset Modules и выпустит отдельный SVG asset.
Если проект уже использует собственный SVG loader, убедитесь, что он не перехватывает generated `sprite.svg` вместо Asset Modules.
Generated-компонент импортирует `react/react-component.module.css`, поэтому Webpack должен обрабатывать CSS Modules через `css-loader` и `style-loader` либо `MiniCssExtractPlugin`. Если TypeScript-проект не содержит декларации для CSS Modules, добавьте её отдельно.
## 6. Добавьте debug-страницу
Webpack не поддерживает Vite API `import.meta.glob`, поэтому передайте статические loaders:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('./ui/file-manager/svg-sprite/.svg-sprite/svg-sprite.manifest.js'),
() => import('./ui/navigation/svg-sprite/.svg-sprite/svg-sprite.manifest.js'),
]
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} title="Иконки проекта" />
)
```
Пути в `import()` должны быть строковыми литералами. Webpack создаст chunks для манифестов и свяжет их с SVG assets.
Размещайте Viewer только на debug-маршруте или во внутреннем инструменте.
## Если что-то не работает
- Нет `.svg-sprite/index.js`: запустите `npm run sprite:file-manager`.
- Viewer не загружает спрайт: проверьте путь в `import()` к `.svg-sprite/svg-sprite.manifest.js`.
- Неверный URL asset: проверьте `output.publicPath`.
- SVG перехватывает другой loader: исключите generated sprite из несовместимого правила.
Для Next.js используйте отдельные mode key из руководств [App Router](next-app.md) и [Pages Router](next-pages.md).
Canonical guide: [React + Webpack](guides/react-webpack.md).

View File

@@ -1,555 +1,3 @@
# Технический справочник
# Технический справочник перемещён
[← Главная](../../README_RU.md)
Справочник по конфигурации, generated API и поведению `@gromlab/svg-sprites`. Пошаговую установку смотрите в руководстве для вашего стека:
- [Next.js App Router](next-app.md)
- [Next.js Pages Router](next-pages.md)
- [React + Vite](react-vite.md)
- [React + Webpack 5](react-webpack.md)
## Требования
- Node.js 18 или новее;
- пакет распространяется как ESM и подключается через `import`;
- React 18 или 19 требуется для generated-компонентов и `@gromlab/svg-sprites/react`;
- для типизации package exports используйте TypeScript 5+ с `moduleResolution: "bundler"`, `"node16"` или `"nodenext"`.
Пакет устанавливается как development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
## CLI и режимы генерации
CLI принимает ровно один путь: явно выбранный config-файл либо каталог для config-less генерации:
```text
svg-sprites [options] <config-file-or-directory>
```
| Среда | Mode |
|---|---|
| Static HTML / собственная публикация | `standalone` |
| Standalone + Vite | `standalone@vite` |
| Standalone + Webpack 5 | `standalone@webpack` |
| React + Vite | `react@vite` |
| React + Webpack 5 | `react@webpack` |
| Next.js App Router + Turbopack | `next@app/turbopack` |
| Next.js App Router + Webpack 5 | `next@app/webpack` |
| Next.js Pages Router + Turbopack | `next@pages/turbopack` |
| Next.js Pages Router + Webpack 5 | `next@pages/webpack` |
Config-файл может иметь любое имя и расширение `.ts`, `.js` или `.json`. CLI не ищет конфиг по соглашению: файл нужно передать явно. В руководствах используется рекомендуемое имя `svg-sprite.config.ts`.
Если передан каталог, все настройки берутся из CLI. Если передан config-файл, CLI-параметры перекрывают значения файла. Общий порядок: `defaults → config → CLI`.
Доступны `--mode`, `--name`, `--description`, `--input-folder`, повторяемый `--input-file`, а также пары `--remove-size`/`--no-remove-size`, `--replace-colors`/`--no-replace-colors`, `--add-transition`/`--no-add-transition` и `--generated-notice`/`--no-generated-notice`. Переданные transform-флаги перекрывают отдельные поля, а хотя бы один `--input-file` заменяет весь массив `inputFiles` из config.
Mode должен соответствовать способу публикации приложения. Bare `standalone` оставляет публичный URL приложению; Vite и Webpack modes генерируют bundler-specific подключение SVG asset.
## Единая конфигурация
Каждый config-файл описывает один независимый спрайт.
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/turbopack',
name: 'app',
description: 'Общие иконки приложения',
inputFolder: './local-icons',
inputFiles: [
'../../assets/icons/search.svg',
'../../assets/icons/settings.svg',
],
transform: {
removeSize: true,
replaceColors: true,
addTransition: true,
},
generatedNotice: true,
})
```
| Опция | Тип | По умолчанию | Назначение |
|---|---|---|---|
| `mode` | `SpriteMode` | Нет | Режим генерации; можно передать через CLI/API |
| `name` | `string` | Выводится из каталога | Имя спрайта, компонента и публичных типов |
| `description` | `string` | Нет | Описание для типов и debug manifest |
| `inputFolder` | `string` | `./icons` | Каталог с SVG относительно корня модуля |
| `inputFiles` | `string[]` | `[]` | Пути к отдельным SVG относительно корня модуля |
| `transform` | `TransformOptions` | Все включены | Настройки подготовки SVG |
| `generatedNotice` | `boolean` | `true` | Полное или короткое предупреждение в generated-файлах |
### Имя спрайта
`name` записывается в kebab-case и должно начинаться с латинской буквы:
```text
app → AppIcon
file-manager → FileManagerIcon
```
Если `name` не задано, генератор выводит его из каталога. Для каталога с именем `svg-sprite` или `svg-sprites` используется имя родительского каталога.
### Источники иконок
`inputFolder` и `inputFiles` объединяются в один набор. Это позволяет хранить локальные SVG рядом с модулем и добавлять общие иконки из других частей проекта без копирования.
Если `inputFiles` заполнен, а неявного каталога `./icons` нет, генерация работает только по списку файлов. Явно указанная отсутствующая `inputFolder` считается ошибкой.
Каталог сканируется только на первом уровне. Вложенные каталоги рекурсивно не обходятся. Для вложенной структуры перечислите точные пути через `inputFiles`.
Одинаковые абсолютные пути дедуплицируются. Разные SVG с одинаковым именем файла считаются конфликтом, потому что публичное имя иконки выводится из basename.
## Generated-модуль
После генерации React- или Next.js-каталог спрайта выглядит так:
```text
app-icons/
├── .gitignore
├── svg-sprite.config.ts
├── index.ts # необязательный пользовательский barrel
└── .svg-sprite/
├── state.json
├── index.js
├── index.d.ts
├── icon-data.js
├── icon-data.d.ts
├── sprite.svg
├── svg-sprite.manifest.js
├── svg-sprite.manifest.d.ts
└── react/
├── react-component.js
├── react-component.d.ts
└── react-component.module.css
```
| Файл | Назначение |
|---|---|
| `.svg-sprite/index.js` | Production exports компонента и runtime-списка имён |
| `.svg-sprite/index.d.ts` | Публичные декларации компонента, props, стилей и union-типа имён |
| `.svg-sprite/svg-sprite.manifest.js` | Debug metadata и URL asset для `SpriteViewer` |
| `.svg-sprite/sprite.svg` | Собранный SVG-спрайт |
| `.svg-sprite/react/react-component.js` | Runtime React-компонента без TypeScript и JSX |
| `.svg-sprite/react/react-component.d.ts` | Props, style и declaration React-компонента |
| `.svg-sprite/react/react-component.module.css` | Стили конкретной React-реализации |
| `.svg-sprite/icon-data.js` | Runtime-список имён и внутренние IDs |
| `.svg-sprite/*.d.ts` | TypeScript-декларации соответствующих JS-модулей |
| `.svg-sprite/state.json` | Mode, версия контракта и список управляемых файлов |
Standalone-контракты не создают каталог `react/`. Bare `standalone` содержит только
runtime asset и deployment-neutral manifest data:
```text
.svg-sprite/
├── state.json
├── sprite.svg
└── svg-sprite.manifest.json
```
`standalone@vite` и `standalone@webpack` дополнительно создают `index.*`,
`icon-data.*` и resolved `svg-sprite.manifest.*`.
Генератор перезаписывает и удаляет только файлы со своим marker. Если в managed-пути находится пользовательский файл, генерация завершается ошибкой. Корневой `index.ts` генератору не принадлежит; при необходимости создайте пользовательский barrel:
```ts
export * from './.svg-sprite'
```
## React-компонент и TypeScript
Спрайт с `name: 'app'` экспортирует:
```ts
export { AppIcon, appIconNames }
export type { AppIconName, AppIconProps, AppIconStyle }
```
### Имена иконок
Имена SVG-файлов становятся допустимыми значениями `icon`:
```tsx
<AppIcon icon="search" />
<AppIcon icon="unknown" /> // ошибка TypeScript
```
Runtime-список содержит те же значения:
```ts
import { appIconNames } from '@/ui/app-icons'
// readonly ['search', 'settings', 'user']
```
Имена с пробелами и другими небезопасными для SVG ID символами остаются частью публичного API. Для внутреннего fragment ID генератор создаёт стабильный безопасный hash:
```text
folder open.svg → icon="folder open" → id="icon-<stable-hash>"
```
Для таких имён используйте generated-компонент или `id` из debug manifest, а не формируйте fragment ID вручную.
### SVG-атрибуты
По умолчанию компонент рендерит `<svg>` и принимает стандартные SVG-атрибуты:
```tsx
<AppIcon
icon="search"
width={24}
height={24}
color="rebeccapurple"
className="searchIcon"
aria-label="Поиск"
/>
```
Компонент не добавляет accessibility-семантику автоматически. Передавайте подходящие `aria-*`, `role` или подпись в зависимости от назначения иконки.
### Обёртка
`wrapped` рендерит `<span>` с внутренним SVG. Остальные props в этом режиме относятся к `<span>`:
```tsx
<AppIcon icon="search" wrapped className="iconWrapper" />
```
### Типизированные CSS-переменные
`AppIconStyle` расширяет `CSSProperties` и поддерживает свойства вида `--icon-color-N`:
```tsx
<AppIcon
icon="user"
style={{
'--icon-color-1': '#2563eb',
'--icon-color-2': '#dbeafe',
}}
/>
```
## Множественные спрайты
Каждый каталог с конфигом создаёт независимый компонент, типы, manifest и SVG asset:
```text
app-icons → AppIcon → общие иконки
analytics-icons → AnalyticsIcon → иконки страницы аналитики
editor-icons → EditorIcon → иконки редактора
```
Один исходный SVG можно добавить через `inputFiles` в несколько конфигураций. Копировать файл в каталоги каждого спрайта не требуется.
Для нескольких спрайтов добавьте отдельную CLI-команду для каждого каталога или объедините команды в общем npm script.
## Форматы и способы отображения
Все текущие modes создают формат `stack`.
| Формат | `<svg><use>` | `<img>` | CSS background |
|---|---:|---:|---:|
| `stack` | Да | Да | Да |
### Generated-компонент
Для React и Next.js используйте generated-компонент. Он знает внутренние ID, формирует URL и предоставляет TypeScript API:
```tsx
<AppIcon icon="search" width={24} height={24} />
```
### Вручную через `<svg><use>`
Способ получения `spriteUrl` зависит от сборщика.
Static HTML после публикации `.svg-sprite/sprite.svg` приложением:
```html
<svg aria-hidden="true">
<use href="/assets/icons.svg#search"></use>
</svg>
```
Standalone Vite/Webpack предоставляет generated `getIconsIconHref()` и mapping
внутренних IDs. Не конструируйте fragment из небезопасного имени файла вручную.
Vite:
```ts
import spriteUrl from './.svg-sprite/sprite.svg?no-inline'
```
Webpack 5, Turbopack и Next.js:
```ts
const spriteUrl = new URL('./.svg-sprite/sprite.svg', import.meta.url).href
```
После получения URL используйте его в JSX:
```tsx
<svg width="24" height="24" aria-label="Поиск">
<use href={`${spriteUrl}#search`} />
</svg>
```
Для имён, небезопасных как SVG ID, используйте внутренний `id` из manifest.
### Через `<img>`
```tsx
<img src={`${spriteUrl}#search`} width={24} height={24} alt="Поиск" />
```
SVG внутри `<img>` изолирован от CSS страницы. `color` и `--icon-color-N` на внешнем элементе не изменяют его внутренние цвета.
### Через CSS
```css
.icon {
background: url('./.svg-sprite/sprite.svg#search') center / contain no-repeat;
}
```
Для одноцветного силуэта можно использовать mask:
```css
.icon {
background-color: currentColor;
mask: url('./.svg-sprite/sprite.svg#search') center / contain no-repeat;
}
```
Mask не сохраняет исходные цвета, gradients и различия между `fill` и `stroke`.
Путь в CSS разрешается относительно самого CSS-файла. В примерах CSS-файл находится рядом с `svg-sprite.config.ts`.
## Assets и кеширование
Generated component или standalone facade передаёт SVG сборщику как отдельный asset:
- Vite использует статический импорт с `?no-inline`;
- Webpack 5, Turbopack и Next.js используют `new URL(..., import.meta.url)`;
- SVG path-данные не сериализуются в generated JavaScript.
Bare `standalone` не участвует в asset pipeline: приложение само копирует или
публикует `sprite.svg` и отвечает за URL, версионирование и cache policy.
При стандартном именовании assets сборщик добавляет content hash:
```text
/assets/sprite-<hash>.svg
```
Это позволяет кешировать SVG отдельно от JavaScript. Изменение React-кода не меняет содержимое спрайта, а изменение иконок создаёт новую версию asset.
HTTP cache headers, CDN и `Cache-Control` настраиваются приложением или платформой размещения. Для Webpack имя итогового файла зависит от `assetModuleFilename` проекта.
## Трансформации SVG
Все трансформации включены по умолчанию и настраиваются независимо:
| Опция | Что делает |
|---|---|
| `removeSize` | Удаляет `width` и `height` с корневого `<svg>`, сохраняя существующий `viewBox` |
| `replaceColors` | Заменяет найденные `fill` и `stroke` на `--icon-color-N` |
| `addTransition` | Добавляет transitions для `fill` и `stroke` в цветные элементы и generated styles |
Чтобы отключить отдельную операцию:
```ts
export default defineSpriteConfig({
mode: 'next@app/turbopack',
transform: {
removeSize: false,
replaceColors: false,
addTransition: false,
},
})
```
Исходные SVG не изменяются. Трансформации применяются только к содержимому generated-спрайта.
## Управление цветами
### Монохромные иконки
Если найден один цвет, fallback становится `currentColor`:
```svg
stroke="var(--icon-color-1, currentColor)"
```
Цвет задаётся через prop или CSS:
```tsx
<AppIcon icon="search" color="rebeccapurple" />
```
### Многоцветные иконки
Каждый уникальный цвет получает отдельную переменную с исходным fallback:
```svg
fill="var(--icon-color-1, #798198)"
fill="var(--icon-color-2, #ffffff)"
fill="var(--icon-color-3, #129d9d)"
```
Можно заменить только необходимые значения:
```css
.icon {
--icon-color-1: #4b5563;
--icon-color-3: #14b8a6;
}
```
### Ограничения
- `none`, `transparent`, `inherit`, `unset` и `initial` не заменяются;
- надёжнее всего обрабатываются цвета в атрибутах `fill`, `stroke` и inline `style`;
- CSS-классы и внешние stylesheets внутри SVG не являются основным сценарием трансформации;
- значения `url(#...)` могут быть заменены вместе с цветами, поэтому gradients и patterns требуют отдельного спрайта с `replaceColors: false`;
- masks, filters и сложные внутренние CSS-правила требуют визуальной проверки;
- CSS-переменные страницы доступны через `<svg><use>`, но не внутри `<img>` и CSS background.
Для сложной иконки можно отключить `replaceColors` в конфигурации отдельного спрайта.
## SpriteViewer
Viewer использует один Web Component с Shadow DOM для всех modes. React и будущие framework-компоненты являются bridge к этому же элементу, поэтому визуал и поведение не дублируются.
Bare `standalone` подключает самостоятельный browser bundle и передаёт URL JSON manifest и опубликованного SVG:
```html
<script
type="module"
src="https://unpkg.com/@gromlab/svg-sprites@<version>/dist/viewer-element.js"
></script>
<gromlab-sprite-viewer
viewer-title="Иконки проекта"
manifest-url="/app-icons/manifest.json"
sprite-url="/app-icons/sprite.svg"
></gromlab-sprite-viewer>
```
`viewer-element.js` не имеет дополнительных runtime-файлов и может быть скопирован с остальными static assets для self-hosting.
`standalone@vite` и `standalone@webpack` регистрируют тот же элемент через npm entry и передают generated JS manifest через свойство `sources`:
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
import spriteManifest from './svg-sprite/.svg-sprite/svg-sprite.manifest.js'
const viewer = document.querySelector<SpriteViewerElement>('gromlab-sprite-viewer')!
viewer.sources = [spriteManifest]
```
React и Next.js сохраняют компонентный API:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
```
Он принимает готовые manifests, remote standalone sources, массив lazy loaders или record формата `import.meta.glob`.
Vite:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
import type { SpriteManifestModule } from '@gromlab/svg-sprites/react'
const sources = import.meta.glob<SpriteManifestModule>(
'/src/**/svg-sprite/.svg-sprite/svg-sprite.manifest.js',
)
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} title="Иконки проекта" />
)
```
Webpack и Next.js:
```tsx
const sources = [
() => import('@/ui/app-icons/.svg-sprite/svg-sprite.manifest.js'),
() => import('@/features/analytics/icons/.svg-sprite/svg-sprite.manifest.js'),
]
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} />
)
```
Viewer показывает группы, поиск, `viewBox`, CSS-переменные и fallback-цвета. React/Next manifests получают вкладки React, SVG, IMG и CSS; standalone manifests получают SVG, IMG и CSS. Цветовые значения можно менять в интерфейсе и сразу проверять результат.
### Тема Viewer
По умолчанию `colorTheme="auto"` следует `prefers-color-scheme`. Можно передать `light` или `dark` явно:
```tsx
<SpriteViewer sources={sources} colorTheme="dark" />
```
Для синхронизации с темой приложения:
```tsx
<SpriteViewer
sources={sources}
colorTheme={appTheme}
onColorThemeChange={setAppTheme}
/>
```
`@gromlab/svg-sprites/react` содержит `'use client'` и рендерит Web Component host; внутренний Shadow DOM создаётся после загрузки browser runtime. В Next.js App Router размещайте Viewer внутри отдельной Client Component boundary и используйте только на debug-маршруте или во внутреннем инструменте.
## Generated-файлы, Git и CI
Современный sprite-модуль создаёт локальный `.gitignore` для:
```text
/.svg-sprite/
```
Локальный `.gitignore` следует один раз добавить в репозиторий. Он исключает остальные generated-файлы, поэтому генерацию нужно запускать перед командами, которые импортируют sprite-модуль:
```json
{
"scripts": {
"sprites": "svg-sprites src/ui/app-icons/svg-sprite.config.ts",
"predev": "npm run sprites",
"prebuild": "npm run sprites",
"pretypecheck": "npm run sprites"
}
}
```
CI должен устанавливать development dependencies и выполнять generation script до сборки или проверки типов.
Если в каталоге спрайта уже находится пользовательский `.gitignore` либо пользовательский файл внутри `.svg-sprite`, генератор не перезапишет его. Корневой `index.ts` остаётся пользовательским и может переэкспортировать generated API.
## Диагностика
- Нет `.svg-sprite/index.js`: запустите generation script до импорта generated-модуля.
- Не найден источник: передайте существующий config-файл или каталог sprite-модуля.
- Не указан mode: добавьте `mode` в config либо передайте `--mode`.
- Иконка отсутствует в типе: проверьте `inputFiles`, расширение `.svg` и уровень вложенности `inputFolder`.
- Конфликт имени: два разных SVG имеют одинаковый basename; переименуйте один файл.
- `Refusing to overwrite a user file`: в managed-пути находится файл без generated marker.
- Иконка не меняет цвет: используйте `<svg><use>` или generated-компонент и проверьте `replaceColors`.
- Webpack выдаёт неверный URL: проверьте Asset Modules, `output.publicPath` и SVG loaders.
- Static sprite возвращает 404: проверьте post-generation copy или server alias и не передавайте filesystem `spritePath` в HTML.
- Viewer не видит спрайт: проверьте путь к `.svg-sprite/svg-sprite.manifest.js` и выполните генерацию до запуска приложения.
- Build и mode не совпадают: используйте target, соответствующий фактическому сборщику.
Для собственного orchestration и низкоуровневой компиляции смотрите [Программный API](programmatic-api.md).
Canonical документ: [технический справочник](reference/technical.md).

View File

@@ -0,0 +1,4 @@
# Справочники
- [Технический справочник](technical.md)
- [Программный API](programmatic-api.md)

View File

@@ -0,0 +1,147 @@
# Программный API
[Индекс документации](../README.md)
Пакет распространяется как ESM и предоставляет единый Node.js API генерации. Framework-neutral Viewer находится в `@gromlab/svg-sprites/viewer`, auto-register entry — в `@gromlab/svg-sprites/viewer/element`, React bridge — в `@gromlab/svg-sprites/react`.
## `generateSprite`
```ts
import { generateSprite } from '@gromlab/svg-sprites'
const result = await generateSprite(
'src/ui/file-manager/svg-sprite/svg-sprite.config.ts',
)
```
Для static standalone mode `result.spritePath` можно использовать в build-скрипте,
чтобы опубликовать SVG по URL приложения:
```ts
import { copyFile } from 'node:fs/promises'
const result = await generateSprite('src/sprite/svg-sprite.config.ts', {
mode: 'standalone',
})
await copyFile(result.spritePath, 'dist/app-icons/sprite.svg')
```
`spritePath` является filesystem path, а не browser URL. Deployment-neutral JSON
manifest доступен через `result.manifestPath` и копируется независимо от SVG.
Первый аргумент принимает полный путь к config-файлу с любым именем и расширением `.ts`, `.js` или `.json`. Каталог вместо файла включает config-less режим: корнем sprite-модуля становится этот каталог.
Второй аргумент содержит необязательные overrides и всегда имеет приоритет над конфигом:
```ts
await generateSprite('src/ui/file-manager/svg-sprite/custom-config.json', {
mode: 'react@webpack',
name: 'documents',
input: ['./assets', '../../shared/search.svg'],
transform: {
addTransition: false,
},
generatedNotice: false,
})
```
Порядок разрешения настроек:
```text
defaults → config → API overrides
```
Для полностью программной генерации передайте каталог и все обязательные настройки через overrides:
```ts
await generateSprite('src/ui/file-manager/svg-sprite', {
mode: 'react@vite',
name: 'file-manager',
input: [
'../../shared/search.svg',
'../../shared/settings.svg',
],
})
```
## Конфигурация
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@vite',
name: 'file-manager',
description: 'Иконки файлового менеджера',
input: ['./icons', '../../shared/check.svg'],
transform: {
removeSize: true,
replaceColors: true,
addTransition: true,
},
generatedNotice: true,
})
```
`input` принимает одну папку, SVG-файл или glob-паттерн либо массив, объединяющий такие источники. Если поле не задано, используется `./icons`; относительные пути считаются от папки с конфигом.
`defineSpriteConfig` является identity helper для TypeScript autocomplete. JS может экспортировать тот же объект через `export default`, а JSON содержит объект непосредственно.
## Специализированные обёртки
Специализированные функции доступны как обёртки над `generateSprite`:
```ts
import { generateNextSprite, generateReactSprite } from '@gromlab/svg-sprites'
await generateReactSprite('path/to/config.ts', 'vite')
await generateNextSprite('path/to/config.ts', {
router: 'app',
bundler: 'turbopack',
})
```
Явно переданный target перекрывает `mode` из файла. Для нового кода используйте `generateSprite`.
## Config API
```ts
import {
loadSpriteConfig,
resolveSpriteConfig,
validateSpriteConfig,
} from '@gromlab/svg-sprites'
```
- `loadSpriteConfig(file)` загружает явно указанный `.ts`, `.js` или `.json` файл.
- `validateSpriteConfig(value)` выполняет runtime-валидацию объекта.
- `resolveSpriteConfig(root, config, overrides)` объединяет значения, добавляет defaults и разрешает пути относительно `root`.
## Низкоуровневый compiler
```ts
import {
compileSprite,
compileSpriteContent,
createShapeTransform,
} from '@gromlab/svg-sprites'
```
Эти функции предназначены для собственного orchestration. Стандартная генерация должна выполняться через `generateSprite`.
## Viewer runtime
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
```
Browser entry регистрирует `<gromlab-sprite-viewer>`. Bare standalone также может загрузить самостоятельный `dist/viewer-element.js` без bundler.
React bridge сохраняет компонентный API:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
```
`SpriteViewer` принимает generated manifests, remote standalone sources, lazy loaders или результат `import.meta.glob`. React entry содержит `'use client'` и предназначен для debug-инструментов; production-компоненты импортируются из локальных sprite-модулей приложения.

View File

@@ -0,0 +1,631 @@
# Технический справочник
[Индекс документации](../README.md)
Справочник по конфигурации, generated API и поведению `@gromlab/svg-sprites`. Пошаговую установку смотрите в руководстве для вашего стека:
- [Bare standalone](../guides/standalone.md)
- [Standalone + Vite](../guides/standalone-vite.md)
- [Standalone + Webpack 5](../guides/standalone-webpack.md)
- [React + Vite](../guides/react-vite.md)
- [React + Webpack 5](../guides/react-webpack.md)
- [Next.js App Router + Turbopack](../guides/next-app-turbopack.md)
- [Next.js App Router + Webpack](../guides/next-app-webpack.md)
- [Next.js Pages Router + Turbopack](../guides/next-pages-turbopack.md)
- [Next.js Pages Router + Webpack](../guides/next-pages-webpack.md)
## Требования
- Node.js 18 или новее;
- пакет распространяется как ESM и подключается через `import`;
- React 18 или 19 требуется только для React/Next generated-компонентов и `@gromlab/svg-sprites/react`;
- для типизации package exports используйте TypeScript 5+ с `moduleResolution: "bundler"`, `"node16"` или `"nodenext"`.
Для генерации не нужна dependency проекта. Запускайте CLI через `npx`:
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites path/to/svg-sprite.config.ts
```
Устанавливайте пакет как development dependency, только если проекту нужны
Viewer, типы конфига или программный API:
```bash
npm install --save-dev @gromlab/svg-sprites
```
## CLI и режимы генерации
CLI принимает ровно один путь: явно выбранный config-файл либо каталог для config-less генерации:
```text
svg-sprites [options] <config-file-or-directory>
```
| Среда | Mode |
|---|---|
| Static HTML / собственная публикация | `standalone` |
| Standalone + Vite | `standalone@vite` |
| Standalone + Webpack 5 | `standalone@webpack` |
| React + Vite | `react@vite` |
| React + Webpack 5 | `react@webpack` |
| Next.js App Router + Turbopack | `next@app/turbopack` |
| Next.js App Router + Webpack 5 | `next@app/webpack` |
| Next.js Pages Router + Turbopack | `next@pages/turbopack` |
| Next.js Pages Router + Webpack 5 | `next@pages/webpack` |
Config-файл может иметь любое имя и расширение `.ts`, `.js` или `.json`. CLI не ищет конфиг по соглашению: файл нужно передать явно. В руководствах используется рекомендуемое имя `svg-sprite.config.ts`.
Если передан каталог, все настройки берутся из CLI. Если передан config-файл, CLI-параметры перекрывают значения файла. Общий порядок: `defaults → config → CLI`.
Доступны `--mode`, `--name`, `--description`, повторяемый `--input <path-or-glob>`, а также пары `--remove-size`/`--no-remove-size`, `--replace-colors`/`--no-replace-colors`, `--add-transition`/`--no-add-transition` и `--generated-notice`/`--no-generated-notice`. Переданные transform-флаги перекрывают отдельные поля, а хотя бы один `--input` полностью заменяет значение `input` из config.
В CLI заключайте glob-паттерны в одинарные кавычки, чтобы shell не раскрыл их до запуска генератора:
```bash
svg-sprites --input './icons/**/*.svg' --input '!./icons/legacy/**' svg-sprite.config.ts
```
Mode должен соответствовать способу публикации приложения. Bare `standalone` оставляет публичный URL приложению; Vite и Webpack modes генерируют bundler-specific подключение SVG asset.
## Единая конфигурация
Каждый config-файл описывает один независимый спрайт.
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/turbopack',
name: 'app',
description: 'Общие иконки приложения',
input: [
'./local-icons',
'../../assets/icons/*.svg',
'!../../assets/icons/deprecated-*.svg',
],
transform: {
removeSize: true,
replaceColors: true,
addTransition: true,
},
generatedNotice: true,
})
```
| Опция | Тип | По умолчанию | Назначение |
|---|---|---|---|
| `mode` | `SpriteMode` | Нет | Режим генерации; можно передать через CLI/API |
| `name` | `string` | Выводится из каталога | Имя спрайта, компонента и публичных типов |
| `description` | `string` | Нет | Описание для типов и debug manifest |
| `input` | `string \| string[]` | `./icons` | Папки, SVG-файлы и glob-паттерны относительно папки конфига |
| `transform` | `TransformOptions` | Все включены | Настройки подготовки SVG |
| `generatedNotice` | `boolean` | `true` | Полное или короткое предупреждение в generated-файлах |
### Имя спрайта
`name` записывается в kebab-case и должно начинаться с латинской буквы:
```text
app → AppIcon
file-manager → FileManagerIcon
```
Если `name` не задано, генератор выводит его из каталога. Для каталога с именем `svg-sprite` или `svg-sprites` используется имя родительского каталога.
### Источники иконок
`SpriteConfig.input` является необязательным и имеет тип `string | string[]`. Если поле отсутствует, источником служит папка `./icons` относительно папки конфига. В config-less режиме относительные пути считаются от каталога, переданного CLI или API.
Каждая строка без префикса `!` может быть путём к конкретной папке, конкретному файлу `.svg` или glob-паттерном. Папка включает только непосредственные дочерние `*.svg`. Для рекурсивного обхода вложенных каталогов укажите явный паттерн, например `icons/**/*.svg`.
Массив объединяет все включающие источники. Паттерн с префиксом `!` глобально исключает совпадения из общего результата независимо от того, какой источник их добавил.
Поддерживается следующий glob-синтаксис:
| Синтаксис | Значение |
|---|---|
| `*` | Любые символы внутри одного сегмента пути |
| `**` | Любое число вложенных каталогов |
| `?` | Один символ внутри сегмента пути |
| `{a,b}` | Одна из альтернатив |
| `[abc]` | Один символ из набора или диапазона |
| `!pattern` | Исключение совпадений из всего объединённого input |
Каждый включающий источник или паттерн должен найти хотя бы один SVG, иначе генерация завершается ошибкой. Повторяющиеся пути удаляются, а итоговый список файлов детерминированно сортируется. Разные SVG с одинаковым basename по-прежнему считаются конфликтом, потому что basename задаёт публичное имя иконки.
## Generated-модуль
После генерации React- или Next.js-каталог спрайта выглядит так:
```text
app-icons/
├── .gitignore
├── svg-sprite.config.ts
├── index.ts # необязательный пользовательский barrel
└── .svg-sprite/
├── index.js
├── index.d.ts
├── icon-data.js
├── icon-data.d.ts
├── sprite.svg
├── svg-sprite.manifest.js
├── svg-sprite.manifest.d.ts
└── react/
├── react-component.js
├── react-component.d.ts
└── react-component.module.css
```
| Файл | Назначение |
|---|---|
| `.svg-sprite/index.js` | Mode-specific production facade и runtime-список имён |
| `.svg-sprite/index.d.ts` | Публичные декларации facade, компонента и union-типа имён |
| `.svg-sprite/svg-sprite.manifest.js` | Debug metadata и URL asset для `SpriteViewer` |
| `.svg-sprite/sprite.svg` | Собранный SVG-спрайт |
| `.svg-sprite/react/react-component.js` | Runtime React-компонента без TypeScript и JSX |
| `.svg-sprite/react/react-component.d.ts` | Props, style и declaration React-компонента |
| `.svg-sprite/react/react-component.module.css` | Стили конкретной React-реализации |
| `.svg-sprite/icon-data.js` | Runtime-список имён и внутренние IDs |
| `.svg-sprite/*.d.ts` | TypeScript-декларации соответствующих JS-модулей |
Standalone-контракты не создают каталог `react/`. Bare `standalone` содержит только
runtime asset и deployment-neutral manifest data:
```text
.svg-sprite/
├── sprite.svg
└── svg-sprite.manifest.json
```
`standalone@vite` и `standalone@webpack` дополнительно создают `index.*`,
`icon-data.*` и resolved `svg-sprite.manifest.*`. Их facade содержит нативный
generated Web Component без внешних runtime-зависимостей. Bare `standalone`
намеренно не создаёт JavaScript-компонент.
Генератор перезаписывает и удаляет только файлы со своим marker. Если в managed-пути находится пользовательский файл, генерация завершается ошибкой. Корневой `index.ts` генератору не принадлежит; при необходимости создайте пользовательский barrel:
```ts
export * from './.svg-sprite'
```
## Standalone Web Component и TypeScript
В modes `standalone@vite` и `standalone@webpack` спрайт с `name: 'app'`
экспортирует функцию регистрации `defineAppIconElement()` и tag `<app-icon>`:
```ts
import { defineAppIconElement } from '@/ui/app-icons'
defineAppIconElement()
```
После регистрации элемент можно использовать в HTML:
```html
<app-icon icon="search" aria-hidden="true"></app-icon>
<app-icon
icon="settings"
role="img"
aria-label="Настройки"
></app-icon>
```
Компонент рендерит `<svg><use>` в открытом Shadow DOM, сам выбирает внутренний
ID и `viewBox`, а URL asset получает через соответствующий Vite или Webpack
механизм. Размер host по умолчанию равен `1em × 1em`; `class`, `style`, `color`
и `--icon-color-N` задаются обычным CSS.
Generated `HTMLElementTagNameMap` типизирует property API:
```ts
const icon = document.createElement('app-icon')
icon.icon = 'search'
icon.icon = 'unknown' // ошибка TypeScript
```
Значения атрибутов в обычной HTML-разметке TypeScript не проверяет. Поэтому
неизвестный `icon="unknown"` дополнительно проверяется в runtime: компонент
скрывает внутренний SVG и сообщает об ошибке, не создавая fragment
`#undefined`. Повторный вызов `defineAppIconElement()` безопасен для того же
спрайта; конфликт с другим элементом под tag `<app-icon>` завершается ошибкой.
## React-компонент и TypeScript
Спрайт с `name: 'app'` экспортирует:
```ts
export { AppIcon, appIconNames }
export type { AppIconName, AppIconProps, AppIconStyle }
```
### Имена иконок
Имена SVG-файлов становятся допустимыми значениями `icon`:
```tsx
<AppIcon icon="search" />
<AppIcon icon="unknown" /> // ошибка TypeScript
```
Runtime-список содержит те же значения:
```ts
import { appIconNames } from '@/ui/app-icons'
// readonly ['search', 'settings', 'user']
```
Имена с пробелами и другими небезопасными для SVG ID символами остаются частью публичного API. Для внутреннего fragment ID генератор создаёт стабильный безопасный hash:
```text
folder open.svg → icon="folder open" → id="icon-<stable-hash>"
```
Для таких имён используйте generated-компонент или `id` из debug manifest, а не формируйте fragment ID вручную.
### SVG-атрибуты
По умолчанию компонент рендерит `<svg>` и принимает стандартные SVG-атрибуты:
```tsx
<AppIcon
icon="search"
width={24}
height={24}
color="rebeccapurple"
className="searchIcon"
aria-label="Поиск"
/>
```
Компонент не добавляет accessibility-семантику автоматически. Передавайте подходящие `aria-*`, `role` или подпись в зависимости от назначения иконки.
### Обёртка
`wrapped` рендерит `<span>` с внутренним SVG. Остальные props в этом режиме относятся к `<span>`:
```tsx
<AppIcon icon="search" wrapped className="iconWrapper" />
```
### Типизированные CSS-переменные
`AppIconStyle` расширяет `CSSProperties` и поддерживает свойства вида `--icon-color-N`:
```tsx
<AppIcon
icon="user"
style={{
'--icon-color-1': '#2563eb',
'--icon-color-2': '#dbeafe',
}}
/>
```
## Множественные спрайты
Каждый каталог с конфигом создаёт независимый компонент, типы, manifest и SVG asset:
```text
app-icons → AppIcon → общие иконки
analytics-icons → AnalyticsIcon → иконки страницы аналитики
editor-icons → EditorIcon → иконки редактора
```
Один исходный SVG можно добавить через `input` в несколько конфигураций. Копировать файл в каталоги каждого спрайта не требуется.
Для нескольких спрайтов добавьте отдельную CLI-команду для каждого каталога или объедините команды в общем npm script.
## Форматы и способы отображения
Все текущие modes создают формат `stack`.
| Формат | `<svg><use>` | `<img>` | CSS background |
|---|---:|---:|---:|
| `stack` | Да | Да | Да |
### Generated-компонент
Для React и Next.js используйте generated React-компонент. Он знает внутренние ID, формирует URL и предоставляет TypeScript API:
```tsx
<AppIcon icon="search" width={24} height={24} />
```
Для `standalone@vite` и `standalone@webpack` используйте generated Web Component:
```html
<app-icon icon="search" style="font-size: 24px"></app-icon>
```
### Вручную через `<svg><use>`
Способ получения `spriteUrl` зависит от сборщика.
Static HTML после публикации `.svg-sprite/sprite.svg` приложением:
```html
<svg aria-hidden="true">
<use href="/assets/icons.svg#search"></use>
</svg>
```
Standalone Vite/Webpack предоставляет generated `getIconsIconHref()` и mapping
внутренних IDs. Не конструируйте fragment из небезопасного имени файла вручную.
Vite:
```ts
import spriteUrl from './.svg-sprite/sprite.svg?no-inline'
```
Webpack 5, Turbopack и Next.js:
```ts
const spriteUrl = new URL('./.svg-sprite/sprite.svg', import.meta.url).href
```
После получения URL используйте его в JSX:
```tsx
<svg width="24" height="24" aria-label="Поиск">
<use href={`${spriteUrl}#search`} />
</svg>
```
Для имён, небезопасных как SVG ID, используйте внутренний `id` из manifest.
### Через `<img>`
```tsx
<img src={`${spriteUrl}#search`} width={24} height={24} alt="Поиск" />
```
SVG внутри `<img>` изолирован от CSS страницы. `color` и `--icon-color-N` на внешнем элементе не изменяют его внутренние цвета.
### Через CSS
```css
.icon {
background: url('./.svg-sprite/sprite.svg#search') center / contain no-repeat;
}
```
Для одноцветного силуэта можно использовать mask:
```css
.icon {
background-color: currentColor;
mask: url('./.svg-sprite/sprite.svg#search') center / contain no-repeat;
}
```
Mask не сохраняет исходные цвета, gradients и различия между `fill` и `stroke`.
Путь в CSS разрешается относительно самого CSS-файла. В примерах CSS-файл находится рядом с `svg-sprite.config.ts`.
## Assets и кеширование
Generated component или standalone facade передаёт SVG сборщику как отдельный asset:
- Vite использует статический импорт с `?no-inline`;
- Webpack 5, Turbopack и Next.js используют `new URL(..., import.meta.url)`;
- SVG path-данные не сериализуются в generated JavaScript.
Bare `standalone` не участвует в asset pipeline: приложение само копирует или
публикует `sprite.svg` и отвечает за URL, версионирование и cache policy.
При стандартном именовании assets сборщик добавляет content hash:
```text
/assets/sprite-<hash>.svg
```
Это позволяет кешировать SVG отдельно от JavaScript. Изменение React-кода не меняет содержимое спрайта, а изменение иконок создаёт новую версию asset.
HTTP cache headers, CDN и `Cache-Control` настраиваются приложением или платформой размещения. Для Webpack имя итогового файла зависит от `assetModuleFilename` проекта.
## Трансформации SVG
Все трансформации включены по умолчанию и настраиваются независимо:
| Опция | Что делает |
|---|---|
| `removeSize` | Удаляет `width` и `height` с корневого `<svg>`, сохраняя существующий `viewBox` |
| `replaceColors` | Заменяет найденные `fill` и `stroke` на `--icon-color-N` |
| `addTransition` | Добавляет transitions для `fill` и `stroke` в цветные элементы и generated styles |
Чтобы отключить отдельную операцию:
```ts
export default defineSpriteConfig({
mode: 'next@app/turbopack',
transform: {
removeSize: false,
replaceColors: false,
addTransition: false,
},
})
```
Исходные SVG не изменяются. Трансформации применяются только к содержимому generated-спрайта.
## Управление цветами
### Монохромные иконки
Если найден один цвет, fallback становится `currentColor`:
```svg
stroke="var(--icon-color-1, currentColor)"
```
Цвет задаётся через prop или CSS:
```tsx
<AppIcon icon="search" color="rebeccapurple" />
```
### Многоцветные иконки
Каждый уникальный цвет получает отдельную переменную с исходным fallback:
```svg
fill="var(--icon-color-1, #798198)"
fill="var(--icon-color-2, #ffffff)"
fill="var(--icon-color-3, #129d9d)"
```
Можно заменить только необходимые значения:
```css
.icon {
--icon-color-1: #4b5563;
--icon-color-3: #14b8a6;
}
```
### Ограничения
- `none`, `transparent`, `inherit`, `unset` и `initial` не заменяются;
- надёжнее всего обрабатываются цвета в атрибутах `fill`, `stroke` и inline `style`;
- CSS-классы и внешние stylesheets внутри SVG не являются основным сценарием трансформации;
- значения `url(#...)` могут быть заменены вместе с цветами, поэтому gradients и patterns требуют отдельного спрайта с `replaceColors: false`;
- masks, filters и сложные внутренние CSS-правила требуют визуальной проверки;
- CSS-переменные страницы доступны через `<svg><use>`, но не внутри `<img>` и CSS background.
Для сложной иконки можно отключить `replaceColors` в конфигурации отдельного спрайта.
## SpriteViewer
Viewer использует один Web Component с Shadow DOM для всех modes. React и будущие framework-компоненты являются bridge к этому же элементу, поэтому визуал и поведение не дублируются.
Bare `standalone` подключает самостоятельный browser bundle и передаёт URL JSON manifest и опубликованного SVG:
```html
<script
type="module"
src="https://unpkg.com/@gromlab/svg-sprites@<version>/dist/viewer-element.js"
></script>
<gromlab-sprite-viewer
viewer-title="Иконки проекта"
manifest-url="/app-icons/manifest.json"
sprite-url="/app-icons/sprite.svg"
></gromlab-sprite-viewer>
```
`viewer-element.js` не имеет дополнительных runtime-файлов и может быть скопирован с остальными static assets для self-hosting.
`standalone@vite` и `standalone@webpack` регистрируют тот же элемент через npm entry и передают generated JS manifest через свойство `sources`:
```ts
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
import spriteManifest from './svg-sprite/.svg-sprite/svg-sprite.manifest.js'
const viewer = document.querySelector<SpriteViewerElement>('gromlab-sprite-viewer')!
viewer.sources = [spriteManifest]
```
React и Next.js сохраняют компонентный API:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
```
Он принимает готовые manifests, remote standalone sources, массив lazy loaders или record формата `import.meta.glob`.
Vite:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
import type { SpriteManifestModule } from '@gromlab/svg-sprites/react'
const sources = import.meta.glob<SpriteManifestModule>(
'/src/**/svg-sprite/.svg-sprite/svg-sprite.manifest.js',
)
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} title="Иконки проекта" />
)
```
Webpack и Next.js:
```tsx
const sources = [
() => import('@/ui/app-icons/.svg-sprite/svg-sprite.manifest.js'),
() => import('@/features/analytics/icons/.svg-sprite/svg-sprite.manifest.js'),
]
export const IconsDebugPage = () => (
<SpriteViewer sources={sources} />
)
```
Viewer показывает группы, поиск, `viewBox`, CSS-переменные и fallback-цвета. React/Next manifests получают вкладки React, SVG, IMG и CSS; standalone manifests получают SVG, IMG и CSS. Цветовые значения можно менять в интерфейсе и сразу проверять результат.
### Тема Viewer
По умолчанию `colorTheme="auto"` следует `prefers-color-scheme`. Можно передать `light` или `dark` явно:
```tsx
<SpriteViewer sources={sources} colorTheme="dark" />
```
Для синхронизации с темой приложения:
```tsx
<SpriteViewer
sources={sources}
colorTheme={appTheme}
onColorThemeChange={setAppTheme}
/>
```
`@gromlab/svg-sprites/react` содержит `'use client'` и рендерит Web Component host; внутренний Shadow DOM создаётся после загрузки browser runtime. В Next.js App Router размещайте Viewer внутри отдельной Client Component boundary и используйте только на debug-маршруте или во внутреннем инструменте.
## Generated-файлы, Git и CI
Все modes, кроме bare `standalone`, создают локальный `.gitignore` для:
```text
/.svg-sprite/
```
Локальный `.gitignore` следует один раз добавить в репозиторий. Он исключает остальные generated-файлы, поэтому генерацию нужно запускать перед командами, которые импортируют sprite-модуль:
```json
{
"scripts": {
"sprites": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/app-icons/svg-sprite.config.ts",
"predev": "npm run sprites",
"prebuild": "npm run sprites",
"pretypecheck": "npm run sprites"
}
}
```
CI должен выполнять generation script до сборки или проверки типов. Для воспроизводимости замените `latest` на точную версию. Локальная установка package не нужна, если CI не использует Viewer, package-типы config или программный API.
Bare `standalone` не создаёт и не изменяет `.gitignore`: приложение само решает, коммитить или игнорировать его `.svg-sprite/`. В остальных modes генератор не перезапишет пользовательский `.gitignore`. Он также откажется перезаписывать пользовательский файл внутри `.svg-sprite`. Корневой `index.ts` остаётся пользовательским и может переэкспортировать generated API.
## Диагностика
- Нет `.svg-sprite/index.js`: запустите generation script до импорта generated-модуля.
- Не найден источник: передайте существующий config-файл или каталог sprite-модуля.
- Не указан mode: добавьте `mode` в config либо передайте `--mode`.
- Иконка отсутствует в типе: проверьте `input`, расширение `.svg`, glob-исключения и необходимость `**/*.svg` для вложенных папок.
- Конфликт имени: два разных SVG имеют одинаковый basename; переименуйте один файл.
- `Refusing to overwrite a user file`: в managed-пути находится файл без generated marker.
- Иконка не меняет цвет: используйте `<svg><use>` или generated-компонент и проверьте `replaceColors`.
- Webpack выдаёт неверный URL: проверьте Asset Modules, `output.publicPath` и SVG loaders.
- Static sprite возвращает 404: проверьте post-generation copy или server alias и не передавайте filesystem `spritePath` в HTML.
- Viewer не видит спрайт: проверьте путь к `.svg-sprite/svg-sprite.manifest.js` и выполните генерацию до запуска приложения.
- Build и mode не совпадают: используйте target, соответствующий фактическому сборщику.
Для собственного orchestration и низкоуровневой компиляции смотрите [Программный API](programmatic-api.md).

View File

@@ -3,6 +3,6 @@ import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/turbopack',
name: 'icons',
inputFiles: ['../../../../fixtures/icons/check.svg'],
input: '../../../../fixtures/icons/check.svg',
generatedNotice: false,
})

View File

@@ -3,6 +3,6 @@ import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/webpack',
name: 'icons',
inputFiles: ['../../../../fixtures/icons/check.svg'],
input: '../../../../fixtures/icons/check.svg',
generatedNotice: false,
})

View File

@@ -3,6 +3,6 @@ import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@pages/turbopack',
name: 'icons',
inputFiles: ['../../../../fixtures/icons/check.svg'],
input: '../../../../fixtures/icons/check.svg',
generatedNotice: false,
})

View File

@@ -3,6 +3,6 @@ import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@pages/webpack',
name: 'icons',
inputFiles: ['../../../../fixtures/icons/check.svg'],
input: '../../../../fixtures/icons/check.svg',
generatedNotice: false,
})

View File

@@ -3,6 +3,6 @@ import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@vite',
name: 'icons',
inputFiles: ['../../../../fixtures/icons/check.svg'],
input: '../../../../fixtures/icons/check.svg',
generatedNotice: false,
})

View File

@@ -3,6 +3,6 @@ import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@webpack',
name: 'icons',
inputFiles: ['../../../../fixtures/icons/check.svg'],
input: '../../../../fixtures/icons/check.svg',
generatedNotice: false,
})

View File

@@ -0,0 +1,9 @@
import type { IconsIconElement } from './sprite'
const icon = document.createElement('icons-icon')
const typedIcon: IconsIconElement = icon
typedIcon.icon = 'check'
// @ts-expect-error Generated icon names form a literal union.
typedIcon.icon = 'missing'

View File

@@ -1,7 +1,7 @@
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
import spriteManifest from './sprite/.svg-sprite/svg-sprite.manifest.js'
import { getIconsIconHref, iconsSpriteUrl } from './sprite'
import { defineIconsIconElement, iconsSpriteUrl } from './sprite'
import './style.css'
const check = spriteManifest.icons.find((icon) => icon.name === 'check')
@@ -9,16 +9,17 @@ if (!check || spriteManifest.spriteUrl !== iconsSpriteUrl) {
throw new Error('Generated Vite facade and manifest disagree.')
}
defineIconsIconElement()
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<h1>Standalone + Vite</h1>
<svg
<icons-icon
data-testid="icon"
data-app="standalone-vite"
viewBox="${check.viewBox ?? '0 0 24 24'}"
icon="check"
role="img"
aria-label="Check icon"
>
<use href="${getIconsIconHref('check')}"></use>
</svg>
></icons-icon>
<gromlab-sprite-viewer></gromlab-sprite-viewer>
`

View File

@@ -1,7 +1,9 @@
export {
defineIconsIconElement,
getIconsIconHref,
iconsIconIds,
iconsIconNames,
iconsIconTagName,
iconsSpriteUrl,
} from './.svg-sprite/index.js'
export type { IconsIconName } from './.svg-sprite/index.js'
export type { IconsIconElement, IconsIconName } from './.svg-sprite/index.js'

View File

@@ -3,6 +3,6 @@ import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'standalone@vite',
name: 'icons',
inputFiles: ['../../../../fixtures/icons/check.svg'],
input: '../../../../fixtures/icons/check.svg',
generatedNotice: false,
})

View File

@@ -0,0 +1,9 @@
import type { IconsIconElement } from './sprite'
const icon = document.createElement('icons-icon')
const typedIcon: IconsIconElement = icon
typedIcon.icon = 'check'
// @ts-expect-error Generated icon names form a literal union.
typedIcon.icon = 'missing'

View File

@@ -1,24 +1,25 @@
import '@gromlab/svg-sprites/viewer/element'
import type { SpriteViewerElement } from '@gromlab/svg-sprites/viewer'
import spriteManifest from './sprite/.svg-sprite/svg-sprite.manifest.js'
import { getIconsIconHref, iconsSpriteUrl } from './sprite'
import { defineIconsIconElement, iconsSpriteUrl } from './sprite'
const check = spriteManifest.icons.find((icon) => icon.name === 'check')
if (!check || spriteManifest.spriteUrl !== iconsSpriteUrl) {
throw new Error('Generated Webpack facade and manifest disagree.')
}
defineIconsIconElement()
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<h1>Standalone + Webpack</h1>
<svg
<icons-icon
data-testid="icon"
data-app="standalone-webpack"
viewBox="${check.viewBox ?? '0 0 24 24'}"
icon="check"
role="img"
aria-label="Check icon"
style="width:64px;height:64px;color:#16a34a;--icon-color-1:#16a34a"
>
<use href="${getIconsIconHref('check')}"></use>
</svg>
></icons-icon>
<gromlab-sprite-viewer></gromlab-sprite-viewer>
`

View File

@@ -1,7 +1,9 @@
export {
defineIconsIconElement,
getIconsIconHref,
iconsIconIds,
iconsIconNames,
iconsIconTagName,
iconsSpriteUrl,
} from './.svg-sprite/index.js'
export type { IconsIconName } from './.svg-sprite/index.js'
export type { IconsIconElement, IconsIconName } from './.svg-sprite/index.js'

View File

@@ -3,6 +3,6 @@ import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'standalone@webpack',
name: 'icons',
inputFiles: ['../../../../fixtures/icons/check.svg'],
input: '../../../../fixtures/icons/check.svg',
generatedNotice: false,
})

View File

@@ -1,2 +0,0 @@
# @generated by @gromlab/svg-sprites. Do not edit.
/.svg-sprite/

View File

@@ -1,6 +1,6 @@
{
"mode": "standalone",
"name": "app-icons",
"inputFiles": ["../../../../fixtures/icons/check.svg"],
"input": "../../../../fixtures/icons/check.svg",
"generatedNotice": false
}

View File

@@ -23,7 +23,8 @@
"dependencies": {
"colorette": "^2.0.20",
"jiti": "^2.6.1",
"svg-sprite": "^2.0.4"
"svg-sprite": "^2.0.4",
"tinyglobby": "^0.2.17"
},
"bin": {
"svg-sprites": "dist/cli.js"

View File

@@ -111,6 +111,12 @@ for (const app of apps) {
await expect(icon).toBeVisible()
await expect(icon).toHaveAttribute('data-app', app.id)
if (app.id === 'standalone-vite' || app.id === 'standalone-webpack') {
expect(await icon.evaluate((element) => element.tagName.toLowerCase())).toBe('icons-icon')
expect(await icon.evaluate((element) => element.shadowRoot !== null)).toBe(true)
await expect(icon.locator('svg')).toHaveAttribute('viewBox', '0 0 24 24')
}
const href = await icon.locator('use').getAttribute('href')
expect(href).toBeTruthy()
expect(href).not.toMatch(/^(?:blob|data|file):/)

12
package-lock.json generated
View File

@@ -11,7 +11,8 @@
"dependencies": {
"colorette": "^2.0.20",
"jiti": "^2.6.1",
"svg-sprite": "^2.0.4"
"svg-sprite": "^2.0.4",
"tinyglobby": "^0.2.17"
},
"bin": {
"svg-sprites": "dist/cli.js"
@@ -1895,7 +1896,6 @@
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
@@ -2443,7 +2443,6 @@
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -2916,10 +2915,9 @@
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"dev": true,
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",

View File

@@ -49,8 +49,8 @@
"dist/chunk-*.js",
"dist/chunk-*.js.map",
"README_RU.md",
"docs/en/*.md",
"docs/ru/*.md",
"docs/en",
"docs/ru",
"LICENSE",
"THIRD_PARTY_NOTICES.md"
],
@@ -97,7 +97,8 @@
"dependencies": {
"colorette": "^2.0.20",
"jiti": "^2.6.1",
"svg-sprite": "^2.0.4"
"svg-sprite": "^2.0.4",
"tinyglobby": "^0.2.17"
},
"peerDependencies": {
"react": ">=18 <20"

View File

@@ -1,6 +1,6 @@
# AI skills
Исходники английского и русского скиллов находятся в `skills/svg-sprites/src/{en,ru}/`. Готовые переносимые артефакты генерируются в `skills/artifacts/`, игнорируются Git и упаковываются в ZIP во время release workflow.
Исходники обязательного контекста английского и русского skills находятся в `skills/svg-sprites/src/{en,ru}/`. Канонические exact-mode guides находятся в `docs/{en,ru}/guides/` и копируются в соответствующий skill без изменения. Готовые переносимые артефакты генерируются в `skills/artifacts/`, игнорируются Git и упаковываются в ZIP во время release workflow.
Обе языковые версии имеют одинаковую структуру:
@@ -17,15 +17,11 @@ src/<language>/
│ ├── 60-verification.md
│ └── 70-diagnostics.md
└── references/
├── react-vite.md
├── react-webpack.md
├── next-app.md
├── next-pages.md
├── programmatic-api.md
└── complex-svg.md
```
`core/` содержит обязательные знания, раскрываемые прямо в итоговый `SKILL.md`. `references/` содержит самостоятельные инструкции для агента по конкретным стекам и редким сценариям. Пользовательские `README*.md` и `docs/{en,ru}/*.md` дополнительно копируются в `references/upstream/` как вторичный источник полного публичного API.
`core/` содержит обязательные знания, раскрываемые прямо в итоговый `SKILL.md`. Локальный `references/` содержит только agent-specific материалы. Девять файлов из `docs/<language>/guides/` копируются в `references/guides/`; английский artifact получает только английские guides, русский только русские. Второй набор mode guides и каталог `references/upstream/` не создаются.
## Композиция Markdown

View File

@@ -1,12 +1,20 @@
const agentReferences = [
'react-vite.md',
'react-webpack.md',
'next-app.md',
'next-pages.md',
'programmatic-api.md',
'complex-svg.md',
]
const guideFiles = [
'standalone.md',
'standalone-vite.md',
'standalone-webpack.md',
'react-vite.md',
'react-webpack.md',
'next-app-turbopack.md',
'next-app-webpack.md',
'next-pages-turbopack.md',
'next-pages-webpack.md',
]
function documents(language) {
return [
{ entry: `src/${language}/SKILL.md`, to: 'SKILL.md', skill: true },
@@ -17,36 +25,28 @@ function documents(language) {
]
}
const upstream = [
{ from: '../../README.md', to: 'references/upstream/README.md' },
{ from: '../../README_RU.md', to: 'references/upstream/README_RU.md' },
{
fromDirectory: '../../docs/en',
toDirectory: 'references/upstream/docs/en',
extensions: ['.md'],
},
{
fromDirectory: '../../docs/ru',
toDirectory: 'references/upstream/docs/ru',
extensions: ['.md'],
},
]
function guides(language) {
return guideFiles.map((file) => ({
from: `../../docs/${language}/guides/${file}`,
to: `references/guides/${file}`,
}))
}
export default [
{
name: 'svg-sprites',
description: 'Use only when configuring, generating, or troubleshooting @gromlab/svg-sprites. Triggers: @gromlab/svg-sprites, svg-sprite.config.ts, defineSpriteConfig, generateSprite, standalone, standalone@vite, standalone@webpack, react@vite, react@webpack, next@app, next@pages, inputFiles, SpriteViewer, or --icon-color-N. Do NOT use for custom SVG sprites, favicons, raster images, icon fonts, choosing an icon set, or inline SVG without this package.',
description: 'Use only when configuring, generating, or troubleshooting @gromlab/svg-sprites. Triggers: @gromlab/svg-sprites, svg-sprite.config.ts, defineSpriteConfig, generateSprite, standalone, standalone@vite, standalone@webpack, react@vite, react@webpack, next@app, next@pages, SpriteConfig.input, --input, SpriteViewer, or --icon-color-N. Do NOT use for custom SVG sprites, favicons, raster images, icon fonts, choosing an icon set, or inline SVG without this package.',
output: '../artifacts/svg-sprites',
maxSkillBytes: 48_000,
documents: documents('en'),
copy: upstream,
copy: guides('en'),
},
{
name: 'svg-sprites-ru',
description: 'Используй только при настройке, изменении или диагностике @gromlab/svg-sprites. Триггеры: @gromlab/svg-sprites, svg-sprite.config.ts, defineSpriteConfig, generateSprite, standalone, standalone@vite, standalone@webpack, react@vite, react@webpack, next@app, next@pages, inputFiles, SpriteViewer и --icon-color-N. НЕ используй для самописных SVG-спрайтов, inline SVG, favicon, растровых изображений, icon fonts или выбора библиотеки иконок.',
description: 'Используй только при настройке, изменении или диагностике @gromlab/svg-sprites. Триггеры: @gromlab/svg-sprites, svg-sprite.config.ts, defineSpriteConfig, generateSprite, standalone, standalone@vite, standalone@webpack, react@vite, react@webpack, next@app, next@pages, SpriteConfig.input, --input, SpriteViewer и --icon-color-N. НЕ используй для самописных SVG-спрайтов, inline SVG, favicon, растровых изображений, icon fonts или выбора библиотеки иконок.',
output: '../artifacts/svg-sprites-ru',
maxSkillBytes: 48_000,
documents: documents('ru'),
copy: upstream,
copy: guides('ru'),
},
]

View File

@@ -11,8 +11,9 @@
## References to open as needed
- For React + Vite, open [React + Vite](./references/react-vite.md); for React with a custom Webpack 5 setup, open [React + Webpack](./references/react-webpack.md).
- For Next.js, open the guide for the [App Router](./references/next-app.md) or [Pages Router](./references/next-pages.md), then select the section for the bundler actually in use.
- For static publishing, open [bare standalone](./references/guides/standalone.md). For vanilla bundler apps, open [standalone + Vite](./references/guides/standalone-vite.md) or [standalone + Webpack](./references/guides/standalone-webpack.md).
- For React, open the exact [Vite](./references/guides/react-vite.md) or [Webpack](./references/guides/react-webpack.md) guide.
- For the Next.js App Router, open the exact [Turbopack](./references/guides/next-app-turbopack.md) or [Webpack](./references/guides/next-app-webpack.md) guide.
- For the Next.js Pages Router, open the exact [Turbopack](./references/guides/next-pages-turbopack.md) or [Webpack](./references/guides/next-pages-webpack.md) guide.
- To invoke the generator from Node.js, open the [programmatic API](./references/programmatic-api.md).
- For gradients, filters, `url(#...)`, unusual colors, and `viewBox` issues, open [complex SVGs](./references/complex-svg.md).
- Use the complete user documentation as a secondary source: [package README](./references/upstream/README.md).

View File

@@ -1,15 +1,16 @@
## What the package does
`@gromlab/svg-sprites` is a CLI generator that builds SVG sprites from user-provided SVG files. The package does not include its own icon set: it compiles the project's SVGs into an external sprite asset, supports standalone projects, and creates typed components for React/Next.js.
`@gromlab/svg-sprites` is a CLI generator that builds SVG sprites from user-provided SVG files. The package does not include its own icon set: it compiles the project's SVGs into an external sprite asset, creates a native typed Web Component for standalone bundler modes, and creates a React component for React/Next.js.
The package supports multiple independent sprites in one project. Each explicitly selected config file or config-less directory describes one sprite and gets its own:
- SVG asset;
- mode-specific manifest data;
- icon name types and `.svg-sprite/index.js` for bundler modes;
- a native Web Component with an explicit registration function for `standalone@vite`/`standalone@webpack`;
- a React component only for React/Next.js;
- a deployment-neutral JSON manifest without a public URL for bare `standalone`.
The project determines how many sprite directories exist and where they live. For example, `name: 'file-manager'` produces `FileManagerIcon`, while another directory with `name: 'navigation'` produces a separate `NavigationIcon`. The names `FileManagerIcon` and `fileManagerIconNames` used below are examples of the API for one possible sprite, not fixed package exports.
Generated production runtime does not import `@gromlab/svg-sprites` at runtime. Install the package as a development dependency so configuration helpers and the local CLI use the version recorded in the project's lockfile.
Generated production runtime and declarations do not import `@gromlab/svg-sprites`. Generation can run through a pinned `npx --package` command without adding the package to the project. Install it as a development dependency only for the Viewer, package-provided config types, or the programmatic API.

View File

@@ -21,10 +21,10 @@ The CLI accepts exactly one path. A `.ts`, `.js`, or `.json` file loads that exa
```json
{
"scripts": {
"sprite:<name>": "svg-sprites <path-to-config>",
"sprite:<name>:cli": "svg-sprites --mode <mode-key> <sprite-directory>"
"sprite:<name>": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites <path-to-config>",
"sprite:<name>:cli": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites --mode <mode-key> <sprite-directory>"
}
}
```
Do not use incomplete `react`, `next@app`, `next@pages`, or `standalone@` keys, or the removed `legacy` mode. Use bare `standalone` only when the application publishes the SVG itself; use the complete Vite/Webpack key otherwise. Create one command per config file or directory when the project has multiple sprites.
Generation through `npx` does not add the package to the project. Pin an exact package version instead of `latest` in CI. Do not use incomplete `react`, `next@app`, `next@pages`, or `standalone@` keys, or the removed `legacy` mode. Use bare `standalone` only when the application publishes the SVG itself; use the complete Vite/Webpack key otherwise. Create one command per config file or directory when the project has multiple sprites.

View File

@@ -9,14 +9,14 @@ Establish the project's actual contract before making changes:
5. For a new sprite, choose a target directory without imposing a particular application layer or architecture.
6. Check TypeScript and alias settings. Package subpath exports require TypeScript 5+ with `moduleResolution: 'bundler'`, `'node16'`, or `'nodenext'`.
All config paths are relative to the directory containing the explicitly selected config file; in config-less mode they are relative to the supplied directory:
All input paths are relative to the directory containing the explicitly selected config file; in config-less mode they are relative to the supplied directory. Inspect `input` using this single contract:
- `inputFolder` defaults to `./icons`;
- `inputFiles` contains additional relative paths to individual SVGs and is merged with `inputFolder`;
- the same absolute file is deduplicated, but different files with the same icon basename cause an error;
- scanning `inputFolder` is shallow: only immediate files ending in `.svg` are read, and nested directories are not traversed;
- an explicitly configured `inputFolder` that does not exist is an error;
- if `inputFolder` is omitted, `./icons` does not exist, and `inputFiles` is non-empty, generation uses only `inputFiles`;
- an empty final input set, a missing file, or a path that does not point to an `.svg` file is an error.
- `input?: string | string[]` defaults to `./icons`;
- each string is a folder, an exact SVG file, or a glob;
- a folder is scanned shallowly; nested files are included only by an explicit recursive glob such as `./icons/**/*.svg`;
- an array combines positive sources, while an item prefixed with `!` excludes its matches from the combined set;
- every positive source must resolve to at least one SVG, so a missing or empty folder, an unmatched glob, a missing file, or a non-SVG exact file is an error;
- resolved files are deduplicated and sorted deterministically;
- different files with the same basename are a conflict, even when they came from different sources.
Do not copy a shared SVG into several folders: add its relative path to `inputFiles` in every sprite that needs it. If a recursive structure is required, list the files explicitly or reorganize the sources; the generator does not perform recursive scanning.
Do not copy a shared SVG into several folders: add its exact path or a suitable glob to `input` in every sprite that needs it. Use `**/*.svg` only when recursive inclusion is intentional.

View File

@@ -12,27 +12,21 @@ src/ui/file-manager/svg-sprite/
One `svg-sprite.config.ts` creates one independent sprite. For multiple sets, choose multiple directories and assign each a unique `name`.
Install the package as a development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Use the configuration helper for autocomplete and type checking:
The generator does not need to be installed in the project. Start with a plain
config that has no package import:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
export default {
mode: 'react@vite',
name: 'file-manager',
description: 'File manager icons',
inputFolder: './icons',
inputFiles: ['../../shared/icons/close.svg'],
})
input: ['./icons', '../../shared/icons/close.svg'],
}
```
The object contract is the same for React and Next.js; only the full `mode` differs.
`input` accepts one folder, exact SVG file, or glob, or an array that combines them. Prefix an array item with `!` to exclude matches. Folders are shallow; use an explicit `**/*.svg` glob for recursion. Omit `input` to use `./icons`. Every path is resolved from the config directory, and every positive source must match at least one SVG.
The object contract is the same for React and Next.js; only the full `mode` differs. Install the package only for the optional Viewer, programmatic API, or package-provided config typing. Exact guides also provide a local copy-paste config type for projects that remain package-free.
`name` must begin with an ASCII letter and use kebab-case. The example `file-manager` produces `FileManagerIcon`, `FileManagerIconName`, and `fileManagerIconNames`. Another sprite gets its own names. If `name` is omitted, the generator derives it from the directory.
@@ -41,7 +35,7 @@ Add a separate command with the selected mode key and one path:
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"sprite:file-manager": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"sprites": "npm run sprite:file-manager"
}
}
@@ -49,9 +43,18 @@ Add a separate command with the selected mode key and one path:
For Next.js, set the full key in the config, for example `next@app/turbopack`. For multiple sprites, add one `sprite:<name>` command per config file and invoke them sequentially from `sprites`.
Generated files in `.svg-sprite` are excluded from Git by default, so run `sprites` before any process that needs the component, types, or asset. If the project imports the sprite-module root, create a user-owned `index.ts` with `export * from './.svg-sprite'`. Add generation to `predev`, `prebuild`, and, when a `typecheck` script exists, `pretypecheck`.
To select sources from the CLI instead, repeat `--input <path-or-glob>`; the values form the same array contract, including `!` exclusions:
If a lifecycle script is missing, create it. If it already exists, preserve its command and append generation with `&&`; for example, change `"prebuild": "npm run lint"` to `"prebuild": "npm run lint && npm run sprites"`. Never replace an existing `pre*` script with generation alone, and never create a duplicate JSON key.
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts \
--input ./icons \
--input '../../shared/icons/**/*.svg' \
--input '!../../shared/icons/legacy-*.svg'
```
Generated files in `.svg-sprite` are excluded from Git by default, so run `sprites` before any process that needs the component, types, or asset. If the project imports the sprite-module root, create a user-owned `index.ts` with `export * from './.svg-sprite'`. Generated declarations are self-contained and do not import the generator package.
Run generation either from `predev`/`prebuild`/`pretypecheck` hooks or explicitly inside the corresponding commands. Do not use both forms for the same command, or generation runs twice. Preserve existing script commands and never create a duplicate JSON key.
Run the first generation manually:

View File

@@ -9,7 +9,6 @@ svg-sprite/
├── index.ts # optional user-owned barrel
├── .gitignore # managed by the generator
└── .svg-sprite/
├── state.json # ownership registry and contract version
├── index.js
├── index.d.ts
├── icon-data.js
@@ -25,23 +24,25 @@ svg-sprite/
Standalone does not create `react/`. Bare `standalone` generates `sprite.svg` and
`svg-sprite.manifest.json`; `standalone@vite`/`standalone@webpack` additionally
generate `index.*`, `icon-data.*`, and a resolved manifest.
generate `index.*`, `icon-data.*`, and a resolved manifest. Their `index.*` also
contains a native generated Web Component; bare `standalone` gets no JavaScript runtime
and does not create or modify `.gitignore`.
Edit the source SVGs, selected config, and user-owned `index.ts`. Do not manually change `.gitignore` or anything in `.svg-sprite`: the next generation will overwrite them. To import from the sprite-module root, create a barrel:
Edit the source SVGs, selected config, and user-owned `index.ts`. Do not manually change anything in `.svg-sprite`: the next generation will overwrite it. In modes other than bare `standalone`, the generated `.gitignore` is also managed and must not be edited manually. To import from the sprite-module root, create a barrel:
```ts
export * from './.svg-sprite'
```
The generator owns `.gitignore` and files inside `.svg-sprite`. The `state.json` registry allows stale generated files to be removed, but the writer refuses to overwrite or delete any file without a generated marker. Do not remove the marker, bypass the refusal, or replace generated paths with symlinks: move the user-owned file or choose a different directory.
The generator owns the complete `.svg-sprite` directory and replaces it on every run. Never put user files inside it. The generator also owns `.gitignore` when the selected mode creates it; bare `standalone` leaves an existing `.gitignore` untouched. Generated paths must not contain symlinks.
The internal `index.js` exports the component from `react/react-component.js` and the readonly name array; the adjacent `index.d.ts` adds props/style types and the icon-name union. The manifest contains the mode, URL, target, icon list, and icon metadata for debug tools and is not imported by the production component.
The internal `index.js` exports the component from `react/react-component.js` and the readonly name array; the adjacent `index.d.ts` adds props/style types and the icon-name union. Bundler-mode manifest declarations define their types locally and do not import the generator package. The manifest contains the mode, URL, target, icon list, and icon metadata for debug tools and is not imported by the production component.
The sprite remains a separate content-hashed asset; SVG path data is not embedded in JavaScript:
- `react@vite` generates a static `sprite.svg?no-inline` import, preventing Vite from inlining it;
- `standalone@vite` uses the same Vite asset mechanism but exports an href helper without React;
- `standalone@webpack` uses Webpack Asset Modules without React;
- `standalone@vite` uses the same Vite asset mechanism and exports an href helper plus a native Web Component without React;
- `standalone@webpack` uses Webpack Asset Modules and exports the same mode-local Web Component without React;
- React Webpack 5 and all Next modes generate `new URL('./sprite.svg', import.meta.url).href`, which must be processed by the selected bundler's Asset Modules;
- a custom Webpack SVG loader must not intercept the generated `sprite.svg`;
- in Next mode, the generated component does not contain `'use client'` and works in Server Components, SSR, and SSG; do not add a client boundary solely for an icon;

View File

@@ -1,6 +1,20 @@
## Usage, accessibility, and colors
The component name depends on the specific sprite's `name`. These examples use `name: 'file-manager'`, so the generated component is called `FileManagerIcon`. For `name: 'navigation'`, use the generated `NavigationIcon`.
The component name depends on the specific sprite's `name`. In `standalone@vite` and `standalone@webpack`, `name: 'file-manager'` creates the `<file-manager-icon>` tag and the `defineFileManagerIconElement()` function:
```ts
import { defineFileManagerIconElement } from './svg-sprite'
defineFileManagerIconElement()
```
```html
<file-manager-icon icon="folder" aria-hidden="true"></file-manager-icon>
```
The native element has no runtime dependencies, selects the generated ID and `viewBox`, obtains the URL through the bundler, and renders `<svg><use>` in Shadow DOM. Its `icon` property is typed with the exact name union, while plain HTML attribute values are validated only at runtime. It defaults to `1em × 1em`; resize the host with CSS. Bare `standalone` does not generate a Web Component.
In React/Next.js, the same `name: 'file-manager'` creates the `FileManagerIcon` React component. For `name: 'navigation'`, use the generated `NavigationIcon`.
Import the component from the root of its sprite directory. `width` and `height` are optional: ordinary CSS classes can control the size.
@@ -64,10 +78,10 @@ The `removeSize`, `replaceColors`, and `addTransition` transforms are enabled by
Automatic replacement targets `fill`/`stroke` attributes and inline `style`. The values `none`, `transparent`, `inherit`, `unset`, and `initial` are not replaced. Check CSS classes and external stylesheets, gradients, patterns, filters, and `url(#...)` against the actual output. Page variables work through `<svg><use>`, but do not cross into an external document loaded through `<img>` or `background-image`; a CSS mask preserves only a monochrome silhouette.
`SpriteViewer` is optional. Import it from `@gromlab/svg-sprites/react` only on a debug route:
`SpriteViewer` is optional. Install `@gromlab/svg-sprites` as a development dependency only when the project needs the Viewer, then import it from `@gromlab/svg-sprites/react` on a debug route:
- in Vite, pass the result of a string-literal `import.meta.glob('/src/**/svg-sprite/.svg-sprite/svg-sprite.manifest.js')`;
- in Webpack, pass an array of static `() => import('.../manifest')` loaders;
- in Webpack, pass an array of static `() => import('.../.svg-sprite/svg-sprite.manifest.js')` loaders;
- in Next.js, use the same static loaders, and for the App Router put the Viewer in a separate file with `'use client'`.
The Viewer accepts manifests/loaders and provides search, themes, colors, and examples, but production components do not depend on it. Import it from the package already installed as a development dependency.
The Viewer accepts manifests/loaders and provides search, themes, colors, and examples, but production components do not depend on it.

View File

@@ -3,7 +3,7 @@
After changing a config or SVG, perform these required quick checks:
1. Run the exact sprite command, for example `npm run sprite:file-manager`; it must exit with code `0` and report the name, icon count, mode, and `.svg-sprite` directory.
2. Confirm that `.svg-sprite/index.js`, `.svg-sprite/index.d.ts`, `sprite.svg`, the `icon-data.js`/`.d.ts` and manifest `.js`/`.d.ts` pairs, `react/react-component.js`, its `.d.ts` and CSS Module, and `state.json` exist.
2. Confirm that `.svg-sprite/index.js`, `.svg-sprite/index.d.ts`, `sprite.svg`, the `icon-data.js`/`.d.ts` and manifest `.js`/`.d.ts` pairs, `react/react-component.js`, its `.d.ts` and CSS Module exist.
3. Confirm that the new icon appears in the readonly name array and is accepted by the `icon` prop.
4. Run the project's existing type check, for example `npm run typecheck`.
5. Check that `target` in `.svg-sprite/svg-sprite.manifest.js` matches the selected mode key; the generated asset expression must use `?no-inline` for Vite and `new URL(...)` for Webpack/Next.

View File

@@ -8,9 +8,10 @@ Match the symptom to the relevant check and fix the root cause:
| `Expected one config file or module directory` | Multiple paths were passed | Create one command per sprite and combine the scripts. |
| `Sprite mode is required` | Mode is absent from both config and CLI | Add `mode` to the object or pass the full `--mode`. |
| `Unsupported sprite config extension` | The supplied file is not `.ts`, `.js`, or `.json` | Use a supported config format. |
| `Input directory does not exist` | The relative path is wrong or an explicitly configured folder is missing | Resolve it from the config directory; create the folder or correct `inputFolder`. |
| Icons from a subdirectory are missing | Recursive scanning was assumed | Move the SVG to the top level of `inputFolder` or list it in `inputFiles`. |
| `SVG file does not exist`, `File is not an SVG`, or an empty input set | Invalid `inputFiles`, extension, or sources | Correct the path/extension and provide at least one input SVG. |
| A positive input source has no SVG matches | A folder is missing or empty, a glob matches nothing, or an exact path is missing or not an SVG | Resolve the source from the config directory and correct `input`; every positive item must produce at least one SVG. |
| Icons from a subdirectory are missing | A folder source was expected to scan recursively | Use an explicit glob such as `./icons/**/*.svg`; folders are shallow. |
| An excluded icon is still present | The exclusion lacks a leading `!`, is not in the `input` array, or is relative to the wrong directory | Add a matching `!` item and resolve it from the config directory. |
| CLI source selection is incomplete | Multiple sources were packed into one `--input` value or an option was omitted | Repeat `--input <path-or-glob>` once per source or exclusion. |
| Icon name or SVG ID collision | Two different files have the same basename, or a hash ID collides with a name | Rename one source SVG; do not select a file implicitly. |
| `Refusing to overwrite/delete a user file` | A user file occupies a managed path or lost its marker | Do not bypass the protection: move the file or choose another sprite directory and regenerate. |
| Missing `.svg-sprite/index.js` or name absent from autocomplete | Generation did not run, the user barrel does not export `.svg-sprite`, or the type server cached an old module | Run the sprite command, check `export * from './.svg-sprite'`, then typecheck; restart the TypeScript server if necessary. |

View File

@@ -131,7 +131,7 @@ It is preserved as the fragment ID. Other names, such as `folder open.svg` or `2
Do not manually construct `#folder open`. Use the generated component or `.svg-sprite/svg-sprite.manifest.js`, which records both `name` and the actual `id`.
Different files with the same basename are forbidden, even from different directories. Rename one source meaningfully; `inputFiles` order does not select a winner.
Different files with the same basename are forbidden, even from different directories. Rename one source meaningfully; source order or overlap never selects a winner.
## Rendering method
@@ -173,4 +173,4 @@ External stack-fragment support and paint-server behavior can vary across browse
- A manual fragment fails for a name containing spaces: use the ID from the manifest.
- One complex icon requires different transforms: move it to a separate sprite; per-icon transform config is not supported.
Target-specific execution and verification are documented in [react-vite.md](react-vite.md), [react-webpack.md](react-webpack.md), [next-app.md](next-app.md), and [next-pages.md](next-pages.md).
Target-specific execution and verification are documented in the exact-mode files under [guides](guides/standalone.md).

View File

@@ -1,149 +0,0 @@
# Next.js App Router: operational reference
## When to use this reference
Use this document when the application uses `app/` or `src/app/` and needs a generated component for Server Components, SSR, or SSG. First determine the actual bundler: Turbopack and Webpack use different targets. For `pages/`, use [next-pages.md](next-pages.md).
## Target matrix
| Bundler | CLI mode |
|---|---|
| Turbopack | `next@app/turbopack` |
| Webpack 5 | `next@app/webpack` |
Do not infer the target only from the presence of `next.config.*`. Check the actual flags in `dev`/`build`. The generator mode and verification-build bundler must agree.
## Prepare the sprite directory
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Install the package as a development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/turbopack',
name: 'file-manager',
description: 'File manager icons',
inputFolder: './icons',
inputFiles: ['../../../../shared/icons/check.svg'],
})
```
- The project chooses the directory for each specific sprite; it does not have to be a module/feature directory. Each config describes one of potentially many application sprites.
- `svg-sprite.config.ts` is the recommended name; the CLI accepts an explicitly selected `.ts`, `.js`, or `.json` file with any name.
- Paths are relative to its directory; `inputFolder` scanning is shallow.
- The local folder and `inputFiles` are merged. Equal basenames from different files are forbidden.
- If no name is configured, it is derived from the directory; for a directory named `svg-sprite`, the parent directory name is used. An explicit `name` is more stable when files move.
- `name` must use kebab-case and begin with a letter.
- `FileManagerIcon` below is only an example generated name for `name: 'file-manager'`.
- The Next preset always generates `stack` and includes a root sprite `viewBox`; this is not a user setting.
## Generation
Example scripts for Turbopack:
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
For Webpack, replace only the complete mode with `next@app/webpack`. Do not run both targets sequentially for one directory: the second generation overwrites the first target's files. Run `npm run sprite:file-manager` for the first generation.
## Server Component
The generated production component does not contain `'use client'`. Import it directly into a server `page.tsx` or `layout.tsx`:
```tsx
import { FileManagerIcon } from '@/ui/file-manager/svg-sprite'
export default function Page() {
return (
<main>
<FileManagerIcon icon="folder" className="icon" aria-label="Folder" />
</main>
)
}
```
`width` and `height` are optional in JSX; set the size with a CSS class or `wrapped`. Do not add a Client Component boundary solely for the icon. The generated module builds the asset URL with static `new URL('./sprite.svg', import.meta.url).href`; the same mechanism is used during SSR and in the browser.
Do not move the SVG into `public`. The generator rewrites `.svg-sprite` and `.gitignore`; the root `index.ts` is application-owned and may contain `export * from './.svg-sprite'`.
## SpriteViewer
The Viewer is interactive and imported from a client-only entry. Create a separate Client Component page or child component:
```tsx
'use client'
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('@/ui/file-manager/svg-sprite/manifest'),
() => import('@/ui/navigation/svg-sprite/manifest'),
]
export default function SpritesPage() {
return <SpriteViewer sources={sources} title="Project icons" />
}
```
The `import()` paths must be string literals. Keep production icon imports outside `@gromlab/svg-sprites/react`: the package React entry is only for the Viewer and contains `'use client'`.
## Verification
Start with generation and typechecking:
```bash
npm run sprite:file-manager
npm run typecheck
```
If the target or Next build/deployment pipeline changed, or a runtime issue is being diagnosed, run the project's production build configured for the selected bundler:
```bash
npm run build
```
After the required generation and typecheck, verify that:
- `.svg-sprite/svg-sprite.manifest.js` contains the exact target `next@app/turbopack` or `next@app/webpack`;
- the server page compiles without adding `'use client'`.
During a conditional production/runtime check, also verify that:
- HTML contains an `href` to `.svg#id`, and the asset URL is available after `next start`;
- the URL does not use `data:`, `file:`, or `blob:`;
- SSR markup and the hydrated page refer to the same asset;
- complex SVGs were examined according to [complex-svg.md](complex-svg.md); claim visual and accessibility results only when the corresponding tools are available.
## Common failures
- `Next.js mode requires a router and bundler`: `--mode next` is invalid; specify the full mode.
- Build passes with one bundler but the runtime asset breaks with another: regenerate for the target actually used by the build.
- Webpack was selected, but the build used Turbopack: use the project's Webpack build command and `next@app/webpack`.
- Viewer causes a Server Component error: the Viewer file needs `'use client'`; the generated icon component does not.
- Config not found: pass the full path to an existing `.ts`, `.js`, or `.json` config file.
- Two CI jobs generate different targets in one checkout: separate their directories or ensure each job uses one consistent target.
- Asset is missing under `basePath`/CDN: inspect Next asset handling and deployment configuration; do not replace the generated URL manually.
- Package subpath type error: use TypeScript 5+ with `moduleResolution: "bundler"`, `"node16"`, or `"nodenext"`.
To invoke the generator from a build script, open [programmatic-api.md](programmatic-api.md).

View File

@@ -1,138 +0,0 @@
# Next.js Pages Router: operational reference
## When to use this reference
Use this document for pages under `pages/` or `src/pages/`, including SSR with `getServerSideProps`, SSG, and client-side navigation. If the route is under `app/`, use [next-app.md](next-app.md).
## Selecting a target
| Bundler | CLI mode |
|---|---|
| Turbopack | `next@pages/turbopack` |
| Webpack 5 | `next@pages/webpack` |
Determine the actual script flags before selecting a mode. The presence of the Pages Router does not automatically imply Webpack; select the target from the bundler used by the project.
## Structure and config
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Install the package as a development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@pages/webpack',
name: 'file-manager',
description: 'File manager icons',
inputFolder: './icons',
inputFiles: ['../../../../shared/icons/check.svg'],
})
```
The project chooses the directory containing each specific sprite config, and it does not have to be a module/feature directory; each config describes one of potentially many application sprites.
All source paths are resolved from the config directory. `inputFolder` defaults to `./icons`, and scanning is shallow. `inputFiles` is merged with the folder. The same path is deduplicated, but two different `check.svg` files conflict. An explicitly configured missing folder is an error even when `inputFiles` is non-empty.
`name` uses kebab-case and determines the public names. `FileManagerIcon` below is only an example generated name for `name: 'file-manager'`. Next modes always create `stack`, not `symbol`.
## Commands
Example lifecycle scripts for Webpack:
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
If pre-scripts already exist, add generation to their current command chain. Generated imports are absent from Git, so generation must precede TypeScript and Next compilation. For Turbopack, replace the complete mode with `next@pages/turbopack`. Run `npm run sprite:file-manager` for the first generation.
## Usage in the Pages Router
```tsx
import { FileManagerIcon } from '@/ui/file-manager/svg-sprite'
export default function FilesPage() {
return <FileManagerIcon icon="folder" className="icon" aria-label="Folder" />
}
export function getServerSideProps() {
return { props: {} }
}
```
`width` and `height` are optional in JSX; set the size with a CSS class or `wrapped`. The component works during SSR, SSG, and client navigation. It uses `new URL('./sprite.svg', import.meta.url).href` so Next emits an external hashed asset. Do not copy the generated SVG into `public` or construct the URL manually.
Import the component and types from the local user-owned `svg-sprite/index.ts`, which re-exports `.svg-sprite`. Do not edit `.svg-sprite` or the generated `.gitignore`.
## SpriteViewer
Pages Router components run on the client as well, so a separate `'use client'` directive is unnecessary:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('@/ui/file-manager/svg-sprite/manifest'),
() => import('@/ui/navigation/svg-sprite/manifest'),
]
export default function SpritesPage() {
return <SpriteViewer sources={sources} title="Project icons" />
}
```
Use literal imports and expose the page only as a debug/internal tool. If the page participates in production routing, restrict access through the application's own controls.
## Verification
```bash
npm run sprite:file-manager
npm run typecheck
```
If the target or Next build/deployment pipeline changed, or a runtime issue is being diagnosed, run the project's production build configured for the selected bundler:
```bash
npm run build
```
After the required generation and typecheck, verify that:
- `.svg-sprite/svg-sprite.manifest.js` contains `next@pages/turbopack` or `next@pages/webpack`;
- `getServerSideProps`/`getStaticProps` does not import the package React Viewer entry.
After the conditional production build, and only when browser tools are available, inspect the SSR route and navigation to it:
- initial HTML contains `.svg#id`;
- the SVG URL responds successfully and is not `data:`, `file:`, or `blob:`;
- after client navigation, the icon keeps the same valid URL;
- gradients, masks, and colors were examined according to [complex-svg.md](complex-svg.md); do not claim a visual or accessibility result without the necessary tools.
## Common failures
- Mode uses `next@app/...`: the module may generate, but its manifest and target contract are wrong; use `next@pages/...`.
- The command and mode select different bundlers: use the project's matching build command and regenerate.
- Viewer manifest is missing from the chunk: the `import()` path must be a string literal and must exist before the Next build.
- Icon appears after a full reload but disappears during navigation: check external asset availability with `basePath`, `assetPrefix`, and the production origin.
- `Refusing to overwrite a user file`: the sprite directory contains an unmanaged file with a reserved name; move it.
- Icon name is absent from the type: folder scanning is not recursive; move the SVG to the top level or add its exact path to `inputFiles`.
For programmatic generation, use [programmatic-api.md](programmatic-api.md).

View File

@@ -18,23 +18,28 @@ await generateSprite('src/ui/icons/svg-sprite.config.ts')
await generateSprite('src/ui/icons', {
mode: 'react@vite',
name: 'app',
inputFolder: './icons',
input: './icons',
})
```
A directory enables config-less mode. `mode` must exist after settings are merged.
`input?: string | string[]` defaults to `./icons`. Each value is a folder, an exact SVG file, or a glob, resolved from the config directory or the config-less source directory. Folder scans are shallow; recurse only with an explicit glob such as `./icons/**/*.svg`. Arrays combine sources, and items prefixed with `!` exclude matches. Every positive item must resolve to at least one SVG. The final files are deduplicated and sorted, while different files with the same basename cause an error.
## Overrides
```ts
await generateSprite('src/ui/icons/custom.json', {
mode: 'react@webpack',
inputFiles: ['../../shared/search.svg'],
input: [
'../../shared/icons/**/*.svg',
'!../../shared/icons/legacy-*.svg',
],
transform: { addTransition: false },
})
```
Values are applied as `defaults → config → API overrides`. `transform` is merged by field; a supplied `inputFiles` replaces the config array.
Values are applied as `defaults → config → API overrides`. `transform` is merged by field; a supplied `input` replaces the config value.
The specialized `generateReactSprite` and `generateNextSprite` functions remain as compatibility wrappers, but prefer `generateSprite` in new code.

View File

@@ -1,166 +0,0 @@
# React with Vite: operational reference
## When to use this reference
Use this document when the project uses React without Next.js and is built with Vite, or when the generated component contains a `sprite.svg?no-inline` import. Do not apply this target to Webpack; use [react-webpack.md](react-webpack.md) instead.
## Establish context first
1. Check `package.json`: confirm React, Vite, and the actual `dev`, `build`, and `typecheck` commands.
2. Find existing `svg-sprite.config.ts` files and scripts containing `svg-sprites`. Do not create a second directory for an existing sprite.
3. Choose a target directory for the specific sprite. It does not have to be a module or feature directory; pass the full path to its config file.
4. Do not manually edit `.svg-sprite` or the local `.gitignore`; the generator owns them. The root `index.ts` is application-owned.
Minimal structure:
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
## Setup
Install the package as a development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
`src/ui/file-manager/svg-sprite/svg-sprite.config.ts`:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@vite',
name: 'file-manager',
description: 'File manager icons',
inputFolder: './icons',
inputFiles: ['../../../../shared/icons/check.svg'],
})
```
- Each config describes one specific sprite; an application may contain many independent configs and sprites.
- Every config path is resolved relative to the directory containing `svg-sprite.config.ts`.
- `inputFolder` defaults to `./icons`; folder scanning is shallow and includes files ending in `.svg`.
- `inputFolder` and `inputFiles` are merged, and duplicate absolute paths are deduplicated.
- If `inputFiles` is non-empty and the implicit `./icons` directory is absent, generation uses only the list. An explicitly configured missing `inputFolder` is always an error.
- Different files with the same basename, such as two `check.svg` files, conflict because they define the same public icon name.
- `name` must use kebab-case and begin with an ASCII letter. `FileManagerIcon`, `FileManagerIconName`, and `fileManagerIconNames` below are only examples of generated names for `name: 'file-manager'`.
- The React preset always creates the `stack` format; `symbol` cannot be selected here.
## Command and scripts
Add the local CLI to `package.json` and run it before processes that need generated imports:
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
If the project already has `predev` or `prebuild`, integrate generation into the existing orchestration instead of overwriting the script.
Run `npm run sprite:file-manager` for the first generation.
## Usage
Import only from the public local entry point:
```tsx
import { FileManagerIcon, fileManagerIconNames } from './svg-sprite'
import type { FileManagerIconName, FileManagerIconStyle } from './svg-sprite'
const colorStyle: FileManagerIconStyle = {
'--icon-color-1': '#2563eb',
}
export function FolderIcon({ icon }: { icon: FileManagerIconName }) {
return <FileManagerIcon icon={icon} className="icon" style={colorStyle} />
}
export const availableIcons = fileManagerIconNames
```
`width` and `height` are optional in JSX; a CSS class can control the size. Without `wrapped`, the component renders `<svg>` and accepts SVG attributes. With `wrapped={true}`, the root becomes a `<span>` and the inner SVG fills its width and height:
```tsx
<FileManagerIcon icon="folder" wrapped className="iconBox" />
```
For normal imports, use a user-owned barrel with `export * from './.svg-sprite'`; the remaining files in the hidden directory are internal.
## Target-specific behavior
The Vite target generates a static import:
```ts
import spriteUrl from './sprite.svg?no-inline'
```
The `?no-inline` query is required: it prevents Vite from converting a small SVG into a data URL. Do not remove the query or copy the generated SVG into `public`; Vite must emit a separate content-hashed asset.
Use the same mechanism for low-level `<use>` integration:
```tsx
import spriteUrl from './svg-sprite/.svg-sprite/sprite.svg?no-inline'
<svg className="icon">
<use href={`${spriteUrl}#check`} />
</svg>
```
A manual `#check` fragment is safe only for names matching `^[a-zA-Z][a-zA-Z0-9_-]*$`. For spaces and other characters, the generated component uses a stable hash ID; the exact ID is available in `.svg-sprite/svg-sprite.manifest.js`.
## SpriteViewer
After generation, add the Viewer only to a debug route:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
import type { SpriteManifestModule } from '@gromlab/svg-sprites/react'
const sources = import.meta.glob<SpriteManifestModule>(
'/src/**/svg-sprite/.svg-sprite/svg-sprite.manifest.js',
)
export function IconsDebugPage() {
return <SpriteViewer sources={sources} title="Project icons" />
}
```
The `import.meta.glob` argument must remain a string literal. Generation must finish before Vite starts, or new manifests will not be included in the glob.
## Verification
```bash
npm run sprite:file-manager
npm run typecheck
```
These are the required quick checks. Inspect the generated files statically:
- the user-owned `index.ts` re-exports the component, props, style, name union, and runtime array from `.svg-sprite`;
- `.svg-sprite/svg-sprite.manifest.js` contains `target: "vite"`, `format: "stack"`, and the expected icon count.
If the target or asset pipeline changed, or a runtime issue is being diagnosed, also run the production build. When browser tools are available, inspect Network for a separate `.svg` asset rather than `data:image/svg+xml`, a successful URL, and `<use href="...svg#...">`. For complex colors, `defs`, and dimensions, follow [complex-svg.md](complex-svg.md); do not claim a visual or accessibility result without the necessary tools.
## Common failures
- Config not found: pass the full path to an existing `.ts`, `.js`, or `.json` config file.
- `Sprite mode is required`: set `mode: 'react@vite'` in the config or pass `--mode react@vite`.
- Icon missing from autocomplete: check the case-sensitive `.svg` suffix and shallow location, then regenerate before typechecking.
- `Refusing to overwrite a user file`: do not remove the marker or bypass the writer; move the user-owned file or choose another sprite directory.
- Viewer is empty: check the literal glob, the existence of `.svg-sprite/svg-sprite.manifest.js`, and the `predev` execution order.
- SVG was inlined: confirm that the module targets `vite` and its import still contains `?no-inline`.
- TypeScript cannot resolve the package subpath: use TypeScript 5+ with `moduleResolution: "bundler"`, `"node16"`, or `"nodenext"`.
For execution without the CLI, use [programmatic-api.md](programmatic-api.md).

View File

@@ -1,152 +0,0 @@
# React with Webpack 5: operational reference
## When to use this reference
Use this document for a React application built with Webpack 5 when SVGs must be processed through Asset Modules. If the project uses Vite, see [react-vite.md](react-vite.md). For Next.js, do not select `react@webpack`; router-specific targets are documented in [next-app.md](next-app.md) and [next-pages.md](next-pages.md).
## Diagnose the environment before making changes
1. Confirm Webpack major version 5 in `package.json` or the lockfile.
2. Inspect `.svg` entries in `module.rules`, `output.publicPath`, the dev server, and existing asset conventions.
3. Find existing `svg-sprite.config.ts` files and generation scripts.
4. Choose the project directory for the specific sprite. It does not have to match a module or feature directory. Pass the full path to its config file.
Minimal structure:
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
## Configuration and generation
Install the package as a development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@webpack',
name: 'file-manager',
description: 'File manager icons',
inputFolder: './icons',
inputFiles: ['../../../../shared/icons/check.svg'],
})
```
Each config describes one of potentially many independent application sprites.
Paths are relative to `svg-sprite.config.ts`. The folder is scanned only at its top level. `inputFolder` and `inputFiles` are merged; duplicate paths are deduplicated, but equal basenames from different files cause an ID conflict. The implicit `./icons` folder may be absent when `inputFiles` is non-empty; an explicitly configured missing folder is an error. `FileManagerIcon` below is only an example generated name for `name: 'file-manager'`.
Recommended lifecycle hooks:
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
Do not overwrite existing pre-scripts; add generation to their current command chain. Run `npm run sprite:file-manager` for the first generation. The React preset always emits `stack`.
## Public component
```tsx
import { FileManagerIcon } from './svg-sprite'
export function OpenFolderButton() {
return (
<button type="button">
<FileManagerIcon icon="folder" className="icon" aria-hidden />
Open
</button>
)
}
```
`width` and `height` are optional in JSX; set the size with a CSS class or `wrapped`. The user-owned barrel also exports `FileManagerIconProps`, `FileManagerIconStyle`, `FileManagerIconName`, and `fileManagerIconNames`. Do not edit `.svg-sprite` or the generated `.gitignore`.
## Webpack target behavior
The generated component obtains its URL only through this static expression:
```ts
const spriteUrl = new URL('./sprite.svg', import.meta.url).href
```
Webpack 5 recognizes it as an Asset Module and replaces it with the public URL of a separate SVG. For correct handling:
- do not move the path into a variable or modify the generated expression;
- ensure Babel/TypeScript does not transform `import.meta.url` before Webpack processes it;
- do not let `@svgr/webpack`, `svg-inline-loader`, `raw-loader`, or a general SVG rule intercept `svg-sprite/.svg-sprite/sprite.svg`;
- with custom rules, either exclude the generated sprite from component/raw loaders or add a dedicated `type: 'asset/resource'` rule;
- check `output.publicPath`, especially for CDN, subpath, and dev-server deployments.
Example dedicated rule when an existing loader intercepts every SVG:
```js
{
test: /svg-sprite[\\/]\.svg-sprite[\\/]sprite\.svg$/,
type: 'asset/resource',
}
```
Integrate this rule with the project's current configuration; do not add a duplicate matcher when standard Asset Modules already handle `new URL` correctly.
## SpriteViewer
Webpack does not provide `import.meta.glob`. Pass static lazy imports with string-literal paths:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('./ui/file-manager/svg-sprite/manifest'),
() => import('./ui/navigation/svg-sprite/manifest'),
]
export function IconsDebugPage() {
return <SpriteViewer sources={sources} title="Project icons" />
}
```
A dynamically assembled `import(path)` is not suitable: Webpack cannot reliably associate the manifests with their assets. Expose the Viewer only on a debug/internal route.
## Verification
```bash
npm run sprite:file-manager
npm run typecheck
```
These are the required quick checks. After generation, verify that:
- `.svg-sprite/svg-sprite.manifest.js` contains `target: "webpack"` and `format: "stack"`;
- the generated component contains `new URL('./sprite.svg', import.meta.url).href`.
A production build and browser/Network checks are additionally required when the target, Webpack asset rules, `publicPath`/deployment pipeline changed, or a runtime issue is being diagnosed. In that case, verify a separate hashed SVG asset, an HTTP(S) `.svg#id` URL, content type, fragment ID, and the correct CDN/subpath URL. Do not claim visual or accessibility correctness without the necessary tools.
For icons with gradients, filters, masks, or internal CSS, perform the checks in [complex-svg.md](complex-svg.md).
## Common failures
- `Unsupported React target`: the programmatic API received a value other than `'webpack'`; the CLI mode must be exactly `react@webpack`.
- Webpack tries to render the SVG as a React component: the generated sprite matched the SVGR rule; exclude it or prioritize `asset/resource`.
- URL points to the wrong host/subpath: fix `output.publicPath` and runtime deployment settings, not the generated file.
- `import.meta` is unsupported: confirm the build really uses Webpack 5 and that an intermediate transpiler preserves the expression.
- Viewer does not load the manifest: check the literal path, chunk loading, and generation before compilation.
- `Refusing to overwrite a user file`: the directory already contains a user-owned `.gitignore` or file in `.svg-sprite`; move it rather than bypassing protection.
- Icon color does not change: use `color` for monochrome icons or `--icon-color-N` through `<svg><use>`; page CSS does not cross into `<img>`.
For custom build orchestration, see [programmatic-api.md](programmatic-api.md).

View File

@@ -11,8 +11,9 @@
## Справочники по необходимости
- Для React + Vite открой [React + Vite](./references/react-vite.md), для React с собственным Webpack 5 — [React + Webpack](./references/react-webpack.md).
- Для Next.js открой руководство для [App Router](./references/next-app.md) или [Pages Router](./references/next-pages.md) и выбери раздел фактического сборщика.
- Для статической публикации открой [bare standalone](./references/guides/standalone.md). Для vanilla-приложений со сборщиком открой [standalone + Vite](./references/guides/standalone-vite.md) или [standalone + Webpack](./references/guides/standalone-webpack.md).
- Для React открой exact guide для [Vite](./references/guides/react-vite.md) или [Webpack](./references/guides/react-webpack.md).
- Для Next.js App Router открой exact guide для [Turbopack](./references/guides/next-app-turbopack.md) или [Webpack](./references/guides/next-app-webpack.md).
- Для Next.js Pages Router открой exact guide для [Turbopack](./references/guides/next-pages-turbopack.md) или [Webpack](./references/guides/next-pages-webpack.md).
- Для вызова генератора из Node.js открой [программный API](./references/programmatic-api.md).
- Для gradients, filters, `url(#...)`, нестандартных цветов и проблем с `viewBox` открой [сложные SVG](./references/complex-svg.md).
- Полную пользовательскую документацию используй как вторичный источник: [README пакета](./references/upstream/README_RU.md).

View File

@@ -1,15 +1,16 @@
## Что делает пакет
`@gromlab/svg-sprites` — CLI-генератор SVG-спрайтов для пользовательских SVG-файлов. Пакет не содержит собственного набора иконок: он собирает SVG проекта во внешний sprite asset, поддерживает standalone-проекты и для React/Next.js создаёт типизированный компонент.
`@gromlab/svg-sprites` — CLI-генератор SVG-спрайтов для пользовательских SVG-файлов. Пакет не содержит собственного набора иконок: он собирает SVG проекта во внешний sprite asset, создаёт нативный типизированный Web Component для standalone bundler modes и React-компонент для React/Next.js.
Пакет рассчитан на несколько независимых спрайтов в одном проекте. Каждый явно выбранный config-файл или config-less каталог описывает один спрайт и получает собственные:
- SVG asset;
- mode-specific manifest data;
- для bundler modes — типы имён и production entry `.svg-sprite/index.js`;
- для `standalone@vite`/`standalone@webpack` — нативный Web Component с явной функцией регистрации;
- только для React/Next.js — React-компонент;
- для bare `standalone` — deployment-neutral JSON manifest без публичного URL.
Количество и расположение каталогов определяет проект. Например, `name: 'file-manager'` создаёт `FileManagerIcon`, а другой каталог с `name: 'navigation'` создаст отдельный `NavigationIcon`. Имена `FileManagerIcon` и `fileManagerIconNames` ниже являются примерами API одного из возможных спрайтов, а не фиксированными экспортами пакета.
Generated production runtime не импортирует `@gromlab/svg-sprites` во время выполнения. Устанавливай пакет как development dependency, чтобы config helpers и локальный CLI использовали версию из lockfile проекта.
Generated production runtime и declarations не импортируют `@gromlab/svg-sprites`. Генерация работает через `npx --package` с зафиксированной версией без добавления package в проект. Устанавливай его как development dependency только для Viewer, package-типов config или программного API.

View File

@@ -21,10 +21,10 @@ CLI принимает ровно один путь. Путь к файлу `.ts
```json
{
"scripts": {
"sprite:<name>": "svg-sprites <path-to-config>",
"sprite:<name>:cli": "svg-sprites --mode <mode-key> <sprite-directory>"
"sprite:<name>": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites <path-to-config>",
"sprite:<name>:cli": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites --mode <mode-key> <sprite-directory>"
}
}
```
Не используй неполные `react`, `next@app`, `next@pages`, `standalone@` или удалённый `legacy`. Bare `standalone` выбирай только когда приложение само публикует SVG; для Vite/Webpack используй соответствующий полный key. Для нескольких спрайтов создай отдельную команду для каждого config-файла или каталога.
Генерация через `npx` не добавляет package в проект. В CI укажи точную версию вместо `latest`. Не используй неполные `react`, `next@app`, `next@pages`, `standalone@` или удалённый `legacy`. Bare `standalone` выбирай только когда приложение само публикует SVG; для Vite/Webpack используй соответствующий полный key. Для нескольких спрайтов создай отдельную команду для каждого config-файла или каталога.

View File

@@ -9,14 +9,14 @@
5. Для нового спрайта выбери целевой каталог, не навязывая конкретный слой или архитектуру приложения.
6. Проверь TypeScript и alias-настройки. Для package subpath exports нужен TypeScript 5+ с `moduleResolution: 'bundler'`, `'node16'` или `'nodenext'`.
Все пути конфига считаются относительно каталога, содержащего явно переданный config-файл; в config-less режиме — относительно переданного каталога:
Все input-пути считаются относительно каталога, содержащего явно переданный config-файл; в config-less режиме — относительно переданного каталога. Проверяй `input` как единый контракт:
- `inputFolder` по умолчанию равен `./icons`;
- `inputFiles` содержит дополнительные относительные пути к отдельным SVG и объединяется с `inputFolder`;
- одинаковый абсолютный файл дедуплицируется, но разные файлы с одинаковым basename иконки вызывают ошибку;
- сканирование `inputFolder` плоское: читаются только непосредственные файлы с окончанием `.svg`, вложенные каталоги не обходятся;
- отсутствующая явно заданная `inputFolder` является ошибкой;
- если `inputFolder` не задана, `./icons` отсутствует, а `inputFiles` непустой, генерация работает только по `inputFiles`;
- пустой итоговый набор, отсутствующий файл или путь не к `.svg` является ошибкой.
- `input?: string | string[]` по умолчанию равен `./icons`;
- каждая строка задаёт папку, точный SVG-файл или glob;
- папка сканируется плоско; вложенные файлы включаются только явным recursive glob, например `./icons/**/*.svg`;
- массив объединяет positive-источники, а элемент с префиксом `!` исключает свои совпадения из общего набора;
- каждый positive-источник должен разрешаться хотя бы в один SVG, поэтому отсутствующая или пустая папка, glob без совпадений, отсутствующий файл или точный путь не к SVG являются ошибкой;
- разрешённые файлы дедуплицируются и детерминированно сортируются;
- разные файлы с одинаковым basename конфликтуют, даже если получены из разных источников.
Не копируй общий SVG в несколько папок: добавь его относительный путь в `inputFiles` каждого нужного спрайта. Если требуется рекурсивная структура, перечисли файлы явно или измени структуру источников; генератор не выполняет recursive scan.
Не копируй общий SVG в несколько папок: добавь его точный путь или подходящий glob в `input` каждого нужного спрайта. Используй `**/*.svg` только для намеренного рекурсивного включения.

View File

@@ -12,27 +12,21 @@ src/ui/file-manager/svg-sprite/
Один `svg-sprite.config.ts` создаёт один независимый спрайт. Для нескольких наборов выбери несколько каталогов и дай каждому уникальное `name`.
Установи пакет как development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
Используй config helper для autocomplete и проверки типов:
Генератор не нужно устанавливать в проект. Начни с plain config без package
import:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
export default {
mode: 'react@vite',
name: 'file-manager',
description: 'Иконки файлового менеджера',
inputFolder: './icons',
inputFiles: ['../../shared/icons/close.svg'],
})
input: ['./icons', '../../shared/icons/close.svg'],
}
```
Контракт объекта одинаков для React и Next.js; отличается полный `mode`.
`input` принимает одну папку, точный SVG-файл или glob либо массив, объединяющий эти источники. Элемент массива с префиксом `!` исключает совпадения. Папки сканируются плоско; для рекурсии нужен явный glob `**/*.svg`. Если `input` не задан, используется `./icons`. Все пути считаются от каталога конфига, и каждый positive-источник должен найти хотя бы один SVG.
Контракт объекта одинаков для React и Next.js; отличается полный `mode`. Устанавливай package только для необязательного Viewer, программного API или package-типизации config. В exact guides также есть локальный copy-paste type для проектов без package.
`name` должен начинаться с латинской буквы и записываться в kebab-case; из примера `file-manager` будут созданы `FileManagerIcon`, `FileManagerIconName` и `fileManagerIconNames`. Другой спрайт получает собственные имена. Если `name` не задан, генератор выводит его из каталога.
@@ -41,7 +35,7 @@ export default defineSpriteConfig({
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"sprite:file-manager": "npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"sprites": "npm run sprite:file-manager"
}
}
@@ -49,9 +43,18 @@ export default defineSpriteConfig({
Для Next.js укажи в config полный ключ, например `next@app/turbopack`. Для нескольких спрайтов добавь по команде `sprite:<name>` на каждый config-файл и последовательно вызови их из `sprites`.
Generated-файлы в `.svg-sprite` по умолчанию исключаются из Git, поэтому запускай `sprites` до процессов, которым нужны компонент, типы или asset. Если проект импортирует корень sprite-модуля, создай пользовательский `index.ts` с `export * from './.svg-sprite'`. Добавь генерацию к `predev`, `prebuild` и, если есть `typecheck`, к `pretypecheck`.
Чтобы задать источники через CLI, повторяй `--input <path-or-glob>`; значения образуют тот же массивный контракт, включая исключения с `!`:
Если lifecycle script отсутствует, создай его. Если он уже существует, сохрани его команду и допиши генерацию через `&&`, например преобразуй `"prebuild": "npm run lint"` в `"prebuild": "npm run lint && npm run sprites"`. Никогда не заменяй существующий `pre*` одной генерацией и не создавай второй одноимённый JSON key.
```bash
npx --yes --package=@gromlab/svg-sprites@latest svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts \
--input ./icons \
--input '../../shared/icons/**/*.svg' \
--input '!../../shared/icons/legacy-*.svg'
```
Generated-файлы в `.svg-sprite` по умолчанию исключаются из Git, поэтому запускай `sprites` до процессов, которым нужны компонент, типы или asset. Если проект импортирует корень sprite-модуля, создай пользовательский `index.ts` с `export * from './.svg-sprite'`. Generated declarations self-contained и не импортируют generator package.
Запускай генерацию либо через `predev`/`prebuild`/`pretypecheck`, либо явно внутри соответствующих команд. Не используй обе формы для одной команды, иначе генерация выполнится дважды. Сохраняй существующие команды и не создавай второй одноимённый JSON key.
Запусти первую генерацию вручную:

View File

@@ -9,7 +9,6 @@ svg-sprite/
├── index.ts # необязательный пользовательский barrel
├── .gitignore # управляет генератор
└── .svg-sprite/
├── state.json # реестр владения и версия контракта
├── index.js
├── index.d.ts
├── icon-data.js
@@ -25,23 +24,25 @@ svg-sprite/
Standalone не создаёт `react/`. Bare `standalone` генерирует `sprite.svg` и
`svg-sprite.manifest.json`; `standalone@vite`/`standalone@webpack` дополнительно
генерируют `index.*`, `icon-data.*` и resolved manifest.
генерируют `index.*`, `icon-data.*` и resolved manifest. Их `index.*` также
содержит нативный generated Web Component; bare `standalone` не получает JS runtime,
не создаёт и не изменяет `.gitignore`.
Редактируй исходные SVG, config-файл и пользовательский `index.ts`. Не изменяй вручную `.gitignore` и содержимое `.svg-sprite`: повторная генерация их перезапишет. Для импорта из корня sprite-модуля создай barrel:
Редактируй исходные SVG, config-файл и пользовательский `index.ts`. Не изменяй вручную содержимое `.svg-sprite`: повторная генерация его перезапишет. Во всех modes, кроме bare `standalone`, generated `.gitignore` также находится под управлением генератора. Для импорта из корня sprite-модуля создай barrel:
```ts
export * from './.svg-sprite'
```
Генератор владеет `.gitignore` и файлами внутри `.svg-sprite`. Реестр `state.json` позволяет удалить устаревший generated-файл, но writer откажется перезаписывать или удалять файл без generated-маркера. Не удаляй маркер, не обходи отказ и не подменяй generated-пути symlink: перенеси пользовательский файл или выбери другой каталог.
Генератор полностью владеет каталогом `.svg-sprite` и заменяет его при каждом запуске. Никогда не помещай туда пользовательские файлы. Генератор также владеет `.gitignore`, когда выбранный mode его создаёт; bare `standalone` оставляет существующий `.gitignore` без изменений. Generated-пути не должны содержать symlink.
Внутренний `index.js` экспортирует компонент из `react/react-component.js` и readonly-массив имён; соседний `index.d.ts` добавляет props/style-типы и union имени. Manifest содержит mode, URL, target, список и метаданные иконок для debug-инструментов и не импортируется production-компонентом.
Внутренний `index.js` экспортирует компонент из `react/react-component.js` и readonly-массив имён; соседний `index.d.ts` добавляет props/style-типы и union имени. Manifest declarations bundler modes объявляют типы локально и не импортируют generator package. Manifest содержит mode, URL, target, список и метаданные иконок для debug-инструментов и не импортируется production-компонентом.
Спрайт остаётся отдельным asset с content hash; SVG path-данные не встраиваются в JavaScript:
- `react@vite` генерирует статический импорт `sprite.svg?no-inline`, запрещающий Vite inline;
- `standalone@vite` использует тот же Vite asset-механизм, но экспортирует href helper без React;
- `standalone@webpack` использует Webpack Asset Modules без React;
- `standalone@vite` использует тот же Vite asset-механизм и экспортирует href helper и нативный Web Component без React;
- `standalone@webpack` использует Webpack Asset Modules и экспортирует такой же mode-local Web Component без React;
- React Webpack 5 и все Next modes генерируют `new URL('./sprite.svg', import.meta.url).href`, который должен обработать Asset Modules соответствующего сборщика;
- кастомный Webpack SVG loader не должен перехватывать generated `sprite.svg`;
- в Next mode generated-компонент не содержит `'use client'` и работает в Server Components, SSR и SSG; не добавляй клиентскую границу только ради иконки;

View File

@@ -1,6 +1,20 @@
## Использование, доступность и цвета
Имя компонента зависит от `name` конкретного спрайта. В примерах используется `name: 'file-manager'`, поэтому generated-компонент называется `FileManagerIcon`. Для `name: 'navigation'` используй сгенерированный `NavigationIcon`.
Имя компонента зависит от `name` конкретного спрайта. В `standalone@vite` и `standalone@webpack` значение `name: 'file-manager'` создаёт tag `<file-manager-icon>` и функцию `defineFileManagerIconElement()`:
```ts
import { defineFileManagerIconElement } from './svg-sprite'
defineFileManagerIconElement()
```
```html
<file-manager-icon icon="folder" aria-hidden="true"></file-manager-icon>
```
Нативный элемент не имеет runtime-зависимостей, сам выбирает generated ID и `viewBox`, получает URL через bundler и рендерит `<svg><use>` в Shadow DOM. Его property `icon` типизирован точным union имён, но строковые HTML attributes проверяются только в runtime. Размер по умолчанию равен `1em × 1em`; меняй его через CSS на host. Bare `standalone` Web Component не генерирует.
В React/Next.js тот же `name: 'file-manager'` создаёт React-компонент `FileManagerIcon`. Для `name: 'navigation'` используй сгенерированный `NavigationIcon`.
Импортируй компонент из корня соответствующего каталога спрайта. `width` и `height` не обязательны: размером можно управлять обычным CSS-классом.
@@ -64,10 +78,10 @@ Generated-компонент не выбирает семантику за пр
Автозамена рассчитана на `fill`/`stroke` attributes и inline `style`. Значения `none`, `transparent`, `inherit`, `unset`, `initial` не заменяются. CSS-классы и внешние stylesheets, gradients, patterns, filters и `url(#...)` проверяй на реальном результате. Переменные страницы работают через `<svg><use>`, но не проникают во внешний документ при `<img>` или `background-image`; CSS mask оставляет только одноцветный силуэт.
`SpriteViewer` необязателен. Подключай его из `@gromlab/svg-sprites/react` только на debug-маршруте:
`SpriteViewer` необязателен. Установи `@gromlab/svg-sprites` как development dependency, только если проекту нужен Viewer, и подключай его из `@gromlab/svg-sprites/react` на debug-маршруте:
- в Vite передай результат строкового literal `import.meta.glob('/src/**/svg-sprite/.svg-sprite/svg-sprite.manifest.js')`;
- в Webpack передай массив статических `() => import('.../manifest')`;
- в Webpack передай массив статических `() => import('.../.svg-sprite/svg-sprite.manifest.js')`;
- в Next.js используй такие же статические loaders, а для App Router помести Viewer в отдельный файл с `'use client'`.
Viewer принимает manifests/loaders, показывает поиск, темы, цвета и примеры, но production-компоненты от него не зависят. Импортируй его из пакета, уже установленного как development dependency.
Viewer принимает manifests/loaders, показывает поиск, темы, цвета и примеры, но production-компоненты от него не зависят.

View File

@@ -3,7 +3,7 @@
После изменения конфига или SVG выполни обязательные быстрые проверки:
1. Запусти точную sprite-команду, например `npm run sprite:file-manager`; процесс должен завершиться с кодом `0` и сообщить имя, число иконок, mode и каталог `.svg-sprite`.
2. Проверь наличие `.svg-sprite/index.js`, `.svg-sprite/index.d.ts`, `sprite.svg`, пары `icon-data.js`/`.d.ts`, manifest `.js`/`.d.ts`, `react/react-component.js`, его `.d.ts` и CSS Module, а также `state.json`.
2. Проверь наличие `.svg-sprite/index.js`, `.svg-sprite/index.d.ts`, `sprite.svg`, пары `icon-data.js`/`.d.ts`, manifest `.js`/`.d.ts`, `react/react-component.js`, его `.d.ts` и CSS Module.
3. Убедись, что новая иконка присутствует в readonly-массиве имён и принимается prop `icon`.
4. Запусти существующую проверку типов проекта, например `npm run typecheck`.
5. Проверь в `.svg-sprite/svg-sprite.manifest.js`, что `target` совпадает с выбранным mode key; generated asset expression должен быть `?no-inline` для Vite и `new URL(...)` для Webpack/Next.

View File

@@ -8,9 +8,10 @@
| `Expected one config file or module directory` | Передано несколько путей | Создай отдельную команду на каждый спрайт и объедини scripts. |
| `Sprite mode is required` | Mode отсутствует и в config, и в CLI | Добавь `mode` в объект или передай полный `--mode`. |
| `Unsupported sprite config extension` | Передан файл не `.ts`, `.js` или `.json` | Используй поддерживаемый формат config-файла. |
| `Input directory does not exist` | Ошибка относительного пути или отсутствует явно заданная папка | Считай путь от каталога конфига; создай папку или исправь `inputFolder`. |
| Иконки из подпапки не появились | Ожидался recursive scan | Перемести SVG на верхний уровень `inputFolder` или перечисли его в `inputFiles`. |
| `SVG file does not exist`, `File is not an SVG` или пустой набор | Неверный `inputFiles`, расширение или источники | Исправь путь/расширение и обеспечь хотя бы один входной SVG. |
| Positive input-источник не нашёл SVG | Папка отсутствует или пуста, glob не совпал либо точный путь отсутствует или ведёт не к SVG | Разреши источник от каталога конфига и исправь `input`; каждый positive-элемент должен дать хотя бы один SVG. |
| Иконки из подпапки не появились | От папки ожидалось рекурсивное сканирование | Используй явный glob, например `./icons/**/*.svg`; папки сканируются плоско. |
| Исключённая иконка всё ещё присутствует | У исключения нет префикса `!`, оно находится не в массиве `input` или считается не от того каталога | Добавь совпадающий `!`-элемент и считай его от каталога конфига. |
| CLI выбрал не все источники | Несколько источников поместили в одно значение `--input` или пропустили option | Повтори `--input <path-or-glob>` отдельно для каждого источника или исключения. |
| Конфликт имени иконки или SVG ID | Два разных файла имеют одинаковый basename либо hash-ID столкнулся с именем | Переименуй один исходный SVG; не выбирай файл неявно. |
| `Refusing to overwrite/delete a user file` | Пользовательский файл занял managed-путь или потерял marker | Не обходи защиту: перенеси файл либо выбери другой sprite-каталог и перегенерируй. |
| Нет `.svg-sprite/index.js` или имя отсутствует в autocomplete | Генерация не запускалась после изменения, пользовательский barrel не экспортирует `.svg-sprite` либо type server держит старый модуль | Запусти sprite-команду, проверь `export * from './.svg-sprite'`, затем typecheck; при необходимости перезапусти TypeScript server. |

View File

@@ -131,7 +131,7 @@ transform: {
Не создавай вручную `#folder open`. Используй generated component либо `.svg-sprite/svg-sprite.manifest.js`, где записаны `name` и фактический `id`.
Разные файлы с одинаковым basename запрещены даже из разных directories. Переименуй один source осмысленно; порядок `inputFiles` не является способом выбрать победителя.
Разные файлы с одинаковым basename запрещены даже из разных directories. Переименуй один source осмысленно; порядок или пересечение источников не выбирают победителя.
## Способ отображения
@@ -173,4 +173,4 @@ External stack fragment support и поведение paint servers могут
- Ручной fragment не работает для имени с пробелом: используй ID из manifest.
- Один сложный icon требует иных transforms: вынеси его в отдельный sprite; per-icon transform config отсутствует.
Target-specific запуск и проверка описаны в [react-vite.md](react-vite.md), [react-webpack.md](react-webpack.md), [next-app.md](next-app.md) и [next-pages.md](next-pages.md).
Target-specific запуск и проверка описаны в exact-mode файлах каталога [guides](guides/standalone.md).

View File

@@ -1,149 +0,0 @@
# Next.js App Router: операционный reference
## Когда открывать
Открывай этот документ, если приложение использует каталог `app/` или `src/app/` и нужен generated-компонент для Server Components, SSR или SSG. Сначала установи реальный сборщик: target для Turbopack и Webpack различается. Для `pages/` используй [next-pages.md](next-pages.md).
## Матрица target
| Сборщик | CLI mode |
|---|---|
| Turbopack | `next@app/turbopack` |
| Webpack 5 | `next@app/webpack` |
Не выводи target только из наличия `next.config.*`. Проверь фактические flags в `dev`/`build`. Mode генератора и сборщик контрольной сборки должны совпадать.
## Подготовка каталога спрайта
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Установи пакет как development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@app/turbopack',
name: 'file-manager',
description: 'Иконки файлового менеджера',
inputFolder: './icons',
inputFiles: ['../../../../shared/icons/check.svg'],
})
```
- Каталог выбирает проект конкретного спрайта и не обязан быть module/feature-каталогом. Каждый config описывает один из потенциально многих спрайтов приложения.
- `svg-sprite.config.ts` — рекомендуемое имя; CLI принимает явно переданный `.ts`, `.js` или `.json` файл с любым именем.
- Пути считаются от его каталога; `inputFolder` сканируется нерекурсивно.
- Локальная папка и `inputFiles` объединяются. Одинаковые basename разных файлов запрещены.
- Если имя не задано, оно выводится из каталога: для папки `svg-sprite` берётся имя родительской папки. Явное `name` надёжнее при перемещениях.
- `name` должен быть kebab-case и начинаться с буквы.
- `FileManagerIcon` ниже — только пример generated-имени из `name: 'file-manager'`.
- Next preset всегда генерирует `stack` и включает root `viewBox` спрайта; это не пользовательская настройка.
## Генерация
Пример scripts для Turbopack:
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
Для Webpack замени только mode целиком на `next@app/webpack`. Не запускай два target последовательно для одного каталога: второй перезапишет generated target первого. Для первой генерации запусти `npm run sprite:file-manager`.
## Server Component
Generated production component не содержит `'use client'`. Импортируй его непосредственно в server `page.tsx` или `layout.tsx`:
```tsx
import { FileManagerIcon } from '@/ui/file-manager/svg-sprite'
export default function Page() {
return (
<main>
<FileManagerIcon icon="folder" className="icon" aria-label="Папка" />
</main>
)
}
```
`width` и `height` в JSX необязательны: размер можно задать CSS-классом или через `wrapped`. Не добавляй Client Component boundary только ради иконки. Generated module формирует asset URL через статический `new URL('./sprite.svg', import.meta.url).href`; один механизм используется при SSR и в браузере.
Не перемещай SVG в `public`. Генератор перезаписывает `.svg-sprite` и `.gitignore`; корневой `index.ts` принадлежит приложению и может содержать `export * from './.svg-sprite'`.
## SpriteViewer
Viewer интерактивен и импортируется из client-only entry. Создай отдельную Client Component страницу или дочерний компонент:
```tsx
'use client'
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('@/ui/file-manager/svg-sprite/manifest'),
() => import('@/ui/navigation/svg-sprite/manifest'),
]
export default function SpritesPage() {
return <SpriteViewer sources={sources} title="Иконки проекта" />
}
```
Пути `import()` должны быть строковыми литералами. Оставь production icon imports за пределами `@gromlab/svg-sprites/react`: package React entry предназначен только для Viewer и содержит `'use client'`.
## Проверка
Сначала генерация и типы:
```bash
npm run sprite:file-manager
npm run typecheck
```
Если менялись target или Next build/deployment pipeline либо диагностируется runtime, запусти production-сборку проекта, настроенную на выбранный сборщик:
```bash
npm run build
```
После обязательных генерации и typecheck проверь:
- `.svg-sprite/svg-sprite.manifest.js` содержит точный target `next@app/turbopack` либо `next@app/webpack`;
- server page компилируется без добавления `'use client'`;
При условной production/runtime-проверке проверь также:
- HTML содержит `href` на `.svg#id`, а URL asset доступен после `next start`;
- URL не использует `data:`, `file:` или `blob:`;
- SSR markup и hydrated page ссылаются на один asset;
- сложные SVG исследованы по [complex-svg.md](complex-svg.md); визуальный и a11y результат утверждай только при наличии соответствующих инструментов.
## Типовые ошибки
- `Next.js mode requires a router and bundler`: нельзя использовать `--mode next`; укажи полный mode.
- Сборка проходит на одном bundler, а runtime asset ломается на другом: перегенерируй тем target, которым реально выполняется build.
- Выбран Webpack, но build ушёл в Turbopack: используй Webpack-команду проекта и `next@app/webpack`.
- Viewer вызывает ошибку Server Component: файл с Viewer должен иметь `'use client'`; generated icon component этого не требует.
- Config не найден: передай полный путь к существующему `.ts`, `.js` или `.json` config-файлу.
- Две CI jobs генерируют разные target в одном checkout: раздели каталоги или обеспечь один согласованный target на job.
- Asset не найден под `basePath`/CDN: проверяй Next asset handling и deployment config, не подменяй generated URL вручную.
- Ошибка package subpath types: используй TypeScript 5+ и `moduleResolution: "bundler"`, `"node16"` или `"nodenext"`.
Для вызова генератора из build script открой [programmatic-api.md](programmatic-api.md).

View File

@@ -1,138 +0,0 @@
# Next.js Pages Router: операционный reference
## Когда открывать
Открывай этот документ для страниц в `pages/` или `src/pages/`, включая SSR через `getServerSideProps`, SSG и клиентские переходы. Если маршрут находится в `app/`, используй [next-app.md](next-app.md).
## Выбор target
| Сборщик | CLI mode |
|---|---|
| Turbopack | `next@pages/turbopack` |
| Webpack 5 | `next@pages/webpack` |
Определи реальные flags scripts до выбора mode. Наличие Pages Router не означает автоматически Webpack: выбирай target по сборщику проекта.
## Структура и конфиг
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
Установи пакет как development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'next@pages/webpack',
name: 'file-manager',
description: 'Иконки файлового менеджера',
inputFolder: './icons',
inputFiles: ['../../../../shared/icons/check.svg'],
})
```
Каталог с config выбирает проект конкретного спрайта и не обязан быть module/feature-каталогом; каждый config описывает один из потенциально многих спрайтов приложения.
Все source paths разрешаются от каталога конфига. `inputFolder` по умолчанию `./icons`, сканирование не рекурсивно. `inputFiles` объединяется с папкой. Один и тот же путь дедуплицируется, но два разных `check.svg` конфликтуют. Явная отсутствующая папка считается ошибкой даже при заполненном `inputFiles`.
`name` задаётся в kebab-case и определяет публичные имена. `FileManagerIcon` ниже — только пример generated-имени из `name: 'file-manager'`. Next modes всегда создают `stack`, не `symbol`.
## Команды
Пример lifecycle для Webpack:
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
Если pre-scripts уже существуют, добавь генерацию в текущую цепочку. Generated imports отсутствуют в Git, поэтому генерация должна предшествовать TypeScript и Next compilation. Для Turbopack замени mode целиком на `next@pages/turbopack`. Для первой генерации запусти `npm run sprite:file-manager`.
## Использование в Pages Router
```tsx
import { FileManagerIcon } from '@/ui/file-manager/svg-sprite'
export default function FilesPage() {
return <FileManagerIcon icon="folder" className="icon" aria-label="Папка" />
}
export function getServerSideProps() {
return { props: {} }
}
```
`width` и `height` в JSX необязательны: размер можно задать CSS-классом или через `wrapped`. Компонент работает при SSR, SSG и client navigation. Он использует `new URL('./sprite.svg', import.meta.url).href`, чтобы Next выпустил внешний hashed asset. Не копируй generated SVG в `public` и не конструируй URL вручную.
Импортируй компонент и типы из локального пользовательского `svg-sprite/index.ts`, который переэкспортирует `.svg-sprite`. Не редактируй `.svg-sprite` и созданный `.gitignore`.
## SpriteViewer
Pages Router компоненты выполняются и на клиенте, поэтому отдельная директива `'use client'` не нужна:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('@/ui/file-manager/svg-sprite/manifest'),
() => import('@/ui/navigation/svg-sprite/manifest'),
]
export default function SpritesPage() {
return <SpriteViewer sources={sources} title="Иконки проекта" />
}
```
Используй literal imports и размещай страницу только как debug/internal tool. Если page участвует в production routing, ограничь доступ средствами самого приложения.
## Проверка
```bash
npm run sprite:file-manager
npm run typecheck
```
Если менялись target или Next build/deployment pipeline либо диагностируется runtime, запусти production build проекта, настроенный на выбранный сборщик:
```bash
npm run build
```
После обязательных генерации и typecheck проверь:
- `.svg-sprite/svg-sprite.manifest.js` содержит `next@pages/turbopack` либо `next@pages/webpack`;
- `getServerSideProps`/`getStaticProps` не импортируют package React Viewer entry.
После условного production build и только при наличии браузерных инструментов проверь SSR route и переход на него:
- initial HTML содержит `.svg#id`;
- SVG URL отвечает успешно и не является `data:`, `file:` или `blob:`;
- после client navigation иконка сохраняет тот же корректный URL;
- gradients, masks и цвета исследованы по [complex-svg.md](complex-svg.md); не утверждай визуальный или a11y результат без доступных инструментов.
## Типовые ошибки
- В mode указан `next@app/...`: модуль может сгенерироваться, но manifest и контракт target неверны; используй `next@pages/...`.
- Command и mode выбирают разные bundler: используй соответствующую build-команду проекта и перегенерируй.
- Viewer manifest не попадает в chunk: путь `import()` должен быть строковым литералом и существовать до Next build.
- Иконка есть после full reload, но пропадает при переходе: проверь доступность внешнего asset с `basePath`, `assetPrefix` и production origin.
- `Refusing to overwrite a user file`: sprite-каталог содержит неуправляемый файл с зарезервированным именем; перенеси его.
- Имя иконки отсутствует в типе: папка не сканируется рекурсивно; перемести SVG на первый уровень либо добавь точный путь в `inputFiles`.
Для программной генерации используй [programmatic-api.md](programmatic-api.md).

View File

@@ -18,23 +18,28 @@ await generateSprite('src/ui/icons/svg-sprite.config.ts')
await generateSprite('src/ui/icons', {
mode: 'react@vite',
name: 'app',
inputFolder: './icons',
input: './icons',
})
```
Каталог включает config-less режим. После объединения настроек `mode` обязателен.
`input?: string | string[]` по умолчанию равен `./icons`. Каждое значение задаёт папку, точный SVG-файл или glob и считается от каталога конфига либо config-less source-каталога. Папка сканируется плоско; рекурсия включается только явным glob, например `./icons/**/*.svg`. Массив объединяет источники, а элементы с префиксом `!` исключают совпадения. Каждый positive-элемент должен разрешаться хотя бы в один SVG. Итоговые файлы дедуплицируются и сортируются, а разные файлы с одинаковым basename вызывают ошибку.
## Overrides
```ts
await generateSprite('src/ui/icons/custom.json', {
mode: 'react@webpack',
inputFiles: ['../../shared/search.svg'],
input: [
'../../shared/icons/**/*.svg',
'!../../shared/icons/legacy-*.svg',
],
transform: { addTransition: false },
})
```
Порядок: `defaults → config → API overrides`. `transform` объединяется по отдельным полям; переданный `inputFiles` заменяет массив из config.
Порядок: `defaults → config → API overrides`. `transform` объединяется по отдельным полям; переданный `input` заменяет значение из config.
Специализированные `generateReactSprite` и `generateNextSprite` оставлены как совместимые обёртки, но для нового кода предпочитай `generateSprite`.

View File

@@ -1,166 +0,0 @@
# React с Vite: операционный reference
## Когда открывать
Открывай этот документ, если проект использует React без Next.js и собирается Vite, либо если generated-компонент содержит импорт `sprite.svg?no-inline`. Не применяй этот target к Webpack: для него открой [react-webpack.md](react-webpack.md).
## Сначала установи контекст
1. Проверь `package.json`: должны быть React, Vite и фактические команды `dev`, `build`, `typecheck`.
2. Найди существующие `svg-sprite.config.ts` и scripts с `svg-sprites`. Не создавай второй каталог для уже существующего спрайта.
3. Выбери целевой каталог для конкретного спрайта. Это не обязан быть каталог module или feature: генератор принимает каталог с config, а не путь к самому config или `icons/`.
4. Не редактируй вручную `.svg-sprite` и локальный `.gitignore`: ими владеет генератор. Корневой `index.ts` принадлежит приложению.
Минимальная структура:
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
## Настройка
Установи пакет как development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
`src/ui/file-manager/svg-sprite/svg-sprite.config.ts`:
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@vite',
name: 'file-manager',
description: 'Иконки файлового менеджера',
inputFolder: './icons',
inputFiles: ['../../../../shared/icons/check.svg'],
})
```
- Каждый такой config описывает один конкретный спрайт; в приложении может быть много независимых config и спрайтов.
- Все пути в конфиге разрешаются относительно каталога `svg-sprite.config.ts`.
- `inputFolder` по умолчанию равен `./icons`; сканирование папки не рекурсивно и включает файлы с окончанием `.svg`.
- `inputFolder` и `inputFiles` объединяются, одинаковый абсолютный путь дедуплицируется.
- Если `inputFiles` заполнен, а неявного `./icons` нет, генерация работает только по списку. Явно заданная отсутствующая `inputFolder` всегда является ошибкой.
- Разные файлы с одинаковым basename, например два `check.svg`, конфликтуют как одно публичное имя иконки.
- `name` должен быть kebab-case и начинаться с латинской буквы. `FileManagerIcon`, `FileManagerIconName` и `fileManagerIconNames` ниже — только пример generated-имён для `name: 'file-manager'`.
- React preset всегда создаёт формат `stack`; выбрать `symbol` здесь нельзя.
## Команда и scripts
Добавь локальный CLI в `package.json` и запускай до процессов, которым нужны generated imports:
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
Если в проекте уже есть `predev` или `prebuild`, объедини команды в существующем orchestration вместо перезаписи script.
Для первой генерации запусти `npm run sprite:file-manager`.
## Использование
Импортируй только публичный локальный entry:
```tsx
import { FileManagerIcon, fileManagerIconNames } from './svg-sprite'
import type { FileManagerIconName, FileManagerIconStyle } from './svg-sprite'
const colorStyle: FileManagerIconStyle = {
'--icon-color-1': '#2563eb',
}
export function FolderIcon({ icon }: { icon: FileManagerIconName }) {
return <FileManagerIcon icon={icon} className="icon" style={colorStyle} />
}
export const availableIcons = fileManagerIconNames
```
`width` и `height` в JSX необязательны: размер можно задать CSS-классом. Без `wrapped` компонент рендерит `<svg>` и принимает SVG attributes. С `wrapped={true}` корнем становится `<span>`, а внутренний SVG занимает его ширину и высоту:
```tsx
<FileManagerIcon icon="folder" wrapped className="iconBox" />
```
Для обычного импорта используй пользовательский barrel с `export * from './.svg-sprite'`; остальные файлы скрытого каталога считаются внутренними.
## Нюанс target
Vite target генерирует статический импорт:
```ts
import spriteUrl from './sprite.svg?no-inline'
```
Query `?no-inline` обязателен: он не даёт Vite превратить небольшой SVG в data URL. Не удаляй query и не копируй generated SVG в `public`; Vite должен выпустить отдельный asset с content hash.
Для низкоуровневого `<use>` применяй тот же механизм:
```tsx
import spriteUrl from './svg-sprite/.svg-sprite/sprite.svg?no-inline'
<svg className="icon">
<use href={`${spriteUrl}#check`} />
</svg>
```
Ручной fragment `#check` безопасен только для имён вида `^[a-zA-Z][a-zA-Z0-9_-]*$`. Для пробелов и других символов generated-компонент использует стабильный hash ID; точный ID находится в `.svg-sprite/svg-sprite.manifest.js`.
## SpriteViewer
После генерации добавь Viewer только на debug-маршрут:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
import type { SpriteManifestModule } from '@gromlab/svg-sprites/react'
const sources = import.meta.glob<SpriteManifestModule>(
'/src/**/svg-sprite/.svg-sprite/svg-sprite.manifest.js',
)
export function IconsDebugPage() {
return <SpriteViewer sources={sources} title="Иконки проекта" />
}
```
Аргумент `import.meta.glob` должен оставаться строковым литералом. Генерация обязана завершиться до старта Vite, иначе новые manifests не попадут в glob.
## Проверка результата
```bash
npm run sprite:file-manager
npm run typecheck
```
Это быстрые обязательные проверки. Проверь generated-файлы статически:
- пользовательский `index.ts` переэкспортирует компонент, props, style, union имени и runtime-массив из `.svg-sprite`;
- `.svg-sprite/svg-sprite.manifest.js` содержит `target: "vite"`, `format: "stack"` и ожидаемое число иконок;
Если менялись target, asset pipeline или диагностируется runtime, дополнительно запусти production build. При наличии браузерных инструментов проверь Network: отдельный `.svg` asset вместо `data:image/svg+xml`, успешный URL и `<use href="...svg#...">`. Для сложных цветов, `defs` и размеров следуй [complex-svg.md](complex-svg.md), не утверждая визуальный или a11y результат без доступных инструментов.
## Типовые ошибки
- Config не найден: передай полный путь к существующему `.ts`, `.js` или `.json` config-файлу.
- `Sprite mode is required`: добавь `mode: 'react@vite'` в config либо передай `--mode react@vite`.
- Иконки нет в autocomplete: проверь case-sensitive окончание `.svg`, нерекурсивное расположение и повторно запусти генерацию до typecheck.
- `Refusing to overwrite a user file`: не удаляй marker и не обходи writer; перенеси пользовательский файл или выбери другой sprite-каталог.
- Viewer пуст: проверь строковый glob, существование `.svg-sprite/svg-sprite.manifest.js` и порядок запуска `predev`.
- SVG оказался inline: проверь, что модуль сгенерирован target `vite` и импорт сохранил `?no-inline`.
- TypeScript не разрешает package subpath: используй TypeScript 5+ и `moduleResolution: "bundler"`, `"node16"` или `"nodenext"`.
Для запуска без CLI используй [programmatic-api.md](programmatic-api.md).

View File

@@ -1,152 +0,0 @@
# React с Webpack 5: операционный reference
## Когда открывать
Открывай этот документ для React-приложения на Webpack 5, когда SVG должен проходить через Asset Modules. Если проект собирается Vite, используй [react-vite.md](react-vite.md). Для Next.js не выбирай `react@webpack`: router-specific targets описаны в [next-app.md](next-app.md) и [next-pages.md](next-pages.md).
## Диагностика окружения до изменений
1. Подтверди Webpack major version 5 в `package.json` или lockfile.
2. Изучи `module.rules` для `.svg`, `output.publicPath`, dev-server и существующие asset conventions.
3. Найди существующие `svg-sprite.config.ts` и scripts генерации.
4. Выбери каталог проекта конкретного спрайта. Он не обязан совпадать с module или feature-каталогом. Команда принимает каталог с config, не файл config и не папку иконок.
Минимальная структура:
```text
src/ui/file-manager/svg-sprite/
├── icons/
│ ├── check.svg
│ └── folder.svg
└── svg-sprite.config.ts
```
## Конфигурация и генерация
Установи пакет как development dependency:
```bash
npm install --save-dev @gromlab/svg-sprites
```
```ts
import { defineSpriteConfig } from '@gromlab/svg-sprites'
export default defineSpriteConfig({
mode: 'react@webpack',
name: 'file-manager',
description: 'Иконки файлового менеджера',
inputFolder: './icons',
inputFiles: ['../../../../shared/icons/check.svg'],
})
```
Каждый config описывает один из потенциально многих независимых спрайтов приложения.
Пути считаются от `svg-sprite.config.ts`. Папка сканируется только на первом уровне. `inputFolder` и `inputFiles` объединяются; одинаковый путь дедуплицируется, но одинаковые basename у разных файлов вызывают конфликт ID. Неявный `./icons` можно не создавать, если заполнен `inputFiles`; явно заданная отсутствующая папка является ошибкой. `FileManagerIcon` ниже — только пример generated-имени из `name: 'file-manager'`.
Рекомендуемые lifecycle hooks:
```json
{
"scripts": {
"sprite:file-manager": "svg-sprites src/ui/file-manager/svg-sprite/svg-sprite.config.ts",
"predev": "npm run sprite:file-manager",
"prebuild": "npm run sprite:file-manager",
"pretypecheck": "npm run sprite:file-manager"
}
}
```
Не затирай уже существующие pre-scripts: включи генерацию в их текущую цепочку. Для первой генерации запусти `npm run sprite:file-manager`. React preset всегда выпускает `stack`.
## Публичный компонент
```tsx
import { FileManagerIcon } from './svg-sprite'
export function OpenFolderButton() {
return (
<button type="button">
<FileManagerIcon icon="folder" className="icon" aria-hidden />
Открыть
</button>
)
}
```
`width` и `height` в JSX необязательны: размер можно задать CSS-классом или через `wrapped`. Пользовательский barrel также экспортирует `FileManagerIconProps`, `FileManagerIconStyle`, `FileManagerIconName` и `fileManagerIconNames`. Не редактируй `.svg-sprite` или созданный `.gitignore`.
## Нюанс Webpack target
Generated-компонент получает URL только через статическое выражение:
```ts
const spriteUrl = new URL('./sprite.svg', import.meta.url).href
```
Webpack 5 распознаёт его как Asset Module и заменяет на публичный URL отдельного SVG. Для корректной обработки:
- не оборачивай путь в переменную и не меняй generated expression;
- убедись, что Babel/TypeScript не преобразует `import.meta.url` до Webpack;
- не позволяй `@svgr/webpack`, `svg-inline-loader`, `raw-loader` или общему SVG rule перехватить `svg-sprite/.svg-sprite/sprite.svg`;
- при custom rule либо исключи generated sprite из component/raw loader, либо добавь отдельное правило `type: 'asset/resource'`;
- проверь `output.publicPath`, особенно при CDN, subpath deployment и dev-server.
Пример отдельного правила, если существующий loader перехватывает все SVG:
```js
{
test: /svg-sprite[\\/]\.svg-sprite[\\/]sprite\.svg$/,
type: 'asset/resource',
}
```
Согласуй это правило с текущим конфигом проекта: не добавляй дублирующий matcher, если стандартные Asset Modules уже обрабатывают `new URL` правильно.
## SpriteViewer
Webpack не предоставляет `import.meta.glob`. Передай статические lazy imports со строковыми литералами:
```tsx
import { SpriteViewer } from '@gromlab/svg-sprites/react'
const sources = [
() => import('./ui/file-manager/svg-sprite/manifest'),
() => import('./ui/navigation/svg-sprite/manifest'),
]
export function IconsDebugPage() {
return <SpriteViewer sources={sources} title="Иконки проекта" />
}
```
Динамически собранный путь `import(path)` не подходит: Webpack не сможет точно связать manifests и assets. Viewer должен быть доступен только на debug/internal route.
## Проверка
```bash
npm run sprite:file-manager
npm run typecheck
```
Это быстрые обязательные проверки. После генерации проверь:
- `.svg-sprite/svg-sprite.manifest.js` содержит `target: "webpack"` и `format: "stack"`;
- generated component содержит `new URL('./sprite.svg', import.meta.url).href`;
Production build и браузер/Network нужны дополнительно, если менялись target, Webpack asset rules, `publicPath`/deployment pipeline или диагностируется runtime. Тогда проверь отдельный hashed SVG asset, HTTP(S)-URL `.svg#id`, content type, fragment ID и корректный CDN/subpath URL. Не утверждай визуальную или a11y корректность без доступных инструментов.
Для иконок с gradients, filters, masks или внутренним CSS выполни проверки из [complex-svg.md](complex-svg.md).
## Типовые ошибки
- `Unsupported React target`: программному API передано не `'webpack'`; CLI mode должен быть ровно `react@webpack`.
- Webpack пытается отрендерить SVG как React-компонент: generated sprite попал под SVGR rule; исключи его или приоритизируй `asset/resource`.
- URL ведёт на неверный host/subpath: исправь `output.publicPath` и настройки runtime deployment, не generated-файл.
- `import.meta` не поддержан: проверь, что сборка действительно Webpack 5 и промежуточный transpiler сохраняет выражение.
- Viewer не загружает manifest: проверь literal path, chunk loading и наличие генерации до компиляции.
- `Refusing to overwrite a user file`: каталог уже содержит пользовательский `.gitignore` или файл в `.svg-sprite`; перенеси его, не обходи защиту.
- Иконка не меняет цвет: используй `color` для монохромной либо `--icon-color-N` через `<svg><use>`; CSS страницы не проникает внутрь `<img>`.
Для custom build orchestration см. [programmatic-api.md](programmatic-api.md).

View File

@@ -25,8 +25,7 @@ export const CLI_USAGE = [
' --mode <mode>',
' --name <name>',
' --description <text>',
' --input-folder <path>',
' --input-file <path> Repeat for multiple files',
' --input <path-or-glob> Repeat for multiple inputs',
' --[no-]remove-size',
' --[no-]replace-colors',
' --[no-]add-transition',
@@ -86,15 +85,12 @@ export function parseCliArgs(argv: string[]): CliArgs | { help: true } {
index = nextIndex
continue
}
if (argument === '--input-folder' || argument.startsWith('--input-folder=')) {
const [value, nextIndex] = optionValue(argv, index, '--input-folder')
overrides.inputFolder = value
index = nextIndex
continue
}
if (argument === '--input-file' || argument.startsWith('--input-file=')) {
const [value, nextIndex] = optionValue(argv, index, '--input-file')
overrides.inputFiles = [...(overrides.inputFiles ?? []), value]
if (argument === '--input' || argument.startsWith('--input=')) {
const [value, nextIndex] = optionValue(argv, index, '--input')
const input = overrides.input === undefined
? []
: Array.isArray(overrides.input) ? overrides.input : [overrides.input]
overrides.input = [...input, value]
index = nextIndex
continue
}

View File

@@ -10,8 +10,7 @@ const CONFIG_FIELDS = new Set([
'mode',
'name',
'description',
'inputFolder',
'inputFiles',
'input',
'transform',
'generatedNotice',
])
@@ -64,6 +63,9 @@ export function validateSpriteConfig(value: unknown): asserts value is SpriteCon
if ('output' in config || 'sprites' in config || 'preview' in config) {
throw configError('legacy config fields are no longer supported.')
}
if ('inputFolder' in config || 'inputFiles' in config) {
throw configError('"inputFolder" and "inputFiles" are no longer supported. Use "input".')
}
for (const field of Object.keys(config)) {
if (!CONFIG_FIELDS.has(field)) throw configError(`unknown field "${field}".`)
}
@@ -76,19 +78,14 @@ export function validateSpriteConfig(value: unknown): asserts value is SpriteCon
if (config.description !== undefined && typeof config.description !== 'string') {
throw configError('"description" must be a string.')
}
if ('icons' in config) {
throw configError('"icons" was renamed to "inputFolder".')
}
if (config.inputFolder !== undefined && (
typeof config.inputFolder !== 'string' || config.inputFolder.trim() === ''
if (config.input !== undefined && (
typeof config.input !== 'string'
? !Array.isArray(config.input)
|| config.input.length === 0
|| config.input.some((entry) => typeof entry !== 'string' || entry.trim() === '')
: config.input.trim() === ''
)) {
throw configError('"inputFolder" must be a non-empty string.')
}
if (config.inputFiles !== undefined && (
!Array.isArray(config.inputFiles)
|| config.inputFiles.some((filePath) => typeof filePath !== 'string' || filePath.trim() === '')
)) {
throw configError('"inputFiles" must be an array of non-empty strings.')
throw configError('"input" must be a non-empty string or an array of non-empty strings.')
}
if (config.transform !== undefined) {
if (
@@ -211,20 +208,20 @@ export function resolveSpriteConfig(
const name = merged.name ?? getDefaultName(rootDir)
validateSpriteName(name)
const inputFiles = (merged.inputFiles ?? []).map((filePath) => path.resolve(rootDir, filePath))
const defaultInputFolder = path.resolve(rootDir, 'icons')
const inputFolder = merged.inputFolder === undefined
&& inputFiles.length > 0
&& !fs.existsSync(defaultInputFolder)
? null
: path.resolve(rootDir, merged.inputFolder ?? 'icons')
const configuredInput = merged.input ?? 'icons'
const input = (Array.isArray(configuredInput) ? configuredInput : [configuredInput])
.map((entry) => {
const negated = entry.startsWith('!')
const resolved = path.resolve(rootDir, negated ? entry.slice(1) : entry)
const normalized = path.sep === '/' ? resolved : resolved.replaceAll(path.sep, '/')
return negated ? `!${normalized}` : normalized
})
return {
mode: merged.mode,
name,
description: merged.description,
inputFolder,
inputFiles,
input,
transform: {
removeSize: transform.removeSize ?? true,
replaceColors: transform.replaceColors ?? true,

View File

@@ -42,6 +42,8 @@ export type ModeResultMetadata = ReactModeResultMetadata | NextModeResultMetadat
export type OutputPlan = {
readonly files: readonly GeneratedFile[]
/** Создавать управляемый `.gitignore` для generated-каталога. По умолчанию: true. */
readonly createGitignore?: boolean
readonly paths: {
readonly generatedDir: '.svg-sprite'
readonly sprite: string
@@ -53,6 +55,5 @@ export type OutputPlan = {
export interface ModeAdapter<M extends SpriteMode = SpriteMode> {
readonly mode: M
readonly contractVersion: number
generate(context: ModeAdapterContext): Promise<OutputPlan>
}

View File

@@ -2,44 +2,17 @@ import { randomUUID } from 'node:crypto'
import fs from 'node:fs'
import path from 'node:path'
import type { SpriteMode } from '../targets/types.js'
import type { GeneratedFile } from './mode-adapter.js'
import {
GENERATED_MARKER,
GENERATED_NOTICE_MARKER,
GENERATOR,
} from './generated-markers.js'
import type { GeneratedFile } from './mode-adapter.js'
const STATE_PATH = '.svg-sprite/state.json'
const PREVIOUS_STATE_PATHS = [
'.svg-sprite-data/state.json',
'generated/state.json',
] as const
const LEGACY_STATE_PATH = 'generated/.svg-sprites.manifest.json'
const DATA_ROOT = '.svg-sprite'
const PREVIOUS_DATA_ROOT = '.svg-sprite-data'
const SYSTEM_FILES: readonly GeneratedFile[] = [
{
path: '.gitignore',
content: `# ${GENERATED_MARKER}. Do not edit.\n/${DATA_ROOT}/\n`,
},
]
type OutputState = {
schemaVersion: 1
generator: typeof GENERATOR
owner: {
mode: SpriteMode
contractVersion: number
}
files: string[]
warning?: string
}
type LegacyState = {
version: 1
generator: typeof GENERATOR
files: string[]
}
const DATA_PREFIX = `${DATA_ROOT}/`
const BACKUP_SUFFIX = '.old'
const STAGING_SUFFIX = '.tmp'
const GITIGNORE_CONTENT = `# ${GENERATED_MARKER}. Do not edit.\n/${DATA_ROOT}/\n`
function normalizeManagedPath(relativePath: string): string {
const normalized = relativePath.replaceAll('\\', '/')
@@ -49,9 +22,6 @@ function normalizeManagedPath(relativePath: string): string {
normalized === ''
|| path.posix.isAbsolute(normalized)
|| parts.some((part) => part === '' || part === '.' || part === '..')
|| normalized === STATE_PATH
|| PREVIOUS_STATE_PATHS.includes(normalized as typeof PREVIOUS_STATE_PATHS[number])
|| normalized === LEGACY_STATE_PATH
) {
throw new Error(`Invalid generated file path: ${relativePath}`)
}
@@ -59,23 +29,11 @@ function normalizeManagedPath(relativePath: string): string {
return normalized
}
function resolveManagedPath(rootDir: string, relativePath: string): string {
const normalized = normalizeManagedPath(relativePath)
const resolved = path.resolve(rootDir, normalized)
const relative = path.relative(rootDir, resolved)
if (relative.startsWith('..') || path.isAbsolute(relative)) {
throw new Error(`Invalid generated file path: ${relativePath}`)
}
return resolved
}
function resolveInternalPath(rootDir: string, relativePath: string): string {
function resolveInsideRoot(rootDir: string, relativePath: string): string {
const resolved = path.resolve(rootDir, relativePath)
const relative = path.relative(rootDir, resolved)
if (relative.startsWith('..') || path.isAbsolute(relative)) {
throw new Error(`Invalid internal output path: ${relativePath}`)
throw new Error(`Invalid generated path: ${relativePath}`)
}
return resolved
}
@@ -97,115 +55,8 @@ function assertNoSymlinks(rootDir: string, filePath: string): void {
}
}
function parseJsonFile(filePath: string): unknown {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf-8'))
} catch {
throw new Error(`Cannot parse generated state: ${filePath}`)
}
}
function readOutputState(statePath: string): OutputState {
const state = parseJsonFile(statePath)
if (
!state
|| typeof state !== 'object'
|| !('schemaVersion' in state)
|| state.schemaVersion !== 1
|| !('generator' in state)
|| state.generator !== GENERATOR
|| !('owner' in state)
|| !state.owner
|| typeof state.owner !== 'object'
|| !('mode' in state.owner)
|| typeof state.owner.mode !== 'string'
|| !('contractVersion' in state.owner)
|| typeof state.owner.contractVersion !== 'number'
|| !('files' in state)
|| !Array.isArray(state.files)
|| !state.files.every((file) => typeof file === 'string')
) {
throw new Error(`Invalid generated state: ${statePath}`)
}
return state as OutputState
}
function readPreviousFiles(rootDir: string): {
files: string[]
obsoleteStatePath: string | null
} {
for (const relativeStatePath of [STATE_PATH, ...PREVIOUS_STATE_PATHS]) {
const statePath = resolveInternalPath(rootDir, relativeStatePath)
assertNoSymlinks(rootDir, statePath)
if (!fs.existsSync(statePath)) continue
return {
files: readOutputState(statePath).files.map(normalizeManagedPath),
obsoleteStatePath: relativeStatePath === STATE_PATH ? null : statePath,
}
}
const legacyStatePath = resolveInternalPath(rootDir, LEGACY_STATE_PATH)
assertNoSymlinks(rootDir, legacyStatePath)
if (!fs.existsSync(legacyStatePath)) return { files: [], obsoleteStatePath: null }
const legacyState = parseJsonFile(legacyStatePath)
if (
!legacyState
|| typeof legacyState !== 'object'
|| !('version' in legacyState)
|| legacyState.version !== 1
|| !('generator' in legacyState)
|| legacyState.generator !== GENERATOR
|| !('files' in legacyState)
|| !Array.isArray(legacyState.files)
|| !legacyState.files.every((file) => typeof file === 'string')
) {
throw new Error(`Invalid generated state: ${legacyStatePath}`)
}
return {
files: (legacyState as LegacyState).files.map(normalizeManagedPath),
obsoleteStatePath: legacyStatePath,
}
}
function removeEmptyStateDirectory(rootDir: string, statePath: string): void {
const directory = path.dirname(statePath)
if (directory === rootDir) return
try {
fs.rmdirSync(directory)
} catch (error) {
if (error instanceof Error && 'code' in error && (
error.code === 'ENOENT' || error.code === 'ENOTEMPTY'
)) return
throw error
}
}
function removeEmptyManagedParents(rootDir: string, filePath: string): void {
for (const relativeDataRoot of [DATA_ROOT, PREVIOUS_DATA_ROOT]) {
const dataRoot = resolveInternalPath(rootDir, relativeDataRoot)
let directory = path.dirname(filePath)
const relative = path.relative(dataRoot, directory)
if (relative.startsWith('..') || path.isAbsolute(relative)) continue
while (directory !== dataRoot) {
try {
fs.rmdirSync(directory)
} catch (error) {
if (error instanceof Error && 'code' in error && (
error.code === 'ENOENT' || error.code === 'ENOTEMPTY'
)) return
throw error
}
directory = path.dirname(directory)
}
return
}
}
function hasGeneratedMarker(filePath: string): boolean {
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) return false
if (!fs.existsSync(filePath) || !fs.lstatSync(filePath).isFile()) return false
const content = fs.readFileSync(filePath, 'utf-8')
return content.includes(GENERATED_MARKER) || content.includes(GENERATED_NOTICE_MARKER)
}
@@ -228,70 +79,142 @@ function writeFileAtomic(rootDir: string, filePath: string, content: string | Ui
}
}
/** Применяет mode output plan и последним записывает ownership state. */
export function writeOutputPlan(
rootDir: string,
mode: SpriteMode,
contractVersion: number,
files: readonly GeneratedFile[],
generatedNotice: boolean,
): void {
const normalizedFiles = [...SYSTEM_FILES, ...files].map((file) => ({
...file,
path: normalizeManagedPath(file.path),
}))
const nextFiles = normalizedFiles.map((file) => file.path)
function validateGitignore(rootDir: string, createGitignore: boolean): void {
if (!createGitignore) return
const gitignorePath = resolveInsideRoot(rootDir, '.gitignore')
assertNoSymlinks(rootDir, gitignorePath)
if (new Set(nextFiles).size !== nextFiles.length) {
throw new Error(`Mode "${mode}" produced duplicate generated file paths.`)
}
const previous = readPreviousFiles(rootDir)
const obsoleteFiles: string[] = []
for (const file of normalizedFiles) {
const filePath = resolveManagedPath(rootDir, file.path)
assertNoSymlinks(rootDir, filePath)
if (fs.existsSync(filePath) && !hasGeneratedMarker(filePath)) {
if (fs.existsSync(gitignorePath) && !hasGeneratedMarker(gitignorePath)) {
throw new Error(
`Refusing to overwrite a user file: ${filePath}\n`
`Refusing to overwrite a user file: ${gitignorePath}\n`
+ 'Move the file or choose another sprite directory.',
)
}
}
for (const relativePath of previous.files) {
if (nextFiles.includes(relativePath)) continue
const filePath = resolveManagedPath(rootDir, relativePath)
assertNoSymlinks(rootDir, filePath)
function updateGitignore(rootDir: string, createGitignore: boolean): void {
const gitignorePath = resolveInsideRoot(rootDir, '.gitignore')
if (fs.existsSync(filePath)) {
if (!hasGeneratedMarker(filePath)) {
throw new Error(`Refusing to delete a user file: ${filePath}`)
}
obsoleteFiles.push(filePath)
if (createGitignore) {
writeFileAtomic(rootDir, gitignorePath, GITIGNORE_CONTENT)
} else if (hasGeneratedMarker(gitignorePath)) {
fs.unlinkSync(gitignorePath)
}
}
for (const file of normalizedFiles) {
writeFileAtomic(rootDir, resolveManagedPath(rootDir, file.path), file.content)
}
for (const filePath of obsoleteFiles) fs.unlinkSync(filePath)
for (const filePath of obsoleteFiles) removeEmptyManagedParents(rootDir, filePath)
if (previous.obsoleteStatePath && fs.existsSync(previous.obsoleteStatePath)) {
fs.unlinkSync(previous.obsoleteStatePath)
removeEmptyStateDirectory(rootDir, previous.obsoleteStatePath)
function transientPaths(rootDir: string, suffix: string): string[] {
return fs.readdirSync(rootDir)
.filter((entry) => entry.startsWith(`${DATA_ROOT}.`) && entry.endsWith(suffix))
.map((entry) => resolveInsideRoot(rootDir, entry))
}
const state: OutputState = {
schemaVersion: 1,
generator: GENERATOR,
owner: { mode, contractVersion },
files: nextFiles,
...(generatedNotice && {
warning: 'АВТОМАТИЧЕСКИ СГЕНЕРИРОВАННЫЙ ФАЙЛ. Не редактируйте вручную: изменения будут перезаписаны.',
}),
function recoverInterruptedReplacement(rootDir: string): void {
const outputPath = resolveInsideRoot(rootDir, DATA_ROOT)
assertNoSymlinks(rootDir, outputPath)
const backups = transientPaths(rootDir, BACKUP_SUFFIX)
.sort((left, right) => fs.lstatSync(right).mtimeMs - fs.lstatSync(left).mtimeMs)
if (!fs.existsSync(outputPath) && backups.length > 0) {
const [latestBackup] = backups.splice(0, 1)
assertNoSymlinks(rootDir, latestBackup)
fs.renameSync(latestBackup, outputPath)
}
for (const transientPath of [
...backups,
...transientPaths(rootDir, STAGING_SUFFIX),
]) {
assertNoSymlinks(rootDir, transientPath)
fs.rmSync(transientPath, { recursive: true, force: true })
}
}
function stageOutput(rootDir: string, files: readonly GeneratedFile[]): string {
const stagingPath = resolveInsideRoot(
rootDir,
`${DATA_ROOT}.${process.pid}.${randomUUID()}${STAGING_SUFFIX}`,
)
assertNoSymlinks(rootDir, stagingPath)
fs.mkdirSync(stagingPath)
try {
for (const file of files) {
const relativePath = file.path.slice(DATA_PREFIX.length)
const filePath = path.resolve(stagingPath, relativePath)
const relative = path.relative(stagingPath, filePath)
if (relative.startsWith('..') || path.isAbsolute(relative)) {
throw new Error(`Invalid generated file path: ${file.path}`)
}
fs.mkdirSync(path.dirname(filePath), { recursive: true })
fs.writeFileSync(filePath, file.content, { flag: 'wx' })
}
return stagingPath
} catch (error) {
fs.rmSync(stagingPath, { recursive: true, force: true })
throw error
}
}
function replaceOutput(rootDir: string, stagingPath: string): void {
const outputPath = resolveInsideRoot(rootDir, DATA_ROOT)
const backupPath = resolveInsideRoot(
rootDir,
`${DATA_ROOT}.${process.pid}.${randomUUID()}${BACKUP_SUFFIX}`,
)
assertNoSymlinks(rootDir, outputPath)
if (fs.existsSync(outputPath) && !fs.lstatSync(outputPath).isDirectory()) {
throw new Error(`Generated output path must be a directory: ${outputPath}`)
}
let movedPrevious = false
try {
if (fs.existsSync(outputPath)) {
fs.renameSync(outputPath, backupPath)
movedPrevious = true
}
fs.renameSync(stagingPath, outputPath)
} catch (error) {
if (movedPrevious && !fs.existsSync(outputPath) && fs.existsSync(backupPath)) {
fs.renameSync(backupPath, outputPath)
}
throw error
}
if (movedPrevious) fs.rmSync(backupPath, { recursive: true, force: true })
}
/** Полностью заменяет generated-каталог output plan. */
export function writeOutputPlan(
rootDir: string,
mode: SpriteMode,
files: readonly GeneratedFile[],
createGitignore = true,
): void {
const normalizedFiles = files.map((file) => ({
...file,
path: normalizeManagedPath(file.path),
}))
const paths = normalizedFiles.map((file) => file.path)
if (paths.some((filePath) => !filePath.startsWith(DATA_PREFIX))) {
throw new Error(`Mode "${mode}" produced a file outside ${DATA_ROOT}.`)
}
if (new Set(paths).size !== paths.length) {
throw new Error(`Mode "${mode}" produced duplicate generated file paths.`)
}
recoverInterruptedReplacement(rootDir)
validateGitignore(rootDir, createGitignore)
const stagingPath = stageOutput(rootDir, normalizedFiles)
try {
replaceOutput(rootDir, stagingPath)
updateGitignore(rootDir, createGitignore)
} finally {
if (fs.existsSync(stagingPath)) {
fs.rmSync(stagingPath, { recursive: true, force: true })
}
}
const statePath = resolveInternalPath(rootDir, STATE_PATH)
writeFileAtomic(rootDir, statePath, `${JSON.stringify(state, null, 2)}\n`)
}

Some files were not shown because too many files have changed in this diff Show More