Skip to content

Commit 7d6e252

Browse files
[feat] improve custom icon build script with TypeScript and error handling (#5202)
- Convert customIconCollection.js to TypeScript with proper interfaces - Add comprehensive SVG validation and error handling - Implement graceful failure - malformed icons don't break builds - Remove verbose logging, keep only errors/warnings - Update documentation in README.md, CONTRIBUTING.md, icons/README.md - Add missing @iconify/tailwind dependency 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 50e0e29 commit 7d6e252

File tree

6 files changed

+125
-34
lines changed

6 files changed

+125
-34
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ The project supports three types of icons, all with automatic imports (no manual
265265
2. **Iconify Icons** - 200,000+ icons from various libraries: `<i-lucide:settings />`, `<i-mdi:folder />`
266266
3. **Custom Icons** - Your own SVG icons: `<i-comfy:workflow />`
267267

268-
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/`.
268+
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.
269269

270270
For detailed instructions and code examples, see [src/assets/icons/README.md](src/assets/icons/README.md).
271271

build/customIconCollection.js

Lines changed: 0 additions & 29 deletions
This file was deleted.

build/customIconCollection.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { existsSync, readFileSync, readdirSync } from 'fs'
2+
import { join } from 'path'
3+
import { dirname } from 'path'
4+
import { fileURLToPath } from 'url'
5+
6+
const fileName = fileURLToPath(import.meta.url)
7+
const dirName = dirname(fileName)
8+
const customIconsPath = join(dirName, '..', 'src', 'assets', 'icons', 'custom')
9+
10+
// Iconify collection structure
11+
interface IconifyIcon {
12+
body: string
13+
width?: number
14+
height?: number
15+
}
16+
17+
interface IconifyCollection {
18+
prefix: string
19+
icons: Record<string, IconifyIcon>
20+
width?: number
21+
height?: number
22+
}
23+
24+
// Create an Iconify collection for custom icons
25+
export const iconCollection: IconifyCollection = {
26+
prefix: 'comfy',
27+
icons: {},
28+
width: 16,
29+
height: 16
30+
}
31+
32+
/**
33+
* Validates that an SVG file contains valid SVG content
34+
*/
35+
function validateSvgContent(content: string, filename: string): void {
36+
if (!content.trim()) {
37+
throw new Error(`Empty SVG file: ${filename}`)
38+
}
39+
40+
if (!content.includes('<svg')) {
41+
throw new Error(`Invalid SVG file (missing <svg> tag): ${filename}`)
42+
}
43+
44+
// Basic XML structure validation
45+
const openTags = (content.match(/<svg[^>]*>/g) || []).length
46+
const closeTags = (content.match(/<\/svg>/g) || []).length
47+
48+
if (openTags !== closeTags) {
49+
throw new Error(`Malformed SVG file (mismatched svg tags): ${filename}`)
50+
}
51+
}
52+
53+
/**
54+
* Loads custom SVG icons from the icons directory
55+
*/
56+
function loadCustomIcons(): void {
57+
if (!existsSync(customIconsPath)) {
58+
console.warn(`Custom icons directory not found: ${customIconsPath}`)
59+
return
60+
}
61+
62+
try {
63+
const files = readdirSync(customIconsPath)
64+
const svgFiles = files.filter((file) => file.endsWith('.svg'))
65+
66+
if (svgFiles.length === 0) {
67+
console.warn('No SVG files found in custom icons directory')
68+
return
69+
}
70+
71+
svgFiles.forEach((file) => {
72+
const name = file.replace('.svg', '')
73+
const filePath = join(customIconsPath, file)
74+
75+
try {
76+
const content = readFileSync(filePath, 'utf-8')
77+
validateSvgContent(content, file)
78+
79+
iconCollection.icons[name] = {
80+
body: content
81+
}
82+
} catch (error) {
83+
console.error(
84+
`Failed to load custom icon ${file}:`,
85+
error instanceof Error ? error.message : error
86+
)
87+
// Continue loading other icons instead of failing the entire build
88+
}
89+
})
90+
} catch (error) {
91+
console.error(
92+
'Failed to read custom icons directory:',
93+
error instanceof Error ? error.message : error
94+
)
95+
// Don't throw here - allow build to continue without custom icons
96+
}
97+
}
98+
99+
// Load icons when this module is imported
100+
loadCustomIcons()

package-lock.json

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/assets/icons/README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,29 @@ Icons are automatically imported using `unplugin-icons` - no manual imports need
247247

248248
### Configuration
249249

250-
The icon system is configured in `vite.config.mts`:
250+
The icon system has two layers:
251+
252+
1. **Build-time Processing** (`build/customIconCollection.ts`):
253+
- Scans `src/assets/icons/custom/` for SVG files
254+
- Validates SVG content and structure
255+
- Creates Iconify collection for Tailwind CSS
256+
- Provides error handling for malformed files
257+
258+
2. **Vite Runtime** (`vite.config.mts`):
259+
- Enables direct SVG import as Vue components
260+
- Supports dynamic icon loading
251261

252262
```typescript
263+
// Build script creates Iconify collection
264+
export const iconCollection: IconifyCollection = {
265+
prefix: 'comfy',
266+
icons: {
267+
'workflow': { body: '<svg>...</svg>' },
268+
'node': { body: '<svg>...</svg>' }
269+
}
270+
}
271+
272+
// Vite configuration for component-based usage
253273
Icons({
254274
compiler: 'vue3',
255275
customCollections: {
@@ -271,8 +291,9 @@ Icons are fully typed. If TypeScript doesn't recognize a new custom icon:
271291
### Icon Not Showing
272292
1. **Check filename**: Must be kebab-case without special characters
273293
2. **Restart dev server**: Required after adding new icons
274-
3. **Verify SVG**: Ensure it's valid SVG syntax
294+
3. **Verify SVG**: Ensure it's valid SVG syntax (build script validates automatically)
275295
4. **Check console**: Look for Vue component resolution errors
296+
5. **Build script errors**: Check console during build - malformed SVGs are logged but don't break builds
276297

277298
### Icon Wrong Color
278299
- Replace hardcoded colors with `currentColor`

tailwind.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @type {import('tailwindcss').Config} */
22
import { addDynamicIconSelectors } from '@iconify/tailwind'
33

4-
import { iconCollection } from './build/customIconCollection.js'
4+
import { iconCollection } from './build/customIconCollection.ts'
55

66
export default {
77
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],

0 commit comments

Comments
 (0)