import fs from 'node:fs' import path from 'node:path' import type { SpriteResult } from './types.js' /** Извлекает id иконок из SVG-спрайта. */ function extractIconIds(spritePath: string): string[] { const content = fs.readFileSync(spritePath, 'utf-8') const ids: string[] = [] const regex = /<(?:svg|symbol)\b[^>]*\bid="([^"]+)"/g let match: RegExpExecArray | null while ((match = regex.exec(content)) !== null) { ids.push(match[1]) } return ids.sort() } /** * Извлекает CSS-переменные var(--icon-color-N, fallback) из фрагмента SVG для конкретной иконки. * * Возвращает массив { varName, fallback } для каждой уникальной переменной. */ function extractIconVars(svgFragment: string): { varName: string; fallback: string }[] { const vars = new Map() const regex = /var\((--icon-color-\d+),\s*([^)]+)\)/g let match: RegExpExecArray | null while ((match = regex.exec(svgFragment)) !== null) { if (!vars.has(match[1])) { vars.set(match[1], match[2].trim()) } } return [...vars.entries()].map(([varName, fallback]) => ({ varName, fallback })) } /** * Парсит SVG-спрайт и возвращает маппинг id → SVG-фрагмент для каждой иконки. */ function extractIconFragments(spritePath: string): Map { const content = fs.readFileSync(spritePath, 'utf-8') const fragments = new Map() // Матчим ... или ... const regex = /<(?:svg|symbol)\b[^>]*\bid="([^"]+)"[^>]*>[\s\S]*?<\/(?:svg|symbol)>/g let match: RegExpExecArray | null while ((match = regex.exec(content)) !== null) { fragments.set(match[1], match[0]) } return fragments } /** Подготавливает SVG-спрайт для инлайна — конвертирует вложенные в . */ function prepareInlineSprite(spritePath: string): string { let content = fs.readFileSync(spritePath, 'utf-8') content = content.replace(/<\?xml[^?]*\?>\s*/g, '') content = content.replace(/' : '' } depth++ if (depth > 1) { const cleanAttrs = attrs.replace(/\s*xmlns="[^"]*"/g, '') return `` } return `` }) return content } /** Конвертирует CSS-цвет в hex для input[type=color]. */ function colorToHex(color: string): string { const named: Record = { red: '#ff0000', blue: '#0000ff', green: '#008000', white: '#ffffff', black: '#000000', yellow: '#ffff00', cyan: '#00ffff', magenta: '#ff00ff', orange: '#ffa500', purple: '#800080', pink: '#ffc0cb', gray: '#808080', grey: '#808080', currentcolor: '#000000', } const lower = color.toLowerCase().trim() if (lower.startsWith('#')) { if (lower.length === 4) { return `#${lower[1]}${lower[1]}${lower[2]}${lower[2]}${lower[3]}${lower[3]}` } return lower } return named[lower] || '#000000' } interface IconData { id: string vars: { varName: string; fallback: string }[] } interface SpriteGroup { name: string mode: string spritePath: string icons: IconData[] } /** Генерирует HTML-строку превью. */ function renderHtml(groups: SpriteGroup[]): string { const totalIcons = groups.reduce((sum, g) => sum + g.icons.length, 0) const inlineSprites = groups .map((g) => prepareInlineSprite(g.spritePath)) .join('\n') // Собираем JSON с данными переменных для JS const iconsData: Record = {} for (const group of groups) { for (const icon of group.icons) { iconsData[icon.id] = icon.vars } } const sections = groups .map((group) => { const cards = group.icons .map((icon) => { const varsHtml = icon.vars.length > 0 ? `
${icon.vars.map((v) => { const isCurrentColor = v.fallback.toLowerCase() === 'currentcolor' const hex = colorToHex(v.fallback) return `` }).join('')}
` : '
no color vars
' return `
${icon.id} ${varsHtml}
` }) .join('') return `

${group.name} ${group.mode} ${group.icons.length}

${cards}
` }) .join('\n') return ` SVG Sprites Preview — ${totalIcons} icons ${inlineSprites}

SVG Sprites

${totalIcons} icons
${sections}
` } /** * Генерирует HTML-файл превью для всех спрайтов. * * Возвращает путь к сгенерированному файлу. */ export function generatePreview( results: SpriteResult[], outputDir: string, ): string { const groups: SpriteGroup[] = results.map((r) => { const fragments = extractIconFragments(r.spritePath) const ids = extractIconIds(r.spritePath) const icons: IconData[] = ids.map((id) => ({ id, vars: extractIconVars(fragments.get(id) || ''), })) return { name: r.name, mode: r.mode, spritePath: r.spritePath, icons, } }) const html = renderHtml(groups) const outputPath = path.join(outputDir, 'preview.html') fs.mkdirSync(outputDir, { recursive: true }) fs.writeFileSync(outputPath, html) return outputPath }