diff --git a/packages/core/src/vite-plugin-astro-icon.ts b/packages/core/src/vite-plugin-astro-icon.ts index 0febd52..b9b2c90 100644 --- a/packages/core/src/vite-plugin-astro-icon.ts +++ b/packages/core/src/vite-plugin-astro-icon.ts @@ -1,130 +1,146 @@ -import type { AstroConfig, AstroIntegrationLogger } from "astro"; -import { mkdir, readFile, writeFile } from "node:fs/promises"; -import type { Plugin } from "vite"; +import type {AstroConfig, AstroIntegrationLogger} from 'astro'; +import {mkdir, readFile, writeFile} from 'node:fs/promises'; +import type {Plugin} from 'vite'; import type { - AstroIconCollectionMap, - IconCollection, - IntegrationOptions, -} from "../typings/integration"; -import loadLocalCollection from "./loaders/loadLocalCollection.js"; -import loadIconifyCollections from "./loaders/loadIconifyCollections.js"; -import { createHash } from "node:crypto"; + AstroIconCollectionMap, + IconCollection, + IntegrationOptions, +} from '../typings/integration'; +import loadLocalCollection from './loaders/loadLocalCollection.js'; +import loadIconifyCollections from './loaders/loadIconifyCollections.js'; +import {createHash} from 'node:crypto'; -interface PluginContext extends Pick { - logger: AstroIntegrationLogger; +interface PluginContext extends Pick { + logger: AstroIntegrationLogger; } export function createPlugin( - { include = {}, iconDir = "src/icons", svgoOptions }: IntegrationOptions, - ctx: PluginContext, + {include = {}, iconDir = 'src/icons', svgoOptions}: IntegrationOptions, + ctx: PluginContext ): Plugin { - let collections: AstroIconCollectionMap | undefined; - const { root } = ctx; - const virtualModuleId = "virtual:astro-icon"; - const resolvedVirtualModuleId = "\0" + virtualModuleId; + let collections: AstroIconCollectionMap | undefined; + 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) { - if (!collections) { - collections = await loadIconifyCollections({ root, include }); - } - try { - // Attempt to create local collection - const local = await loadLocalCollection(iconDir, svgoOptions); - collections["local"] = local; - } catch (ex) { - // Failed to load the local collection - } - logCollections(collections, { ...ctx, iconDir }); - await generateIconTypeDefinitions(Object.values(collections), root); + return { + name: 'astro-icon', + resolveId(id) { + if (id === virtualModuleId) { + return resolvedVirtualModuleId; + } + }, - return `export default ${JSON.stringify( - collections, - )};\nexport const config = ${JSON.stringify({ include })}`; - } - }, - }; + 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('change', async () => { + 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: AstroIconCollectionMap, - { logger, iconDir }: PluginContext & { iconDir: string }, + collections: AstroIconCollectionMap, + {logger, iconDir}: PluginContext & {iconDir: string} ) { - if (Object.keys(collections).length === 0) { - logger.warn("No icons detected!"); - return; - } - const names: string[] = Object.keys(collections).filter((v) => v !== "local"); - if (collections["local"]) { - names.unshift(iconDir); - } - logger.info(`Loaded icons from ${names.join(", ")}`); + if (Object.keys(collections).length === 0) { + logger.warn('No icons detected!'); + return; + } + const names: string[] = Object.keys(collections).filter((v) => v !== 'local'); + if (collections['local']) { + names.unshift(iconDir); + } + logger.info(`Loaded icons from ${names.join(', ')}`); } async function generateIconTypeDefinitions( - collections: IconCollection[], - rootDir: URL, - defaultPack = "local", + collections: IconCollection[], + rootDir: URL, + defaultPack = 'local' ): Promise { - 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 + 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).map( - (icon) => - `\n\t\t| "${ - collection.prefix === defaultPack - ? "" - : `${collection.prefix}:` - }${icon}"`, - ), - ) - .flat(1) - .join("") - : "never" - }; -}`, - ); + collections.length > 0 + ? collections + .map((collection) => + Object.keys(collection.icons).map( + (icon) => + `\n\t\t| "${ + collection.prefix === defaultPack + ? '' + : `${collection.prefix}:` + }${icon}"` + ) + ) + .flat(1) + .join('') + : 'never' + }; +}` + ); } function collectionsHash(collections: IconCollection[]): string { - const hash = createHash("sha256"); - for (const collection of collections) { - hash.update(collection.prefix); - hash.update(Object.keys(collection.icons).sort().join(",")); - } - return hash.digest("hex"); + const hash = createHash('sha256'); + for (const collection of collections) { + hash.update(collection.prefix); + hash.update(Object.keys(collection.icons).sort().join(',')); + } + return hash.digest('hex'); } async function tryGetHash(path: URL): Promise { - try { - const text = await readFile(path, { encoding: "utf-8" }); - return text.split("\n", 3)[1].replace("// ", ""); - } catch {} + try { + const text = await readFile(path, {encoding: 'utf-8'}); + return text.split('\n', 3)[1].replace('// ', ''); + } catch {} } async function ensureDir(path: URL): Promise { - try { - await mkdir(path, { recursive: true }); - } catch {} + try { + await mkdir(path, {recursive: true}); + } catch {} }