feat: инициализировать пакет генерации SVG-спрайтов

- создан NPM-пакет @gromlab/svg-sprites (ESM, TypeScript)
- реализован CLI через citty и программный API
- добавлена компиляция SVG в спрайты (stack/symbol) через svg-sprite
- добавлена генерация TypeScript union-типов имён иконок
- реализованы SVG-трансформации: замена цветов на CSS-переменные,
  удаление width/height, добавление transition к элементам с цветом
- добавлен генератор HTML-превью с color picker-ами, авто-темой,
  синхронизацией currentColor с темой и поиском по иконкам
- добавлены тестовые SVG-файлы (icons, logos)
This commit is contained in:
2026-04-21 23:07:34 +03:00
commit aad1c97f50
24 changed files with 3888 additions and 0 deletions

85
src/compiler.ts Normal file
View File

@@ -0,0 +1,85 @@
import fs from 'node:fs'
import path from 'node:path'
import SVGSpriter from 'svg-sprite'
import { createShapeTransform } from './transforms.js'
import type { SpriteFolder, SpriteMode, TransformOptions } from './types.js'
/** Конфигурация режима для svg-sprite. */
function getModeConfig(mode: SpriteMode, destDir: string) {
return {
dest: destDir,
sprite: `sprite.${mode}.svg`,
example: false,
rootviewbox: false,
}
}
/** Строит массив shape.transform на основе опций. */
function buildShapeTransforms(transform: TransformOptions) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const transforms: any[] = ['svgo']
const hasCustomTransform =
transform.removeSize !== false ||
transform.replaceColors !== false ||
transform.addTransition !== false
if (hasCustomTransform) {
transforms.push(createShapeTransform(transform))
}
return transforms
}
/**
* Компилирует папку с SVG-файлами в спрайт.
*
* Возвращает путь к сгенерированному SVG-файлу.
*/
export async function compileSprite(
folder: SpriteFolder,
outputDir: string,
transform: TransformOptions = {},
): Promise<string> {
const destDir = path.join(outputDir, folder.name)
const config = {
shape: {
transform: buildShapeTransforms(transform),
},
mode: {
[folder.mode]: getModeConfig(folder.mode, destDir),
},
}
const spriter = new SVGSpriter(config)
for (const filePath of folder.files) {
spriter.add(filePath, null, fs.readFileSync(filePath, 'utf-8'))
}
return new Promise((resolve, reject) => {
spriter.compile((error, result) => {
if (error) {
reject(error)
return
}
let spritePath = ''
for (const modeResult of Object.values(result)) {
for (const resource of Object.values(
modeResult as Record<string, { path: string; contents: Buffer }>,
)) {
fs.mkdirSync(path.dirname(resource.path), { recursive: true })
fs.writeFileSync(resource.path, resource.contents)
if (resource.path.endsWith('.svg')) {
spritePath = resource.path
}
}
}
resolve(spritePath)
})
})
}