零配置注册表:新增一个游戏等于新建一个目录
列表、详情路由、首页精选、sitemap 全部由 import.meta.glob 自动发现。代价是必须接受「字面量相对路径」这条 Vite 的硬规则。
这个站有 12 个游戏和 20 个工具。加第 33 个的时候,我没有改任何配置。
契约只有三行
每个应用是一个自包含目录:
src/games/<slug>/
meta.ts 默认导出 AppMeta(三语标题/简介、order、featured)
App.vue 默认导出 Vue 组件,挂载即用
logic/ 纯逻辑,不碰 DOM,自检能直接跑
目录名就是 slug,也就是 URL 里的那一段。
注册表怎么发现它们
const gameMeta = import.meta.glob('../games/*/meta.ts', { eager: true });
function collect(kind, modules) {
return Object.entries(modules)
.map(([path, mod]) => {
// 路径形如 ../games/snake/meta.ts,倒数第二段是 slug
const slug = path.split('/').slice(-2)[0];
if (!mod.default) throw new Error('缺少默认导出: ' + path);
return { kind, slug, ...mod.default };
})
.filter((entry) => !entry.draft)
.sort((a, b) => (a.order ?? 999) - (b.order ?? 999));
}
三件事值得说:slug 从路径里取,不靠文件名之外的信息;缺默认导出时直接抛错,构建期失败远好过线上少一张卡片;排序只看 order,不依赖文件系统返回顺序。
一处改成三处
列表页、详情页、首页精选都从同一个 listApps(kind) 派生:路由在 appDetailPaths(kind) 里一次生成三语,列表页从同一份数组出卡片,首页只取 featured 的前三个。
只有一个来源时,「新增应用要改哪几个地方」的答案是「零」。
那条不能绕的规矩
Vite 只能在构建期静态分析字面量路径,所以 glob 模式既不能换成别名,也不能放进变量。这是本项目仅有的两处「必须写相对路径」的例外之一,另一处是 mount.ts 里的 App.vue 注册表。
代价
「零配置」的前提是契约稳定。一旦某个应用需要额外的构建期信息,不该往 meta.ts 里堆,而要先问这个字段是不是所有应用都该有。

评论
…