122 lines
4.8 KiB
JavaScript
122 lines
4.8 KiB
JavaScript
import { createHash } from "node:crypto";
|
|
import { parse, resolve } from "node:path";
|
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import loadLocalCollection from "./loaders/loadLocalCollection.js";
|
|
import loadIconifyCollections from "./loaders/loadIconifyCollections.js";
|
|
export function createPlugin({ include = {}, iconDir = "src/icons", svgoOptions }, ctx) {
|
|
let collections;
|
|
const { root } = ctx;
|
|
const virtualModuleId = "virtual:astro-icon";
|
|
const resolvedVirtualModuleId = "\0" + virtualModuleId;
|
|
return {
|
|
name: "astro-icon",
|
|
resolveId(id) {
|
|
if (id === virtualModuleId) {
|
|
return resolvedVirtualModuleId;
|
|
}
|
|
},
|
|
async load(id) {
|
|
if (id === resolvedVirtualModuleId) {
|
|
try {
|
|
if (!collections) {
|
|
collections = await loadIconifyCollections({ root, include });
|
|
}
|
|
const local = await loadLocalCollection(iconDir, svgoOptions);
|
|
collections["local"] = local;
|
|
logCollections(collections, { ...ctx, iconDir });
|
|
await generateIconTypeDefinitions(Object.values(collections), root);
|
|
}
|
|
catch (ex) {
|
|
// Failed to load the local collection
|
|
}
|
|
return `export default ${JSON.stringify(collections)};\nexport const config = ${JSON.stringify({ include })}`;
|
|
}
|
|
},
|
|
configureServer({ watcher, moduleGraph }) {
|
|
watcher.add(`${iconDir}/**/*.svg`);
|
|
watcher.on("all", async (_, filepath) => {
|
|
const parsedPath = parse(filepath);
|
|
const resolvedIconDir = resolve(root.pathname, iconDir);
|
|
const isSvgFileInIconDir = parsedPath.dir.startsWith(resolvedIconDir) &&
|
|
parsedPath.ext === ".svg";
|
|
const isAstroConfig = parsedPath.name === "astro.config";
|
|
if (!isSvgFileInIconDir && !isAstroConfig)
|
|
return;
|
|
console.log(`Local icons changed, reloading`);
|
|
try {
|
|
if (!collections) {
|
|
collections = await loadIconifyCollections({ root, include });
|
|
}
|
|
const local = await loadLocalCollection(iconDir, svgoOptions);
|
|
collections["local"] = local;
|
|
logCollections(collections, { ...ctx, iconDir });
|
|
await generateIconTypeDefinitions(Object.values(collections), root);
|
|
moduleGraph.invalidateAll();
|
|
}
|
|
catch (ex) {
|
|
// Failed to load the local collection
|
|
}
|
|
return `export default ${JSON.stringify(collections)};\nexport const config = ${JSON.stringify({ include })}`;
|
|
});
|
|
},
|
|
};
|
|
}
|
|
function logCollections(collections, { logger, iconDir }) {
|
|
if (Object.keys(collections).length === 0) {
|
|
logger.warn("No icons detected!");
|
|
return;
|
|
}
|
|
const names = Object.keys(collections).filter((v) => v !== "local");
|
|
if (collections["local"]) {
|
|
names.unshift(iconDir);
|
|
}
|
|
logger.info(`Loaded icons from ${names.join(", ")}`);
|
|
}
|
|
async function generateIconTypeDefinitions(collections, rootDir, defaultPack = "local") {
|
|
const typeFile = new URL("./.astro/icon.d.ts", rootDir);
|
|
await ensureDir(new URL("./", typeFile));
|
|
const oldHash = await tryGetHash(typeFile);
|
|
const currentHash = collectionsHash(collections);
|
|
if (currentHash === oldHash) {
|
|
return;
|
|
}
|
|
await writeFile(typeFile, `// Automatically generated by astro-icon
|
|
// ${currentHash}
|
|
|
|
declare module 'virtual:astro-icon' {
|
|
\texport type Icon = ${collections.length > 0
|
|
? collections
|
|
.map((collection) => Object.keys(collection.icons)
|
|
.concat(Object.keys(collection.aliases ?? {}))
|
|
.map((icon) => `\n\t\t| "${collection.prefix === defaultPack
|
|
? ""
|
|
: `${collection.prefix}:`}${icon}"`))
|
|
.flat(1)
|
|
.join("")
|
|
: "never"};
|
|
}`);
|
|
}
|
|
function collectionsHash(collections) {
|
|
const hash = createHash("sha256");
|
|
for (const collection of collections) {
|
|
hash.update(collection.prefix);
|
|
hash.update(Object.keys(collection.icons)
|
|
.concat(Object.keys(collection.aliases ?? {}))
|
|
.sort()
|
|
.join(","));
|
|
}
|
|
return hash.digest("hex");
|
|
}
|
|
async function tryGetHash(path) {
|
|
try {
|
|
const text = await readFile(path, { encoding: "utf-8" });
|
|
return text.split("\n", 3)[1].replace("// ", "");
|
|
}
|
|
catch { }
|
|
}
|
|
async function ensureDir(path) {
|
|
try {
|
|
await mkdir(path, { recursive: true });
|
|
}
|
|
catch { }
|
|
}
|