核心只有一个数据流
静态博客生成器的本质非常朴素:
- 读
content/posts/*.md,用 front matter 解析出元数据; - 把正文渲染成 HTML,同时抽出目录和摘要;
- 套进统一模板,写出一个个
index.html; - 顺带生成
feed.xml、sitemap.xml和搜索索引。
第一步:解析 front matter
不要为了几十行 YAML 去装一个依赖。博客需要的子集很小:字符串、数字、布尔、数组。
JS
export function parseFrontMatter(raw) {
const text = raw.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
const match = /^---[ \t]*\n([\s\S]*?)\n---[ \t]*(?:\n|$)/.exec(text);
if (!match) return { data: {}, body: text.trim() };
return { data: parseYamlLite(match[1]), body: text.slice(match[0].length) };
}支持这两种写法就够了:
YAML
tags: [工程, Node.js]
tags:
- 工程
- Node.js第二步:渲染 Markdown
marked 的 ESM 单文件版本只有 40KB,直接放进仓库,离线也能构建:
JS
import { marked } from '../../vendor/marked.esm.mjs';
const html = marked.parse(md);难点从来不是 Markdown 本身,而是周边:
| 需求 | 做法 |
|---|---|
| 标题要有 id 和锚点 | 渲染后用正则补 <a class="heading-anchor"> |
| 代码要高亮 | 自己写个 200 行的分词器,避免引入 highlight.js |
| 外链新窗口打开 | 给 <a href="http..."> 加 target="_blank" |
| 图片懒加载 | 给 <img> 补 loading="lazy" |
第三步:写一个能用的本地服务器
http 模块 40 行就能得到一个带目录索引的静态服务器:
JS
const server = http.createServer((req, res) => {
const urlPath = decodeURIComponent((req.url || '/').split('?')[0]);
let file = path.join(base, urlPath);
if (!path.extname(file) || urlPath.endsWith('/')) file = path.join(file, 'index.html');
if (!fs.existsSync(file)) return show404(res);
res.writeHead(200, { 'content-type': MIME[path.extname(file)] || 'application/octet-stream' });
fs.createReadStream(file).pipe(res);
});再配一层 fs.watch 做热重载,写作体验就不输任何框架了。
为什么不直接用现成的框架
不是因为框架不好,而是因为这个博客的复杂度配不上它的依赖树:
- 没有客户端路由,每个页面都是真实的 HTML;
- 没有组件状态,交互仅限主题切换和搜索;
- 没有构建性能问题,50 篇文章的构建时间是 0.3 秒。
当你不需要一个工具时,不用它才是最省心的决定。
收尾
顺便给项目加了一个 --watch:
BASH
node scripts/build.mjs --watch保存 Markdown 的瞬间,浏览器里就是最新的内容。写博客这件事,反馈越快,越容易坚持下去。