手写一个 astro-icon

日期:
分类: Changelog 2 页面仔的自我修养 9
标签: Astro 4

一直以来我的博客的开发体验都不是很好:即使不是依赖变更后的冷启动,仅是在有缓存的情况下,Astro 的 dev 在 ready 前和渲染出第一个页面这两个过程之间,都需要十几秒以上的时间。最近再次心血来潮,想看看到底是谁干的好事。

照例 dev,然后发现,在 [astro-icon] 的输出后整个卡住了。遂以 --verbose 模式启动,然后看到下面两行 +23s 和 +22s:

dev --verbose 的输出
dev --verbose 的输出
---
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",
      })}
    />
  )
}

评论

评论将在审核后显示,阁下可以在本博客的 Github 仓库的 拉取请求列表 中查看。提交成功后会自动跳转。

本站不支持 Dark Reader 的暗色模式,请对本站关闭后再访问,亮色模式的对比度、亮度等选项不受影响。部分页面右上角提供暗色模式切换按钮,如果你没看到,说明你的浏览器尚不支持此特性。本提示不依赖于 JavaScript,你可自行查找其他用户在本站发表的关于如何关闭此提示的评论。