feat: добавить серверную генерацию спрайтов

This commit is contained in:
2026-07-16 09:14:11 +03:00
parent e9ae91815a
commit 57342fae4e
379 changed files with 3108 additions and 655 deletions

View File

@@ -5,7 +5,7 @@ can also be used unchanged by AI skills.
The common format for JSON, JavaScript, and TypeScript config files is described in the [configuration guide](configuration.md).
## Quick Start Guides
## Consumer Quick Starts
| Project | Exact mode | Guide |
|---|---|---|
@@ -39,12 +39,16 @@ The common format for JSON, JavaScript, and TypeScript config files is described
| 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:
Every consumer guide follows the same order:
1. Generate the sprite through `npx` without adding the package to the project.
2. Use the sprite in the application.
3. Optionally add the Viewer for debugging and previews.
## Server-Side Generation
Use [`standalone@server`](guides/standalone-server.md) to generate a universal SVG sprite on a server or in CI/CD for all consumer modes.
## Reference
- [Configuration](configuration.md)

View File

@@ -32,6 +32,7 @@ JSON works for most projects and does not require installing the package locally
| Field | Default | Purpose |
|---|---|---|
| `mode` | None | Exact mode matching the framework and bundler |
| `source` | `local` | `local` for source SVG files or `remote` for a `standalone@server` manifest |
| `name` | Kebab-case module directory name; for `svg-sprite` and `svg-sprites`, the parent directory name | Sprite name; in modes with a component, it also determines the component and type names |
| `description` | None | Description used in types and the Viewer |
| `input` | `./icons` | Directory, SVG file, glob pattern, or array of sources |
@@ -40,6 +41,45 @@ JSON works for most projects and does not require installing the package locally
Paths and glob patterns in `input` are resolved relative to the config file's directory. A pattern prefixed with `!` excludes matches.
## Remotely built sprite
A consumer config for a server manifest only contains the mode, source, and input:
```json
{
"mode": "react@vite",
"source": "remote",
"input": "https://assets.example/releases/app/svg-sprite.manifest.json"
}
```
`input` accepts one HTTP(S) URL or local manifest path. The name, description,
transforms, and generated notice come from the manifest. The generator downloads
and verifies the matching SVG profile before the adapter creates its normal local
components, types, and bundler asset.
## Server build
`standalone@server` combines local paths/globs with HTTP(S) SVG descriptors:
```js
export default {
mode: 'standalone@server',
name: 'app',
input: [
'./icons/**/*.svg',
{
name: 'remote-logo',
url: 'https://assets.example/logo.svg',
},
],
}
```
The mode creates two content-addressed SVG profiles and `svg-sprite.manifest.json`.
`sha256` is optional for HTTP inputs; when present, it must be the expected 64-character
hexadecimal SHA-256 digest and the build verifies the received bytes.
## JavaScript
A JavaScript config default-exports a plain object:

View File

@@ -1,5 +1,7 @@
# Quick Start Guides
## Consumer Applications
- `standalone`: [bare standalone](standalone.md)
- `standalone@vite`: [standalone with Vite](standalone-vite.md)
- `standalone@webpack`: [standalone with Webpack](standalone-webpack.md)
@@ -29,3 +31,7 @@
- `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)
## Server-Side Generation
- `standalone@server`: [generate a universal sprite on the server](standalone-server.md)

View File

@@ -0,0 +1,113 @@
# Universal SVG Sprite Generated on a Server
Generate a universal SVG sprite in CI or a server worker for applications that use different frameworks and bundlers.
## Generate the sprite
You do not need to install the package in the worker.
### 1. Prepare the workspace
Place the source SVGs in the current workspace's `icons` directory:
```text
.
└── icons/
├── search.svg
└── settings.svg
```
Each filename without the extension becomes an icon name.
### 2. Run generation
Pass the mode, sprite name, and SVG path through the CLI:
```bash
npx --yes @gromlab/svg-sprites \
--mode standalone@server \
--name app \
--input './icons/**/*.svg' \
.
```
This worker workflow does not need a config file. The result appears in `./.svg-sprite`:
```text
.
├── icons/
│ ├── search.svg
│ └── settings.svg
└── .svg-sprite/
├── sprite.<content-hash>.svg
├── sprite-root-viewbox.<content-hash>.svg
└── svg-sprite.manifest.json
```
### 3. Publish the result
Upload the contents of `.svg-sprite` to a dedicated S3 bucket directory:
```bash
aws s3 sync ./.svg-sprite/ s3://my-bucket/app-icons/
```
The same directory can be served through a CDN. The public URL does not contain a `.svg-sprite` segment:
```text
https://cdn.example.com/app-icons/
├── sprite.<content-hash>.svg
├── sprite-root-viewbox.<content-hash>.svg
└── svg-sprite.manifest.json
```
You can also run `standalone@server` through a JSON, JavaScript, or TypeScript config. A config is useful for persistent settings, local SVGs from several directories, and SVGs loaded over HTTP(S).
## Use the sprite
Create a regular config in the consumer application. For example, with React and Vite:
```text
src/app-icons/
├── index.ts
└── svg-sprite.config.json
```
Set the consumer mode and the CDN manifest URL:
```json
{
"mode": "react@vite",
"source": "remote",
"input": "https://cdn.example.com/app-icons/svg-sprite.manifest.json"
}
```
Add a user-owned entry point:
```ts
// src/app-icons/index.ts
export * from './.svg-sprite/index.js'
```
Run normal generation:
```bash
npx --yes @gromlab/svg-sprites src/app-icons/svg-sprite.config.json
```
Then use the generated component exactly as with a sprite built from local SVGs:
```tsx
import { AppIcon } from './app-icons'
export function SearchButton() {
return <AppIcon icon="search" aria-label="Search" />
}
```
The same CDN manifest works with all 29 consumer modes. Each one preserves the native API of its selected framework and bundler.
## Debug and preview
`standalone@server` does not create a separate icon preview page. Connect the published sprite to a consumer application and open it in SpriteViewer: the remote set appears in the same way as a local one.

View File

@@ -30,7 +30,8 @@ result.manifestPath
Next.js modes additionally return `router` and `bundler`.
For bare `standalone`, `target` is `static`; standalone bundler and React modes
return `vite` or `webpack`; Next.js modes return their full exact mode as the
target.
target. `standalone@server` returns `server`; its `spritePath` identifies the
standard content-addressed profile and `manifestPath` identifies the server manifest.
For static standalone mode, use `result.spritePath` in a build script to publish the
SVG under an application URL:
@@ -105,6 +106,11 @@ export default defineSpriteConfig({
`defineSpriteConfig` is an identity helper for TypeScript autocomplete. JavaScript can export the same object with `export default`, while JSON contains the object directly.
The public `ServerSvgInput`, `ServerSpriteManifest`, `ServerSpriteAsset`, and
`SpriteCompileProfile` types describe `standalone@server` inputs and release data.
A consumer uses the same API with `source: 'remote'` and one local path or HTTP(S)
manifest URL in `input`.
## Specialized wrappers
The specialized functions are available as wrappers around `generateSprite`:

View File

@@ -69,6 +69,7 @@ svg-sprites [options] <config-file-or-directory>
| Static HTML / custom publishing | `standalone` |
| Standalone + Vite | `standalone@vite` |
| Standalone + Webpack 5 | `standalone@webpack` |
| Server release | `standalone@server` |
| React + Vite | `react@vite` |
| React + Webpack 5 | `react@webpack` |
| Vue + Vite | `vue@vite` |
@@ -100,7 +101,7 @@ The config file may have any name and use the `.ts`, `.js`, or `.json` extension
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`.
`--help` and `-h` print usage information without requiring a path. Generation 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.
`--help` and `-h` print usage information without requiring a path. Generation options are `--mode`, `--source <local|remote>`, `--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:
@@ -138,12 +139,20 @@ export default defineSpriteConfig({
| Option | Type | Default | Purpose |
|---|---|---|---|
| `mode` | `SpriteMode` | None | Generation mode; may be supplied by CLI/API |
| `source` | `local \| remote` | `local` | Source SVG files or a ready server manifest |
| `name` | `string` | Derived from the directory | Sprite name; in modes with a component, it also determines the component and public type names |
| `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 |
| `input` | `SpriteInput \| SpriteInput[]` | `./icons` | Local SVG sources, server HTTP descriptors, or one remote manifest, depending on mode and source |
| `transform` | `TransformOptions` | All enabled | SVG preparation settings |
| `generatedNotice` | `boolean` | `true` | Full or abbreviated warning in generated files |
With `source: 'remote'`, `input` contains one local path or HTTP(S) URL to a
manifest produced by `standalone@server`. A remote consumer config may contain
only `mode`, `source`, and `input`: the name, description, transforms, and generated
notice are verified and inherited from the server manifest. Generation downloads
the profile required by the exact consumer mode and verifies its SHA-256 and byte
length before codegen. There is no runtime network dependency on the server manifest.
### Sprite name
`name` is written in kebab-case and must start with an ASCII letter:
@@ -176,6 +185,26 @@ Supported glob syntax includes:
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.
### Server SVG inputs
`standalone@server` accepts the same local strings plus HTTP(S) descriptors in its
`input` array:
```ts
{
name: 'brand-logo',
url: 'https://assets.example.com/brand-logo.svg',
sha256: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
}
```
`name` becomes the public icon name. `sha256` is optional and, when present, is
checked against the downloaded bytes. URL credentials and active SVG content such
as scripts, event handlers, `foreignObject`, or a doctype are rejected. One HTTP
source is limited to 2 MiB, all combined sources to 25 MiB, and requests time out
after 15 seconds. Local and HTTP entries share one namespace, so duplicate icon
names fail generation.
## Generated module
After generation, a React or Next.js sprite directory looks like this:
@@ -225,6 +254,20 @@ runtime asset and deployment-neutral manifest data:
native generated Web Component with no external runtime dependencies. Bare
`standalone` intentionally does not generate a JavaScript component.
`standalone@server` creates a publishable release without JavaScript runtime or
`.gitignore`:
```text
.svg-sprite/
├── sprite.<content-hash>.svg
├── sprite-root-viewbox.<content-hash>.svg
└── svg-sprite.manifest.json
```
The manifest identifies both compile profiles by relative `href`, full SHA-256,
and byte length. Publish the complete directory atomically; consumers resolve each
profile relative to the manifest URL or local manifest path.
The generator fully manages `.svg-sprite` and replaces the whole directory on every generation through a staged write with rollback on replacement failure. Any files added inside it are deleted during the next generation. Keep user-owned files alongside it, for example in a root `index.ts` barrel:
```ts