Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: `<i-lucide:settings />`, `<i-mdi:folder />`
3. **Custom Icons** - Your own SVG icons: `<i-comfy:workflow />`

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).

Expand Down
29 changes: 0 additions & 29 deletions build/customIconCollection.js

This file was deleted.

100 changes: 100 additions & 0 deletions build/customIconCollection.ts
Original file line number Diff line number Diff line change
@@ -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<string, IconifyIcon>
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('<svg')) {
throw new Error(`Invalid SVG file (missing <svg> tag): ${filename}`)
}

// Basic XML structure validation
const openTags = (content.match(/<svg[^>]*>/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()
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 23 additions & 2 deletions src/assets/icons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<svg>...</svg>' },
'node': { body: '<svg>...</svg>' }
}
}

// Vite configuration for component-based usage
Icons({
compiler: 'vue3',
customCollections: {
Expand All @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
@@ -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}'],
Expand Down