给自己搭个人网站,第一反应通常是 Hexo 或 Hugo。我最后选了自己写 —— 不是为了炫技,而是算了下账,发现手搓更划算。
账是这么算的
框架的价值在于「别人已经解决了你的问题」。但个人博客的问题实在太简单了:
- Markdown 转 HTML →
marked,一行 - 文章元信息 → front matter,一个正则
- 列表页、详情页 → 两个模板函数
- RSS → 拼字符串
而框架带来的成本是实打实的:主题定制要学它的模板语法,改个样式要翻文档,插件版本冲突要查 issue,一年不动之后升级可能直接跑不起来。
我写的这个生成器一共 200 多行,所有逻辑我都清楚。要改什么直接改,不用先去理解别人的抽象。
整个结构
site/
├── build.js # 生成器本体
├── content/
│ ├── site.json # 个人信息、项目列表
│ └── posts/*.md # 文章
├── static/style.css # 样式
└── dist/ # 产物,丢给 Nginx
写文章就是往 posts/ 里丢 .md,跑一下 node build.js,dist/ 里就是完整站点。
两个值得说的实现细节
Front matter 解析不用引 yaml 库。 个人博客的 front matter 就那么几种字段,一个正则加逐行 split 够了:
const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
// 然后逐行匹配 key: value,数组用 [a, b] 的形式
少一个依赖,少一份升级负担。
中英文混排的阅读时长要分开算。 直接按字符数估会很不准,中文阅读速度约 400 字/分钟,英文约 220 词/分钟:
const cn = (md.match(/[\u4e00-\u9fa5]/g) || []).length;
const en = (md.replace(/[\u4e00-\u9fa5]/g, ' ').match(/[A-Za-z0-9]+/g) || []).length;
const minutes = Math.max(1, Math.round(cn / 400 + en / 220));
部署也简单
产物是纯静态文件,Nginx 配一个 root 就完事。更新用 rsync:
rsync -avz --delete dist/ user@server:/var/www/site/
--delete 保证服务器上不会残留已删除的旧文章。整个部署过程两秒,比很多框架的构建都快。
什么情况下不该这么做
说句公道话,如果你需要全文搜索、评论系统、多语言、几百篇文章的增量构建 —— 老老实实用成熟框架。手搓的优势只在「需求足够简单」这个前提下成立。
但对于一个个人主页 + 偶尔更新的博客,这个前提是成立的。