发现并解决lucide-astro导致的Astro中HMR缓慢问题

问题来源

在我的博客从Hexo换到Astro自己编写之后,也不知道是何时开始的,我在使用pnpm dev并开始修改页面代码的时候,我发现编译速度和HMR的速度极慢无比。每次看控制台的日志输出,HMR的时间基本上在2s左右,如果在浏览器上加载页面、等待某一个页面更新,实际上需要等待5s以上。以至于有时候我选择直接使用pnpm build && pnpm preview来查看每次代码修改后的页面变动。

zhelearn@ap-mba-m4 ~/R/zhelearn.com (main)> pnpm dev

> zhelearn.com@0.0.1 dev /Users/zhelearn/Repos/zhelearn.com
> astro dev

08:17:46 [types] Generated 0ms
08:17:46 [content] Syncing content
08:17:46 [content] Synced content

 astro  v5.16.6 ready in 744 ms

 Local    http://localhost:4321/
 Network  use --host to expose

08:17:46 watching for file changes...
08:18:01 [200] / 4012ms
08:18:09 [200] /journal 3616ms
08:18:13 [200] /journal 3609ms
08:18:18 [200] /notes 3599ms
08:18:22 [200] /notes 3599ms
08:18:25 [200] /journal/posts/2026-refactoring-blog-with-astro/ 3624ms

如上所示,每次HMR的时间极其的漫长。

排查

接下来就是进行排查问题在哪,我创建了一个最简单的Astro页面,例如如下页面:

---

---

<h1>Hello</h1>

这样的一个最简单的页面,加载时间是正常的:

08:20:34 [200] /test/ 5ms

那接下来的操作就是进行逐步加代码排查了,检查现有页面有哪些Astro组件脚本会拖慢HMR的时间,就例如我有一个页面的代码如下:

---
import { getCollection } from "astro:content";
import Layout from "../../layouts/Layout.astro";
import { Rss } from "lucide-astro";

const posts = (await getCollection("journal")).filter((post) => !post.data.draft && !post.data.hidden);
posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());

const groupedPosts = posts.reduce((acc, post) => {
  const year = post.data.date.getFullYear();
  if (!acc.has(year)) {
    acc.set(year, []);
  }
  acc.get(year)!.push(post);
  return acc;
}, new Map<number, typeof posts>());

const years = Array.from(groupedPosts.keys()).sort((a, b) => b - a);
---

<Layout title="Zhe_Learn | Journal">
...
</Layout>

排查方法就是创建一个空页面,一个一个试哪部分代码会拖慢HMR的时间。

一开始我怀疑是Markdown文件每次都会进行全量的编译导致的HMR时间过长,但是经过测试页面中不管有没有getCollection都不会对HMR有太大的影响。

问题处

接下来在测试中发现当我移除掉import { Rss } from "lucide-astro";后,HMR时间立马就恢复正常了,那么此时基本上就可以确定问题是这个lucide-astro导致的了。

进一步去查看lucide-astroindex.d.ts

/// <reference types="astro/astro-jsx" />

export interface Props extends astroHTML.JSX.SVGAttributes {
	size?: number
}
export { default as AArrowDown } from './AArrowDown.astro'
export { default as AArrowUp } from './AArrowUp.astro'
export { default as ALargeSmall } from './ALargeSmall.astro'
......

这个文件有1900行,按理来说Vite一般不会受到这种影响,但是我怀疑导致问题的原因是这里export的文件都是来源于.astro的文件,Vite应该不认这个文件,所以Vite没法进行优化。(这个只是我的猜想,没有实际证明)

那接下来的解决方案也比较简单了,我们把它换成:

 const { title, description, wide = false } = Astro.props;
-import { Sun, Moon, Monitor, Rss } from "lucide-astro";
+import Sun from "lucide-astro/Sun";
+import Moon from "lucide-astro/Moon";
+import Monitor from "lucide-astro/Monitor";
+import Rss from "lucide-astro/Rss";
 import { Icon } from "astro-icon/components";
 import { ClientRouter } from "astro:transitions";
 ---

或者是

---
 import { getCollection } from "astro:content";
 import Layout from "../../layouts/Layout.astro";
-import { Rss } from "lucide-astro";
+import Rss from "lucide-astro/Rss";

 const posts = (await getCollection("journal")).filter((post) => !post.data.draft && !post.data.hidden);
 posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());

之后的HMR时间就基本上能够完全恢复正常了:

08:36:22 [200] / 25ms
08:36:22 [200] / 23ms
08:36:23 [200] /notes 44ms
08:36:24 [200] /notes 24ms
08:36:24 [200] /journal 20ms
08:36:25 [200] /journal 21ms

原因

然后我查询了相关资料,我在lucide的GitHub仓库上查询有没有人遇到相关问题,找到了这一条Issue

作者指出的问题是在使用 lucide-svelte(以及其他包)时,默认的 barrel import(从包入口统一导入)会导致 Vite 优化很慢。,那么基本上可以确定是.astro有和.svelte类似的问题,会导致HMR时间拉长。

不过似乎没有一个最好的解决方案,如果嫌麻烦,将barrel import换成路径import就是最快的解决方案了。