Repository Wiki
LyraVoid/Mizuki

安装与本地开发

Mizuki 是一个基于 Astro 7 + Svelte 5 + Tailwind CSS 4 的静态博客模板,本页讲解如何在本地把它跑起来:环境要求、依赖安装、内容同步机制(scripts/sync-content.js)、开发服务器、构建管线以及日常开发脚本的完整工作方式。

Purpose and Scope

本页覆盖 Mizuki 从"克隆仓库"到"本地可开发、可构建"的完整链路,重点包括:

  • 环境要求与依赖安装(Node / pnpm / Corepack 的强制约束)
  • package.json 中开发相关脚本的语义与执行顺序(predev、prebuild 钩子)
  • 内容分离机制:scripts/sync-content.js 如何加载 .env、克隆/同步内容仓库并通过符号链接挂载内容
  • 构建管线 pnpm build 的多阶段校验链

以下相关主题有意留给兄弟页面,不在本页展开:

  • 站点与各模块的具体配置项(src/config/siteConfig.ts 等)→ 参见配置相关页面
  • 文章编写、frontmatter 字段与 Markdown 扩展语法 → 参见内容编写相关页面
  • 生产环境部署(Vercel / Netlify / GitHub Pages / Cloudflare Pages)与环境变量注入 → 参见部署相关页面

Overview

Mizuki 的本地开发模型可以用一句话概括:包管理器被锁定为 pnpm,每次 dev/build 前都会自动执行一次内容同步,把(可选的)独立内容仓库挂载进项目源码树,然后再交给 Astro 工具链。

关键概念:

  • 内容分离:博客正文(posts、spec)、结构化数据(data)、图片(images)与配置覆盖(overrides)可以放在一个独立的 Git 仓库中,与主题代码解耦。ENABLE_CONTENT_SYNC 默认开启,但只在配置了 CONTENT_REPO_URL 或本地已存在内容目录时才真正生效。
  • 钩子式同步:package.json 通过 predev / prebuild 两个 npm 生命周期钩子,把同步逻辑织入开发与构建流程,且都以 || true 结尾——同步失败不会阻断开发。
  • 强制 pnpm:preinstall 脚本执行 npx only-allow pnpm,同时 packageManager: "pnpm@11.5.3" 字段配合 Corepack 锁定版本,避免不同包管理器产生的锁文件差异。

Architecture

下图展示本地开发工具链的整体结构与数据流,节点均对应仓库中真实存在的文件/命令:

Loading diagram...

架构要点说明:

  • preinstall 守门:在依赖安装阶段就拒绝非 pnpm 的包管理器,这是团队协作与 CI 一致性的第一道防线。
  • sync-content.js 是开发循环的心脏:它既是 predev 又是 prebuild 的执行体,保证开发服务器与生产构建看到的内容一致。
  • 两种挂载策略:posts/spec/data/images 使用符号链接(零拷贝、内容仓库可独立提交),而 overrides 必须复制——源码注释明确说明原因:覆盖文件是带相对导入的 TS 模块,符号链接会被 Vite 解析到内容仓库真实路径,导致找不到 types/config。

安装步骤

1. 环境要求

要求最低版本说明
Node.js>= 20Astro 7 与构建脚本的运行时基线
pnpm>= 11packageManager: "pnpm@11.5.3" 精确锁定
Git任意克隆主题仓库与可选内容仓库

pnpm 的版本由 package.json 的 packageManager 字段声明,通过 Corepack 启用:

json
1{ 2 "scripts": { 3 "predev": "node scripts/sync-content.js || true", 4 "prebuild": "node scripts/sync-content.js || true", 5 "dev": "astro dev", 6 "start": "astro dev", 7 "build": "node scripts/update-anime.mjs && astro build && node scripts/check-global-style-loading.mjs && pagefind --site dist && node scripts/check-font-loading.mjs", 8 "preinstall": "npx only-allow pnpm", 9 "packageManager": "pnpm@11.5.3" 10 } 11}

Source: package.json

几个值得注意的设计意图:

  • predev/prebuild 的 || true:内容同步属于"尽力而为"的辅助步骤,网络失败、仓库不可达都不应阻止开发者进入开发服务器或阻断生产构建。
  • preinstall 的 npx only-allow pnpm:在依赖安装前就拦截 npm/yarn,避免产生不一致的锁文件。

2. 克隆与安装依赖

README 给出的标准流程:

bash
1git clone https://github.com/LyraVoid/Mizuki.git 2cd Mizuki 3 4# 启用项目声明的包管理器版本 5corepack enable 6 7# 安装项目依赖 8pnpm install

Source: README.md

3. 配置博客(可选)

只使用本地内容时,在项目根目录 .env 中设置 ENABLE_CONTENT_SYNC=false;随后编辑 src/config/siteConfig.ts 与 src/config/ 下的其他模块,至少将 siteURL 替换为部署后的公开网址。

4. 启动开发服务器

bash
pnpm dev

启动后博客运行在 http://localhost:3000。

Source: README.md

核心流程:内容同步机制(sync-content.js)

scripts/sync-content.js 是理解 Mizuki 本地开发的关键。它在每次 pnpm dev 与 pnpm build 之前自动运行,完整流程如下:

Loading diagram...

步骤 1:加载环境变量

脚本首先调用 loadEnv() 读取根目录 .env,随后从环境变量中读取三个关键配置:

javascript
const ENABLE_CONTENT_SYNC = process.env.ENABLE_CONTENT_SYNC !== "false"; // 默认启用 const CONTENT_REPO_URL = process.env.CONTENT_REPO_URL || ""; const CONTENT_DIR = process.env.CONTENT_DIR || path.join(rootDir, "content");

Source: scripts/sync-content.js

注意 ENABLE_CONTENT_SYNC 采用反向判断(!== "false"):未设置时视为启用。这是一个安全的默认值设计——配置分离内容仓库时无需显式写 true。

步骤 2:同步决策

sync-content.js 的分支逻辑覆盖三种场景:

javascript
1// 检查是否启用内容分离 2if (!ENABLE_CONTENT_SYNC) { 3 console.log("内容分离功能已关闭(ENABLE_CONTENT_SYNC=false)"); 4 // ... 打印启用方法提示后 process.exit(0) 5} 6 7// 检查内容目录是否存在 8if (!fs.existsSync(CONTENT_DIR)) { 9 console.log(`内容目录不存在:${CONTENT_DIR}`); 10 if (!CONTENT_REPO_URL) { 11 console.warn("警告:未设置 CONTENT_REPO_URL,将使用本地内容"); 12 process.exit(0); 13 } 14 // 存在 URL 时执行 git clone --depth 1 15}

Source: scripts/sync-content.js

对于已存在的内容目录,脚本执行强同步:git stash push --include-untracked → git fetch --all --prune → 探测 origin/main(失败则回退 master)→ git checkout + git reset --hard origin/<branch>。这一连串操作保证本地内容永远与远程一致,代价是会丢弃内容目录中的本地未提交修改(先被 stash 保护)。

步骤 3:建立内容链接(核心映射表)

javascript
1const contentMappings = [ 2 { src: "posts", dest: "src/content/posts" }, 3 { src: "spec", dest: "src/content/spec" }, 4 { src: "data", dest: "src/data" }, 5 { src: "images", dest: "public/images" }, 6 // 覆盖文件是带相对导入的 TS 模块,符号链接会被 Vite 解析到内容仓库真实 7 // 路径导致找不到 types/config,因此复制进代码仓库而不是建链接 8 { src: "overrides", dest: "src/config/overrides", copy: true }, 9];

Source: scripts/sync-content.js

每个映射项的处理规则:

映射目标挂载方式原因
postssrc/content/posts符号链接纯静态内容,无需解析
specsrc/content/spec符号链接同上(关于页/友链页内容)
datasrc/data符号链接结构化数据文件
imagespublic/images符号链接静态资源
overridessrc/config/overrides复制(copy: true)TS 模块含相对导入,链接会被 Vite 解析到内容仓库路径,导致找不到 types/config

对符号链接目标已存在且非链接的情况,脚本会先把原目录备份为 <dest>.backup 再删除重建链接——这解释了项目中偶见的 src/data.backup 等目录的来源。对 copy: true 的 overrides 则直接删除重建,且当内容仓库删除 overrides/ 后会主动清理旧副本,防止失效配置继续生效(对应源码 L110-L140 的清理逻辑)。

符号链接在 Windows 上需要管理员权限,源码相应做了降级:链接失败时退化为复制文件(L159 起的实现)。

Usage Examples:日常开发命令

新建文章

bash
pnpm new-post -- <文件名>

支持 .md 与 .mdx,对应脚本 scripts/new-post.js。文章存放于 src/content/posts/。

内容管理路径约定

README 明确的目录职责划分:

text
1创建文章: pnpm new-post -- <文件名> (.md / .mdx) 2编辑文章: 修改 src/content/posts/ 中的文件 3编辑关于/友链: 修改 src/content/spec/ 中对应的文件 4编辑结构化数据: 修改 src/data/ 中对应的文件 5文章本地图片: 放在文章旁边,使用 ./cover.webp 相对路径 6公共图片: 放在 public/ 下,使用 /images/example.webp 根路径

Source: README.md

发布前注意:仓库自带示例文章、页面数据、相册与图片,部署个人站点前需删除或替换。

构建管线

pnpm build 不是单一的 astro build,而是一条多阶段校验链:

json
"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

按 && 串联的五个阶段,任一失败即中止:

顺序阶段作用
1scripts/update-anime.mjs构建前刷新追番页面数据源
2astro build生成静态站点到 dist/
3scripts/check-global-style-loading.mjs校验全局样式加载完整性
4pagefind --site dist基于 dist/ 建立全文搜索索引
5scripts/check-font-loading.mjs校验字体子集化后的加载情况

设计意图:把"内容数据刷新 → 站点产出 → 质量校验 → 搜索索引 → 字体验证"固化为一条不可拆分的流水线,避免产出缺少索引或字体缺失的"看似成功"的构建产物。Pagefind 索引必须在 astro build 之后、以 dist 为输入构建,这一顺序不可颠倒。

API Reference:开发相关脚本一览

命令实际执行作用
pnpm install(含 preinstall)npx only-allow pnpm强制使用 pnpm
pnpm dev / pnpm startpredev 同步 → astro dev启动开发服务器(localhost:3000)
pnpm build见上节流水线生产构建
pnpm previewastro preview预览 dist/ 构建产物
pnpm checkastro checkAstro 诊断检查
pnpm type-checktsc --noEmitTypeScript 类型检查(不产出文件)
pnpm testnode --experimental-strip-types --test tests/*.test.mjs + tests/crypto.test.mjs运行测试套件
pnpm new-postnode scripts/new-post.js创建新文章
pnpm formatbiome format --write ./src用 Biome 格式化源码
pnpm lintbiome check --write ./srcBiome 检查并自动修复
pnpm sync-contentnode scripts/sync-content.js手动触发内容同步
pnpm init-contentnode scripts/init-content-repo.js初始化内容仓库
pnpm export-confignode --experimental-transform-types scripts/export-config.mjs导出配置
pnpm prepare-fontsnode scripts/prepare-fonts.mjs字体准备/子集化
pnpm check-fontsnode scripts/check-font-loading.mjs字体加载校验
pnpm prepare-imagesnode scripts/prepare-default-images.mjs生成默认图片
pnpm update-anime / update-bangumi / update-bilibili对应 scripts/update-*.mjs刷新追番数据源
pnpm submitnode scripts/indexnow-submit.js提交 IndexNow 索引推送

Source: package.json

Configuration Options:环境变量

.env 由 scripts/load-env.js(被 sync-content.js 调用)读取,作用于开发与构建前的内容同步阶段:

环境变量类型默认值说明
ENABLE_CONTENT_SYNCstring ("false" 生效)启用(未设置时 !== "false" 为真)关闭后完全使用本地内容,不同步远程仓库
CONTENT_REPO_URLstring""内容仓库 Git 地址,首次同步时 git clone --depth 1
CONTENT_DIRstring(路径)<项目根>/content内容仓库本地目录位置

Source: scripts/sync-content.js

README 还提到 .env.example 中包含 Bilibili 会话数据与 IndexNow 凭据等可选配置,仅在需要时设置,且真实值不应提交到 Git(托管构建时放入平台环境变量/Secret)。

Failure Modes, Edge Cases & Concurrency

同步失败的容错边界

  • 钩子层容错:predev/prebuild 中的 || true 意味着 sync-content.js 即使以非零码退出,astro dev / astro build 仍会继续执行。这是有意设计——开发不应因网络问题被阻断。唯一例外是克隆失败:脚本在 git clone 失败时 process.exit(1),但由于外层 || true,流程仍会继续。
  • 强同步会丢弃本地修改:git reset --hard origin/<branch> 会覆盖内容目录中未提交的修改。脚本先用 git stash push --include-untracked 兜底,但 stash 的内容不会自动恢复——需要手动 git stash pop。在内容仓库中直接编辑文件而不提交,可能丢失工作。
  • 内容目录无 .git:脚本跳过远程同步,仅执行挂载逻辑,适用于手工放置内容的场景。

Windows 与符号链接

Windows 创建符号链接默认需要管理员权限。源码在链接创建处做了 try/catch 降级:失败时退化为复制文件(scripts/sync-content.js L159 起)。这意味着 Windows 普通权限用户会得到内容的副本而非链接——内容仓库中的更新在下一次同步时会重新覆盖,但你在副本中的直接修改会被覆盖丢失。

.backup 目录的来源

当 src/content/posts 等目标位置已存在真实目录(非链接)时,同步会先把它重命名为 src/content/posts.backup。如果你在启用内容分离前已在这些目录写过内容,它们不会消失,而是被移动到了 .backup 目录,可手动找回。

并发与锁

仓库没有为同步加互斥锁。如果同时运行 pnpm dev(触发 predev)和另一个 pnpm build(触发 prebuild),两个同步进程可能竞争 content/ 目录(如同时执行 git stash / reset)。实际影响有限(npm 钩子在命令串行执行),但应避免手动并行调用 pnpm sync-content。

构建链的硬失败

与同步的"尽力而为"相反,build 中五个阶段用 && 串联:字体加载校验、全局样式校验、Pagefind 索引任一失败都会让整个构建以非零码结束。这保证 CI/托管平台不会把残缺产物当作成功部署。

Performance / Operational Notes

  • --depth 1 浅克隆:首次克隆内容仓库时只取最新一次提交,显著降低大图片仓库的初始化时间。后续更新走 fetch --all --prune + reset --hard,保持目录始终是浅层快照。
  • 开发服务器入口:astro dev 默认监听 http://localhost:3000(README 明确说明)。
  • 测试执行方式:pnpm test 使用 Node 原生 test runner(--experimental-strip-types --test)直接运行 .mjs 测试,无需额外测试框架依赖;覆盖 markdown 增强处理、布局回归、图片加载、音乐播放器加载与加密(crypto)等主题关键路径。
  • 代码风格工具:格式化与 lint 由 Biome 承担(format/lint 两个脚本),作用域限定在 ./src。

Extension Points

  • 新增内容映射:sync-content.js 中的 contentMappings 数组是内容挂载的唯一扩展点。如需让内容仓库提供更多目录(例如自定义组件覆盖),向数组追加 { src, dest, copy? } 即可复用现有的符号链接/复制/备份/清理逻辑。
  • 新增构建校验:build 脚本的 && 链是插入自定义校验的位置。参考 scripts/check-font-loading.mjs 与 scripts/check-global-style-loading.mjs 的模式——独立 Node 脚本、以 dist/ 为输入、失败时以非零码退出。
  • 初始化内容仓库:pnpm init-content 对应 scripts/init-content-repo.js,用于从现有项目生成/初始化可分离的内容仓库。
  • package.json — 全部脚本定义与依赖清单
  • scripts/sync-content.js — 内容同步核心实现
  • scripts/load-env.js — .env 加载器
  • README.md — 官方快速开始指南
  • 站点配置详解(src/config/siteConfig.ts 等)→ 参见配置相关兄弟页面
  • 内容编写与 Markdown 扩展 → 参见内容编写相关兄弟页面
  • 部署与环境变量注入 → 参见部署相关兄弟页面

Sources

(3 files)