diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c25dc96741..8280246b78 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -265,7 +265,7 @@ The project supports three types of icons, all with automatic imports (no manual 2. **Iconify Icons** - 200,000+ icons from various libraries: ``, `` 3. **Custom Icons** - Your own SVG icons: `` -Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `src/assets/icons/custom/`. +Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `src/assets/icons/custom/` and processed by `build/customIconCollection.ts` with automatic validation. For detailed instructions and code examples, see [src/assets/icons/README.md](src/assets/icons/README.md). diff --git a/build/customIconCollection.js b/build/customIconCollection.js deleted file mode 100644 index ae52b97ae0..0000000000 --- a/build/customIconCollection.js +++ /dev/null @@ -1,29 +0,0 @@ -import { readFileSync, readdirSync } from 'fs' -import { join } from 'path' -import { dirname } from 'path' -import { fileURLToPath } from 'url' - -const fileName = fileURLToPath(import.meta.url) -const dirName = dirname(fileName) -const customIconsPath = join(dirName, '..', 'src', 'assets', 'icons', 'custom') - -// Create an Iconify collection for custom icons -export const iconCollection = { - prefix: 'comfy', - icons: {}, - width: 16, - height: 16 -} - -// Read all SVG files from the custom icons directory -const files = readdirSync(customIconsPath) -files.forEach((file) => { - if (file.endsWith('.svg')) { - const name = file.replace('.svg', '') - const content = readFileSync(join(customIconsPath, file), 'utf-8') - - iconCollection.icons[name] = { - body: content - } - } -}) diff --git a/build/customIconCollection.ts b/build/customIconCollection.ts new file mode 100644 index 0000000000..f2d823ed5d --- /dev/null +++ b/build/customIconCollection.ts @@ -0,0 +1,100 @@ +import { existsSync, readFileSync, readdirSync } from 'fs' +import { join } from 'path' +import { dirname } from 'path' +import { fileURLToPath } from 'url' + +const fileName = fileURLToPath(import.meta.url) +const dirName = dirname(fileName) +const customIconsPath = join(dirName, '..', 'src', 'assets', 'icons', 'custom') + +// Iconify collection structure +interface IconifyIcon { + body: string + width?: number + height?: number +} + +interface IconifyCollection { + prefix: string + icons: Record + width?: number + height?: number +} + +// Create an Iconify collection for custom icons +export const iconCollection: IconifyCollection = { + prefix: 'comfy', + icons: {}, + width: 16, + height: 16 +} + +/** + * Validates that an SVG file contains valid SVG content + */ +function validateSvgContent(content: string, filename: string): void { + if (!content.trim()) { + throw new Error(`Empty SVG file: ${filename}`) + } + + if (!content.includes(' tag): ${filename}`) + } + + // Basic XML structure validation + const openTags = (content.match(/]*>/g) || []).length + const closeTags = (content.match(/<\/svg>/g) || []).length + + if (openTags !== closeTags) { + throw new Error(`Malformed SVG file (mismatched svg tags): ${filename}`) + } +} + +/** + * Loads custom SVG icons from the icons directory + */ +function loadCustomIcons(): void { + if (!existsSync(customIconsPath)) { + console.warn(`Custom icons directory not found: ${customIconsPath}`) + return + } + + try { + const files = readdirSync(customIconsPath) + const svgFiles = files.filter((file) => file.endsWith('.svg')) + + if (svgFiles.length === 0) { + console.warn('No SVG files found in custom icons directory') + return + } + + svgFiles.forEach((file) => { + const name = file.replace('.svg', '') + const filePath = join(customIconsPath, file) + + try { + const content = readFileSync(filePath, 'utf-8') + validateSvgContent(content, file) + + iconCollection.icons[name] = { + body: content + } + } catch (error) { + console.error( + `Failed to load custom icon ${file}:`, + error instanceof Error ? error.message : error + ) + // Continue loading other icons instead of failing the entire build + } + }) + } catch (error) { + console.error( + 'Failed to read custom icons directory:', + error instanceof Error ? error.message : error + ) + // Don't throw here - allow build to continue without custom icons + } +} + +// Load icons when this module is imported +loadCustomIcons() diff --git a/package-lock.json b/package-lock.json index 2031b3341d..2193dd5fc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2344,7 +2344,6 @@ "resolved": "https://registry.npmjs.org/@iconify/tailwind/-/tailwind-1.2.0.tgz", "integrity": "sha512-KgpIHWOTcRYw1XcoUqyNSrmYyfLLqZYu3AmP8zdfLk0F5TqRO8YerhlvlQmGfn7rJXgPeZN569xPAJnJ53zZxA==", "dev": true, - "license": "MIT", "dependencies": { "@iconify/types": "^2.0.0" }, diff --git a/src/assets/icons/README.md b/src/assets/icons/README.md index cd92f1ddff..b01a3e3ef6 100644 --- a/src/assets/icons/README.md +++ b/src/assets/icons/README.md @@ -247,9 +247,29 @@ Icons are automatically imported using `unplugin-icons` - no manual imports need ### Configuration -The icon system is configured in `vite.config.mts`: +The icon system has two layers: + +1. **Build-time Processing** (`build/customIconCollection.ts`): + - Scans `src/assets/icons/custom/` for SVG files + - Validates SVG content and structure + - Creates Iconify collection for Tailwind CSS + - Provides error handling for malformed files + +2. **Vite Runtime** (`vite.config.mts`): + - Enables direct SVG import as Vue components + - Supports dynamic icon loading ```typescript +// Build script creates Iconify collection +export const iconCollection: IconifyCollection = { + prefix: 'comfy', + icons: { + 'workflow': { body: '...' }, + 'node': { body: '...' } + } +} + +// Vite configuration for component-based usage Icons({ compiler: 'vue3', customCollections: { @@ -271,8 +291,9 @@ Icons are fully typed. If TypeScript doesn't recognize a new custom icon: ### Icon Not Showing 1. **Check filename**: Must be kebab-case without special characters 2. **Restart dev server**: Required after adding new icons -3. **Verify SVG**: Ensure it's valid SVG syntax +3. **Verify SVG**: Ensure it's valid SVG syntax (build script validates automatically) 4. **Check console**: Look for Vue component resolution errors +5. **Build script errors**: Check console during build - malformed SVGs are logged but don't break builds ### Icon Wrong Color - Replace hardcoded colors with `currentColor` diff --git a/tailwind.config.js b/tailwind.config.js index 19cb6507de..9c809eaa80 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,7 +1,7 @@ /** @type {import('tailwindcss').Config} */ import { addDynamicIconSelectors } from '@iconify/tailwind' -import { iconCollection } from './build/customIconCollection.js' +import { iconCollection } from './build/customIconCollection.ts' export default { content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],