jekyll 迁移至 hexo

2021 年之后工作忙的飞起,却又无事足道。趁着国庆假期,折腾了一下博客,将原来的 jekyll 替换为了 hexo。期间踩了一些坑,以下仅以记录。

更换原因

回想起迁移的原因,突然间竟觉得有些复杂。最初 jekyll 用的好好的,从写作到发布的流水线也跑的比较顺,如果不是闲的蛋疼,断然是没有更换的必要。只不过原来的 jekyll 版本中一直含有「暗伤」,即:评论功能要使用科学上网工具才能使用。之前也找过一些方案,大致思路是使用一个反向代理来访问 disqusAPI。具体的实现方案,网上各有所表,始终不能满足自己的需求。

这两天得闲,又在网上搜索起来,发现了 DisqusJS 这个方案,使用起来好像也挺简单,反向代理的代码别人也提供了 disqusjs-proxy-vercel, 正巧我也在使用 vercel,那就折腾一下呗。

仔细看了一下说明文档,顺带访问了使用案例中的几个博客。嗨,怎么都是 「Powered by Hexo」呢?回头再看看自己的 jekyll 博客,虽然主题用的是高仿 「Next」(Hexo 上的著名主题),可想改改前端模板部分,居然感觉无从下手。再想想当初搞 jekyll 折腾 Ruby 的过往,索性换成 NodejsHexo 算了, 就算出了什么岔子,大概率也不用从头学起,现代前端的 node、npm 总还是知道在什么地方找问题的。

操作步骤

按照比较正统的工程做法应该是:

  1. 备份数据
  2. 创建新的站点
  3. 迁移数据
  4. 上线新的站点
  5. 域名记录变更
  6. 下线旧站点

这套工程实践没太大问题,而且对于风险也有较好的应对方案,比如万一搞砸了,还可以反向执行一遍回滚到原来的状态。可是,毕竟手头没有那么多宽裕的资源和时间,直接更新算了。所以大致分为以下几步:

  1. 打开冰箱门
  2. 把大象塞进冰箱
  3. 把冰箱门关上

说人话就是:

  1. 在原来的站点上直接改代码
  2. 把新的代码更新上线,直接替换旧的站点

这样考虑基于:

  1. 个人博客没有什么用户数据,文章数据就是代码,在 Git 仓库中有版本记录
  2. 要搞多个生产环境,域名记录变更都有时间开销,直接代码更新花的时间更少

本地环境搭建

按照 Hexo官方的说明 对于熟悉 npm 的进阶用户,可以执行

1
$ npm install hexo

然后通过

1
$ npx hexo <command>

或把 node_moduleshexo 加入环境变量后使用

1
$ hexo <command>

这两个我都试了,并没有什么用。直到我按照常规方法在 package.json 中添加了 scripts 来做了 npm run 的映射,才可以通过

1
$ npm run hexo <command>

进行使用

为了不这么恶心,干脆做回小白用户全局安装了 hexo

1
$ npm i hexo -g

为了测试效果,我还是在 package.json 中添加了几个 scripts

1
2
3
4
5
6
7
8
9
10
{
...
"scripts": {
"build": "hexo generate",
"clean": "hexo clean",
"deploy": "hexo deploy",
"server": "hexo server"
},
...
}

按照 Hexo官方的说法,我就只需要拷贝一下 _posts 里的文件就行了。

直接 yarn run server 后,除了一堆报错,并没有什么效果。开了一个全新的 hexo 项目跑了一下,才明白首先要执行 hexo init 命令,于是我又屁颠屁颠的执行了 hexo init。继续报错,大意是: 文件夹不是空的,没办法初始化。

此时进入了逻辑黑洞,初始化就得是全新的开始,迁移就肯定不是空白的项目,但是要迁移之后能运行,又得执行初始化命令。这不是死局了吗? 又查看了一下Hexo文档,手动将 Hexo 所需的文件都建好,再把原来的 jekyll 的文件删个七七八八,终于生成成功。

分支合并

为了以防万一,前面的所有操作都在一个新开的分支 Hexo 上完成,最后则利用 GithubPull Request 合并至主分支上并完成部署。由于之前的博客使用了 vercel 部署,新建 Pull Request 会触发 vercel 的构建,构建通过后才进行真正的合并操作。到此时为止,一切都那么顺利。

设置主题

Hexo 的默认主题是 landscape,我还是想换回 「原版」的 Next。继续按照 Next的官方文档 的说法,直接进入博客目录

1
$ git clone https://github.com/theme-next/hexo-theme-next themes/next

然后再把博客的 _config.yml 中的 theme 改为 next。 官方还提供了几种方式,比如直接下载文件,下载指定文件之类的。为了方便,不纠结直接用官方默认推荐的吧。

令人感到困惑不已的是,NextGithub 上居然有两个仓库,一个是 theme-next,一个是 next-themetheme-next提供了 npm 安装的方式, 而 next-theme 则没有。当然这些都是事后发现的,官方的 Github 并没有更新有关文档,而是写在了 官方站点的文档 里。

集成好了主题之后预览,发现和原来的主题还是不一样。查了下资料发现 Next 主题分成了几种 Scheme:

  • Muse
  • Mist
  • Pisces
  • Gemini

原先博客用的正是其中的 Pisces,可是要去哪里修改这个配置呢?博客的主题配置项内并没有相关设置。自己观察一下,themes/next 下居然也有一个 _config.yml,把里面的 Scheme 修改一下:

1
2
3
4
5
# Schemes
# scheme: Muse
#scheme: Mist
scheme: Pisces
#scheme: Gemini

其他七七八八的设置,也按网上找的一个 文档 改改。

还是不要随便找个文档看,官方文档 还是所有文档里最靠谱的

此时,本地预览一切正常。我主要进行了以下操作

迁移文章

迁移文章很简单,直接把原来的 _posts 文件拷贝到 source/_posts 即可。我的博客带着沉重的历史,所以每个 .md 里都有一个 permalink,且原来的 permalink 都不会带有后缀 .html,我尝试设置了博客的 _config.yml 中的

1
2
3
pretty_urls:
trailing_index: true # Set to false to remove trailing 'index.html' from permalinks
trailing_html: true # Set to false to remove trailing '.html' from permalinks

发现无论怎么设置,产生的文件都不会带有 .html 后缀,这就导致直接浏览器预览的时候无法正常打开页面,而是直接下载文件。不浪费时间,索性一把梭把所有 .md 中的 permalink 都删除。 具体方法是使用 VSCODE 的全局搜索功能,搜索 _posts 文件夹下所有文件,并打开正则模式,搜索 permalink: (.*) 全部替换为 「空」 即可。

迁移页面

原来的博客里除了博文之外,还有「关于」、「标签」、「实验室」几个页面,这部分 Hexojekyll 非常类似,只要在 source 下建立对应的文件夹,并放入一个 index.md,写好 Front Matter 即可。

比如「关于」页面,就在 source 下建立一个 about 文件夹,里面放一个 index.md 文件,内容为

1
2
3
4
5
6
---
title: 关于
layout: page
type: about
---
...

还原功能

最主要的数据迁移完成之后,开始还原原来博客的功能。所有的 jekyll 下面有的,Hexo 都支持。

侧边菜单

themes/next/_config.yml 查找 menu,把它改成自己要的:

1
2
3
4
5
6
7
8
9
10
menu:
home: / || fa fa-home
categories: /categories/ || fa fa-th
about: /about/ || fa fa-user
archives: /archives/ || fa fa-archive
tags: /tags/ || fa fa-tags
lab: /lab/ || fa fa-flask
#schedule: /schedule/ || fa fa-calendar
#sitemap: /sitemap.xml || fa fa-sitemap
#commonweal: /404/ || fa fa-heartbeat

其中「搜索」菜单是打开本地搜索自动增加的,所以这里不用设置

menu 下的每一项代表一个菜单,前面的 key 代表菜单名, 后面的 value 分为两部分。|| 前的是路径,|| 后的是图标。看名字就知道使用的是 font-awesome 字体图标库,所用图标标识出门左转去 fontAwesome 找吧。

i18n

菜单改完,预览都是英文的,设置 _config.yml 里的 languagezh-Hans,还是英文的。进入 themes/next/languages 一看,原来这里的文件是 zh-CN.yml。这就对不上啊,改成 zh-Hans.yml 后搞定。因为我自己的 menu 里加了点东西,所以这里在多语言文件里也加上对应的。

Next 官方在 6.0 以上版本特意的把 zh-Hans 改成了 zh-CN

Google Analytics

themes/next/_config.yml 查找 google_analytics, 把 tracking_id 填上 UA-********-* 这样的值即可,和原来 jekyll 差不多。

Disqus

themes/next/_config.yml 查找 disqusenabletrue, shortname 填注册的 disqus 用户名。在搜索 disqus 发现有 disqusjs 的设置,前面说过,要用起来需要搭建反向代理,以后再说吧。

Latex

我的个别几篇文章里面用到了公式,所以需要开启。Next 里面提供 MathjsKaTex 两种方案,官方也给出了说明对比。折腾了一下发现太麻烦,得修改原来书写公式的方式,而且都会在前端额外注入 js。误打误撞发现了 hexo-filter-mathjax,使用的是服务端方式,也就是说在构建时就直接生成了公式,这岂不美哉?用之!

凡事有得必有失,使用这个插件后, 1. 需要将 Hexo 的渲染器改为 hexo-renderer-pandoc 2. 需要将 Nextmath.mathjax.enablemath.katax.enable 统统设置为 false 3. 需要在用到公式的博客的 Front Matter 里添加 mathjax: true

还好,这些都是小事。

安装插件:

1
$ yarn add hexo-filter-mathjax

换渲染器:

1
2
$ yarn remove hexo-renderer-marked
$ yarn add hexo-renderer-pandoc

全套搞完之后,本地预览公式正常,和原来一模一样。

该插件依赖了 pandoc,导致在 vercel构建栽了跟头,此处不表,后面详述。

fancyBox

预览时发现图片直接都显示出来,点击没有放大效果,原来 jekyll 主题里这一部分是通过 fancyBox 实现了,这里应该也类似。 在 themes/next/_config.yml 查找 fancybox,设置为 true

本地搜索

themes/next/_config.yml 查找 local_search,把 enable 设置为 true,这样菜单里的最后一项将会多出一个 「搜索」,点击「搜索」,熟悉的搜索弹窗出现了。

自动摘要截断

按照官方的说明,在 themes/next/_config.yml 设置了 excerpt_descriptiontrue,并没有什么用。Next 官方在某个版本里删除了这个功能,意不意外,惊不惊喜? 按照官方的推荐,安装 hexo-excerpt

1
$ yarn add hexo-excerpt

然后配置博客 _config.yml,根据我自己的需求,我改成了这样

1
2
3
4
5
excerpt:
depth: 1
excerpt_excludes: ['img', 'h2']
more_excludes: []
hideWholePostExcerpts: true

新增功能

字数统计和阅读时长

看到别人博客里有本文多少字,大约需要多少分钟读完,发现也支持了,具体使用 hexo-symbols-count-time 插件。首先先安装

1
$ yarn add hexo-symbols-count-time

然后在博客的 _config.yml 里添加:

1
2
3
4
5
6
7
8
symbols_count_time:
symbols: true
time: true
total_symbols: true
total_time: true
exclude_codeblock: false
awl: 2
wpm: 275

DisqusJS

正如前文所言,先按照 disqusjs-proxy-example 中的方法搭建反代,直接用 vercel 部署好,将部署完成后的域名填写到 themes/nextdisqusjs 部分里即可完成。此处直接按 disqusjs 文档 操作即可.

提交修改

本地预览一切就绪,准备提交到远程的 Github,让 vercel 部署。然而,提交不了了。想了一下,问题就出在最开始提到的 Next 主题安装环节。本来的博客属于一个 Git 仓库,里面又克隆了 Next 主题的仓库。对主题配置的修改都在主题仓库里,那提交到哪里去?提交到 Next 官方的仓库吗?

按照现代前端的逻辑,安装的应该是个 npm 包啊,而且大概率不会去修改 node_modules 里的东西。事已至此,看看怎么处理吧。 即然不可能提交到 Next 官方仓库,那么我们就把官方仓库 Fork 一份出来呗。然后通过 git remote 的命令把 themes/next 的远端仓库设置为我自己的 Fork 出来的仓库地址。

现在有了两个仓库,这两个仓库管理原则上是独立的,凭啥博客的仓库会更新和纳入主题的仓库内容呢?Git Submodule 即时登场,利用 git submodule addthemes/next 作为子模块加入了博客仓库。提交、推送这些常规操作都可以完成了。然后在 Github 里创建一个新的 Pull Request,开始发布前预构建。

vercel 部署

构建模板修改

第 1 次构建失败,错误出现在构建完成时

1
No Output Directory named "_site" found after the Build completed.

这是因为忘记了修改 vercel 的构建模板,在 vercelSettings 面板下的 Build & Development Settings 中,把原来的模板 jekyll 修改为 Hexo

手动安装 Pandoc

第 2 次至第 N 次,打包一开始就报 ERROR,一直报

1
pandoc exited with code null.

查了资料,有文章说是因为博客里面包含引号导致,解决方案是卸载。这?这?这是自断一臂吗?继续查继续试,最后发现是因为 vercel 的构建环境里压根没有 pandoc 啊,预装列表 别人写好了。那我自己装吧,又找到了 一篇文章 说是自己把 Pandoc 的安装包从 deb 转为 rpm, 然后上传到自己的仓库里再用构建命令调用。这?不应该吧?继续找,又找到了一篇时间较近的文章,而且名字一看就相当靠谱 Running a Pandoc build on Vercel,按照文章说的,自己在博客仓库内建一个 build.sh,内容为

1
2
3
4
5
6
7
yum install wget
mkdir pandoc
wget -qO- https://github.com/jgm/pandoc/releases/download/2.11.1.1/pandoc-2.11.1.1-linux-amd64.tar.gz | \
tar xvzf - --strip-components 1 -C ./pandoc
export PATH="./pandoc/bin:$PATH"

yarn run build

vercelsettings 面板中找到 Build Command 打开 OVERRIDE 开关,输入 sh ./build.sh 之后保存,终于构建成功。

修改 submodule 的 url

构建开始时,始终会报获取 submodule 失败

1
Warning: Failed to fetch one or more git submodules

原因是 vercel 不支持 ssh 协议的 url, 之前有人遇到过,改为 https 即可修复。当然这样的话,仓库地址必须是公开的,有一定的安全风险。如果想要使用私有仓库,则可以参考 Vercel private submodule 中的方法