问题:为什么书本不够好
翻开一本教科书。它假设你会从第一页读到最后一页,它不检查你是否真的理解了,它不能回应你的错误。你跳过困难的部分,它不抗议;你误解了一个概念,它不纠正;你失去兴趣,它不挽留。
书是死的。 它在纸上等待你翻阅,而翻阅是一种单向的、不需要反馈的行为。
另一方面,一个好的游戏——比如《传送门》或《Baba Is You》——根本不会给你一本规则手册。它给你一个安全的、可以试错的环境,让你通过做来学习。失败没有惩罚,成功有即时的反馈。难度逐步增加,每一个新关卡都建立在上一个关卡学到的技能之上。
加载挑战中...
这个博客的信仰是:游戏是比死书本更先进的知识载体。 它不是一个”技术博客”——它是一个交互式知识系统,伪装成博客的样子。
整体架构:一个目录 + 一个构建脚本
这个框架没有数据库,没有管理后台,没有黑盒。它的全部输入是文件系统上的一个目录,输出是一堆可以用浏览器打开的 HTML 文件。
作者写的(输入) 框架处理的 读者看到的(输出)
───────────────── ──────────────── ─────────────────
src/content/posts/ https://reveriel.com/
shader-intro/ /posts/shader-intro/
index.mdx → MDX → HTML → 带有交互组件的页面
demos/ ShaderPlayground 可编辑
circle.wgsl → 资源打包 → WGSL 代码嵌入页面
sim.py → Pyodide 引用 → 读者浏览器里跑 Python
核心原则只有五个:
- 文件即文章。 一个目录 = 一篇文 = 一个自包含的知识单元。目录名就是 URL。
- 组件即交互。 需要互动的地方,插入一个组件标签(如
<ShaderPlayground />),而不是写一堆<script>标签。 - 元数据即图谱。 frontmatter 里的
prerequisites和concepts自动生成知识依赖关系。 - 按需加载。 WASM、shader、Pyodide 只在使用时加载。首页的速度和纯文本博客一样快。
- 对搜索引擎友好。 静态生成 + 语义 HTML + Open Graph 标签 + sitemap,让知识能被发现。
品牌:为什么叫 Reveriel
Reveriel 这个名字来自 “reverie”(沉思 / 幻想)—— 知识不应该是枯燥的、单向的灌输,而应该是一种沉浸式的、可以玩的体验。
每篇文章的页面 title 格式是 {文章标题} — Reveriel,Open Graph 标签里 site_name 是 Reveriel · Interactive Knowledge。这些标签决定了:
- 搜索引擎结果页上你的链接长什么样
- 分享到 Twitter / Slack / Telegram 时卡片怎么展示
- RSS 阅读器里标题怎么显示
Tagline 是 “知识是游戏,阅读是游玩”。这个 tagline 本身就在回击传统的”读书”概念——这里没有书,只有关卡。
目录结构:每篇文章是一个项目
src/content/posts/
shader-intro/ ← 目录名 = 文章 slug = URL
index.mdx ← 正文(唯一必需的文件)
demos/ ← 属于这篇文章的交互式 demo 代码
circle.wgsl ← ShaderPlayground 引用的 shader
raymarch.wgsl
attention-viz.ts ← Canvas 2D 可视化脚本
ml-sim.py ← Pyodide 跑的 Python
assets/ ← 静态资源
diagram.png
model.glb ← 3D 模型
每篇文章的代码资源和文章放在一起,不是散落在全局的 /static/ 或 /public/ 里。这样你写完一篇文章后,它就是一个完整的、可以独立理解的知识包。
为什么这样设计
传统的 CMS(比如 WordPress)把内容和资源分开:文章在数据库里,图片在一个媒体库,代码片段嵌在 HTML 里。时间长了,它们之间的关系就丢了——你没法把一篇文章的完整上下文(文字 + 代码 + 资源)打包成一个目录复制给别人。
把文章目录当作项目的根意味着:
- Git 可以精确追踪一篇文章的所有修改(包括代码)
- 你可以把一篇文章的目录整个复制给别人,对方就能构建和运行
- 不会有”这张图到底在哪个文章用到了”的问题
内容管线:从 MDX 到交互式页面
数据流是这样的:
index.mdx (你写的)
│
├── YAML frontmatter → 元数据(title, stage, concepts...)
│ → 知识图谱数据库
│
├── Markdown 正文 → Astro MDX 解析器
│ → Shiki 语法高亮
│ → HTML
│
└── 组件标签 → Astro 组件渲染
<ShaderPlayground /> → WebGPU 初始化脚本
<CodeCell /> → Pyodide / JS 运行时
<QuizChain /> → 交互式测验逻辑
<ProofStep /> → 可折叠的 <details>
<GiscusComments /> → GitHub Discussions 评论区
构建时(npm run build),Astro 遍历所有 .mdx 文件,提取 frontmatter,渲染 Markdown 为 HTML,把组件标签替换为真实的交互式组件代码,然后输出纯静态文件到 dist/ 目录。
关键点:输出的是静态 HTML + CSS + JS,不需要服务器。 部署到 GitHub Pages、Cloudflare Pages 或任何一个能托管静态文件的地方就行。
知识图谱:概念 DAG
每篇文章的 frontmatter 里有两个关键字段:
prerequisites: # 读者在读本文之前应该先理解的概念
- coordinate-systems
- vectors
concepts: # 本文会教给读者的概念
- signed-distance-functions
- fragment-shaders
框架在构建时自动分析所有文章的 prerequisites 和 concepts,构建一个有向无环图(DAG):
coordinate-systems ──→ fragment-shaders ──→ signed-distance-functions ──→ ray-marching
vectors ──────────────┘ └── matrices ──────────────────┘
访问 /graph 可以看到这个图谱。读者可以:
- 看到每个概念的依赖链
- 按 Stage 递进学习
- 知道自己已经”解锁”了哪些概念
概念 ID 怎么起
概念 ID 是短横线连接的小写英文,比如 signed-distance-functions。同一个概念 ID 可以在多篇文章中出现——这意味着同一概念有多种解释方式,读者可以选择适合自己的路线。
组件系统:交互是怎么工作的
这个框架提供了一套交互式组件库。每个组件解决一类特定的教学需求:
下面是一个 Shader Playground。试着改里面 WGSL 代码的最后一行的 0.5,改成 0.0,然后按 Ctrl+Enter。
你刚才改的是一个 fragment shader 的代码。ShaderPlayground 组件在读者的浏览器里做三件事:
- 请求 WebGPU 设备
- 把你写的 WGSL 编译为 GPU shader module
- 渲染一个全屏四边形,把 shader 的结果画到 canvas 上
如果读者的浏览器不支持 WebGPU,组件会显示降级信息。如果读者根本没有滚动到这个组件的位置,它不会初始化——IntersectionObserver 懒加载。
组件一览
| 组件 | 解决的问题 | 读者体验 |
|---|---|---|
<ShaderPlayground> | 如何直观地理解 shader | 改代码 → 实时看效果 |
<CodeCell> | 代码不是用来”看”的 | 在浏览器里运行 JS / Python |
<QuizChain> | 如何确认读者真的懂了 | 递进式问答,答对解锁下一题 |
<ProofStep> | 数学证明容易让人迷失 | 读者自己控制展开节奏 |
<Simulation> | 参数变化的意义无法用文字描述 | 拖滑块看实时变化 |
<WasmDemo> | 如何跑高性能计算 | Rust → WASM,按需加载 |
<GiscusComments> | 读者想讨论、提问、质疑 | GitHub Discussions 评论,懒加载 |
一个 CodeCell 的实例——不只是展示代码,读者可以编辑和运行:
点击"运行"以执行代码
什么是 Astro 组件?
你可能会问:ShaderPlayground、CodeCell 这些标签是从哪来的?
它们是 Astro 组件——后缀为 .astro 的文件,存放在 src/components/ 目录下。一个 .astro 文件长这样:
---
// 前半部分(--- 之间):JavaScript / TypeScript,在构建时运行
interface Props {
width?: number
height?: number
}
const { width = 640, height = 360 } = Astro.props
---
<!-- 后半部分:HTML 模板,构建时输出为静态 HTML -->
<div class="interactive-block">
<canvas id="my-canvas" width={width} height={height}></canvas>
</div>
<script>
// <script> 标签里的 JS 在读者浏览器里运行
const canvas = document.getElementById('my-canvas')
// ... WebGPU 初始化 ...
</script>
关键理解: Astro 组件有两个执行环境——
| 在哪里运行 | 什么时候 | 做什么 |
|---|---|---|
--- 之间的代码 | 构建时(服务器/你的电脑上) | 处理 props、读取文件、生成 HTML |
<script> 标签里的代码 | 运行时(读者浏览器里) | 操作 DOM、初始化 WebGPU、响应用户输入 |
构建完成后,<script> 之外的模板部分已经变成了纯 HTML 字符串。这也是为什么这个博客不需要服务器——所有的逻辑要么在构建时执行完了,要么在读者浏览器里按需加载。
WASM:在浏览器里跑 Rust
当交互需要的计算量超过 JavaScript 的能力范围时(比如物理模拟、图像处理、数值计算),可以把 Rust 代码编译为 WebAssembly,直接在浏览器里运行。
下面是一个 Mandelbrot 集的 WASM 渲染器。核心计算用 Rust 写,编译为 .wasm 文件,通过 <WasmDemo> 组件加载:
加载 WASM 模块中...
WASM 的构建流程:
# 1. 在文章目录下创建 Rust 项目
src/content/posts/hello-interactive-world/demos/mandelbrot/
├── Cargo.toml
└── src/lib.rs
# 2. 编译
cd demos/mandelbrot
wasm-pack build --target web --out-dir ../pkg
# 3. 在 MDX 中引用产物
<WasmDemo wasm="./demos/pkg/xxx_bg.wasm" js="./demos/pkg/xxx.js" />
WASM 模块也是懒加载的——只有读者滚动到它的位置时才开始下载和初始化。
SEO:让知识被找到
一个博客如果没有人读到,交互做得再好也没有意义。Reveriel 从第一天起就内置了 SEO 基础设施:
每篇文章自动生成:
<title>和<meta description>(从 frontmatter summary 读取)- Open Graph 标签(
og:title,og:description,og:image,og:type: article)—— 决定分享到社交媒体的卡片 - Twitter Card(
summary_large_image)—— Twitter / Slack / Telegram 的链接预览 article:published_time和article:tag—— 搜索引擎的结构化时间 + 标签- 语义 HTML(
<article>,<header>,<time>,<nav>)—— 搜索引擎理解页面结构
待规划:
sitemap.xml—@astrojs/sitemap自动生成- RSS Feed —
@astrojs/rss让文章被 RSS 阅读器抓取 - JSON-LD 结构化数据 — Google Rich Results(
Article,BreadcrumbList)
评论区:知识需要讨论
每篇文章底部集成了 Giscus——一个基于 GitHub Discussions 的评论系统:
- 读者用 GitHub 账号登录 → 在文章下留言
- 评论数据存在你的 GitHub repo 的 Discussions 里(不依赖第三方数据库)
- 通过 IntersectionObserver 懒加载——不滚动到评论区不加载脚本
- 暗色主题,中文界面
如果没有配置 Giscus,评论区显示提示文字,不影响页面。
系列(Series):线性学习路径
有些知识天然的应该按顺序阅读。比如一篇”Shader 入门”之后是”SDF 画形状”,然后是”光线步进做 3D 渲染”——这三篇构成了一个线性系列。
在文章的 frontmatter 里声明系列:
---
title: "SDF 入门"
series: "shader-from-scratch"
seriesOrder: 2 # 在系列中的位置
---
同一个系列的所有文章会被自动收集和排序。访问 /graph?series=shader-from-scratch 可以看到这个系列的线性阅读顺序。
Stage 和 Series 是正交的:
- Stage 描述难度(1→5 递进,跨系列的)
- Series 描述主题(同一主题的线性阅读顺序)
一篇 Stage 4 的文章可能属于某个系列的第 3 篇——说明这个系列到后面会变得很难。
递进式难度:Stage 系统
不是所有知识都应该用同样的交互密度呈现。框架定义了 5 个 Stage:
Stage 1 🌱: 90% 阅读 + 10% 演示 (读者刚入门,先看)
Stage 2 🌿: 70% 阅读 + 20% 交互 + 10% 挑战 (开始动手)
Stage 3 🌳: 40% 阅读 + 40% 交互 + 20% 挑战 (动手为主)
Stage 4 ⚡: 20% 阅读 + 50% 交互 + 30% 挑战 (综合运用)
Stage 5 🔥: 10% 方向 + 70% 自由探索 + 20% 创造 (读者自己探索前沿)
脚手架的逐渐撤走是游戏设计的核心原则:
- Stage 1 的文章几乎没有前置知识要求,交互以”看”为主
- Stage 3 的文章开始有递归的前置知识链,交互以”做”为主
- Stage 5 的文章给出方向而不是答案——读者应该能自己写一个 path tracer,而不是跟着教程敲
这篇文章本身是 Stage 1。它不需要任何前置知识,交互密度很低——你只需要读。
一个完整的文章是怎么写出来的
假设你要写一篇”Shader 入门:从像素理解 GPU”。你做的事情:
第一步:创建目录
mkdir -p src/content/posts/shader-from-pixels/demos
touch src/content/posts/shader-from-pixels/index.mdx
第二步:写 frontmatter
---
title: "Shader 入门:从像素理解 GPU"
date: 2026-07-30
stage: 1
prerequisites: []
concepts: [fragment-shaders, gpu-paradigm, uv-coordinates]
tags: [graphics, shader]
summary: "每一个像素都是一个独立的小程序。理解这一点是图形学的第一道门。"
---
第三步:写正文 + 嵌入组件
import ShaderPlayground from '@components/ShaderPlayground.astro'
## 像素是并行的
当你在屏幕上看到 1920×1080 个像素时,GPU 并没有一个一个地计算它们的颜色。
每个像素有自己的"微型程序"在同时运行。
<ShaderPlayground shader="./demos/first-pixel.wgsl" editable />
第四步:把 shader 代码放在 demos/ 里
// demos/first-pixel.wgsl
@fragment
fn main(@builtin(position) coord: vec4f) -> @location(0) vec4f {
let uv = coord.xy / vec2f(1920.0, 1080.0);
return vec4f(uv.x, uv.y, 0.5, 1.0);
}
第五步:npm run build → 部署
证明:这个设计是正确的
📐 证明:文件驱动的交互式博客比数据库驱动的 CMS 更适合知识传播
前提 1:知识应该可复现。 一篇文章 + 它的交互式代码应该构成一个封闭的、可执行的系统。读文章的人可以在自己浏览器里重新运行文章中的所有计算。
前提 2:知识应该有结构。 概念之间有前置依赖关系。一个读者不知道”向量”就看不懂”矩阵乘法”——这个依赖关系应该被显式建模,而不是靠读者自己猜。
前提 3:知识应该被验证。 读完不等于学会。交互式挑战(QuizChain、CodeCell 的修改任务)让读者运用刚学到的概念,从而确认自己真的理解了。
文件驱动(而不是数据库驱动)满足前提 1:文章目录是自包含的、可版本控制的、可复制的。知识图谱满足前提 2。交互式组件满足前提 3。
因此,这个设计比传统的 Markdown → HTML 静态博客更适合知识传播。∎
接下来
以下是 Reveriel 的路线图:
| 状态 | 内容 | 说明 |
|---|---|---|
| ✅ 完成 | 框架骨架 | Astro 5 + MDX + Shiki 高亮 |
| ✅ 完成 | 交互式组件库 | ShaderPlayground, CodeCell, QuizChain, ProofStep, WasmDemo, Simulation |
| ✅ 完成 | 知识图谱 | 概念 DAG + Stage 分组 + Series 过滤,/graph 可视化 |
| ✅ 完成 | 品牌 & SEO | OG / Twitter Card 标签、语义 HTML、Giscus 评论系统 |
| ✅ 完成 | 旧文章迁移 | 10 篇非数学类文章已迁入,数学类文章留在 archive/ 待 math 渲染方案就绪 |
| 🔜 现在 | sitemap + RSS | @astrojs/sitemap + @astrojs/rss |
| 🔜 现在 | 第一篇真正的交互式文章 | Shader 入门或线性代数可视化 |
| 📋 近期 | 数学渲染 | 解决 MDX + LaTeX 的构建时解析问题,迁入 archive/ 里的数学文章 |
| 📋 近期 | WebGPU compute shader | 不只是渲染,还能在 GPU 上做并行计算 |
| 📋 远期 | 读者进度追踪 | localStorage 记录已解锁的概念 |
| 📋 远期 | 多语言支持 | 概念 ID 跨语言共享,zh.mdx / en.mdx |
Reveriel 不是用来展示技术栈的。 它探索一个问题:如果知识不是印在纸上等你读,而是像一个精心设计的游戏一样等你玩——会发生什么?