/** * Vite SVG Sprite Generator Plugin * Production-ready plugin for automatic SVG sprite generation * with HMR support, SVGO optimization, and security features * * @version 1.3.0 * @package vite-svg-sprite-generator-plugin * * @changelog v1.3.0 * - IMPROVED: Aligned with Vite best practices (enforce, apply, createFilter) * - OPTIMIZED: Parallel SVG processing for 2-3x faster builds (50+ icons) * - FIXED: TypeScript types - added HMR event types, fixed ctx.filename * - REMOVED: Manual preview mode detection (handled by apply() now) * - IMPROVED: Using createFilter from Vite for better file filtering * * @changelog v1.2.1 * - FIXED: Per-page tree-shaking - each HTML page now gets only its own icons * - Added findUsedIconIdsInFile() for per-file icon detection * - transformIndexHtml now analyzes each HTML file separately * - Example: about.html uses only "search" → gets only "search" icon in sprite * - Cached per-page sprites for performance * * @changelog v1.2.0 * - Added tree-shaking support: include only used icons in production builds * - Scans HTML/JS/TS files to find used icon IDs () * - Zero external dependencies - uses built-in fs/promises for file scanning * - Works ONLY in production mode (dev includes all icons for DX) * - New options: treeShaking (default: false), scanExtensions (default: ['.html', '.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte']) * - Compatible with vite-multi-page-html-generator-plugin - no conflicts * * @changelog v1.1.9 * - Added currentColor option (default: true) for SVGO to convert colors to currentColor * - Allows easy color control via CSS (e.g., .icon { color: red; }) * - Works only when SVGO is installed and svgoOptimize is enabled * * @changelog v1.1.8 * - Synchronized with JS version: added SECURITY_PATTERNS, readFileSafe, improved security * * @changelog v1.1.7 * - Updated version for publication * * @changelog v1.1.6 * - FIXED: Preview mode detection now works correctly * - Preview detected as: serve + production + !SSR * - Added debug logging for mode detection * - Confirmed: Preview mode skips validation (0ms) * * @changelog v1.1.4 * - Intelligent mode detection for preview command * - Preview mode skips unnecessary operations (0ms vs 583ms) * - Automatic command detection (serve/build/preview) * * @changelog v1.1.1 * - Using vite.normalizePath for better cross-platform compatibility * * @changelog v1.1.0 * - Path traversal protection via validateIconsPath() * - All FS operations are now async (no event loop blocking) * - Precompiled RegExp patterns (~20% faster sanitization) * - New configResolved() hook for early validation * - Enhanced error messages with examples * * Note: This is the TypeScript source file. * The main distribution file is vite-svg-sprite-generator-plugin.js */ import { readFile, readdir, stat, access } from 'fs/promises'; import { join, extname, basename, resolve, relative, isAbsolute } from 'path'; import { createHash } from 'crypto'; import { normalizePath, createFilter } from 'vite'; import type { Plugin, ViteDevServer, IndexHtmlTransformContext, ResolvedConfig } from 'vite'; // Опциональный импорт SVGO type SVGOConfig = any; type OptimizeResult = { data: string }; /** * Опции для SVG Sprite плагина */ export interface SvgSpriteOptions { /** Путь к папке с иконками (по умолчанию: 'src/icons') */ iconsFolder?: string; /** ID для SVG спрайта (по умолчанию: 'sprite-id') */ spriteId?: string; /** CSS класс для SVG спрайта (по умолчанию: 'sprite-class') */ spriteClass?: string; /** Префикс для ID символов (по умолчанию: '' - только имя файла) */ idPrefix?: string; /** Отслеживать изменения в dev режиме (по умолчанию: true) */ watch?: boolean; /** Задержка debounce для HMR (по умолчанию: 100ms) */ debounceDelay?: number; /** Подробное логирование (по умолчанию: только в dev) */ verbose?: boolean; /** Оптимизация SVGO (по умолчанию: только в production, если svgo установлен) */ svgoOptimize?: boolean; /** Настройки SVGO (опционально) */ svgoConfig?: SVGOConfig; /** Конвертировать цвета в currentColor для управления через CSS (по умолчанию: true) */ currentColor?: boolean; /** * Tree-shaking: включать только используемые иконки (по умолчанию: false) * Сканирует HTML/JS/TS файлы и находит все * Работает только в production режиме для оптимизации bundle size */ treeShaking?: boolean; /** * Расширения файлов для сканирования при tree-shaking * (по умолчанию: ['.html', '.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte']) */ scanExtensions?: string[]; } /** * Результат парсинга SVG файла */ interface ParsedSVG { viewBox: string; content: string; } // Дефолтные опции плагина const defaultOptions: Required = { iconsFolder: 'src/icons', spriteId: 'sprite-id', spriteClass: 'sprite-class', idPrefix: '', watch: true, debounceDelay: 100, verbose: process.env.NODE_ENV === 'development', svgoOptimize: process.env.NODE_ENV === 'production', svgoConfig: undefined, currentColor: true, treeShaking: false, scanExtensions: ['.html', '.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte'] }; // Размеры кэша const MAX_CACHE_SIZE = 1000; /** * Предкомпилированные RegExp паттерны для санитизации SVG * Компилируются один раз при загрузке модуля для оптимизации производительности * Дает ~20% улучшение для проектов с большим количеством файлов */ const SECURITY_PATTERNS = Object.freeze({ /** Удаляет