Xavier Liu
← 返回札记
4 分钟读完

本站技术架构:从 Rust WASM 到 Astro 全栈

本文另有英文版本 — 阅读 English 版本

概览

这个站点是一个完全静态生成(SSG)的个人门户,包含札记、工具和关于页。技术栈如下:

选型
框架 Astro 7(静态生成)
交互 SolidJS(客户端水合)
样式 Tailwind CSS 4 + 语义 Token
运行时 Bun(构建、包管理、脚本)
高性能计算 Rust → WASM
类型约束 TypeScript + Zod

Monorepo 分层架构

项目使用 Bun workspaces 管理三个内部包 + 一个应用:

packages/
  ui/          → 设计系统原语(Button, Icon, Prose...)
  content/     → Zod schema + 领域类型
  wasm/        → Rust 编译的 WASM 模块
apps/
  i/           → Astro 站点

依赖方向严格单向:

pages(薄路由)
  └── features/*(home / essays / tools / about)
        └── shared / i18n / config / lib(跨功能共享)
              └── packages/ui | content | wasm

features 之间禁止互相 import。组件访问 features 一律走 barrel export(index.ts),内部结构对外不可见。


静态生成 + i18n 路由

Astro 配置 output: "static",所有页面在构建时预渲染为纯 HTML。i18n 使用文件路由:

pages/
  index.astro          → /        (默认 zh)
  about.astro          → /about/
  en/                  → /en/ 前缀
    index.astro        → /en/
    about.astro        → /en/about/

每个页面文件是 3-15 行的薄代理,真正的组件和逻辑都在 features/ 中:

---
import { AboutPage } from "@/features/about";
import { asLocale } from "@/i18n";
---
<AboutPage locale={asLocale(Astro.currentLocale)} />

i18n 字典采用 type-safe 模式:zh.ts 定义 Messages 类型,en.ts 受其约束。任何新增 key 如果漏翻译会在编译期报错。


SolidJS + client:load 水合

10 个在线工具(JSON 格式化、Base64 编解码、哈希计算器、二维码生成器等)全部是 SolidJS 组件,通过 Astro 的 client:load 指令进行客户端水合。

一个关键细节:Astro 的水合要求组件必须是字面 JSX 标签<JsonFormatter client:load />),不能通过变量或对象映射动态引用。因此 ToolPage.astro 中为每个工具保留了静态分支,并于 registry.ts 中的 toolIds 数组保持同步。

工具共享组件(InputCardOutputCardCopyButton)放在 features/tools/shared/,遵循“不到 2 处复用不放入 packages/ui”的原则。


Rust → WASM:哈希计算器

哈希计算器(SHA-256、SHA-512、MD5、SHA-1、BLAKE3)的后端是 Rust 编译的 WASM。

packages/wasm/native/src/crypto.rs 使用 sha2md-5blake3sha1 crate 实现五个哈希函数,通过 #[no_mangle] 导出为 C ABI:

#[no_mangle]
pub extern "C" fn sha256(ptr: *const u8, len: usize, out: *mut u8) {
    let data = unsafe { std::slice::from_raw_parts(ptr, len) };
    let hash = Sha256::digest(data);
    unsafe { std::ptr::copy_nonoverlapping(hash.as_ptr(), out, 32) };
}

TypeScript 侧通过 packages/wasm/src/loader.ts 加载并封装为类型安全的 WasmTools 接口。构建脚本 bun scripts/build.ts 调用 cargo build --target wasm32-unknown-unknown 并将产物复制到 dist/tools.wasm


Tailwind CSS + 语义 Token

不使用 dark: 双写,而是基于 CSS 自定义属性的语义 token 体系。所有颜色定义集中在 packages/ui/src/styles/theme.css

:root {
  --color-bg: #fff;
  --color-ink: #111;
  --color-ink-muted: #555;
  --color-accent: #2563eb;
}
.dark {
  --color-bg: #111;
  --color-ink: #eee;
  --color-accent: #60a5fa;
}

组件层只用语义类名(bg-surfacetext-inkborder-line),绝不直接写 zinc-*。换肤只需改一个文件。


暗色模式

暗色模式通过 <script is:inline><head> 中内联执行,阻止 FOUC(闪白)

const stored = localStorage.getItem("theme");
const dark = stored
  ? stored === "dark"
  : matchMedia("(prefers-color-scheme: dark)").matches;
document.documentElement.classList.toggle("dark", dark);

主题切换由 ThemeToggle.tsx(SolidJS island)控制,同步更新 localStorage 和 DOM class。同时监听 astro:after-swap 事件兼容 Astro 视图过渡。


内容管理

札记使用 Astro Content Collections,通过 Zod schema 校验 frontmatter:

const postSchema = z.object({
  lang: z.enum(["zh", "en"]),
  title: z.string(),
  pubDate: z.coerce.date(),
  tags: z.array(z.string()).default([]),
  draft: z.boolean().default(false),
  sameAs: z.string().optional(), // 跨语言互链
});

所有数据访问统一经 features/essays/data.ts,换 CMS 只改一个文件。卡片渲染、RSS 生成、标签聚合都调用同一组数据函数。


构建与部署

bun run build   # astro build → dist/ (40 页,~1.5s)

构建产出纯静态文件,零 JS 服务端依赖。@/ 路径别名由 tsconfig.jsonpaths 和 Astro 的 Vite alias 共同保障,消除所有 ../../../../ 深度相对导入。


小结

这个站点虽小,但覆盖了现代 Web 开发的多个关键领域:SSG 架构、i18n 路由、前端水合、WASM 计算、设计 token 系统、内容型 schema 校验。每一层的选型都遵循“够用但不简陋”的原则,没有为 future-proof 预留过度抽象。


后续计划

当前站点是纯静态生成,所有内容以 Markdown 文件管理。下一阶段规划了几项关键演进:

数据库接入。 将札记、标签、翻译映射从文件系统迁移到数据库(SQLite 或 PostgreSQL),支持更灵活的查询和关联。工具的用户偏好(收藏、历史记录)也需要持久化存储。

Dashboard 后台。 搭建管理后台,提供可视化的札记编辑器、标签管理、翻译关联面板。接口层面设计 RESTful API,前台和后台共享同一数据层。

在线编辑与发布流程。 在 Dashboard 中实现 Markdown 实时预览、草稿/发布状态切换、定时发布。

自动化部署。 站点的前后端代码通过 Webhook 在 Git 推送后自动拉取并部署更新。

这些方向不追求一步到位,而是按实际需求逐步接入,保持架构的可演进性。

系列文章 · site-build

  1. 01本站技术架构:从 Rust WASM 到 Astro 全栈