组件开发规范(docs/rule)
docs/rule/ 目录是 Mizuki 项目的开发规范中心,由一份索引文档(README.md)与七份编号规范(01–07)组成,定义了组件分层架构、组件拆分方法、文件组织结构、CSS 样式约束、原子组件优先原则、侧栏组件接入流程与图标使用方式。它是提交代码前代码审查(Code Review)的判定依据。
Purpose and Scope
本页面覆盖 docs/rule/ 规范体系作为整体的结构、职责与核心规则,包括:
- 规范目录的组成(README 索引 + 7 份编号规范)及各规范之间的分工
- 组件分层架构(atoms / molecules / organisms / pages + widgets)的设计意图
- 侧栏组件接入的三步强制流程(类型声明 → 布局配置 → componentMap 注册),这是规范中最容易出错的环节
- 代码审查检查清单(Code Review Checklist)的 18 项条款
- CSS 约束(禁用
!important,Twikoo 例外)与原子组件优先原则
以下相关主题属于兄弟页面,不在本页展开:
- 部署流程与构建触发:见
docs/DEPLOYMENT.md、docs/AUTO_BUILD_TRIGGER.md - 内容创作与渲染管线:见
docs/CONTENT_AUTHORING.md、docs/CONTENT_RENDERING.md - 项目整体文档结构:见
docs/README.md
Overview
Mizuki 是一个 Astro 驱动的组件化博客主题项目。当项目规模增长(出现"超大型组件"、重复 UI 代码、侧栏组件配置后不显示等问题)时,团队将开发约束沉淀为 docs/rule/ 下的成文规范,用于:
| 使用场景 | 解决的问题 |
|---|---|
| 新增 UI 功能 | 决定是复用现有原子组件,还是创建新的 atoms / molecules |
| 重构超大型组件 | 提供拆分判断标准、拆分步骤与验证方法 |
| 新增侧栏小部件 | 避免"配置了组件但页面不显示"的遗漏 |
| 编写样式 | 禁用 !important,统一使用 CSS 变量与 Tailwind 工具类 |
| 提交前自查 | 18 项代码审查检查清单 |
规范目录的文件构成:
| 文件 | 主题 | 核心内容 |
|---|---|---|
README.md | 索引 | 规范列表总览 + 代码审查检查清单 + 参考资源 |
01-component-architecture.md | 组件架构设计 | 原子设计分层、命名规范、职责分离、复用模式 |
02-component-split-guide.md | 组件拆分指南 | 拆分判断标准、超大型组件拆分实例、验证方法 |
03-file-organization-architecture.md | 文件组织架构 | 完整目录树、目录职责、命名规范、依赖管理 |
04-css-style-guide.md | CSS 样式指南 | 禁用 !important(Twikoo 除外)、CSS 变量、暗色主题 |
05-atom-component-usage.md | 原子化组件使用 | 优先复用 atoms/ 与 misc/,重复超过 2 次即抽取组件 |
06-sidebar-widget-dev.md | 侧栏组件开发 | 3 步接入流程,重点是所有渲染器的 componentMap 注册 |
07-icon-usage-specification.md | 图标使用规范 | 图标的引用与使用方式 |
Architecture
规范体系并非孤立的文档集合,而是一套"规范 → 审查 → 代码"的闭环:README.md 作为索引入口汇总各规范要点并沉淀为检查清单,各编号规范分别约束 src/ 下的具体实现层(组件目录结构、类型系统 src/types/config.ts、配置 src/config.ts、侧栏渲染器)。
图中的关键关系说明:
README.md是唯一的规范入口,它将六份编号规范(01–06)的关键点压缩成检查清单,开发者日常只看清单,深入细节时再跳转对应编号文档。- 组件分层由
01定义并在src/components/的目录结构(atoms / molecules / organisms / widgets)中落地;02针对 organisms 层的超大型组件提供拆分路径。 - 侧栏组件开发(
06)是唯一一份跨三个源码文件(类型、配置、渲染器)的"流程型"规范,因此其依赖关系最复杂,也最易遗漏,见下文 侧栏组件接入:三步流程。
组件分层架构(Atomic Design)
规范采用**原子设计(Atomic Design)**理念,将组件分为四层,另设一个"小部件"类别:
各层的职责与代表组件(摘自规范文档的组件清单):
| 层 | 定义 | 特点 | 代表组件 |
|---|---|---|---|
| atoms | UI 最基础、不可再分的元素 | 职责单一、无业务逻辑、高度可复用、不依赖其他组件 | Button.astro、Card.astro、Input.astro、Badge.astro、Chip.astro、Icon.astro、Avatar.astro |
| molecules | 由多个原子组合的单一职责小组件 | 2-5 个原子组合、简单交互、仍高度可复用 | SearchBar.astro、Pagination.astro、DropdownMenu.astro、FormItem.astro、ChipCloud.astro |
| organisms | 复杂业务组件 | 复杂业务逻辑、多个子组件、专用于特定页面、可能需拆分子目录 | Navbar.astro、Sidebar.astro、MusicPlayer.svelte、Footer.astro、TOC.astro |
| widgets | 侧边栏小功能模块 | 相对独立、可配置显示位置、统一 UI 风格、使用通用容器 WidgetLayout.astro | Profile.astro、Calendar.astro、Categories.astro |
设计意图:让"不可复用的复杂性"只存在于 organisms 层。atoms 与 molecules 不含业务逻辑,因此可以在任意页面安全复用;widgets 通过统一的 WidgetLayout.astro 容器获得一致的侧栏视觉风格,同时其挂载位置完全由配置驱动(见下文核心流程)。规范中的组件拆分指南(02-component-split-guide.md)则规定了"组件行数控制在合理范围内(< 500 行)、复杂组件已按功能拆分为子组件"等量化红线(见 README 检查清单)。
核心流程:侧栏组件接入(三步缺一不可)
06-sidebar-widget-dev.md 是整个规范体系中唯一一份跨类型系统、配置与渲染器的流程型规范。它针对的实际痛点在文档开头即点明:避免"配置了组件但页面不显示"的遗漏——因为侧栏渲染依赖手动的 componentMap,未注册的组件类型会被静默忽略。
步骤 1:在类型系统中声明组件类型
文件:src/types/config.ts。所有侧栏组件必须先在 WidgetComponentType 联合类型中声明,缺少此步 TypeScript 编译不通过,后续配置也无意义:
1export type WidgetComponentType =
2 | "profile"
3 | "announcement"
4 | "categories"
5 | "tags"
6 | "toc"
7 | "music-player"
8 | "music-sidebar" // ✅ 新增类型
9 | "pio"
10 | "site-stats"
11 | "calendar"
12 | "custom";Source: 06-sidebar-widget-dev.md
步骤 2:在 sidebarLayoutConfig 中配置布局
文件:src/config.ts。职责划分为:properties 定义组件的存在性(position: top / sticky)、动画与延迟;components.left / right / drawer 定义组件在哪个侧栏显示、按什么顺序:
1export const sidebarLayoutConfig: SidebarLayoutConfig = {
2 properties: [
3 {
4 type: "music-sidebar",
5 position: "sticky",
6 class: "onload-animation",
7 animationDelay: 100,
8 },
9 // ... 其他组件
10 ],
11 components: {
12 left: ["profile", "announcement", "categories", "tags"],
13 right: ["site-stats", "calendar", "music-sidebar"], // 在右侧栏显示
14 drawer: [
15 "profile",
16 "announcement",
17 "music-sidebar",
18 "categories",
19 "tags",
20 ],
21 },
22 // ...
23};Source: 06-sidebar-widget-dev.md
步骤 3:在所有侧栏渲染器的 componentMap 中注册(最易遗漏)
规范强调"这是最常出错的步骤",且左侧栏与右侧栏的 componentMap 相互独立,必须在每个渲染器中分别注册。以左侧栏 src/components/widgets/sidebar/SideBar.astro 为例:
1import { MusicSidebarWidget } from "../music-sidebar";
2
3const componentMap: Record<string, unknown> = {
4 profile: Profile,
5 announcement: Announcement,
6 categories: Categories,
7 tags: Tags,
8 toc: SidebarTOC,
9 "music-player": MusicPlayer,
10 "music-sidebar": MusicSidebarWidget, // ✅ 必须注册
11 "site-stats": SiteStats,
12 calendar: Calendar,
13};Source: 06-sidebar-widget-dev.md
右侧栏 src/components/layout/RightSideBar.astro 使用同一套键名,但导入路径改为别名 @/components/widgets/music-sidebar,并需同样注册 "music-sidebar": MusicSidebarWidget——规范用注释 (与左侧栏独立) 提醒这不是同一份 map:
1import { MusicSidebarWidget } from "@/components/widgets/music-sidebar";
2
3const componentMap: Record<string, unknown> = {
4 profile: Profile,
5 announcement: Announcement,
6 categories: Categories,
7 tags: Tags,
8 toc: SidebarTOC,
9 "music-player": MusicPlayer,
10 "music-sidebar": MusicSidebarWidget, // ✅ 必须注册(与左侧栏独立)
11 "site-stats": SiteStats,
12 calendar: Calendar,
13};Source: 06-sidebar-widget-dev.md
分层与容器的落地示例
原子层组件(如 Button.astro)通过 TypeScript Props 接口声明可选变体与默认值,保证无业务逻辑且高度可配置:
1// Button.astro
2interface Props {
3 variant?: 'primary' | 'secondary' | 'ghost'
4 size?: 'sm' | 'md' | 'lg'
5 disabled?: boolean
6 icon?: string
7}
8
9const { variant = 'primary', size = 'md', disabled = false, icon } = Astro.props
10---
11
12<button class={`btn btn-${variant} btn-${size} ${disabled ? 'disabled' : ''}`}>
13 {icon && <Icon name={icon} />}
14 <slot />
15</button>Source: 01-component-architecture.md
分子组件(如 SearchBar.astro)组合原子并封装简单交互,体现"2-5 个原子 + 简单逻辑"的分层约束:
1---
2// SearchBar.astro
3import Button from '../atoms/Button.astro'
4import Input from '../atoms/Input.astro'
5
6interface Props {
7 placeholder?: string
8 onSearch?: (query: string) => void
9}
10
11const { placeholder = '搜索...', onSearch } = Astro.props
12---
13
14<div class="search-bar">
15 <Input {placeholder} id="search-input" />
16 <Button variant="primary" size="md" icon="material-symbols:search">
17 搜索
18 </Button>
19</div>
20
21<script>
22 const input = document.getElementById('search-input')
23 const button = document.querySelector('.search-bar button')
24
25 const handleSearch = () => {
26 const query = input?.value || ''
27 if (onSearch) {
28 onSearch(query)
29 }
30 }
31
32 button?.addEventListener('click', handleSearch)
33 input?.addEventListener('keypress', (e) => {
34 if (e.key === 'Enter') handleSearch()
35 })
36</script>Source: 01-component-architecture.md
widget 层组件则必须通过通用容器 WidgetLayout.astro 获得统一的侧栏视觉风格,实现"可配置显示位置 + 统一 UI"的目标:
1---
2// widget/Profile.astro
3import WidgetLayout from './common/WidgetLayout.astro'
4import Avatar from '../atoms/Avatar.astro'
5---
6
7<WidgetLayout name="个人资料">
8 <Avatar src="/avatar.png" />
9 <div class="profile-info">
10 <h3>Mizuki</h3>
11 <p>前端开发者</p>
12 </div>
13</WidgetLayout>Source: 01-component-architecture.md
配置选项
WidgetComponentType(类型声明)
src/types/config.ts 中的联合类型,规范要求所有侧栏组件先在此声明。当前合法取值(以新增 music-sidebar 为例):
| 取值 | 说明 |
|---|---|
profile | 个人资料 |
announcement | 公告 |
categories | 分类 |
tags | 标签 |
toc | 目录 |
music-player | 音乐播放器 |
music-sidebar | 侧栏音乐(示例新增类型) |
pio | 看板娘 |
site-stats | 站点统计 |
calendar | 日历 |
custom | 自定义 |
sidebarLayoutConfig(布局配置)
src/config.ts 中的布局配置对象,两块职责互相独立:
| 字段 | 类型 | 作用 |
|---|---|---|
properties[].type | WidgetComponentType | 组件存在性声明 |
properties[].position | "top" | "sticky" | 组件在侧栏内的定位方式 |
properties[].class | string | 附加样式类,如 onload-animation |
properties[].animationDelay | number | 入场动画延迟(毫秒) |
components.left | WidgetComponentType[] | 左侧栏显示的组件及顺序 |
components.right | WidgetComponentType[] | 右侧栏显示的组件及顺序 |
components.drawer | WidgetComponentType[] | 抽屉侧栏显示的组件及顺序 |
componentMap(渲染注册表)
每个侧栏渲染器内手写的 Record<string, unknown>,键为 WidgetComponentType 字符串,值为对应的 Astro/Svelte 组件。键名必须与 WidgetComponentType 完全一致,且左侧栏与右侧栏的 map 相互独立,需分别注册。
代码审查检查清单
README.md 将六份规范压缩为提交前自查的 18 项条款(规范落地的最终形态):
- 组件遵循分层架构规范(atoms/molecules/organisms)
- 组件文件名符合命名规范(PascalCase)
- 组件行数控制在合理范围内(< 500行)
- 复杂组件已按功能拆分为子组件
- 优先使用现有原子化组件(atoms/、misc/)
- 重复 UI 代码超过 2 次应抽取为新组件
- 使用现有通用组件和 Hooks,避免重复代码
- 组件职责单一明确
- 样式使用原子组件或统一样式系统
- 组件使用 TypeScript 定义 Props 接口
- 代码格式化通过(
pnpm run format) - Lint 检查通过(
pnpm run lint) - 没有使用
!important(Twikoo 组件除外) - 使用 Tailwind 工具类或 CSS 变量
- 暗色主题使用 CSS 变量实现
- 侧栏组件已在所有相关 componentMap 中注册
- 侧栏组件的类型已在
WidgetComponentType中声明 - 侧栏组件已在
sidebarLayoutConfig.components中配置
Source: README.md
清单中加粗的三条侧栏条款与三步接入流程一一对应,其余条款分别溯源到 01(分层/PascalCase/行数)、02(拆分)、03(文件组织)、04(CSS)、05(原子组件优先)五份规范——这体现了 README.md 作为"索引 + 审查出口"的双重定位。
Failure Modes, Edge Cases & Concurrency
| 失败模式 | 根因 | 规范规定的规避方式 |
|---|---|---|
| 配置了组件但页面不显示 | 渲染器 componentMap 未注册该类型,查找失败后静默忽略(无报错) | 三步流程中的步骤 3,在全部 3 个渲染器中逐一注册 |
| 组件仅在左侧栏显示、右侧栏缺失 | 左右两侧栏 componentMap 相互独立,只注册了一处 | 检查 SideBar.astro 与 RightSideBar.astro 两个文件(含抽屉侧栏) |
| 配置键无效 / TS 编译失败 | 未先在 WidgetComponentType 中声明类型 | 步骤 1 前置类型声明,让编译器成为第一道防线 |
| 超大型组件难以维护 | 职责堆积、未按功能拆分子组件 | 02 拆分指南 + 清单"< 500 行"量化红线 |
| 重复 UI 代码扩散 | 绕过现有原子组件重复实现 | 05:重复超过 2 次必须抽取为新组件 |
| 样式互相覆盖、暗色主题失效 | 使用 !important 强行覆盖 | 04:禁用 !important(Twikoo 除外),改用提高选择器优先级 / CSS 变量 / Tailwind 工具类 |
值得注意的设计取舍:规范故意没有把 componentMap 做成自动化注册(如自动扫描目录生成映射),而是保留手动注册并配以三步清单。代价是容易遗漏,收益是每个渲染器对可用组件集合有完全显式的控制,避免侧栏之间的隐式耦合。
Performance / Operational Notes 与 Extension Points
- 性能导向的分层:atoms / molecules 无业务逻辑且不依赖其他组件,可被任意页面安全复用而不引入副作用;
onload-animation+animationDelay的入场动画配置在sidebarLayoutConfig.properties中按组件粒度控制,避免全局动画阻塞首屏。 - 扩展点 1 —— 新增侧栏小部件:完整路径为
WidgetComponentType新增枚举 →sidebarLayoutConfig.properties+components.*配置 → 所有渲染器componentMap注册 → 组件内部使用WidgetLayout.astro容器。 - 扩展点 2 —— 新增原子/分子组件:先检索 atoms/ 与 misc/ 是否已有合适组件;只有当"重复 UI 代码超过 2 次"且确无现成组件时才创建新组件,并保持无业务逻辑、PascalCase 命名、TS Props 接口。
- 扩展点 3 —— 超大型组件拆分:遵循
02的拆分标准与验证方法,organisms 可拆分为子目录(如Navbar.astro+ 若干.svelte分子/原子子组件)。 - 运维入口:提交前执行
pnpm run format与pnpm run lint,两项均为清单强制项。
Related Links
- 规范索引:README.md
- 01-component-architecture.md — 组件架构设计规范
- 02-component-split-guide.md — 组件拆分指南
- 03-file-organization-architecture.md — 文件组织架构规范
- 04-css-style-guide.md — CSS 样式指南
- 05-atom-component-usage.md — 原子化组件使用规范
- 06-sidebar-widget-dev.md — 侧栏组件开发指南
- 07-icon-usage-specification.md — 图标使用规范
- 外部参考(规范文档内引用):Aruma 组件架构、Astro 组件最佳实践、组件驱动开发
- 兄弟页面:部署指南
docs/DEPLOYMENT.md、内容创作docs/CONTENT_AUTHORING.md、内容渲染docs/CONTENT_RENDERING.md
说明:
07-icon-usage-specification.md与02/03/04/05的逐条细则未在本次源工具预算内全文读取,本文对其内容的概括均来自docs/rule/README.md中的关键点描述;深入细节请直接查阅对应编号文档。