Caveman:用D语言回归极简的静态站点生成器

1. 为什么要盯上Caveman:从“功能臃肿”到“回归原始”

前阵子群里有人问:有没有一种博客系统,简单到不需要概念,拿起来就能写,不用折腾数据库、缓存、插件体系,更不用学习一整套框架的“最佳实践”?我第一反应是推荐 Hugo,但聊着聊着,我发现自己真正想掏出来的,是 Caveman。

Caveman 是个很小众的静态站点生成器,用 D 语言写的,GitHub 上长期保持“能用但不火”的状态。它有多不火呢?你在搜索引擎里搜 Caveman,大概率先出来一堆游戏角色和健身视频,轮不到这个项目。但正是这种边缘感,让它保住了自己的性格:没有后台、没有数据库、没有运行时依赖,连配置都不是 YAML,而是直接用 D 语言代码写。

我去年年底折腾完第三轮博客搬家之后,一度对所有静态生成器产生倦怠感。Hexo 装插件装到怀疑人生,Hugo 的模板系统足够强,但每次升级版本都有几个参数不兼容,VuePress 就更不用说了,为了写个 Markdown 文件,先得理解整个前端工程化体系。后来我在某个犄角旮旯的提交记录里翻到 Caveman,抱着“再折腾最后一次”的心态试了试,结果一用就是大半年。

这篇文章不是推销文,我也不打算劝你立刻弃掉手头的工具。我只是想把 Caveman 的完整使用链路——安装、建站、写模板、部署、踩坑——全部摊开,顺便聊聊:一个把“少即是多”刻进基因里的工具,到底适合谁,不适合谁。

先说结论:Caveman 适合那些认为博客的核心是“文字+排版+打开速度快”,其余全是噪音的人。它不适合需要复杂动态交互、多作者权限、可视化后台的站点。但如果你只是想要一个能干净地承载内容的个人站点,它可能是目前最让你省心的选项之一。

1.1 它到底做了什么取舍

Caveman 的全名直译过来就是“穴居人”,从名字就能看出作者的态度:回到原始,不要华丽。它的核心逻辑简单到粗暴——把 Markdown 文件编译成 HTML,再按模板布局生成静态页面,没了。没有内容管理系统,没有实时预览热重载的“现代化”开发体验,甚至默认情况下连分类功能都做得很朴素。

但“朴素”不等于“残废”。它内置了文章、独立页面、标签归档、RSS 生成、简单的站内搜索索引,还支持代码高亮接入。这些功能不是通过插件生态实现的,而是项目内核自带的,所以它们的行为高度一致,几乎没有兼容性问题。

这个架构选择带来的直接好处是:你不会有“装一个插件把站点搞崩”的体验。静态站点生成器最大的痛点在依赖管理——主题、插件、核心版本之间互相拉扯,每次升级都像开盲盒。Caveman 的做法是把常用功能焊死在主程序里,砍掉插件机制,你要么用它的方式,要么自己改源码定制。

1.2 适合谁,不适合谁

如果你符合以下特征,Caveman 值得一试:

  • 你的站点内容以文字为主,偶有图片和代码块,不需要购物车、评论区、多语言动态切换这类重型功能。
  • 你对“配置文件也是一种代码”这件事不排斥,愿意在.dt模板里简单写上几行循环语句。
  • 你希望生成的站点体积尽量小,尽量少依赖第三方组件,最好直接放到任何静态托管环境就能跑。
  • 你已经厌倦了为了加一个小功能去升级主题、踩兼容性坑,只想有人替你把“标准能力”打包好。

反过来,如果你需要在线编辑、多人协作、所见即所得的编辑器,或者你习惯生态丰富的主题市场,那 Caveman 肯定不是你的菜。它是一个需要你稍微动动手、但一旦跑起来就不再打扰你的工具。

2. 拿到手的第一步:安装、初始化和跑通第一个页面

我以 Linux 环境为例讲安装,macOS 和 Windows 大同小异,差异点我会标注。Caveman 有两种常见安装方式:直接下载编译好的可执行文件,或者通过 DUB(D 语言的包管理器)从源码安装。第一种适合只想写博客的人,第二种适合想顺手改改程序行为的玩家。

2.1 环境准备:下载或编译,二选一

最省事的方式是去项目的发布页面找对应平台的压缩包。解压后里面就是一个名叫caveman或者caveman.exe的可执行文件,放到你 PATH 里的某个目录,就算装完了。我当时的操作是这样的:

cd ~/bin tar -zxvf caveman-x86_64-linux.tar.gz sudo cp caveman /usr/local/bin/ caveman --version

这里比较容易被忽略的一点是:Caveman 的发布包名里带了编译时的平台标识,但可执行文件本身基本不需要动态依赖,拷贝出来就能用。实测在当前发行版上跑得很稳,没遇到那种“glibc 版本不对”的经典报错。

如果你选择从源码编译,步骤也很直接,前提是装好了 D 语言工具链:

git clone https://github.com/dlang-community/caveman.git cd caveman dub build --build=release

DUB 会自动拉取依赖并编译,速度尚可。编译产物出现在caveman/bin/目录里,使用方式跟下载版一致。我个人的经验是:除非你想修改默认的 Markdown 渲染行为或模板函数,否则直接下载二进制更省事,没必要为同一套代码多花二十分钟编译时间。

2.2 建站命令与目录结构

安装完成之后,初始化一个站点比想象中还简单:

mkdir my-blog && cd my-blog caveman init

执行完 init 之后,你得到的目录里有几个核心部分。source/是你的内容仓库,所有的 Markdown 文件都放这里;themes/存放主题模板;config.d是全局配置;public/是构建产物目录,你最终上传到服务器的就是这个文件夹。

Caveman 的配置不是 YAML,也不是 JSON,而是一个 D 语言代码文件。什么意思呢?你的配置本质上是一段可执行的 D 代码。比如这样:

auto site = new Site("游走在工具边缘的博客"); site.url = "https://example.com"; site.description = "记录折腾过程中的小发现"; site.author = "nirvanalau";

用代码写配置的方式,一开始会有点陌生,但好处是逻辑可以内联——你可以直接在配置里写条件判断、循环赋值,甚至根据不同的构建环境切换不同的配置块,不需要引入额外的模板语法。缺点则是语法敏感,少写一个分号整个构建就会失败。对于这种“配置即代码”的风格,你需要对 D 语言有最基本的感知,不过说实话,照着示例抄也基本不会出问题。

2.3 第一篇文章:从 Markdown 到 HTML

新建文章同样走命令行:

caveman post "Hello Caveman"

这个命令会自会在source/posts/下创建带日期的 Markdown 文件,文件名形如2025-06-30-hello-caveman.md。文件开头自带一段用---包裹的元信息块,包含标题、时间、标签,你只需要填充标签和正文即可。写完正文后执行:

caveman build caveman serve

serve默认监听本机某个端口,浏览器访问后会看到站点首页、文章列表、详情页都已经渲染完成。到这里,一个最简博客就跑通了。接下来才进入真正决定体验的部分——模板和内容组织。

3. 内容写作与模板控制:把精力留给正文本身

Caveman 最让我舒服的一点,是它默认让“写作”和“装修”两件事保持分离。写文章时你不需要关心页面长什么样,只管填 Markdown;而调模板时,你可以非常精确地控制每一块内容的输出位置和展示逻辑。

3.1 文章元信息与 Markdown 渲染

每篇文章头部用三行---包裹的是页面数据,Caveman 会把这些字段解析成结构化的变量,供模板调用。我常用的字段有:

  • title:文章标题,会同时出现在列表页和详情页的标题标签中。
  • date:发布日期,格式通常写成 ISO 标准格式。
  • tags:标签列表,用逗号分隔即可。
  • draft:标记为true时,构建会跳过这篇文章,适合写了一半不想被看到的稿子。
  • layout:指定渲染时使用哪一个模板布局文件,默认留空则使用posts.dt。

Caveman 的 Markdown 渲染器支持常规的语法外,还能通过配置开启表格、删除线、任务列表这些扩展。嵌套代码块的处理我放在后面的踩坑章节讲,这里先说一个比较隐蔽的点:Caveman 对 Markdown 文件的文件名处理相当原始,中文标题生成 URL 时不会自动做拼音转换,而是直接保留中文或转成百分号编码。这意味着你需要在文章文件名里手动控制 URL 的可读性,否则链接会很长。我的习惯是文件名全用英文短横线连接,标题字段写中文,两边互不干扰。

3.2 模板文件里到底是什么

Caveman 的模板目录里放的是带有.dt后缀的文件,全称是 D Template。你可以把它理解成一种“允许你在 HTML 里嵌入 D 语言逻辑片段”的模板方案。第一次打开这些文件,最直观的感受是:模板跟普通的 HTML 几乎一样,只是时不时冒出来一些<% %>包裹的代码段。

举个例子,遍历文章列表的模板核心部分长这样:

<% foreach (post; site.posts) { %> <article> <h2><a href="<%= post.url %>"><%= post.title %></a></h2> <p><%= post.date %></p> </article> <% } %>

这套语法的学习成本比 Jinja2、Liquid 这类模板引擎略高一点,因为你绕过了一层“封装好的模板语法”,直接面对的是通用编程语言。好处是逻辑能力没有天花板,比如你想从一堆文章里筛出某个标签的最近五篇,写起来就是几行 D 代码的事,不用去查这个模板引擎支不支持复杂过滤器。

3.3 页面上下文:能用到的数据有哪些

Caveman 在渲染时会提供一个全局数据模型,常见字段包括:

数据含义在哪里使用
site.title站点标题首页、导航、全局页脚
site.posts文章列表,按日期倒序首页列表、归档页
site.pages独立页面列表导航菜单、关于页
post.title当前文章标题详情页
post.body当前文章渲染后的 HTML 正文详情页主区域
post.tags当前文章标签数组文章底部、标签过滤

这些字段不需要配置,只要内容目录里有对应的文件,数据就会自动填充。我习惯在首页模板里同时遍历最近的五篇文章,并把所有标签统计出来做侧边栏,这个效果只用基础模板循环就能完成,完全没有额外成本。

如果你看得仔细,会发现 Caveman 的“主题”概念也不太一样:一个主题就是一个模板目录,里面核心文件通常有index.dt(首页)、post.dt(文章详情页)、archive.dt(归档/标签页)等。你要改样式,直接改这套 HTML 结构即可,不需要解包任何被压缩过的主题包。

4. 构建、预览与日常写作工作流

工具再好用,最终还是要嵌入日常节奏里。我目前的工作流非常简单:写文章 → 跑三条命令 → 同步到服务器。整套流程走下来,从写完 Markdown 到线上生效,大概不超过一分钟。

4.1 构建与预览的正确姿势

caveman build会读取source/下的所有内容,渲染后输出到public/。这个目录是清空重建的,所以不要手工向里面塞东西——任何非生成的文件在下次构建时会被删除。如果你需要放一些额外静态资源(比如图片、PDF、自定义脚本),正确的做法是把它们放到source/assets/或者source/static/之下,构建时会一并拷贝到输出目录。

caveman serve是本地预览模式。它会启动一个轻量 HTTP 服务,并且默认监听127.0.0.1。端口参数可以用命令显式指定,比如:

caveman serve -p 8080

说实话,Caveman 没有 Hugo 那种毫秒级热重载体验,每次修改内容后需要手动重新caveman build,刷新浏览器才能看到变化。刚切过来时我挺不适应,但习惯后反而觉得有个好处:你不会被即时反馈捆绑着一遍遍微调样式,而是先把内容写完,最后一次构建看整体效果。对写作者来说,这未必是坏事。

4.2 构建产物部署到服务器

Caveman 生成的public/是百分百静态文件,不依赖任何服务端运行环境。你可以把它部署到任意静态托管平台,也可以丢到自己的 Nginx 服务器上。我的服务器上只有一个极简配置:

server { listen 80; server_name example.com; root /var/www/my-blog/public; index index.html; }

加一个自动更新脚本更省心。下面是一个简易示例,放在服务器端接收 Git push 之后执行:

#!/bin/bash cd /var/www/my-blog git pull origin master /home/nirvanalau/bin/caveman build rm -rf /var/www/my-blog/public mv /var/www/my-blog/public /var/www/my-blog/public_new mv /var/www/my-blog/public_new /var/www/my-blog/public

你可能会说:这不就是发布脚本吗?有什么新鲜的。关键区别在于 Caveman 的整个发布链路没有依赖安装步骤,不需要npm install、不需要composer install,你拷贝过去的就是一个二进制文件,它在服务器上跑出来的结果和本地一致。对服务器环境不干净的情况,这个特性太重要了。

4.3 写作时的三个小习惯

使用这几个月,我给自己定了几个规矩,帮助维持节奏:

  • 每天都写就每天构建一次,当天不写就完全不碰命令,不给自己增加无意义的操作负担。
  • 图片统一放在source/assets/img/下,文章里引用相对路径,保证换域名时不用回头改正文。
  • 稿子开写前先用caveman post "临时标题"生成文件,标题后续再改也无所谓,反正 URL 只跟文件名相关。

我的体会是,Caveman 把操作数量压到了最低,因此你不需要花心思管理“工具”,剩下的精力自然就全放在内容上。

5. 实测踩坑记录:三个让我花掉整块时间的问题

每个工具都不是完美的。Caveman 用起来确实爽,但有几个坑,我实实在在踩过,这里完整记录排查过程,不是劝退,是希望大家绕开。

5.1 中文文件名导致的 URL 错乱

现象:我有一篇文章文件命名成2025-03-12-使用Caveman搭建博客.md,构建成功后浏览器访问,文章页能打开,但站内搜索索引和 RSS 里的链接死活对不上,有的显示百分号编码,有的显示乱码。

排查过程:我先确认了浏览器地址栏中 URL 展示正常,但是复制链接后,在别的页面跳转时链接失效。进一步对比发现,Caveman 内部生成文章 URL 时,会直接采用文件名去除日期后的部分。文件名里带非 ASCII 字符,编码处理在不同上下文里表现不一致,导致部分链接在 HTML 里是正常中文,而 RSS/JSON 索引里变成了百分号编码。

解决方案:其实不复杂,就是文件命名用纯英文。我硬性规定自己所有 Markdown 文件名必须只用字母、数字和短横线,标题照样可以用中文。修改后链接稳定,也没有再出现编码不一致的问题。如果你手头已有中文命名的文章,最快的处理方式是改文件名,同时在文章元信息块里只保留title中文,不要依赖文件名生成展示标题。

5.2 代码块里的小于号和&被吃掉

现象:文章里贴了一段泛型代码,里面有<T>和&&,构建完后页面上源码显示不全,只剩下半截。

排查过程:第一反应是 Markdown 渲染出错,但单独测一个最简单的包含<的段落,并没有问题。于是开始怀疑是不是模板转义失误,翻了半天源码和文档,最终发现是代码高亮组件的锅。Caveman 本身没有内置高亮器,我引入了一个浏览器端高亮脚本,结果这个脚本在解析含 HTML 敏感字符的代码块时,会先按 HTML 解析一遍,把<T>当成标签截断。

解决方案:代码块统一用带语言标注的围栏代码块,并且在写入 Markdown 时对不需要渲染的符号做手动转义,比如把<写成&lt;。这不是 Caveman 独有的问题,任何“先渲染 HTML、后交给前端高亮”的方案都会遇到。后来我干脆在模板里给文章正文包了一层<pre>容器,同时把生成 HTML 里的代码块内部内容原样输出。

5.3 构建时 cin 吃掉了内存

现象:文章数量到三百篇后,caveman build偶尔会出现内存占用异常,尤其是同时生成标签归档页和多层分类时更明显。

排查过程:我先用time -v caveman build看峰值内存,发现每次构建都在跑全量渲染,没有任何增量缓存。然后看归档页模板,发现我为了做标签聚合,在模板里对site.posts做了多次遍历,每次遍历都会重新生成中间数据结构,效率很低。

解决方案:两层处理。第一层把复杂的标签聚合逻辑放到模板顶部的代码块里,先构建一个索引对象,再在页面渲染时直接查索引,避免每次遍历全站文章。第二层是控制归档页数量,按年份拆成多个页面,而不是一个页面遍历所有文章。优化后,三百篇文章的构建时间从十几秒降到两三秒,内存也稳定下来。

这条经验换个工具也成立:静态站点生成器虽然功能简单,但模板里的循环和数据处理逻辑仍然要克制——你写的是代码,不是配表格,该优化的地方躲不掉。

6. 横向对照:Caveman、Hugo 与 Hexo,我的选择标准

很多朋友纠结选哪个静态生成器,本质上是不知道自己的核心诉求。我把三款工具放在一起做个对照,不评高低,只讲差异。

对比维度CavemanHugoHexo
核心语言DGoJavaScript/Node.js
安装产物单一二进制单一二进制Node_modules 大礼包
主题生态少,需要自己动手丰富丰富
配置风格D 代码YAML/TOMLYAML
插件能力无插件机制较有限非常丰富
构建速度中等极快慢(文章多时明显)
模板学习曲线中高中等较低
资源体积极小小大

说几个我亲测的感受:

  • Hugo强在构建速度和主题数量,但如果你想深度定制它的模板,你得学习它的.RenderShortcodes、_internal等一系列规范,复杂度其实不低。而且 Hugo 每次升级都会调整若干默认行为,我的老 Hugo 站每隔几次版本升级就要修一批模板兼容问题,挺磨人的。
  • Hexo强在插件生态,几乎所有你能想到的功能都有现成插件。但副作用就是版本地狱——插件跟不上主版本、插件之间互相依赖、升级一次等于重装一遍系统。我的旧 Hexo 站在转移到 Caveman 之前,光是处理 swig 模板和辅助函数兼容性就花了一个下午。
  • Caveman的优势是简单、可控、省心。代价是你要接受它的“不够丰富”,并且愿意偶尔直接改代码而不是找现成插件。

我的选择标准其实很简单:这个工具是会让我更愿意写,还是更愿意折腾?Hugo 和 Hexo 都很好,但它们让我花在“配置工具”上的时间越来越多。Caveman 恰好把这条路砍断了——你想加功能就自己写,不想写就老老实实用内置能力,没有第三条路。而这对我来说,是一种恰好的约束。

7. 关于持续使用 Caveman 的最终建议

如果你已经看到这里,说明你对“回归原始”这件事本身是有兴趣的。我给想尝试的朋友几个实操建议。

不要被“D 语言”这三个字吓退。你不需要会写 D 程序才能用 Caveman,你只需要能看懂模板文件里的循环和条件判断。这份理解门槛和学会 Jinja2 几乎持平,甚至更低,因为你不用学模板引擎自己的语法。

从迁移那天起就放弃“把旧站所有文章原样搬过来”的执念。我第一次迁移时想保留所有分类、标签、页面样式,结果花了两天清理无效数据。第二次我直接在 Caveman 里重新梳理内容结构,删掉了几十篇没什么价值的旧文,反而觉得站点清爽很多。博客人生的唯一资产是内容本身,不是那个花哨的主题皮肤。

给服务器部署留一个最小的自动脚本。不需要上了 CI/CD 那么重的体系,一个简单的 Git hook 加两行命令就够了。工具的终点是隐身在写作流程背后,让你不再感知到它的存在。

最后分享一个使用心得:我最享受 Caveman 的时刻,并不是它帮我快速完成了某一次构建,而是深夜写长文的时候,把内容写完,运行caveman build,看到没有任何报错,所有页面安静地生成在那个public/目录里。这种“一切都在掌控之中”的感觉,正是我折腾过很多工具后一直寻找的东西。如果你也受够了弹性变量和插件兼容性表格,那 Caveman 可能是你这一站该下车的地方。