2026-04-21 23:07:34 +03:00
|
|
|
|
import fs from 'node:fs'
|
|
|
|
|
|
import path from 'node:path'
|
|
|
|
|
|
import SVGSpriter from 'svg-sprite'
|
2026-07-11 07:00:59 +03:00
|
|
|
|
import { getSpriteShapeId } from './shape-id.js'
|
2026-04-21 23:07:34 +03:00
|
|
|
|
import { createShapeTransform } from './transforms.js'
|
2026-07-11 07:00:59 +03:00
|
|
|
|
import type { SpriteFolder, SpriteFormat, TransformOptions } from './types.js'
|
|
|
|
|
|
|
|
|
|
|
|
export type CompileSpriteOptions = {
|
|
|
|
|
|
/** Добавлять вычисленный viewBox корневому stack-спрайту. */
|
|
|
|
|
|
rootViewBox?: boolean
|
|
|
|
|
|
}
|
2026-04-21 23:07:34 +03:00
|
|
|
|
|
|
|
|
|
|
/** Конфигурация режима для svg-sprite. */
|
2026-07-11 07:00:59 +03:00
|
|
|
|
function getModeConfig(
|
|
|
|
|
|
format: SpriteFormat,
|
|
|
|
|
|
destDir: string,
|
|
|
|
|
|
name: string,
|
|
|
|
|
|
options: CompileSpriteOptions,
|
|
|
|
|
|
) {
|
2026-04-21 23:07:34 +03:00
|
|
|
|
return {
|
|
|
|
|
|
dest: destDir,
|
2026-04-22 16:54:35 +03:00
|
|
|
|
sprite: `${name}.sprite.svg`,
|
2026-04-21 23:07:34 +03:00
|
|
|
|
example: false,
|
2026-07-11 07:00:59 +03:00
|
|
|
|
rootviewbox: options.rootViewBox ?? false,
|
2026-04-21 23:07:34 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-22 16:54:35 +03:00
|
|
|
|
/** Строит массив shape.transform. */
|
|
|
|
|
|
function buildShapeTransforms(transform: TransformOptions = {}) {
|
2026-04-21 23:07:34 +03:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
2026-04-22 16:54:35 +03:00
|
|
|
|
const transforms: any[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
svgo: {
|
|
|
|
|
|
plugins: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: 'preset-default',
|
|
|
|
|
|
params: {
|
|
|
|
|
|
overrides: {
|
|
|
|
|
|
removeViewBox: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
createShapeTransform(transform),
|
|
|
|
|
|
]
|
2026-04-21 23:07:34 +03:00
|
|
|
|
|
|
|
|
|
|
return transforms
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Компилирует папку с SVG-файлами в спрайт.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Возвращает путь к сгенерированному SVG-файлу.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function compileSprite(
|
|
|
|
|
|
folder: SpriteFolder,
|
|
|
|
|
|
outputDir: string,
|
|
|
|
|
|
transform: TransformOptions = {},
|
2026-07-11 07:00:59 +03:00
|
|
|
|
options: CompileSpriteOptions = {},
|
2026-04-21 23:07:34 +03:00
|
|
|
|
): Promise<string> {
|
2026-07-11 07:00:59 +03:00
|
|
|
|
const contents = await compileSpriteContent(folder, transform, options)
|
|
|
|
|
|
const spritePath = path.join(outputDir, `${folder.name}.sprite.svg`)
|
|
|
|
|
|
|
|
|
|
|
|
fs.mkdirSync(outputDir, { recursive: true })
|
|
|
|
|
|
fs.writeFileSync(spritePath, contents)
|
|
|
|
|
|
|
|
|
|
|
|
return spritePath
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Компилирует SVG-спрайт в памяти, не изменяя файловую систему. */
|
|
|
|
|
|
export async function compileSpriteContent(
|
|
|
|
|
|
folder: SpriteFolder,
|
|
|
|
|
|
transform: TransformOptions = {},
|
|
|
|
|
|
options: CompileSpriteOptions = {},
|
|
|
|
|
|
): Promise<Uint8Array> {
|
2026-04-21 23:07:34 +03:00
|
|
|
|
const config = {
|
|
|
|
|
|
shape: {
|
2026-07-11 07:00:59 +03:00
|
|
|
|
id: {
|
|
|
|
|
|
generator: (filePath: string) => getSpriteShapeId(filePath),
|
|
|
|
|
|
},
|
|
|
|
|
|
dimension: {
|
|
|
|
|
|
attributes: transform.removeSize === false,
|
|
|
|
|
|
},
|
2026-04-21 23:07:34 +03:00
|
|
|
|
transform: buildShapeTransforms(transform),
|
|
|
|
|
|
},
|
|
|
|
|
|
mode: {
|
2026-07-11 07:00:59 +03:00
|
|
|
|
[folder.format]: getModeConfig(folder.format, '.', folder.name, options),
|
2026-04-21 23:07:34 +03:00
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-11 07:00:59 +03:00
|
|
|
|
let spriteContents: Uint8Array | undefined
|
2026-04-21 23:07:34 +03:00
|
|
|
|
|
|
|
|
|
|
for (const modeResult of Object.values(result)) {
|
|
|
|
|
|
for (const resource of Object.values(
|
2026-07-11 07:00:59 +03:00
|
|
|
|
modeResult as Record<string, { path: string; contents: Uint8Array }>,
|
2026-04-21 23:07:34 +03:00
|
|
|
|
)) {
|
|
|
|
|
|
if (resource.path.endsWith('.svg')) {
|
2026-07-11 07:00:59 +03:00
|
|
|
|
spriteContents = resource.contents
|
2026-04-21 23:07:34 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-11 07:00:59 +03:00
|
|
|
|
if (!spriteContents) {
|
|
|
|
|
|
reject(new Error(`Failed to compile sprite "${folder.name}".`))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
resolve(spriteContents)
|
2026-04-21 23:07:34 +03:00
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|