# 开发指南 本文档面向 WarmIsland 主题的开发者,介绍如何搭建开发环境、构建主题以及参与贡献。 ## 环境要求 | 工具 | 最低版本 | 推荐版本 | |------|----------|----------| | Node.js | 22.12.x | 22.x LTS | | pnpm | 12.x | 12.x | | Git | 2.x | 最新 | ### 安装 pnpm ```bash npm install -g pnpm ``` ### 验证环境 ```bash node -v # v22.x.x(需 >= 22.12.0) pnpm -v # 12.x.x git --version # git version 2.x.x ``` ## 克隆与安装 ```bash git clone https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland.git cd halo-theme-WarmIsland pnpm install ``` ## 开发模式 `pnpm dev` 用 nodemon 监听 `src/`、`public/`、`settings.yaml` 等文件的变化, 每次变化自动执行一次 `astro build`,把结果写入 `templates/`。 本项目是「静态模板产物 + Halo 运行时渲染」模式,没有 Astro 开发服务器, 也不依赖 `vite-plugin-halo-theme`;本地预览请直接用 Halo 实例加载 `templates/`。 ## 构建 ### 生产构建 ```bash pnpm build ``` 构建产物位于 **`templates/`**(21 个 `.html` 模板 + `assets/` 静态资源), 并由 `scripts/postbuild.mjs` 合并 `compat/assets` 中的历史哈希资源、校验资源引用。 注意:`pnpm build` **不会**生成 ZIP 安装包。 ### 打包主题 ```bash pnpm release # 推荐:build → snapshot → package(含合规审计) # 或分步:pnpm build && pnpm snapshot && pnpm audit:external && pnpm package ``` 生成 `dist/warm-island-.zip`,可直接在 Halo 后台上传安装。 ZIP 根目录直接放 `theme.yaml` / `settings.yaml` / `templates/`,不要额外套一层目录; `docs/*.md` 与预览图也会一并打包,保证上架说明里的引用在制品中真实存在。 > **`templates/` 被 `.gitignore` 忽略**,克隆仓库后默认不存在。若跳过 `pnpm build` > 直接 `pnpm package`,过去会产出不含任何模板的空壳包,上传后 Halo 报 > `Template index was not found.` 并让首页 404。现在打包脚本会先校验 > `index.html` 等模板、资源目录与版本号,不满足即报错退出。 ### 上架合规审计 `scripts/audit-external.mjs`(`pnpm audit:external`)在打包前自动执行,对应应用市场审核指南 1.4 / 4.4.4 / 4.6 / 5.1,检查: - 模板中不含第三方 `preconnect` / `dns-prefetch` / `preload` - 不含 `src` / `href` 指向 http(s) 的远程脚本、样式、图片、媒体 - 不含硬编码的外部 `fetch()` / 动态 `import()` / CSS `url(https://...)`, 也不含 Prism autoloader 之类的动态加载痕迹与被移除的服务域名 - README 中的相对链接都能在安装包清单里找到对应文件 - `templates/logo.png` 不存在(只允许一份压缩后的 `templates/assets/logo.png`) 任一不通过,`pnpm package` 直接 `exit 1`,避免打出不合规制品。 `` 这类需要访客主动点击的外链是允许的,但必须在 README 「外部资源与隐私」章节逐项披露。新增第三方资源时,请先自托管到 `public/assets/` 并在 README 的许可表中登记来源与协议。 ## 性能与滚动顺滑 首页滚动曾经卡顿,根因不是 DOM 数量,而是「每帧高成本绘制 + 劫持滚动输入」: | 问题 | 原因 | 现在的做法 | |------|------|------------| | 全屏模糊逐帧重算 | 滚动回调每帧改写 `backdrop-filter: blur(Npx)`,Chromium 每帧重做一次全屏背景采样 + 高斯模糊 | 模糊半径固定,滚动只改整层 `opacity`(合成层操作) | | 吸附吞掉滚动惯性 | 旧实现在 `wheel` / `touchend` 上 `preventDefault()` 并固定锁 800ms | 保留整屏吸附,但只在 `0 ↔ 内容区顶部` 边界接管一次;进入内容区后完全交给原生滚动。解锁用 `scrollend` + 600ms 兜底,已在目标位置时不再接管 | | Hero 一直绘制 | 固定全屏 Hero 的光球动画与模糊层在滚过首屏后仍在跑 | 内容区盖满视口后加 `wi-hero--offscreen` 隐藏并暂停动画 | | 状态反复写入 | 导航栏每个滚动帧都 `classList.add/remove` | 缓存状态,只在变化时写 | **为什么吸附不用 CSS `scroll-snap`**:v1.2.5 初版试过 `html { scroll-snap-type: y proximity }` 加内容区 `scroll-snap-align: start`,实际几乎不吸附——只有内容区顶部一个吸附点,页面顶部 `0` 没有吸附点,`proximity` 只在滚动结束位置接近吸附点时才生效;换成 `mandatory` 则会在 长内容的每次滚动后把页面拉回内容区顶部,正文无法阅读。`prefers-reduced-motion` 下 CSS 方案 还会直接关闭吸附。因此改为脚本的边界吸附,CSS 侧不再设置 `scroll-snap`,避免与 `scrollTo` 平滑动画抢夺滚动位置。 涉及文件:`src/pages/index.astro`(滚动渐隐 + 边界吸附脚本)、`src/components/HeroSection.astro`(模糊层与离屏规则)、 `src/components/Navbar.astro`(状态去重)、`src/styles/main.scss`(平滑滚动与 reduced-motion)。 **可调项**:后台「总体样式 → 启用动效」可一键关闭滚动渐入,「Hero 首屏」可关闭整个 Hero; 系统开启「减少动态效果」(`prefers-reduced-motion: reduce`)时,平滑滚动与光球 / 箭头动画停用, 吸附改为即时定位(仍然可用)。长列表使用 `content-visibility: auto` 跳过视口外渲染, 图片统一 `loading="lazy" decoding="async"`。 > 改动滚动逻辑后建议用浏览器 Performance 面板录制一次首页滚动:滚动帧里不应出现大面积 > `Paint` / `Update Layer Tree`;也不要在 `scroll` 回调里读取 `getBoundingClientRect()` 之类 > 会强制同步布局的属性(当前实现只在初始化 / resize 时测量内容区位置)。 ## 项目结构 以下按磁盘实际文件列出(`src/pages/` 下每个 `.astro` 对应一个输出模板,共 21 个): ``` WarmIsland/ ├── src/ │ ├── components/ # 18 个组件:Navbar / Footer / HeroSection / WelcomePopup / │ │ # LightGallery / ClickEffect / ScrollReveal / ThemeSwitcher 等 │ ├── layouts/ # Layout.astro(含页面布局契约渲染模式) │ ├── pages/ # index / post / page / archives / categories / category / │ │ # tags / tag / links / friends / moments / moment / photos / │ │ # douban / equipments / important-dates / steam / weread / │ │ # page_messageboard / page_subscriptions / layout │ └── styles/ # main.scss + 12 个 _*.scss 片段 ├── public/ │ ├── assets/ │ │ ├── data/ # hitokoto.json 内置本地句子库 │ │ ├── fonts/ # LXGW WenKai 本地 woff2 分片与 result.css │ │ ├── vendor/ # 自托管 prism-warm.css / prism-cold.css │ │ └── logo.png # 主题图标(唯一一份,256×256 压缩版) │ ├── error/ # 错误页静态资源 │ └── favicon.ico ├── compat/assets/ # 历史版本哈希静态资源,构建时合并进 templates/assets ├── scripts/ # postbuild / snapshot-assets / package / audit-external / smoke-clickfx ├── docs/ # VitePress 文档站(*.md 随安装包分发) ├── templates/ # 构建产物(被 .gitignore 忽略) ├── dist/ # ZIP 安装包(被 .gitignore 忽略) ├── theme.yaml # 主题清单与自定义页面模板注册 ├── settings.yaml # 后台设置项定义 ├── astro.config.mjs / package.json / pnpm-lock.yaml / tsconfig.json └── WarmIsland Preview.webp ``` ## 技术栈 ### 核心框架 | 技术 | 版本 | 用途 | |------|------|------| | [Halo](https://www.halo.run/) | 2.27+ | 博客运行时,提供 Finder API 与主题渲染 | | [Astro](https://astro.build) | 6.x | 组件开发,构建输出静态 `.html` 模板 | | [Thymeleaf](https://www.thymeleaf.org/) | - | Halo 服务端模板引擎(源码里直接写 `th:*`) | ### 前端技术 | 技术 | 用途 | |------|------| | [SCSS](https://sass-lang.com/) | 样式预处理,CSS 变量驱动的配色系统 | | 原生 JavaScript | 滚动、菜单、主题切换、无限滚动等交互,无前端框架运行时 | | [LightGallery](https://www.lightgalleryjs.com/) 2.9.0(GPLv3) | 图片灯箱,默认内置;安装 lightgallery.js 插件后自动让位 | | [LXGW WenKai](https://github.com/lxgw/LxgwWenKai)(OFL 1.1) | 本地字体分片,随主题自托管,无远程字体请求 | | Halo 插件生态 | 评论、代码高亮、服务端字数统计等由 Halo 与可选插件提供 | ### 构建工具 | 技术 | 用途 | |------|------| | [Vite](https://vitejs.dev/)(Astro 内置) | 资源打包与内容哈希 | | [pnpm](https://pnpm.io/) | 包管理 | | [TypeScript](https://www.typescriptlang.org/) | 类型定义(`env.d.ts` / `pages.ts`) | ## 模板语法 WarmIsland 使用 Astro 编写组件,构建后输出为 Thymeleaf 模板供 Halo 使用。 ### Astro 组件 → Thymeleaf 模板 Astro 组件里直接书写 Thymeleaf 属性(`th:*`),构建时原样保留并输出成 `.html` 模板, 由 Halo 在运行时渲染: ```astro --- // Astro 前置脚本 const title = "Hello"; ---

{title}

``` 转换为 Thymeleaf: ```html

Hello

``` ### Halo Finder API 在模板中使用 Halo 的 Finder API 获取数据: ```html
``` ### Vue 3 交互组件 需要客户端交互的组件使用 Vue 3 编写,通过 ` ``` ## Docker 部署 ### 构建镜像 ```bash docker build -t warmisland-theme . ``` ### 运行 ```bash docker run -d \ --name warmisland \ -p 8090:8090 \ -v ~/.halo2:/root/.halo2 \ warmisland-theme ``` ### Docker Compose ```yaml version: '3' services: halo: image: warmisland-theme ports: - "8090:8090" volumes: - ./halo2:/root/.halo2 restart: unless-stopped ``` ## 贡献指南 ### 提交 Issue - 使用 GitHub Issues 提交 Bug 报告或功能建议 - 请提供详细的复现步骤和环境信息 - 附上截图或错误日志 ### 提交 Pull Request 1. Fork 本仓库 2. 创建特性分支:`git checkout -b feature/your-feature` 3. 提交更改:`git commit -m 'feat: add your feature'` 4. 推送分支:`git push origin feature/your-feature` 5. 提交 Pull Request ### Commit 规范 使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范: | 类型 | 说明 | |------|------| | `feat` | 新功能 | | `fix` | 修复 Bug | | `docs` | 文档更新 | | `style` | 样式调整(不影响功能) | | `refactor` | 代码重构 | | `perf` | 性能优化 | | `test` | 测试相关 | | `chore` | 构建/工具变更 | ## 代码规范 ### BEM 命名 WarmIsland 使用 BEM(Block Element Modifier)命名规范,前缀为 `wi-`: ``` .wi-block .wi-block__element .wi-block__element--modifier ``` **示例**: ```css .wi-post { } /* Block */ .wi-post__title { } /* Element */ .wi-post__title--large { } /* Modifier */ .wi-post__content { } /* Element */ .wi-post__meta { } /* Element */ .wi-post__meta-item { } /* Element */ .wi-post__like { } /* Element */ .wi-post__like--active { } /* Modifier */ ``` ### SCSS 规范 - 使用 SCSS 变量(`$variable`)定义设计 token - 使用 CSS 变量(`--variable`)暴露可覆盖的值 - 嵌套不超过 3 层 - 使用 `@mixin` 封装复用样式 - 使用 `@include` 引入混入 ### Vue 组件规范 - 使用 Composition API - 使用 `