为什么我把博客的依赖从"能跑就行"改成了"每一步都要能解释"
一次依赖事故之后的想法:版本号不是护身符,可复现的构建才是。
技术
引子
上周我的博客挂了。
不是内容出问题,是构建直接失败:
Rolldown failed to resolve import "bits-ui"
from "src/components/ui/sheet/sheet-close.svelte"
bits-ui 被十几个组件 import,但它从来没出现在 package.json 里。
第一反应:装上不就完了
确实,pnpm install bits-ui 之后构建就过了。整个过程不到三分钟。
但我盯着那个绿色输出看了很久,心里有点不踏实。
不踏实在哪
我试着回想:bits-ui 是什么时候开始被 import 的?
想不起来。
那批代码是合并进来的。合并之前它可能是好的,合并之后 package.json 冲突,我选了“保留本地版本”——然后这十几个组件就变成了悬空引用。
更让我不安的是:这个错误存在了多久?
我翻了一下提交记录。构建失败是最近才暴露的,但这不代表问题是最近才有的。它可能在仓库里躺了几周甚至几个月,期间我只顾着写技术教程,没人跑过完整构建。
而我写的教程里,有一篇叫《Cloudflare 优选教程》。
教别人怎么配 DNS、怎么选节点、怎么让国内访问不卡。
而我自己的站点,连 CSS 变量都没定义过。
依赖管理的三个阶段
复盘下来,我把依赖问题分成了三个阶段,每个阶段的做法不一样:
阶段一:能跑就行
npm install xxx # 装上,继续写
这个阶段的问题不是慢,而是你没有记录任何东西。三个月后你不会知道当初为什么装这个版本。
阶段二:锁文件
有了 package-lock.json / pnpm-lock.yaml,至少能保证“和上次一样”。
但这次事故里,锁文件是有的——问题在于 package.json 和锁文件对不上。合并时我手动选了 package.json,锁文件却跟着另一份走了。
锁文件保证的是依赖树一致,保证不了 manifest 和实际安装一致。
阶段三:可复现构建
现在我的做法是:
# 改依赖后立刻装
pnpm add xxx
# 提交前必跑
pnpm check # 类型
pnpm build # 完整构建
pnpm audit # 死链 / 变量完整性
三个都过才提交。
pnpm audit 是我自己写的,检查两件事:
- 站内有没有死链
- CSS 变量有没有“引用了但没定义”
第二条就是这次事故的直接教训——var(--su-*) 被引用了 190 多次,定义是 0 次。CSS 变量未定义时不会报错,只会静默回退成 transparent 或继承值,于是界面“看起来有点淡”,而不是“明显坏掉”。
一个具体的建议
如果你的项目里有这些情况,建议现在就查一遍:
# 找出所有被引用但可能没定义的 CSS 变量
grep -rhoE 'var\(--[a-z0-9-]+' src/ | sort -u
然后把结果和你 :root 里定义的对照一遍。
你会发现有些变量确实是运行时注入的(组件库爱这么干),但有些就是漏了——漏了很久,没人发现。
关于依赖本身
说句实在话,bits-ui 这类依赖引入是有价值的:它让抽屉、折叠、Tooltip 这些交互不用自己从头写。
问题从来不是“要不要用依赖”,而是:
你知不知道自己在用,以及为什么。
这次事故里我最大的收获不是“以后要加依赖”,而是——我对自己项目的了解,比我以为的少得多。
那 190 个未定义的 CSS 变量,不是别人坑我的,是我自己写的,然后忘了。
最后
博客修好了,现在跑构建 20 秒左右,干净无警告。
但我不敢说“以后不会再犯”。我只能说:现在有一套机制,能让它在 20 秒内暴露,而不是在访客点开页面时暴露。
这就够了。至少对我来说够了。