一直以來我的部落格的開發體驗都不是很好:即使不是依賴變更後的冷啟動,僅是在有快取的情況下,Astro 的 dev 在 ready 前和渲染出第一個頁面這兩個過程之間,都需要十幾秒以上的時間。最近再次心血來潮,想看看到底是誰幹的好事。
照例 dev,然後發現,在 [astro-icon] 的輸出後整個卡住了。遂以
--verbose 模式啟動,然後看到下面兩行 +23s 和 +22s:

---
import type { HTMLAttributes } from "astro/types";
import { readFile } from "node:fs/promises";
import type { ExtendedIconifyIcon } from "@iconify/types";
import { iconToSVG, iconToHTML, getIconData } from "@iconify/utils";
import { locate } from "@iconify/json";
export type Props = HTMLAttributes<"svg"> &
(
| {
name: string;
}
| {
set: string;
icon: string;
}
) & {
"is:inline"?: boolean;
title?: string;
desc?: string;
size?: number | string;
width?: number | string;
height?: number | string;
class?: string;
};
const fsCache = new Map<string, string>();
async function readFileCached(path: string) {
let res = fsCache.get(path);
if (res) return res;
res = await readFile(path, "utf8");
fsCache.set(path, res);
return res;
}
async function generateSVG(
prefix: string,
name: string,
customisations: Record<string, unknown> = {}
) {
let data: ExtendedIconifyIcon | null = null;
if (prefix) {
const filename = locate(prefix);
/** @type {import("@iconify/types").IconifyJSON} */
const iconSet = JSON.parse(await readFileCached(filename));
data = getIconData(iconSet, name);
} else {
const body = await readFileCached(`./src/icons/${name}.svg`);
// extract width and height from the SVG: <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"
data = {
body,
width: Number.parseInt(body.match(/ width="(\d+)(px)?"/)?.[1] ?? "32"),
height: Number.parseInt(body.match(/ height="(\d+)(px)?"/)?.[1] ?? "32"),
};
}
if (!data) {
throw new Error(`Icon not found: ${prefix}:${name}`);
}
/**
* Default dimensions of generated SVG:
* - '1em' -> 1em, easy to resize icons with font-size.
* - 'auto' -> same as icon's viewBox.
* - 'unset' -> no width/height in generated icons. You'll need to assign width and height in CSS.
*/
const height = (customisations?.height as string | undefined) ?? "1em";
const { attributes, body } = iconToSVG(data, {
height,
});
return iconToHTML(body, attributes);
}
function generateAPIimg(prefix: string, name: string) {
if (prefix === "") {
return `/icons/${name}.svg`;
}
return `https://api.iconify.design/${prefix}:${name}.svg`;
}
let prefix = "";
let name = "";
if (Astro.props.name) {
const namen = Astro.props.name;
if (namen.includes(":")) [prefix, name] = namen.split(":");
else [prefix, name] = ["", namen];
} else if ("set" in Astro.props) {
prefix = Astro.props.set;
name = Astro.props.icon;
} else {
console.log(Astro.props);
throw new Error("Icon name or set and icon must be provided.");
}
---
{
Astro.locals.renderer === "rss" ? (
<img
src={generateAPIimg(prefix, name)}
width={Astro.props.width ?? Astro.props.size ?? "1em"}
height={Astro.props.height ?? Astro.props.size ?? "1em"}
alt={name}
class={Astro.props.class}
/>
) : (
<Fragment
set:html={await generateSVG(prefix, name, {
height: Astro.props.height ?? "1em",
})}
/>
)
}
評論