为Hexo添加热更新

hexo server 虽然原生支持监听文件变化并重新生成页面,但遗憾的是它不会主动通知浏览器刷新。每次修改完文章,我们还是得手动切回浏览器按下 F5 or ⌘ + R

之前用过 Astro,它能做到这一点。既然是可行的,于是就让 CodeX 输出脚本:用户更新 .md -> Hexo 重新生成页面 -> 浏览器会自动刷新当前 http://localhost:4000 页面。

虽然没系统学过前端,不过之前用Astro、Vite 等前端工具时,也体验过“热更新”这个 feature,大概能确认这个需求是可行的,于是就让CodeX 帮忙写一个脚本,实现:用户更新 .md -> Hexo 重新生成页面 -> 浏览器自动刷新。

Experiences:相见恨晚

引入脚本后,现在也可以电脑分屏,右边放 修改 md 文件 ,左边放 Edge/Chrome/Zen/Safari 看实时预览。体验方面的提升可以这样概括:

  1. 最直接的,不需要手动刷新。过去由于 Blog 不能在浏览器热更新,通常就凑合着用编辑器内置的预览
  2. 在浏览器中预览,是真正的、开箱即用的所见即所得

所见即所得:Browser Or Editors?

关于所见即所得这点,现在主流编辑器都内置Markdown预览,但效果差强人意。Typora 比较美观,但编辑体验略逊于 Obsidian/VSCode/NeoVim/Zed。

另外,为了追求更好的 UI/UX,我经常会在 Markdown 里缝合一些 HTML 代码。大多数编辑器的预览引擎在处理这些自定义标签时都会“翻车”,无法正确渲染,当然这不是编辑器本身的缺点。

列出一些无法正确渲染的情况:

  • Shiki 代码高亮的 Transformers: 目前在 Obsidian 中使用插件可部分渲染,但语法采是基于Expressive Code 的标准
  • 部分markdown-it 插件:其中比较流行的 callout/math/mermaid 在 Obsidian/Typora 均能预览,但是相对小众但却很实用的 tab 组件却不能。此外 Obsidian 的双链也是如此
  • 自定义的 HTML Elements(Web Components): 自定义 HTML Elements 的用法可以见这篇文章;本站所自定义的组件陈列在该页面
  • Markdown 内嵌入的 CSS 样式同样不能预览

Appendix: Source Code

Hexo 会自动加载项目根目录/主题目录下 scripts/ 里的脚本。这里选择在主题目录下的 /scripts 目录中新建 scripts/hot-reload.js

考虑到 GitHub 的代码阅读体验更好,更新更及时,这里不贴代码

Appendix: How it Work

既然是 vibe,那么实现原理自然是一概不知,能用即可🤓

直接让 AI 给我一个不饶、不躲的解释,需要吗?

BTW,输出进行了适当调整/删减;这里实际问得是 AI Studio 中的 Gemini,不过它的 Logo 太丑了,暂且换成 OpenAI 的 Logo 吧

解释一下工作原理,使用的技术

核心技术:SSE (Server-Sent Events)

脚本的核心使用了 SSE (服务器发送事件)。这是一种允许服务器向浏览器推送实时通知的技术。

实现流程

  1. 环境检测 (isHexoServerCommand)

    脚本会首先判断当前是否运行在 hexo server 模式下。这样可以确保在执行 hexo generate 静态构建或部署到生产环境时,不会注入这些开发专用的代码。

  2. 监听 Hexo 生命周期

    • hexo.on('generateAfter'):监听 Hexo 页面重新生成完成。每次 Hexo 完成页面生成后,脚本就会触发刷新。由于 Hexo server 会在文件变化后自动触发重新生成,所以只需要监听这一个事件即可。
  3. 防抖与冷却期 (Debounce & Cooldown)

    为了防止编辑器频繁保存(如自动保存功能)导致浏览器疯狂刷新,脚本加入了 150 ms 的防抖和 500 ms 的冷却间隔,确保刷新动作既及时又稳定。

  4. 中间件注入 (server_middleware)

    脚本在 Hexo 内置服务器中开辟了一个特殊的"暗号"接口:/__hexo_live_reload。浏览器会一直连接这个接口,等待服务器发令。

  5. 前端注入 (injector)

    通过 Hexo 的 injector 功能,脚本会自动在每个页面的 <body> 末尾插入一段极其微小的 JavaScript。这段代码负责监听来自服务器的 reload 信号,并在接收到信号后调用 window.location.reload()

`window.location.reload` 是? `__hexo_live_reload` 是?

1. window.location.reload() 是什么?

这是浏览器原生 JavaScript 提供的一个方法,它的作用非常简单直接:刷新当前页面

  • 效果: 相当于你在键盘上按下了 F5 键,或者点击了浏览器地址栏旁边的"刷新"按钮。
  • 在脚本中的作用: 当浏览器接收到服务器传来的"文件已更新"的消息时,它会执行这行代码。这样你就不用手动去点刷新,页面会自动变到最新的状态。

2. /__hexo_live_reload 是什么?

这是一个虚拟的通信地址(Endpoint)

在正常的 Hexo 预览中,你访问 http://localhost:4000 会看到你的博客。但这个脚本在后台偷偷开辟了一个隐藏的"专线":http://localhost:4000/__hexo_live_reload

它的工作原理如下:

  • 它是"管道": 这个地址并不存在于你的磁盘上,它是通过 Node.js 脚本拦截请求而创建的一个长连接通道(基于 SSE 技术)。

  • 服务器端(Hexo): 当你在 scripts/live-reload.js 中看到 req.url === LIVE_RELOAD_PATH 时,就是在告诉 Hexo:“如果有浏览器请求这个地址,不要给它网页内容,而是保持连接不断开,随时准备发信号。”

  • 客户端(浏览器): 脚本在页面里写了 new EventSource('/__hexo_live_reload')。这行代码的意思是让浏览器一直"监听"这个地址,就像在等一个电话。


总结:两者是如何配合的?

  1. 连接: 浏览器打开你的博客,自动连接到 /__hexo_live_reload 管道。
  2. 等待: 浏览器在管道另一头等着,无事发生。
  3. 触发: 你修改了 .md 文件,Hexo 重新生成了页面。
  4. 发令: Hexo 服务器往 /__hexo_live_reload 管道里塞入一条消息:{"type": "reload"}
  5. 动作: 浏览器在管道这头收到了消息,立刻执行 window.location.reload()
  6. 结果: 页面变新了。

Author

GnixAij

Posted

2026-04-08

Updated

2026-04-08

License