Hello blog
今年夏天,我偶然读到了《重构》英文版。这让我萌生了自己尝试(技术)写作的念头。然而,我需要一种简单的方式来发布博客文章,同时也需要一个无法立即开始写作的借口。于是,作为开发者的我告诉自己:
“首要之事,与其练习写作、阅读书籍或草拟可以写的内容,不如先定制一个静态网站生成器。”
但现在,这反而成了 nobloat.org 上的第一篇文章,我觉得这很好。
TL;DR
这个博客由 400 行手写的 Go 代码生成,主要是因为它很有趣,同时也因为我对现有解决方案每次更新带来的破坏性变更感到厌烦。
这种情况不会再发生了 :)
我对写作的构想
现有工具让我分心
静态网站生成器的生态系统非常庞大,但大多数解决方案都伴随着显著的复杂性。从写作的角度来看,最大的问题其实是建立一个既能让我不分散注意力,又看起来顺眼的工作环境。
Hugo 速度很快,也很流行,我过去也用过它。但是,它是一个庞大的框架,包含了数百个我永远不会用到的功能,却仍然要为此付出复杂性的代价。配置文件、主题系统和插件生态系统增加了一层层的抽象,让人难以理解实际发生了什么。当某些东西出错或我想自定义行为时,这一切对我来说都过于复杂,难以理清。而且我过去遇到过问题,一两年后,未经修改的静态网站突然就无法构建了。
Jekyll 需要 Ruby、Bundler 和一个复杂的 gem 生态系统。每次我想要更新或部署时,我都需要确保使用正确的 Ruby 版本,管理 gem 依赖项,并处理潜在的版本冲突。仅仅为了生成静态 HTML 而维护一个 Ruby 环境,感觉开销太大了。同样的问题也适用于 Pelican,只不过是用 Python 代替了 Ruby。
Next.js 和其他基于 React 的静态网站生成器功能强大,但它们带来了整个 JavaScript 生态系统。Node 模块、构建工具、转译,以及 npm 生态系统的持续动荡——而这一切本质上只是为了文本处理和模板渲染。
即便是更简单的工具,如 Zola 或 11ty,仍然需要学习它们特定的约定、配置格式和模板语言。它们比那些重量级工具要好,但它们仍然是带有自己抽象层的框架。
我需要的是:
- 写 Markdown 文件
- 运行一个简单的命令,得到 HTML。
- 一切都应该在 Git 中,能在文本编辑器中使用,并且除了安装 Go 之外,不需要任何设置。
- 无需配置文件、无需主题系统、无需我先去学习的插件架构。
现有的解决方案没有一个能满足这些要求。它们要么需要复杂的设置,有太多依赖项,引入了不必要的抽象,要么对结构有太多固执己见。此外,这也可以成为公园里某个阳光明媚的下午的一个有趣项目。
实现
实现包含两个 Go 文件:main.go(核心功能)和 data.go(站点配置),除了标准库外没有外部依赖。它读取 Markdown 文件,将其转换为 HTML,生成索引页,创建 RSS 订阅源,并将所有内容输出到 public/ 目录。整个代码库不到 400 行,只做我需要的事情,不多不少。
工作原理
博客生成器遵循一个简单的工作流程:
1. 内容结构
文章是位于 articles/ 目录下的 Markdown 文件,文件名带有日期前缀:YYYY-MM-DD-title.md。日期前缀有两个用途:一是为排序和 RSS 订阅提供发布日期,二是使得按时间顺序浏览文件时结构一目了然。
|
|
每个 Markdown 文件的第一行被视为标题(一个 # 标题),其余部分则转换为 HTML 内容。
2. Markdown 到 HTML 的转换
Markdown 解析器有意设计得极简。它处理:
- 标题(
#,##,###) - 段落
- 列表(
- item) - 行内格式(粗体、斜体、代码、链接、图片)
- 带有语法高亮类的代码块
- 为
##标题自动生成锚点
解析器逐行处理 Markdown 文件,将支持的表达式转换为 HTML。对于列表和代码块,它会跟踪是否仍在列表或代码片段内部。
代码片段有所缩短,只显示相关部分。
|
|
3. 文章加载与排序
loadPosts() 函数扫描 articles 目录,读取每个 .md 文件,从文件名前缀解析日期,将 Markdown 转换为 HTML,并按日期降序(最新的在前)对文章进行排序。
|
|
如果文件不符合预期的格式,它会记录警告并跳过,确保只包含格式正确的文章。
4. HTML 和 RSS 订阅源生成
生成器创建三种类型的 HTML:
- 索引页 (
index.html): 列出所有文章及其链接,外加一个用于外部资源的链接部分。 - 文章页 (
YYYY-MM-DD-title.html): 单个文章页面,包含返回索引页的导航。 - RSS 订阅源 (
feed.xml): 供 RSS 阅读器使用的标准 Atom 订阅源。
所有 HTML 都是使用 Go 标准库中的 html/template 包生成的。模板从简单的 HTML 文件(index.html 和 article.html)中读取,这些文件使用 Go 的模板语法——没有复杂的模板系统,只是带有模板变量的简单明了的 HTML。
5. 配置
站点元数据作为简单的 Go 结构体存储在 data.go 中。这包括站点标题、口号、基础 URL、链接,以及出现在索引页上的项目、工具等。配置只是一个变量声明,因此没有 YAML,没有 JSON,没有复杂的配置解析。
要更改站点标题或添加链接,我只需直接编辑 data.go。
|
|
6. 文件监控(可选)
对于开发,main.go 包含一个 --watch 标志,它使用 fsnotify 包来监控 articles 目录、CSS 文件、模板文件和生成器本身。当任何文件发生更改时,它会自动重新构建站点。
|
|
当你修改内容、模板或 CSS 时,更改会立即被检测到,站点会自动重新构建。编辑一篇文章,可以看到它更新。修改 HTML 模板,获得即时反馈。更改样式表,看到新样式生效。
然而,它不会检测 *.go 文件本身的变化,因为这需要更复杂的重启机制,而且我反正也很少碰它们。
这是唯一的外部依赖(github.com/fsnotify/fsnotify),并且仅用于监控功能。核心构建功能不需要外部包。
结论
这个博客生成器完全满足我的需求:将 Markdown 转换为 HTML,生成索引和 RSS 订阅源,并输出静态文件。它不到 400 行代码,核心功能仅使用 Go 标准库,并且我理解它的每一部分。
对于那些需要复杂功能(如标签、分类、分页或主题系统)的人来说,它可能不合适。但对于一个简单的博客来说,它是完美的。它符合 “nobloat”(无膨胀)的哲学。
整个代码库非常小,易于阅读、修改和维护。
对我个人来说,最好的部分是:我不需要 node、npm 或类似工具来构建它。本地预览只需在 Firefox 中打开 public/index.html。部署只是一个
|
|
我还有一些关于在 nobloat 的背景下可以进一步写作的想法。鼓起勇气发布这篇文章是我迈出的最大一步。
参考资料
- github.com/nobloat/blog
- nobloat.org
- GitHub: nobloat
欢迎随时反馈至 dev@spiessknafl.at