跳到正文

用 pnpm patch 给 Hexo 补上首页隐藏

同一主题连续写了九篇,发布后它们占了首页开头很长一段,其他主题的文章被推到了后面。把篇目集中到一篇总览以后,我希望首页只留下这个入口,其他文章仍能从归档和搜索找到,已经发出去的链接也继续有效。

这件事需要调整首页的文章列表。正文仍然是正常发布的文章,只是不再逐篇占用首页位置。找到 hexo-generator-index 的 hidden: true 配置后,又遇到一个版本差异:GitHub 上已经有实现,npm 最新发布包却还没有。

最终处理很小:给九篇子文章增加 hidden: true,再用 pnpm patch 补上首页生成器里的一行过滤。以下记录基于 2026-10-30、Hexo 8.1.2、hexo-generator-index 4.0.0 和 pnpm 12.11.2。

先确定要从哪里隐藏

总览是一篇正常文章,里面放背景、各篇链接和简短说明。给子文章的 frontmatter 增加布尔值:

---
title: 连载第 1 篇
hidden: true
---

这里用 Hexo 常见的 title 示意;实际项目保留已有标题、网址和其他字段,只增加 hidden。总览不设置这个字段,或者显式设为 false。

这个方案的展示范围如下:

入口 总览 子文章
首页及首页分页 展示 不展示,也不占分页名额
归档 展示 展示
本站站内搜索 收录 收录
原文链接 正常访问 正常访问

hidden 的含义由读取它的插件决定,不是 Hexo 核心对所有入口统一生效的隐私开关。本站搜索生成器读取完整文章集合,因此保留子文章;换成其他搜索插件,也要检查它自己的过滤规则。

例如 hexo-hide-posts 默认会从多个文章列表移除隐藏文章,并添加 noindex。它适合更广的隐藏需求,与这次只调整首页的范围不同。published: false 则属于未发布状态,也不能用来替代首页聚合。

为什么归档和正文可以保留

Hexo 用不同生成器处理首页、归档和文章页。生成器接收 locals 中的站点数据,再返回需要生成的路由和模板数据。官方 Generator 文档分别给出了列表分页和独立文章生成的例子。

首页生成器可以取得一个经过筛选的集合,再做排序和分页。只要不修改全局文章集合,其他生成器仍能读取完整文章。

只有首页生成器筛选 hidden,归档、搜索和正文仍使用完整集合

因此过滤应该放在分页之前。假设每页三篇,前三篇恰好都是隐藏的子文章:先分页、再在模板里隐藏,第一页就会空掉,分页总数也仍按未筛选的数据计算。先过滤再分页,才能让首页条数与实际内容一致。

用 CSS 隐藏卡片也有同样的问题。它只改变浏览器显示,生成器计算的分页和页面内容仍然存在。

已合并的功能为什么装不到

排查时先确认 npm 的发布版本:

pnpm view hexo-generator-index version

本次返回 4.0.0,时间线是:

时间 事件
2024-07-09 4.0.0 发布
2024-12-04 支持首页隐藏的 PR #125 合并
2026-10-30 查询时 npm 最新版仍为 4.0.0

主分支 README 已经说明 hidden: true,但发布包早于这个 PR。只升级到 npm 的 latest,拿到的仍然是不含该功能的版本。

核对发布包时,可以查看当前实际解析的文件位置:

node -p "require.resolve('hexo-generator-index/lib/generator.js')"

这个路径指向当前安装结果;已经应用补丁时,看到的会是修复后的代码。判断发布包是否缺少功能,要与未打补丁的版本对照。

4.0.0 原始发布包的 lib/generator.js 直接使用全部文章排序。上游提交 5cb1ecb增加的处理只有这一处:

- const posts = locals.posts.sort(config.index_generator.order_by);
+ const posts = locals.posts.filter(post => !post.hidden).sort(config.index_generator.order_by);

后面的置顶、分页和模板选择继续使用原实现。本次只补这一行,不复制整个首页生成器,也不改全局 locals.posts。

用 pnpm 保存这行修改

直接编辑安装目录可以临时验证,但重装依赖后会丢失,其他设备和 CI 也拿不到修改。pnpm 自带的补丁功能会把差异保存到项目中,在后续安装时重新应用。

准备编辑副本

先确保项目已经安装依赖,然后执行:

pnpm install
pnpm patch hexo-generator-index@4.0.0

pnpm 会输出可编辑目录,后面修改的应当是这个副本。需要固定工作位置时,可以给同一命令加上 --edit-dir;例如下面这一条替代上面的 pnpm patch:

pnpm patch hexo-generator-index@4.0.0 --edit-dir .patch-work/hexo-generator-index

把 .patch-work/ 加进项目的 .gitignore,避免提交临时副本。这个目录需要不存在或为空。打开其中的 lib/generator.js,只应用前面的那一行 diff。patch 命令说明

生成补丁并登记配置

使用固定编辑目录时,接着执行:

pnpm patch-commit .patch-work/hexo-generator-index

如果使用默认目录,就把参数替换为 pnpm patch 输出的实际路径。patch-commit 比较副本与原始包,默认生成 patches/hexo-generator-index@4.0.0.patch,登记补丁并执行安装。patch-commit 命令说明

这里的 commit 是记录依赖补丁,不是执行 Git commit。仍需自己审查文件并提交到版本库。

pnpm 12.11.2 在本项目的 pnpm-workspace.yaml 中写入了下面的映射;已有其他补丁时,在同一个 patchedDependencies 下增加一项:

patchedDependencies:
  hexo-generator-index@4.0.0: patches/hexo-generator-index@4.0.0.patch

编辑副本经 patch-commit 生成项目补丁,其他设备通过安装恢复相同修改

这几个文件一起构成可复现的安装输入:

文件 保存的内容
patches/hexo-generator-index@4.0.0.patch 对原包的具体差异
pnpm-workspace.yaml 哪个包、哪个版本使用哪份补丁
pnpm-lock.yaml 包版本、补丁信息及依赖解析结果

这些文件都要追踪,子文章新增的 frontmatter 也要提交。其他设备使用同一份文件安装:

pnpm install --frozen-lockfile

这次改的是包里的 JavaScript 实现,没有修改依赖声明。需要改变包的依赖关系时,应另外评估 overrides 或包钩子,不能把改 package.json 依赖项也当成普通源码补丁。

本次生成的补丁文件快照弹窗保留了完整差异,可对照检查是否多改了文件。

验证文章还在,首页名额已经释放

安装没有报错,只能说明依赖与补丁能够安装,还要检查 Hexo 的生成结果:

pnpm install --frozen-lockfile
pnpm exec hexo generate

修改 frontmatter 后,需要让生成过程重新读取文章。若复用了旧数据库,先处理对应预览的缓存;多个预览或发布任务并行时,使用独立数据库和输出目录,避免清掉另一份正在使用的产物。本站的发布脚本会上传文件,因此这次验收只执行本地生成。

检查范围应当覆盖:

  1. 首页所有分页只保留该系列总览,其他普通文章正常展示,子文章不占分页名额。
  2. 归档和搜索索引仍有每篇子文章,标题与 URL 正确。
  3. 总览的九个正文链接、原文页面、图片和代码工作区资源都能访问。
  4. 桌面和手机上都能从首页进入总览,再找到各篇。

检查搜索索引时,要看真实条目的标题和 URL。总览正文也会包含子文章链接,仅在 XML 文本中搜到链接,不能证明子文章本身仍是搜索结果。

接入时的历史验收共检查了 133 篇文章:九篇退出首页后,两个首页分页合计 124 个条目;完整归档和站内搜索仍各保留 133 个条目。桌面 1440 与手机 390 宽度都通过,总览九个正文链接正常。验收记录弹窗保留了入口、计数和资源样本。

官方发布后怎样移除

这份补丁针对确切的 4.0.0。以后看到新版本,先检查已发布包是否包含同等的首页过滤,再决定移除;主分支已经合并、README 已更新,都不能代替这一步。

确认新版本包含功能后,在单独的升级改动中移除旧补丁:

pnpm patch-remove hexo-generator-index@4.0.0

这个命令会删除对应补丁文件与 patchedDependencies 项。patch-remove 命令说明

随后把依赖更新到已经核实的目标版本,重新安装并更新 lockfile,再在不使用补丁的产物上重复前面的验证。不要照抄一个尚未发布的版本号,也不要只升级包却留下不会再命中的旧补丁。

各篇的 hidden: true 继续保留,因为移除的是临时实现补丁,文章的首页展示意图没有改变。补丁背景记录也应更新为已移除,并注明由哪个正式版本接替。

什么时候考虑别的方案

从 GitHub 固定提交安装,也能取得这项功能,但会同时采用那个提交对应的整个包。本次只缺上游一行,所以继续使用 npm 发布版本并保存最小补丁。

如果需求发展成自动按系列折叠、分栏目编排或生成专题页,可以使用项目层生成器。它先处理传给首页的数据,再复用或替换原生成流程;归档、搜索和正文仍可各自使用完整集合。原首页生成器会再次排序,复杂顺序不能仅靠提前排好数组;新增卡片布局也需要主题配合。

总结

首页隐藏是文章集合的筛选,应该发生在分页之前。只过滤首页输入,就能保留总览入口,同时让归档、搜索和原文继续承担各自的用途。

pnpm patch 则让尚未进入正式发布包的一行改动成为项目可追踪的安装输入。差异要小,来源和适用版本要明确,安装与页面行为都要验证;官方发布等价实现后,再把这层临时补丁移除。

首页过滤的补丁文件快照

打开原文件 hexo-generator-index@4.0.0.patch

此文件是 2026-10-30 从项目 patches/ 目录保存的只读快照,维护源仍是项目的依赖补丁。它只替换 lib/generator.js 的一行,与 PR #125 的过滤逻辑一致,没有包含页面模板或其他插件修改。

首页聚合接入时的历史验收

打开原文件 home-overview-validation.json

记录来自接入补丁时的隔离 Hexo 产物。检查范围包括全部首页分页、完整归档和站内搜索索引、九篇正文、资源别名,以及桌面和手机上的总览入口。133 与 124 是该次站点快照的计数,不是平台限制。

入口与计数记录
{
  "checkedAt": "2026-10-30T04:29:37.240Z",
  "patch": "patches/hexo-generator-index@4.0.0.patch",
  "patchChangedLines": {
    "removed": 1,
    "added": 1
  },
  "overview": "/2026/10/30/esa-pages-journey/",
  "hidden": [
    {
      "title": "ESA Pages 体验之旅(1):为什么选择 ESA Pages",
      "url": "/2026/10/29/esa-pages-plans-and-certificates/"
    },
    {
      "title": "ESA Pages 体验之旅(2):部署静态页面和 API",
      "url": "/2026/10/29/esa-pages-fullstack-deployment/"
    },
    {
      "title": "ESA Pages 体验之旅(4):小项目的存储选型",
      "url": "/2026/10/29/esa-pages-storage-selection/"
    },
    {
      "title": "ESA Pages 体验之旅(3):ESA CLI 的构建配置",
      "url": "/2026/10/29/esa-pages-build-platform/"
    },
    {
      "title": "ESA Pages 体验之旅(5):修复 TableStore SDK 的变量声明错误",
      "url": "/2026/10/29/esa-pages-sdk-source/"
    },
    {
      "title": "ESA Pages 体验之旅(6):检查 ESA 的运行环境",
      "url": "/2026/10/27/esa-runtime-dev-sdk-compatibility/"
    },
    {
      "title": "ESA Pages 体验之旅(8):四次 fetch 限制下的接口设计",
      "url": "/2026/10/29/esa-pages-subrequest-budget/"
    },
    {
      "title": "ESA Pages 体验之旅(7):适配 Node.js 依赖",
      "url": "/2026/10/29/esa-pages-tablestore-adaptation/"
    },
    {
      "title": "ESA Pages 体验之旅(9):缓存、防刷与日志",
      "url": "/2026/10/29/esa-pages-cache-and-operations/"
    }
  ],
  "views": [
    {
      "viewport": 1440,
      "homePages": 2,
      "homeEntries": 124,
      "overviewEntries": 1,
      "hiddenHomeEntries": 0,
      "archivePages": 14,
      "archiveEntries": 133,
      "searchEntries": 133,
      "childRoutes": 9,
      "overviewLinks": 9
    },
    {
      "viewport": 390,
      "homePages": 2,
      "homeEntries": 124,
      "overviewEntries": 1,
      "hiddenHomeEntries": 0,
      "archivePages": 14,
      "archiveEntries": 133,
      "searchEntries": 133,
      "childRoutes": 9,
      "overviewLinks": 9
    }
  ],
  "resourceSamples": [
    "files/esa-pages-fullstack-deployment/data/verification.json",
    "files/nodejs-build-and-distribution/data/verification.json",
    "files/esa-pages-subrequest-budget/kits/identity-migration/FILES.json",
    "files/better-auth-edge-storage-evaluation/kits/identity-migration/FILES.json"
  ],
  "globalPostCount": 133,
  "errors": [],
  "deployed": false
}