Cut iconify

This commit is contained in:
2026-05-21 20:59:02 +04:00
parent 73dfd4e750
commit 95342ae758
27 changed files with 733 additions and 686 deletions

135
scripts/collect-icons.mjs Normal file
View File

@@ -0,0 +1,135 @@
import { readFileSync, readdirSync, statSync, writeFileSync, mkdirSync } from "fs";
import { join, relative } from "path";
const ROOT = join(import.meta.dirname, "..");
const SRC = join(ROOT, "src");
const OUT_DIR = join(SRC, "generated");
const OUT_FILE = join(OUT_DIR, "icon-inventory.json");
const OUT_INCLUDE = join(OUT_DIR, "icon-include.mjs");
/** @type {Record<string, string>} */
const ICON_ALIASES = {
"eos-icons:loading": "material-symbols:progress-activity",
};
const KNOWN_PREFIXES = new Set([
"material-symbols",
"fa6-brands",
"fa6-regular",
"fa6-solid",
"mdi",
"logos",
"devicon",
"hugeicons",
"mingcute",
"skill-icons",
"vscode-icons",
"material-icon-theme",
"game-icons",
"iconoir",
]);
const ICON_PATTERN =
/(?:name|icon)=["'{]([a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9._-]*)/gi;
const JSON_ICON_PATTERN = /"icon"\s*:\s*"([^"]+)"/g;
const YAML_ICON_PATTERN = /\bicon:\s*["']([^"']+)["']/g;
const TS_ICON_PATTERN = /\bicon\s*:\s*["']([^"']+)["']/g;
const RETURN_ICON_PATTERN =
/return\s+["']([a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9._-]*)["']/g;
function walk(dir, files = []) {
for (const entry of readdirSync(dir)) {
const full = join(dir, entry);
if (statSync(full).isDirectory()) {
walk(full, files);
} else {
files.push(full);
}
}
return files;
}
function normalizeIcon(name) {
const trimmed = name.trim();
return ICON_ALIASES[trimmed] ?? trimmed;
}
function addIcon(icons, raw) {
const name = normalizeIcon(raw);
if (!name.includes(":")) return;
const [prefix, iconName] = name.split(":");
if (!prefix || !iconName || !KNOWN_PREFIXES.has(prefix)) return;
if (!icons[prefix]) icons[prefix] = new Set();
icons[prefix].add(iconName);
}
function scanFile(path, icons) {
const content = readFileSync(path, "utf8");
const rel = relative(ROOT, path).replace(/\\/g, "/");
if (rel.endsWith(".json")) {
for (const match of content.matchAll(JSON_ICON_PATTERN)) {
addIcon(icons, match[1]);
}
return;
}
if (rel.endsWith(".yaml") || rel.endsWith(".yml")) {
for (const match of content.matchAll(YAML_ICON_PATTERN)) {
addIcon(icons, match[1]);
}
return;
}
if (!/\.(astro|svelte|ts|tsx|js|mjs)$/.test(rel)) return;
for (const match of content.matchAll(ICON_PATTERN)) {
addIcon(icons, match[1]);
}
for (const match of content.matchAll(TS_ICON_PATTERN)) {
addIcon(icons, match[1]);
}
for (const match of content.matchAll(RETURN_ICON_PATTERN)) {
addIcon(icons, match[1]);
}
}
function main() {
/** @type {Record<string, Set<string>>} */
const icons = {};
const files = walk(SRC);
for (const file of files) {
if (file.includes("generated")) continue;
scanFile(file, icons);
}
const extraConfigFiles = [join(ROOT, "twilight.config.yaml")];
for (const file of extraConfigFiles) {
try {
scanFile(file, icons);
} catch {
/* optional config */
}
}
/** @type {Record<string, string[]>} */
const inventory = {};
for (const [prefix, names] of Object.entries(icons)) {
inventory[prefix] = [...names].sort();
}
mkdirSync(OUT_DIR, { recursive: true });
writeFileSync(OUT_FILE, `${JSON.stringify(inventory, null, 2)}\n`, "utf8");
writeFileSync(
OUT_INCLUDE,
`// AUTO-GENERATED by scripts/collect-icons.mjs — do not edit\nexport default ${JSON.stringify(inventory, null, 4)};\n`,
"utf8",
);
const total = Object.values(inventory).reduce((n, arr) => n + arr.length, 0);
console.log(`Collected ${total} icons in ${Object.keys(inventory).length} sets -> ${relative(ROOT, OUT_FILE)}`);
}
main();

View File

@@ -1,9 +0,0 @@
const { copyFileSync, mkdirSync } = require("fs");
const { dirname, join } = require("path");
const src = join(__dirname, "../node_modules/iconify-icon/dist/iconify-icon.min.js");
const dest = join(__dirname, "../public/assets/iconify/iconify-icon.min.js");
mkdirSync(dirname(dest), { recursive: true });
copyFileSync(src, dest);
console.log(`Copied iconify-icon.min.js to ${dest}`);

View File

@@ -0,0 +1,92 @@
import { readFileSync, writeFileSync, mkdirSync } from "fs";
import { join } from "path";
import { createRequire } from "module";
import { getIconData, iconToSVG } from "@iconify/utils";
const require = createRequire(import.meta.url);
const ROOT = join(import.meta.dirname, "..");
const INVENTORY_FILE = join(ROOT, "src/generated/icon-inventory.json");
const OUT_DIR = join(ROOT, "src/generated");
const OUT_FILE = join(OUT_DIR, "site-icon-svgs.ts");
const PACKAGE_BY_PREFIX = {
"fa6-brands": "@iconify-json/fa6-brands",
"fa6-regular": "@iconify-json/fa6-regular",
"fa6-solid": "@iconify-json/fa6-solid",
mdi: "@iconify-json/mdi",
"material-symbols": "@iconify-json/material-symbols",
logos: "@iconify-json/logos",
devicon: "@iconify-json/devicon",
hugeicons: "@iconify-json/hugeicons",
mingcute: "@iconify-json/mingcute",
"skill-icons": "@iconify-json/skill-icons",
"vscode-icons": "@iconify-json/vscode-icons",
"material-icon-theme": "@iconify-json/material-icon-theme",
"game-icons": "@iconify-json/game-icons",
iconoir: "@iconify-json/iconoir",
};
function loadIconSet(prefix) {
const pkg = PACKAGE_BY_PREFIX[prefix];
if (!pkg) return null;
return require(`${pkg}/icons.json`);
}
function buildSvg(iconName) {
const [prefix, name] = iconName.split(":");
const iconSet = loadIconSet(prefix);
if (!iconSet) {
console.warn(`Unknown icon set: ${prefix} (${iconName})`);
return null;
}
const data = getIconData(iconSet, name);
if (!data) {
console.warn(`Icon not found: ${iconName}`);
return null;
}
const { body, attributes } = iconToSVG(data, { height: "1em" });
const attrs = Object.entries(attributes)
.map(([k, v]) => `${k}="${v}"`)
.join(" ");
return `<svg xmlns="http://www.w3.org/2000/svg" ${attrs}>${body}</svg>`;
}
function main() {
const inventory = JSON.parse(readFileSync(INVENTORY_FILE, "utf8"));
/** @type {Record<string, string>} */
const svgs = {};
for (const [prefix, names] of Object.entries(inventory)) {
for (const name of names) {
const fullName = `${prefix}:${name}`;
const svg = buildSvg(fullName);
if (svg) svgs[fullName] = svg;
}
}
const fallback =
svgs["material-symbols:help-outline"] ??
svgs["material-symbols:info"] ??
Object.values(svgs)[0] ??
"";
const lines = [
"// Generated by scripts/generate-site-icons.mjs — do not edit manually",
"",
`export const siteIconFallback = ${JSON.stringify(fallback)};`,
"",
"export const siteIconSvgs: Record<string, string> = {",
];
for (const [name, svg] of Object.entries(svgs).sort(([a], [b]) => a.localeCompare(b))) {
lines.push(` ${JSON.stringify(name)}: ${JSON.stringify(svg)},`);
}
lines.push("};", "");
mkdirSync(OUT_DIR, { recursive: true });
writeFileSync(OUT_FILE, `${lines.join("\n")}\n`, "utf8");
console.log(`Generated ${Object.keys(svgs).length} SVGs -> ${OUT_FILE}`);
}
main();

View File

@@ -0,0 +1,17 @@
import { readFileSync } from "fs";
import { join } from "path";
const inventoryPath = join(import.meta.dirname, "../src/generated/icon-inventory.json");
export function loadIconInventory() {
return JSON.parse(readFileSync(inventoryPath, "utf8"));
}
export function toAstroIconInclude(inventory) {
/** @type {Record<string, string[]>} */
const include = {};
for (const [prefix, icons] of Object.entries(inventory)) {
include[prefix] = icons;
}
return include;
}