# 架构设计:Gitea GitHub Theme ## 项目概述 Gitea GitHub Theme 是一个构建时 CSS 生成框架,为 [Gitea](https://github.com/go-gitea/gitea) 自托管代码平台产出静态 `.css` 主题文件。项目以设计令牌和颜色定义为输入,通过分层管道转换,最终输出 30+ 个压缩后的 CSS 文件,覆盖 7 个主题系列(default、colorblind、tritanopia、pink、gitea、catppuccin、high-contrast)。 **核心原则:** 开发时享受完整的类型安全和 CSS-in-JS 开发体验,浏览器端交付纯静态 CSS,零运行时开销。 --- ## 架构分层 ``` ┌──────────────────────────────────────────────────────────────┐ │ 主题配置层 │ │ theme.config.ts: 声明主题系列、颜色类型、显示名称、无障碍元信息 │ └──────────────────────────┬───────────────────────────────────┘ │ ┌──────────────────────────▼───────────────────────────────────┐ │ 颜色定义层 │ │ ┌──────────┐ ┌──────────────┐ ┌───────────────────┐ │ │ │ primer/ │ │ themes/ │ │ themes/catppuccin │ │ │ │ (自动生成│ │ github.ts │ │ .ts │ │ │ │ 的 Primer│ │ gitea*.ts │ │ │ │ │ │ 设计令牌)│ │ │ │ │ │ │ └────┬─────┘ └──────┬───────┘ └─────────┬─────────┘ │ │ │ │ │ │ │ primer2GitHubColor defineTheme catppuccin2ThemeColor │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ GitHubColor Theme ThemeColor │ │ (colorType: │ │ "github") │ └──────────────────────────┬───────────────────────────────────┘ │ ┌──────────────────────────▼───────────────────────────────────┐ │ 颜色转换层 │ │ src/palette/ │ │ ┌────────────┐ ┌────────────┐ ┌──────────────────┐ │ │ │ github.ts │ │ theme.ts │ │ gitea.ts │ │ │ │ GitHubColor│ │ ThemeColor │ │ GiteaColor │ │ │ │ → │ │ → │ │ → │ │ │ │ ThemeColor │ │ ThemeVars │ │ ThemeVars │ │ │ └────────────┘ └────────────┘ └──────────────────┘ │ │ │ │ 统一输出: ThemeVars (CSS 变量的扁平键值映射) │ └──────────────────────────┬───────────────────────────────────┘ │ ┌──────────────────────────▼───────────────────────────────────┐ │ 核心框架层 │ │ src/core/ │ │ ┌──────────────┐ ┌──────────┐ ┌───────────────────┐ │ │ │ theme.ts │ │ css.ts │ │ chroma.ts │ │ │ │ createTheme()│ │ css() │ │ codemirror.ts │ │ │ │ defineTheme()│ │cssCombine│ │ meta.ts │ │ │ └──────┬───────┘ │cssStyle()│ └───────────────────┘ │ │ │ └────┬─────┘ │ │ │ │ │ │ vanilla-extract 适配器 (src/vanilla-extract.ts) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ setAdapter() → 拦截 appendCss, │ │ │ │ registerClassName, registerComposition │ │ │ │ → 在构建时将全部 CSS 序列化为字符串 │ │ │ └──────────────────────────────────────────────────┘ │ └──────────────────────────┬───────────────────────────────────┘ │ ┌──────────────────────────▼───────────────────────────────────┐ │ 样式覆写层 │ │ styles/ │ │ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ public/ │ │ components/ │ │ templates/ │ │ │ │ (基础 │ │ (页面级别 │ │ (模板专属样式) │ │ │ │ 元素) │ │ 样式) │ │ │ │ │ └──────────┘ └──────────────┘ └──────────────────┘ │ │ common/ (可复用 CSS 片段: 按钮、标签、动画等) │ │ │ │ 每个文件使用 css`...` 模板字面量 │ │ → 在导出边界用 cssCombine() 拼接 → 单个 CSS 字符串 │ └──────────────────────────┬───────────────────────────────────┘ │ ┌──────────────────────────▼───────────────────────────────────┐ │ 构建管道 │ │ src/vite-plugin/index.ts (Vite 插件) │ │ │ │ config() 钩子: │ │ 1. 动态导入 theme.config.ts │ │ 2. 解析所有主题条目 + 生成 auto 自动主题 │ │ │ │ generateBundle() 钩子: │ │ 1. 导入 styles/ → CSS 字符串 (public + components) │ │ 2. 对每个主题条目: │ │ a. compileCSS(createThemeMetaInfo + createTheme) │ │ b. lightningcss.transform() → 压缩 │ │ c. 追加样式覆写 (仅非 auto 主题) │ │ d. emitFile 输出 .css 资源 │ │ 3. 对 auto 主题: 生成 @import 声明 │ └──────────────────────────┬───────────────────────────────────┘ │ ▼ dist/theme-github-*.css ``` --- ## 核心设计模式 ### 1. Vanilla-Extract 适配器劫持 **解决的问题:** Vanilla-extract 是一个零运行时 CSS-in-JS 库,在构建时通过其 Vite/esbuild 插件生成静态 CSS 文件。但 Gitea 主题需要单文件 CSS 输出,并且需要对主题生成过程进行编程控制(每个主题一个输出文件、自动生成 auto 主题等)。 **方案:** 绕过 vanilla-extract 的默认插件,直接使用其底层适配器 API。 在 [src/vanilla-extract.ts](src/vanilla-extract.ts) 中: ```typescript export function compileCSS(compileFn: () => void): string { // 1. 安装自定义适配器,将 CSS 对象捕获到数组 setAdapter({ appendCss: css => capturedCssObjs.push(css), registerClassName: className => localClassNames.add(className), registerComposition: composition => composedClassLists.push(composition), // ... 其他回调实现为空操作 }); // 2. 设置文件作用域用于调试 setFileScope(getCallerInfo(1)); // 3. 执行调用方的 vanilla-extract API 调用 compileFn(); // 4. 拆除适配器 endFileScope(); removeAdapter(); // 5. 将捕获的 CSS 对象序列化为字符串 return transformCss({ localClassNames: Array.from(localClassNames), composedClassLists, cssObjs: capturedCssObjs, }).join("\n"); } ``` **工作原理:** 1. `compileCSS` 包装一个同步函数,该函数内部调用 vanilla-extract API(`createGlobalTheme`、`globalStyle`、`globalKeyframes` 等) 2. 自定义适配器拦截所有 CSS 生成调用,将它们捕获到内存数组 3. 函数执行完毕后,`transformCss()` 将捕获的对象序列化为 CSS 字符串 4. 适配器被拆除,不留下任何全局副作用 **安全性保证:** 每次 `compileCSS` 调用完全自包含:安装 → 捕获 → 序列化 → 拆除。调用之间无状态泄漏。 **关键洞察:** Vanilla-extract 的 `createGlobalThemeContract` + `createGlobalTheme` 完全适配此模式——它们仅在 `createGlobalTheme` 调用期间产生 CSS,而该调用恰好发生在我们的捕获范围内。 ### 2. 三条颜色输入管道 **解决的问题:** 不同的颜色主题来源有不同的数据结构。Primer 设计令牌(来自 GitHub)有数百个嵌套键。Catppuccin 有自己的调色板结构。Gitea 的原生格式使用扁平结构。需要同时支持这三种。 **方案:** 三条转换路径,全部汇聚到 `ThemeVars`: ``` Primer 令牌 ──→ GitHubColor ──→ ThemeColor ──→ ThemeVars Catppuccin flavor ────────────→ ThemeColor ──→ ThemeVars GiteaColor ──────────────────────────────────→ ThemeVars ``` 每条路径有不同的复杂度要求: | 路径 | 适用场景 | 复杂度 | 示例 | | -------- | ------------------------- | ---------------------------- | --------------------------- | | `github` | 完整 Primer 令牌合规 | 高(数百个映射键) | Default、色盲、高对比度主题 | | `theme` | 15 个基础色定义自定义主题 | 低(自动派生 200+ CSS 变量) | Catppuccin、社区主题 | | `gitea` | Gitea 原生颜色格式 | 中(直接映射) | Gitea 兼容主题 | **设计理由:** `theme` 路径是"入门路径"——贡献者只需定义 15 个语义色(`red`、`orange`、`yellow`、……、`white`)加上 primary 和 secondary。框架自动计算: - 7 级亮度变体(dark-1 到 dark-7、light-1 到 light-7) - 9 级透明度变体(alpha-10 到 alpha-90) - Badge 颜色及其 hover 状态 - 消息颜色(error/success/warning/info) - ANSI 控制台颜色 - 方向感知(暗/亮主题下的缩放方向反转) 实现在 [src/palette/theme.ts](src/palette/theme.ts) 的 `theme2ThemeVars()` 函数中。 ### 3. CSS 变量命名约定 **解决的问题:** Gitea 期望特定命名模式的 CSS 变量(`--color-primary-dark-1`、`--color-red-badge-bg` 等)。我们在 TypeScript 中需要类型安全的访问方式(`themeVars.color.primary.dark.num1`)。 **方案:** 使用 vanilla-extract 的 `createGlobalThemeContract` 配合自定义 `varMapper`。 在 [src/types/vars.ts](src/types/vars.ts) 中: ```typescript function varMapper(prefix: string | null = null) { return (value: string | null, path: string[]) => { if (value === null) { // 从路径自动生成名称: ["color","primary","dark","num1"] → "color-primary-dark-1" path = path.filter(item => item !== "self"); // 移除 "self" 键 path = path.map(item => item.replace(/^num/, "")); // 去掉 "num" 前缀 value = path.join("-"); } if (prefix) value = `${prefix}-${value}`; return value; }; } ``` 由此创建了双向映射: | TypeScript 访问 | CSS 变量 | | ---------------------------------------------- | -------------------------------------- | | `themeVars.color.primary.self` | `--color-primary` | | `themeVars.color.primary.dark.num1` | `--color-primary-dark-1` | | `themeVars.github.button.primary.bgColor.rest` | `--github-button-primary-bgColor-rest` | | `syntaxVars.keyword` | `--color-syntax-keyword` | | `customThemeVars.cloneMenuWidth` | `--custom-clone-menu-width` | ### 4. 构建时 CSS 语法校验 **解决的问题:** 模板字面量中的 CSS 可能包含语法错误,这些错误只有到浏览器运行时才会显现。 **方案:** `css()` 模板标签在构建时使用 lightningcss 校验 CSS 语法。 在 [src/core/css.ts](src/core/css.ts) 中: ```typescript export function css(strings: TemplateStringsArray, ...values: CSSInterpolation[]): CSSString { const result = strings.reduce((acc, s, i) => acc + s + String(values[i] ?? ""), ""); // 构建时校验: lightningcss 解析并报告语法错误 transform({ filename: getCallerInfo(1), code: Buffer.from(result) }); return result as CSSString; } ``` 如果开发者编写了无效 CSS,构建会立即失败,并附带指向问题模板字面量的 `文件:行号` 信息。这能捕捉到原本只能通过浏览器 DevTools 才能发现的错误。 **权衡:** 每次 `css` 模板调用都会执行一次 `transform()`。对于约 60 个样式文件,这会为初始构建增加约 200ms(一次性开销,后续由 Bun 的模块缓存加速)。 --- ## Vite 构建管道 构建由自定义 Vite 插件在 [src/vite-plugin/index.ts](src/vite-plugin/index.ts) 中编排: ### 阶段一:配置解析(`config` 钩子) 1. 通过 Bun 原生 TypeScript + package exports 自引用(`@lutinglt/gitea-github-theme/theme.config.ts`)动态导入 `theme.config.ts` 2. 遍历每个主题系列,为每个主题变体生成条目(dark、light、soft-dark、frappe、macchiato 等) 3. 当系列同时具有 dark 和 light 变体时,自动生成 `auto` 主题条目 4. 创建占位入口文件(Vite 需要至少一个输入) ### 阶段二:CSS 生成(`generateBundle` 钩子) 对每个 **非 auto 主题**: 1. 调用 `compileCSS()`,传入一个函数,该函数内部: - 创建 Gitea 主题元信息(`createThemeMetaInfo`)——设置 `--theme-color-scheme`、`--theme-display-name`、`--theme-colorblind-type` - 创建主题 CSS 变量(`createTheme`)——生成所有 `--color-*` 和 `--github-*` 变量、全局样式、关键帧动画、代码高亮 2. 捕获的 CSS 字符串通过 `lightningcss.transform()` 进行压缩 3. 追加公共样式覆写(`styles/`) 对每个 **auto 主题**: 1. 生成 `@import` 声明,配合 `prefers-color-scheme` 媒体查询,引用对应的暗/亮主题文件 2. 包含主题元信息,供 Gitea 识别为自动颜色主题 ### CSS 输出结构 ``` theme-github-dark.css: ├── @keyframes (overlayAppear, overlayAppearDown, overlayAppearUp) ├── gitea-theme-meta-info { --theme-color-scheme: dark; ... } ├── :root { --theme-version: "1.26.2.260605"; --is-dark-theme: true; ... } ├── :root { --color-primary: #4493f8; --color-primary-contrast: ... } ├── ... (200+ 个 CSS 变量声明) ├── .chroma { ... } (代码语法高亮) ├── .code-editor-container { ... } (CodeMirror 高亮) └── <公共样式覆写> (~60KB 单行压缩) theme-github-auto.css: ├── @import "./theme-github-dark.css" (prefers-color-scheme: dark); ├── @import "./theme-github-light.css" (prefers-color-scheme: light); └── gitea-theme-meta-info { --theme-color-scheme: auto; ... } ``` --- ## 颜色系统深入 ### 颜色缩放计算 [src/functions/color.ts](src/functions/color.ts) 中的 `scaleColorLight()` 实现了 Sass `color.scale()` 算法: ``` 变暗: newL = currentL × (1 + scale/100) [scale < 0] 变亮: newL = currentL + (1 - currentL) × (scale/100) [scale > 0] ``` 这是**比例缩放**,并非线性偏移。其意义在于: - `color.scale(#000000, $lightness: 50%)` → 50% 灰色(而非"比黑色暗 50%",那仍然是黑色) - `color.scale(#ffffff, $lightness: -50%)` → 50% 灰色(而非"比白色亮 50%",那仍然是白色) - 比例方法确保在亮度两端都能产生视觉上有意义的变化 ### ThemeColor → ThemeVars 派生 [src/palette/theme.ts](src/palette/theme.ts) 中的 `theme2ThemeVars()` 接收 `ThemeColor`(15 个基础色 + 元数据),派生完整的 `ThemeVars`(200+ 个 CSS 变量值): 1. **主色阶(Primary scale)**:7 个暗色变体、7 个亮色变体、9 个透明度变体、hover/active 状态——全部从一个 hex 颜色生成 2. **副色阶(Secondary scale)**:13 个暗色变体、4 个亮色变体、9 个透明度变体、按钮 hover/active 状态 3. **命名颜色**(15 个基础色中的 13 个):每个生成 self/light/dark-1/dark-2/badge-self/badge-bg/badge-hover-bg 4. **语义消息色**:bg/border/text,配合 rgba 透明度和饱和度调整 5. **ANSI 颜色**:从命名色映射的 16 色调色板 6. **方向感知**:`isDarkTheme` 标志反转缩放方向——暗色主题中"dark"变体走向更亮的方向(为保持对比度) --- ## 样式覆写策略 ### 选择器优先级层级 样式覆写使用 Gitea 现有的 DOM 选择器。覆写策略遵循优先级阶梯: 1. **颜色变量**——所有颜色引用主题中的 CSS 变量(绝不硬编码 hex 值) 2. **元素级覆写**——基础元素如 `.ui.button`、`#navbar`、`input` 3. **组件级覆写**——页面特定区域:heatmap、issues、actions、仓库页面 4. **`!important` 逃离**——仅在 Gitea 通过内联 `style` 属性设置颜色时使用(如热力图贡献方块) ### cssCombine() 显式组合 样式通过 `cssCombine()` 调用树组合在一起: ``` styles/index.ts ├── public/index.ts (基础元素、按钮、输入框、下拉菜单、菜单、表格) ├── components/index.ts (导航栏、热力图、议题、仓库文件、Actions 等) └── templates/index.ts (模板特定样式) ``` 每个叶子模块导出 `default cssCombine(...)`,将 CSS 片段串联在一起。这种显式组合使依赖关系图可见,避免了 CSS `@import` 或 `@use` 的隐式排序问题。 ### cssStyle() 可复用片段 [src/core/css.ts](src/core/css.ts) 中的 `cssStyle()` 允许提取可复用的 CSS 属性集,同时保留 vanilla-extract 的类型检查: ```typescript // 在 styles/common/button.ts 中: export const primaryButtonStyle = cssStyle({ color: themeVars.github.button.primary.fgColor.rest, backgroundColor: themeVars.github.button.primary.bgColor.rest, borderColor: themeVars.github.button.primary.borderColor.rest, boxShadow: themeVars.github.shadow.resting.small, }); // 在 styles/public/button.ts 中: const baseButton = css` .ui.primary.button { ${primaryButtonStyle} } `; ``` 这在最终输出静态 CSS 字符串的系统中保留了类型安全(vanilla-extract 校验属性名和值类型)。 --- ## 目录结构 ``` ├── theme.config.ts # 主题系列声明(框架入口点) ├── src/ │ ├── index.ts # 公共 API 导出 │ ├── core/ # 核心框架 │ │ ├── theme.ts # createTheme、defineTheme(颜色 → CSS 变量) │ │ ├── css.ts # css()、cssCombine()、cssStyle() │ │ ├── chroma.ts # Chroma(服务端)语法高亮 │ │ ├── codemirror.ts # CodeMirror(客户端)语法高亮 │ │ ├── meta.ts # Gitea 主题元信息(color scheme, display name) │ │ └── types.d.ts # MapLeafNodes、CSSVarFunction、Tokens │ ├── palette/ # 颜色转换与变换 │ │ ├── theme.ts # ThemeColor → ThemeVars(简化 API) │ │ ├── github.ts # GitHubColor → ThemeColor(Primer 路径) │ │ ├── gitea.ts # GiteaColor → ThemeVars(原生路径) │ │ ├── catppuccin.ts # CatppuccinFlavor → ThemeColor │ │ ├── primer.ts # Primer 令牌 → GitHubColor / Chroma / CodeMirror │ │ ├── display.ts # Display color scale → GitHubColor 覆写 │ │ └── catppuccinSyntax.ts # Catppuccin 语法高亮 │ ├── functions/ # 工具函数 │ │ ├── color.ts # scaleColorLight(Sass color.scale)+ rgba 转换 │ │ └── var.ts # extractVarName(CSS 变量名提取) │ ├── types/ # 类型定义与 CSS 变量契约 │ │ ├── color/ # 颜色结构定义(primary, secondary, named, 等) │ │ └── vars.ts # createGlobalThemeContract + varMapper │ ├── vite-plugin/ # 构建工具 │ │ ├── index.ts # 主 Vite 插件 │ │ ├── themeConfig.ts # defineThemeConfig、TypeScript 类型 │ │ └── utils.ts # buildFullThemeName、buildFullDisplayName │ ├── vanilla-extract.ts # 适配器劫持(compileCSS、captureStyle) │ ├── selectors.ts # 共享 CSS 选择器(root、chroma、codeMirror、emoji) │ └── utils.ts # deepOverride、getCallerInfo ├── styles/ # CSS 样式覆写 │ ├── common/ # 可复用 CSS 片段(按钮、标签、动画) │ ├── public/ # 基础元素(按钮、输入框、下拉菜单、菜单、表格) │ ├── components/ # 页面特定样式(导航栏、热力图、仓库、议题、Actions) │ └── templates/ # 模板特定样式 ├── themes/ # 颜色主题定义 │ ├── github.ts # GitHub 主题颜色(来自 Primer 令牌) │ ├── giteaDark.ts # Gitea 暗色主题颜色(原生格式) │ ├── giteaLight.ts # Gitea 亮色主题颜色(原生格式) │ └── catppuccin.ts # Catppuccin 主题颜色 ├── primer/ # 自动生成的 Primer 设计令牌(来自 @primer/primitives) ├── scripts/ # 构建与工具脚本 │ ├── buildPrimerTSToken.ts # 从 @primer/primitives 生成 primer/ TypeScript 令牌 │ ├── syncTheme.ts # 同步构建产物到远程 Gitea 实例 │ ├── updateGitea.ts # 更新 Gitea 参考文件 │ ├── translate.ts # 生成本地化/翻译文件 │ └── version.ts # 版本管理 ├── dist/ # 构建产物(30+ CSS 文件) └── templates/ # 可选的 Gitea Go 模板覆写 ``` --- ## 关键设计决策与权衡 ### 决策一:Vanilla-Extract 仅用于 CSS 编写,不用于运行时 **选择:** 使用 vanilla-extract 的 `createGlobalTheme`/`createGlobalThemeContract` 进行类型安全的 CSS 变量生成,但通过适配器劫持在构建时序列化为静态 CSS 字符串。 **放弃:** 使用 vanilla-extract 内置 Vite 插件(生成按模块拆分的 CSS 文件,不适合单文件主题输出)。 **权衡:** 适配器 API 无官方文档,可能在 vanilla-extract 版本间变化。通过锁定 `@vanilla-extract/css` 版本并使用快照测试检测 CSS 输出变化来缓解风险。 ### 决策二:单文件 CSS 输出 **选择:** 每个主题是包含所有变量和所有样式覆写的单个 CSS 文件。 **放弃:** 通过 `@import` 链拆分为多个 CSS 文件(Gitea 主题系统每个主题只加载一个 CSS 文件)。 **权衡:** 样式覆写在所有主题文件中重复嵌入(约 60KB × 30 个主题 ≈ 1.8MB 冗余)。这是 Gitea 的约束而非设计选择。替代方案(独立 common.css)需要 Gitea 支持多文件主题加载。 ### 决策三:LightningCSS 同时用于语法校验和输出压缩 **选择:** 使用 LightningCSS 进行构建时 CSS 语法校验(在 `css()` 模板标签中)和输出压缩(在 Vite 插件中)。 **放弃:** PostCSS(更慢)、esbuild CSS(功能不够完整)、完全不校验。 **权衡:** LightningCSS 较宽容——它会从许多语法错误中恢复而非抛出异常。这意味着 `css()` 中的"校验"能捕获严重错误,但可能静默接受某些边缘情况。构建产物快照测试部分补偿了这个不足。 ### 决策四:仅支持 Bun 运行时 **选择:** 要求 Bun ≥ 1.3.14 用于开发和构建。 **放弃:** 跨运行时支持(Node + Bun)。 **权衡:** 限制了贡献者环境。收益:原生 TypeScript 执行(无 ts-node/esbuild 开销)、一致的堆栈追踪格式(用于调试工具)、更快的构建速度(Bun 原生打包器)。 ### 决策五:包自引用用于内部导入 **选择:** 即使在同一个包内也使用 `@lutinglt/gitea-github-theme/core` 风格的导入,通过 `package.json` 中的 `exports` 映射实现。 **放弃:** 全程使用相对路径导入。 **权衡:** 需要 `tsconfig.json` 中的 `paths` 映射支持 IDE。收益:无论作为依赖使用还是本地开发,导入路径完全一致;`src/`、`styles/`、`themes/` 三个目录之间没有路径混淆。 --- ## 添加新主题 ### 快速路径(15 个颜色定义) 1. 创建 `themes/my-theme.ts`: ```typescript import { defineTheme } from "@lutinglt/gitea-github-theme/core"; import type { ThemeColor, Syntax } from "@lutinglt/gitea-github-theme/core"; const themeColor: ThemeColor = { isDarkTheme: true, primary: "#8b5cf6", // 强调色(紫色) primaryContrast: "#ffffff", secondary: "#27272a", // 边框色 base: { red: "#ef4444", orange: "#f97316", yellow: "#eab308", olive: "#84cc16", green: "#22c55e", teal: "#14b8a6", cyan: "#06b6d4", blue: "#3b82f6", violet: "#8b5cf6", purple: "#a855f7", pink: "#ec4899", brown: "#a16207", black: "#18181b", grey: "#71717a", gold: "#eab308", white: "#fafafa", }, console: { /* Actions 日志颜色 */ }, diff: { /* 代码差异颜色 */ }, other: { /* body、text、input、shadow 等 */ }, github: { /* GitHub 专有变量 */ }, }; const syntaxColor: Syntax = { /* 代码高亮色 */ }; export default defineTheme({ colorType: "theme", themeColor, syntaxColor }); ``` 2. 在 `theme.config.ts` 中注册: ```typescript { themeSeriesName: "my-theme", themes: { dark: { theme: myDarkTheme }, // 可选: 添加 light 变体后自动生成 auto 主题 light: { theme: myLightTheme }, }, } ``` 3. 运行 `bun bundle`——主题 CSS 将出现在 `dist/` 目录中。 ### 完整路径(Primer 令牌) 如需像素级精准的 GitHub 颜色匹配,使用 `github` 路径直接映射 Primer 设计令牌。完整映射参考见 `themes/github.ts` 和 `src/palette/github.ts`。 --- ## 测试策略 | 层级 | 测试类型 | 位置 | 验证内容 | | -------- | -------- | ---------------------------- | ----------------------------------------------------------------------------------------------- | | 工具函数 | 单元测试 | `src/utils.test.ts` | `deepOverride` 行为、`getCallerInfo` 堆栈解析 | | 计算函数 | 单元测试 | `src/functions/*.test.ts` | `scaleColorLight` 数值计算、`extractVarName` 解析 | | 核心框架 | 单元测试 | `src/core/css.test.ts` | `css()` 校验、`cssCombine()` 组合、`cssStyle()` 序列化 | | 调色板 | 快照测试 | `src/palette/*.test.ts` | 完整颜色转换链路(Primer → ThemeColor → ThemeVars、Catppuccin → ThemeColor → ThemeVars 等) | | 调色板 | 结构测试 | `src/palette/github.test.ts` | 颜色值合法性(所有叶子节点都是有效 CSS 颜色字符串)、必需键存在 | | 构建产物 | 集成测试 | `src/build-output.test.ts` | 预期文件存在、CSS 内容有效性(变量存在、无调试残留、hex 小写、color-scheme 匹配)、文件体积上限 | --- ## 相关文档 - [CONTRIBUTING_CN.md](CONTRIBUTING_CN.md) — 开发环境配置与贡献指南 - [README.md](README.md) — 用户面安装与使用 - [README_CN.md](README_CN.md) — 中文用户安装与使用 - [primer/README.md](primer/README.md) — 自动生成的 Primer 令牌说明 - [CHANGELOG.md](CHANGELOG.md) — 发布历史