File size: 3,324 Bytes
1b50562 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | #!/usr/bin/env node
/**
* Build script to generate SVG symbol sprites from heroicons.
*
* Reads icons.config.json and generates _icons.mustache partial
* containing SVG symbols that can be referenced via <use href="#id">.
*
* Usage: node scripts/build-icons.js
*/
import { readFileSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT_DIR = join(__dirname, '..');
const CONFIG_PATH = join(ROOT_DIR, 'icons.config.json');
const OUTPUT_PATH = join(ROOT_DIR, 'src/aspara/dashboard/templates/_icons.mustache');
const HEROICONS_PATH = join(ROOT_DIR, 'node_modules/heroicons/24');
/**
* Parse SVG file and extract attributes and inner content.
* @param {string} svgContent - Raw SVG file content
* @returns {{ attrs: Object, innerContent: string }}
*/
function parseSvg(svgContent) {
// Extract attributes from the opening <svg> tag
const svgMatch = svgContent.match(/<svg([^>]*)>([\s\S]*)<\/svg>/);
if (!svgMatch) {
throw new Error('Invalid SVG format');
}
const attrsString = svgMatch[1];
const innerContent = svgMatch[2].trim();
// Parse attributes
const attrs = {};
const attrRegex = /(\S+)=["']([^"']*)["']/g;
for (const match of attrsString.matchAll(attrRegex)) {
attrs[match[1]] = match[2];
}
return { attrs, innerContent };
}
/**
* Convert SVG to symbol element.
* @param {string} svgContent - Raw SVG file content
* @param {string} id - Symbol ID
* @returns {string} Symbol element string
*/
function svgToSymbol(svgContent, id) {
const { attrs, innerContent } = parseSvg(svgContent);
// Build symbol attributes (keep viewBox, fill, stroke, stroke-width)
const symbolAttrs = [`id="${id}"`];
if (attrs.viewBox) {
symbolAttrs.push(`viewBox="${attrs.viewBox}"`);
}
if (attrs.fill) {
symbolAttrs.push(`fill="${attrs.fill}"`);
}
if (attrs.stroke) {
symbolAttrs.push(`stroke="${attrs.stroke}"`);
}
if (attrs['stroke-width']) {
symbolAttrs.push(`stroke-width="${attrs['stroke-width']}"`);
}
return ` <symbol ${symbolAttrs.join(' ')}>\n ${innerContent}\n </symbol>`;
}
/**
* Main build function.
*/
function build() {
console.log('Building icon sprites...');
// Read config
const config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
console.log(`Found ${config.icons.length} icons in config`);
const symbols = [];
for (const icon of config.icons) {
const svgPath = join(HEROICONS_PATH, icon.style, `${icon.name}.svg`);
console.log(` Processing: ${icon.name} (${icon.style}) -> #${icon.id}`);
try {
const svgContent = readFileSync(svgPath, 'utf-8');
const symbol = svgToSymbol(svgContent, icon.id);
symbols.push(symbol);
} catch (err) {
console.error(` Error reading ${svgPath}: ${err.message}`);
process.exit(1);
}
}
// Generate output
const output = `{{!
Auto-generated icon sprites from heroicons.
Do not edit manually - run "pnpm build:icons" to regenerate.
Source: icons.config.json
}}
<svg style="display: none" aria-hidden="true">
${symbols.join('\n')}
</svg>
`;
writeFileSync(OUTPUT_PATH, output);
console.log(`\nGenerated: ${OUTPUT_PATH}`);
console.log('Done!');
}
build();
|