目录组织与代码组织约定
Mizuki 是一个基于 Astro + Svelte + Tailwind CSS 的博客主题仓库(package.json 中 type: "module"、version: "9.0")。本页系统说明该仓库的目录划分、组件目录「三件套」约定、命名规则、scripts/ 构建流水线、tests/ 测试组织,以及由 CLAUDE.md 定义的代码行为约定,帮助贡献者快速定位代码位置并以统一方式扩展。
目的与范围
本页覆盖以下内容:
- 仓库根目录的顶层文件职责与配置文件分布
src/下assets/、components/、类型声明与内容集合配置的组织规则- 组件目录的标准结构(
index.ts+types.ts+ 组件本体) - 仓库中观察到的命名约定(PascalCase 与 kebab-case 的并存规则)
scripts/工程脚本目录与package.json驱动的构建流水线tests/测试目录的组织与注册方式CLAUDE.md中定义的行为约定(思考先行、最简实现、外科手术式修改、安全基线)- 工具链约束(Biome 负责格式化与静态检查、pnpm 强制使用)
以下内容由兄弟页面承接,本页不展开:
- 各组件的具体 Props、内部实现与渲染行为(参见对应组件的文档页)
src/content.config.ts中内容集合(content collections)的数据模型细节scripts/sync-content.js、scripts/update-anime.mjs等脚本的内部实现逻辑- Astro / Svelte / Tailwind 的框架级配置项含义(
astro.config.mjs等的逐项解读)
概述
Mizuki 的代码组织遵循几个清晰的原则:
- 按职责分层的顶层目录:框架配置(
astro.config.mjs、svelte.config.js等)平铺在仓库根目录,源代码集中于src/,工程脚本集中于scripts/,测试集中于tests/。 - 组件即目录(component-as-folder):每个原子组件拥有独立目录,目录内固定包含组件本体、
index.ts桶导出与types.ts类型定义三件套。 - 静态与交互分离:组件同时存在
.astro(服务端渲染/静态)与.svelte(客户端交互岛屿)两种形态,按交互需求选择。 - 命令式流水线:构建、数据更新、产物校验全部通过
package.jsonscripts 显式编排,且测试文件以白名单方式逐一列举而非 glob 匹配。 - 行为约定文档化:
CLAUDE.md将 LLM/协作者的行为准则写入仓库,约束改动范围与实现复杂度。
架构总览
下图展示仓库的整体目录组织与关键依赖关系(依据仓库文件清单与 package.json 脚本引用整理):
图中关键关系说明:
package.json是流水线的唯一入口:所有工程命令(内容同步、数据更新、构建校验、测试)都由根目录package.json的scripts字段显式编排,不依赖隐藏的任务运行器。src/components/通过「目录 + 桶导出」向外暴露组件:消费方从index.ts导入,而不是直接深入组件文件路径,从而隔离内部文件结构的变更。scripts/与src/职责正交:src/是被 Astro 构建消费的源代码,scripts/是 Node 脚本(内容同步、番剧/Bangumi/Bilibili 数据更新、产物校验),二者互不嵌套。- 测试与源码分离:
tests/位于仓库根目录,测试命令在package.json中逐文件列举,新增测试必须显式注册。
仓库顶层目录结构
Mizuki 仓库根目录的文件按职责分为四类(依据仓库文件清单):
| 路径 | 类别 | 职责 |
|---|---|---|
astro.config.mjs、svelte.config.js、postcss.config.mjs | 框架配置 | Astro / Svelte / PostCSS 的构建配置 |
biome.json | 代码质量配置 | Biome 格式化与静态检查规则 |
tsconfig.json | TypeScript 配置 | 编译与类型检查选项(type-check 脚本使用 tsc --noEmit) |
pagefind.yml | 搜索配置 | Pagefind 站内搜索索引配置 |
pnpm-workspace.yaml | 包管理配置 | pnpm workspace 定义 |
package.json | 工程清单 | scripts 命令、依赖声明、packageManager: "pnpm@11.5.3" |
CLAUDE.md | 行为约定 | LLM/协作者行为准则(详见下文「代码行为约定」) |
README.md、README.en.md、README.ja.md、README.tw.md | 文档 | 多语言项目说明 |
LICENSE、LICENSE.MIT、THIRD_PARTY_NOTICES.md | 法律 | 许可证与第三方声明 |
_frontmatter.json | 内容元数据 | 前置数据配置 |
设计意图:框架配置平铺在根目录而非 config/ 子目录,符合 Astro 项目的主流惯例,降低新贡献者的定位成本;法律文件与文档集中在根目录便于维护。
src/ 内部结构
src/ 顶层只保留少量全局文件,其余按子目录划分:
| 路径 | 职责 |
|---|---|
src/content.config.ts | Astro 内容集合(content collections)的 schema 与配置入口 |
src/global.d.ts、src/env.d.ts | 全局 TypeScript 类型声明(含 import.meta.env 等环境类型) |
src/FooterConfig.html | 页脚 HTML 片段,独立于组件体系之外的全局注入点 |
src/assets/ | 静态资源:字体(fonts/)、头像与音乐封面(images/、music/cover/)、横幅图(public/assets/) |
src/components/ | UI 组件,按层级分层(atoms/ 等) |
src/assets/ 中的资源按用途进一步细分(如 fonts/ 存放 loli.woff2、ZenMaruGothic-Medium.woff2 等字体;music/cover/ 存放音乐封面图),且同时保留 .ttf 与 .woff2 两种格式 —— 与 package.json 中 prepare-fonts / check-font-loading 脚本配套,构建时校验字体可用性。
组件三件套约定
src/components/atoms/ 下每个组件占据一个独立目录,目录内固定结构如下(以 Badge 为例):
1src/components/atoms/Badge/
2├── Badge.svelte # 组件本体(交互岛屿,Svelte 实现)
3├── index.ts # 桶导出(barrel export)
4└── types.ts # 该组件的 Props/类型定义Badge、Button、Chip、custom-scrollbar、filter-tabs、Icon、Image 孌套目录均严格遵循这一结构:index.ts 负责对外导出,types.ts 集中放置类型契约,组件本体为 .astro 或 .svelte。
这套约定的设计意图:
- 消费方解耦:外部代码从
atoms/Badge导入即可,无需感知目录内文件是.astro还是.svelte。当某个组件从静态.astro迁移为交互式.svelte时,导入路径不变,仅桶导出内容变化,改动被限制在组件目录内部。 - 类型与实现分离:
types.ts单独成文件,使得类型契约可以被其它模块独立引用(例如组件容器读取多个原子组件的 Props 类型做联合推导),也避免在.astro中维护复杂类型。 - 双形态并存:
Icon目录下同时存在Icon.astro与LocalIcon.svelte,表明同一功能域内静态渲染与客户端交互可并存——静态优先(Astro islands 架构),仅在需要状态时才引入 Svelte 岛屿。
命名规则
仓库中存在两种命名风格并存的目录:
- PascalCase 目录:
Badge、Button、Chip、Icon、Image—— 组件目录与其组件文件同名(Badge/Badge.svelte)。 - kebab-case 目录:
custom-scrollbar(内部为CustomScrollbar.astro)、filter-tabs(内部为FilterTabs.astro) —— 目录 kebab-case、文件 PascalCase。
引入新组件时的实践建议:优先沿用 PascalCase 目录名(与目录内组件文件同名的样式,仓库中数量占优),若组件名含多个单词则目录可用 kebab-case,但内部文件保持 PascalCase。
构建与工程脚本流水线
package.json 的 scripts 是工程命令的唯一编排处。完整命令表如下:
| 命令 | 类型 | 作用 |
|---|---|---|
sync-content | 数据同步 | 运行 scripts/sync-content.js,同步外部内容仓库 |
init-content | 初始化 | 运行 scripts/init-content-repo.js,初始化内容仓库 |
export-config | 导出 | 以 --experimental-transform-types 运行 scripts/export-config.mjs |
predev / prebuild | 钩子 | 自动运行 sync-content,后接 || true 保证内容同步失败不阻断流程 |
dev / start | 开发 | astro dev 启动开发服务器 |
check | 校验 | astro check 类型检查 |
update-anime / update-bangumi / update-bilibili | 数据更新 | 拉取番剧 / Bangumi / Bilibili 数据 |
build | 构建 | 完整流水线(见下文分解) |
submit | SEO | scripts/indexnow-submit.js 提交 IndexNow |
preview | 预览 | astro preview |
type-check | 校验 | tsc --noEmit 纯类型检查 |
test | 测试 | node --experimental-strip-types --test 逐文件运行(见下文) |
prepare-fonts / check-fonts / prepare-images | 资产准备 | 字体与默认图片的预处理与校验 |
new-post | 内容创作 | scripts/new-post.js 新建文章 |
format / lint | 代码质量 | biome format --write ./src / biome check --write ./src |
preinstall | 守卫 | npx only-allow pnpm 强制使用 pnpm |
其中 build 命令是理解流水线的关键:
"build": "node scripts/update-anime.mjs && astro build && node scripts/check-global-style-loading.mjs && pagefind --site dist && node scripts/check-font-loading.mjs"Source: package.json
这一行串联了五个阶段:数据更新 → Astro 构建 → 全局样式加载校验 → Pagefind 索引 → 字体加载校验。设计意图是把「产出正确性」的校验直接挂进构建链——任何一环失败(样式未加载、字体缺失)都会导致整体构建失败,避免发布损坏产物。
注意 predev / prebuild 中的容错写法:
"predev": "node scripts/sync-content.js || true",
"prebuild": "node scripts/sync-content.js || true"Source: package.json
|| true 使内容同步失败不阻断 dev/build —— 因为 Astro 构建在无内容时仍可产出主题骨架,内容仓库属于可缺省的外部依赖。
测试组织
测试文件位于仓库根目录 tests/,通过 test 命令显式列举运行:
"test": "node --experimental-strip-types --test tests/markdown-enhancements.test.mjs tests/layout-regressions.test.mjs tests/image-loading.test.mjs tests/music-player-loading.test.mjs && node tests/crypto.test.mjs"Source: package.json
组织要点:
- 使用 Node 内置
node:test运行器(无 Vitest/Jest 等外部测试框架),--experimental-strip-types允许在.mjs测试中直接使用 TypeScript 语法。 - 白名单式注册:测试文件逐个列出而非 glob 匹配,新增测试文件必须手动加入
test命令,避免误吞无关文件。 tests/crypto.test.mjs单独用一条命令运行(&&串联在前一条之后),暗示其运行方式与node:test批量运行存在差异。- 测试命名按「功能域 + regressions」划分(
markdown-enhancements、layout-regressions、image-loading、music-player-loading),表明测试以回归防护为主。
代码行为约定(CLAUDE.md)
CLAUDE.md 是仓库中面向 LLM 与人类协作者的行为准则文档,共五条,直接约束代码组织与修改方式:
- 先思考再编码:不假设、不隐藏困惑、显式陈述假设;存在多种解释时全部列出而非默默选择其一;有更简单方案时主动提出。
- 最简优先:只写解决问题的最小代码,不做投机性抽象,不添加未被要求的功能或「灵活性」,不为不可能的场景写错误处理。文档中给出自检标准:「如果写了 200 行而 50 行够用,就重写」。
- 外科手术式修改:只改必须改的,只清理自己制造的垃圾;不「顺手改进」相邻代码、注释或格式;不重构没坏的东西;即使风格不同也匹配既有风格。判断标准是「每一行改动都应能追溯到用户请求」。
- 目标驱动执行:把任务转化为可验证目标(如「写测试 → 让它通过」),多步任务先列出每步的验证方式。
- 安全基线:所有密钥、token、密码、私钥、数据库连接串必须通过环境变量或
.env管理,.env必须加入.gitignore;若确需在代码中包含敏感信息,必须先征得同意;发现已提交的敏感数据须立即上报以便轮换密钥并清理 Git 历史。
文档末尾给出效果度量:「diff 中不必要的改动更少、因过度复杂导致的重写更少、澄清问题发生在实现之前而非错误之后」。这解释了本仓库代码组织的一个深层意图——目录与命名约定的稳定性本身就是协作契约,任何偏离约定的「顺手改进」都被第 3 条显式禁止。
package.json 中对 src/ 限定的 format/lint 命令与该约定互相印证:
"format": "biome format --write ./src",
"lint": "biome check --write ./src"Source: package.json
格式化与静态检查只作用于 ./src,即源代码目录被定义为需要统一风格的边界;scripts/、tests/ 等工程代码不在 Biome 管辖范围内,保持了「框架源码严格规范、工程脚本宽松灵活」的分层治理。
工具链与包管理约定
- 包管理器锁定:
package.json声明"packageManager": "pnpm@11.5.3",并通过preinstall钩子npx only-allow pnpm强制所有贡献者使用 pnpm,防止 lockfile 漂移。 - Biome 而非 ESLint/Prettier:格式化与 lint 由单一工具 Biome 承担(
@biomejs/biome^2.5.5 devDependency),规则配置在根目录biome.json。 - TypeScript 严格分离:
check(astro check)与type-check(tsc --noEmit)是两个独立命令,前者校验.astro内的类型,后者做纯 TS 源码检查。 - 模块体系:
"type": "module"且工程脚本使用.mjs扩展名(update-anime.mjs、export-config.mjs),在 CommonJS 仍可能被 Node 解析的场景下显式声明 ESM。
失败模式与边界情况
依据 package.json 与 CLAUDE.md 可归纳出以下已显式处理的失败模式:
| 场景 | 处理方式 | 位置 |
|---|---|---|
| 内容仓库同步失败 | predev / prebuild 中 || true 吞掉非零退出码,dev/build 不被阻断 | package.json L9-L10 |
| 构建产物缺样式 | check-global-style-loading.mjs 校验失败使 build 整体失败 | package.json L17 |
| 字体加载失败 | check-font-loading.mjs 在 Pagefind 索引后校验,失败即中断发布 | package.json L17 |
| 使用非 pnpm 安装 | preinstall 钩子 only-allow pnpm 直接拒绝安装 | package.json L29 |
| 密钥泄漏 | CLAUDE.md 安全基线要求立即上报、轮换并清理 Git 历史 | CLAUDE.md §5 |
| 过度改动扩散 | CLAUDE.md 外科手术式修改条款限制改动半径 | CLAUDE.md §3 |
未显式处理、需贡献者自行注意的边界:
- 测试白名单的遗漏风险:新增
tests/*.test.mjs后若忘记加入test命令,该测试永远不会运行——CI 不会因此报错。这是白名单式注册的固有代价(换取对运行集合的完全控制)。 - 命名风格漂移:PascalCase 与 kebab-case 组件目录并存,无自动化工具(Biome 规则未见涉及目录命名)约束新增目录的风格,依赖 review 把关。
scripts/与tests/无 lint 管辖:Biome 只作用于./src,工程脚本与测试文件的格式不受约束。
扩展点
基于上述约定,向仓库添加新能力的标准路径:
- 新增原子组件:在
src/components/atoms/<Name>/下建立三件套(<Name>.astro或<Name>.svelte、index.ts、types.ts),消费方一律从index.ts导入。需要交互状态时用.svelte,纯静态展示用.astro。 - 新增测试:在
tests/下创建<domain>.test.mjs,并必须手动加入package.json的test命令参数列表。 - 新增数据更新流程:在
scripts/下添加脚本,然后在package.jsonscripts 中注册命令(如已有update-bangumi的模式)。 - 新增静态资源:按
src/assets/既有子目录分类(字体入fonts/、图片入images/、横幅入public/assets/),涉及字体的改动需确认check-font-loading仍通过。 - 新增构建校验:在
build命令的&&链上追加node scripts/<check>.mjs,保持「校验失败即构建失败」的语义。
相关链接
- CLAUDE.md — 行为约定全文
- package.json — 工程命令与依赖清单
- src/content.config.ts — 内容集合配置入口
- src/components/atoms/Badge/index.ts — 组件桶导出示例
- src/components/atoms/Badge/types.ts — 组件类型契约示例
- biome.json — 代码质量规则配置