-
-
Notifications
You must be signed in to change notification settings - Fork 60
/
vite-plugin-astro-icon.ts
130 lines (120 loc) · 3.73 KB
/
vite-plugin-astro-icon.ts
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
121
122
123
124
125
126
127
128
129
130
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";
interface PluginContext extends Pick<AstroConfig, "root" | "output"> {
logger: AstroIntegrationLogger;
}
export function createPlugin(
{ 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;
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 `export default ${JSON.stringify(
collections,
)};\nexport const config = ${JSON.stringify({ include })}`;
}
},
};
}
function logCollections(
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(", ")}`);
}
async function generateIconTypeDefinitions(
collections: IconCollection[],
rootDir: URL,
defaultPack = "local",
): Promise<void> {
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"
};
}`,
);
}
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");
}
async function tryGetHash(path: URL): Promise<string | void> {
try {
const text = await readFile(path, { encoding: "utf-8" });
return text.split("\n", 3)[1].replace("// ", "");
} catch {}
}
async function ensureDir(path: URL): Promise<void> {
try {
await mkdir(path, { recursive: true });
} catch {}
}