Skip to content

Commit 1e548e0

Browse files
committed
feat(icon): inline SVG icons eagerly and remove async loading
There are not so many icons to load them async
1 parent 08157f0 commit 1e548e0

1 file changed

Lines changed: 15 additions & 62 deletions

File tree

Lines changed: 15 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,29 @@
1-
import { FC, useLayoutEffect, useRef } from "react";
1+
import { FC } from "react";
22
import type { IconName } from "./iconNames";
33

44
const svgModules = import.meta.glob("./icons/*.svg", {
55
query: "?raw",
66
import: "default",
7-
}) as Record<string, () => Promise<string>>;
7+
eager: true,
8+
}) as Record<string, string>;
89

9-
const icons: Record<string, () => Promise<string>> = {};
10-
for (const [path, loadIcon] of Object.entries(svgModules)) {
10+
const icons: Record<string, string> = {};
11+
for (const [path, svg] of Object.entries(svgModules)) {
1112
const name = path.replace(/^.*\/(.+)\.svg$/, "$1");
12-
icons[name] = loadIcon;
13-
}
14-
15-
const iconCache: Record<string, string> = {};
16-
const iconLoadCache: Record<string, Promise<string | undefined> | undefined> = {};
17-
18-
function getIconMarkup(name: IconName): Promise<string | undefined> {
19-
const cachedIcon = iconCache[name];
20-
if (cachedIcon !== undefined) return Promise.resolve(cachedIcon);
21-
22-
const loadIcon = icons[name];
23-
if (!loadIcon) return Promise.resolve(undefined);
24-
25-
const pending = iconLoadCache[name];
26-
if (pending !== undefined) return pending;
27-
28-
const promise = loadIcon()
29-
.then((svgMarkup) => {
30-
iconCache[name] = svgMarkup;
31-
iconLoadCache[name] = undefined;
32-
return svgMarkup;
33-
})
34-
.catch((err) => {
35-
iconLoadCache[name] = undefined;
36-
// oxlint-disable-next-line no-console
37-
console.error(`Failed to load icon "${name}":`, err);
38-
return undefined;
39-
});
40-
41-
iconLoadCache[name] = promise;
42-
return promise;
13+
icons[name] = svg;
4314
}
4415

4516
type Props = {
4617
name: IconName;
4718
size?: number;
4819
};
4920

50-
export const Icon: FC<Props> = ({ name, size = 16 }) => {
51-
const ref = useRef<HTMLElement>(null);
52-
53-
useLayoutEffect(() => {
54-
const node = ref.current;
55-
if (!node) return;
56-
57-
let cancelled = false;
58-
void getIconMarkup(name).then((svgMarkup) => {
59-
if (cancelled || ref.current !== node) return;
60-
node.innerHTML = svgMarkup ?? "";
61-
});
62-
63-
return () => {
64-
cancelled = true;
65-
};
66-
}, [name]);
67-
68-
return (
69-
<i
70-
ref={ref}
71-
className="icon"
72-
aria-hidden="true"
73-
style={{ display: "inline-flex", width: size, height: size }}
74-
/>
75-
);
76-
};
21+
export const Icon: FC<Props> = ({ name, size = 16 }) => (
22+
<i
23+
className="icon"
24+
aria-hidden="true"
25+
style={{ display: "inline-flex", width: size, height: size }}
26+
// oxlint-disable-next-line react/no-danger
27+
dangerouslySetInnerHTML={{ __html: icons[name] ?? "" }}
28+
/>
29+
);

0 commit comments

Comments
 (0)