本文主要记录我将 GitHub 的个人博客从 Jekyll 迁移到 Hexo 的一些踩坑实践。

为什么迁移

  • Jekyll搭建博客所需的技术栈(如Liquid语言、Ruby、gem等)我不太熟悉,每次想改点啥都不方便;而 Hexo 是基于Node.js的,js相对熟悉一点,定制起来方便一些。
  • Hexo 生成站点的速度更快,且该框架是台湾人贡献的,中文文档很多。

网上对比的文章 How to Choose the Right Static Generator: Jekyll vs. Hugo vs. Hexo

Hexo和博客主题

Hexo 简介

Hexo的安装可以参考Hexo官网,需要先安装node.js和git,照着文档安装就行了。

常用指令有:

  • 清除缓存: hexo clean
  • 生成静态文件:hexo generate,简写hexo g
  • 启动服务器:hexo server,简写hexo s
  • 部署到远程站点: hexo deploy,简写hexo d

更多指令:https://hexo.io/zh-cn/docs/commands

主题选择

Hexo的主题有很多,可以从下面几篇推荐里挑挑:

我主要有几个需求:

  • 支持搜索
  • 目录显示当前所处章节:阅读文章时显示目录TOC,且滑动到文章相应位置时,目录同步定位
  • 支持赞赏展示和自定义页面
  • 支持标签和分类

简单比较了下,选择了indigo

搭建博客

大致以下几个步骤:

  1. 安装Hexo
  2. 选用主题,并根据主题的安装步骤去更改配置项
  3. 本地hexo clean & hexo g生成本地文件,hexo s启动本地服务器,访问http://localhost:4000/查看效果

可以通读以下博客了解细节:

indigo 主题的wiki: https://github.com/yscoder/hexo-theme-indigo/wiki

我的定制修改

我写了个Java程序把原来Jekyll下博客的md文件copy到了新的文件夹下,并对每个md文件需要适配的地方(如摘要是通过<!-- more -->来区分的)进行了批量处理。

统计

很多主题都支持访问统计,一般需要在配置文件配置自己对应账号的key,我一般使用Google Analytics+百度统计:

其中有官方提供的统计js代码,indigo的代码有些过时了,可通过配置文件里统计配置对应的关键字全局搜索,找到对应代码,并更新成最新的js。

具体地,indigo主题的统计代码在themes/indigo/layout/_partial/plugins目录下的baidu.ejsgoogle-analytics.ejs,其他主题也可通过类似方法修改

搜索

针对无数据库的静态博客搜索方案一般有两种:

  • 第三方搜索服务;
  • 序列化站点内容作为数据源,然后自己写查询方法。

indigo作者是通过自定义搜索实现的,搜索方案为:Hexo添加站内搜索功能初步完成。原理比较简单,简单概括就是:
先通过hexo-generator-json-content插件生成json格式的数据文件,然后将这个数据文件作为数据源,对输入进行正则匹配。由于我只需要简单搜索,简单正则就能满足我的要求,该方案基本可以,所有没有增加分词等高级搜索的功能。

但有他的实现有几个问题:

  • 没有对输入转义,直接根据输入生成正则,当输入包含正则元字符时前端会报错,且把空格替换为了的逻辑,而不是的逻辑
  • 没有权重,标题、正文等只要出现关键字就显示,导致我很多文章都有git地址,只要输入git,全匹配了,没有区分度
  • 没有对摘要进行搜索

所以我做了简单的修改:

  • 转义用户输入中的正则元字符
  • 生成json数据文件时增加摘要字段,去掉正文字段,以减少json文件大小(可选,也可以不去掉)
  • 增加权重,权重大小:标题>摘要>标签>正文

修改themes/indigo/source/js/search.js即可在本地测试,发布线上的话,需要mimify js文件,然后替换掉原来的themes/indigo/source/js/search.min.js,压缩js的工具网站:https://javascript-minifier.com/

以下是具体实现,没兴趣可略过

修改前后的正则(注释的为修改前的):

1
2
3
4
5
6
7
// var regExp = new RegExp(key.replace(/[ ]/g, '|'), 'gmi');
var regExp = new RegExp(escapeRegExp(key), 'gmi');

function escapeRegExp(string) {
return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$&');
//$&表示整个被匹配的字符串
}

其中function escapeRegExp(string)参考正则escape

同时,匹配结果不再只返回true/false,而是返回权重,搜索结果更根据权重排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 匹配文章内容返回结果,返回值代表权重。按照标题,摘要,标签,正文的顺序递减
function matcher(post, regExp) {
if (regtest(post.title, regExp)) {
return 10;
}
if (regtest(post.excerpt, regExp)) {
return 5;
}
if (post.tags.some(function (tag) {
return regtest(tag.name, regExp);
})) {
return 3;
}
if(regtest(post.text, regExp)){
return 1;
}
return 0;
}

// 匹配文章内容返回结果
loadData(function (data) {
// 结果按照匹配程度过滤并排序
var result = data.map(post => {
post.search_order = matcher(post, regExp);
return post;
});
result = result.filter(post => post.search_order && post.search_order > 0);
result = result.sort((a, b) => b.search_order - a.search_order);

render(result);
Control.show();
});

归档样式

indigo主题默认archives(归档)的样式有两点不满足我的偏好:

  1. 默认的卡片样式太占地方,一个分页放不下几篇博客名。归档我想尽可能多的按时间降序排列展示博客列表,而默认的归档样式,和categories(分类)以及tags(标签)的样式一样,使用了卡片作为列表项,不太喜欢。
  2. 时间按月划分而非按年划分。我希望以年为粒度来归档

涉及的文件/用途:

  • themes/indigo/layout/archive.ejs: 归档页面主体内容对应的文件,类似HTML模板的作用
  • themes/indigo/layout/_partial/archive.ejs: 列表项的展示内容,类似可复用的HTML公共模板
  • themes/indigo/source/css/_partial/archives.less: 归档页面的对应样式
  • themes/indigo/source/js/main.js: 原列表项的瀑布流是每行两列的,一些展示逻辑例如每一项(.waterfall-item)的元素位置高度在该文件计算和补充

我的修改:

  • 修改归档时间粒度:将themes/indigo/layout/archive.ejs文件内的“by年月”分组的逻辑改为按年分组。同时,列表项的时间格式改为自己喜欢的MM-DD
  • 修改每个列表项的展示内容
    • 上述 archive.ejs 文件使用了 hexo 的 partial 辅助函数引用了themes/indigo/layout/_partial/archive 文件,但由于该文件被categories(分类)以及tags(标签)页面共用了,所以不要直接修改,而是在同目录(_partial)下新建自己的归档文件my-archive并修改引用
    • my-archive文件定制自己喜欢的列表展示信息,可借鉴参考原来的写法。并在 themes/indigo/source/css/_partial/archives.less 定制CSS样式,建议添加新className(例如.archive-item)而非直接在原样式上修改
    • 原列表项样式.waterfall-item的渲染逻辑涉及到了themes/indigo/source/js/main.js文件,仿照其写法补充自定义列表项(.archive-item)的元素位置高度
  • bugfix: 修改原博客列表项的日期格式config.date_format-> date_format,(位于文件themes/indigo/layout/_partial/post/date.ejs),否则引用它的文件传入的日期格式没法生效

main.js的补充代码如下(原.waterfall-item的渲染是两列,归档.archive-item的渲染改为一列)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 自定义的归档
forEach.call($$('.waterfall'), function (el) {
var childs = el.querySelectorAll('.archive-item');
if (!(childs && childs.length > 0)) {
// 避免干扰分cate和tag的样式
return;
}
var itemHeight = 0;
forEach.call(childs, function (item) {
item.style.cssText = 'top:' + itemHeight + 'px;';
itemHeight += item.offsetHeight;
});
el.style.height = Math.max(itemHeight, 0) + 'px';
el.classList.add('in');
});

和前面的“搜索”定制一样,修改themes/indigo/source/js/main.js即可在本地测试,发布线上的话,需要mimify js文件,然后替换掉原来的themes/indigo/source/js/main.min.js,压缩js的工具网站:https://javascript-minifier.com/

遇到的问题

安装hexo失败

  • 报错:Missing write access to /usr/local/lib/node_modules/hexo-cli
  • 解决: 进入文件夹/usr/local/lib,修改权限
1
2
$ cd /usr/local/lib where the global node_module folder is found
$ sudo chown -R username:group node_modules

自定义域名

  • 问题:绑定失败
  • 解决:
    • 申请并购买一个自定义域名
    • ping你的github.io域名,得到一个IP
    • 在你的域名的 DNS 配置中添加A类型的记录,主机记录为@,记录值为上述IP
    • 在仓库根目录添加CNAME文件,并在文件中填写绑定的顶级域名
    • 在对应仓库的设置里Settings->GitHub Pages->Custom domain写上你的自定义域名
    • 有的博客说还要更换DNS服务器地址为f1g1ns1.dnspod.netf1g1ns2.dnspod.net,我没换也成功了。
  • 参考

由于自定义域名后原来的统计数据就从零重计了,所以就放弃了,还是使用github.io的域名算了。

清理DNS缓存

  • 问题:配置自定义域名失败,一直跳空白或者找不到地址,删除DNS解析配置也没用
  • 解决:需要清除本地DNS缓存以及chrome缓存
1
2
3
sudo killall -HUP mDNSResponder; sleep 2;
sudo killall mDNSResponderHelper
sudo dscacheutil -flushcache

chrome缓存清理:设置->高级->隐私设置和安全性->清除浏览数据或者右上角三个点->更多工具->清除浏览数据

博客原文的备份

  • 问题:使用Hexo后,仓库只有生成的public文件夹下的文件,博客都是html,原来的博客原文没有版本管理
  • 解决:新建分支hexo-public(名字自取),将hexo的根目录作为仓库根目录,这样发布的内容在master分支,而编辑的内容和各种配置等原始信息在hexo-public实现版本管理
    • 使用的主题的对应文件夹下会包含.gitgit add时会有一些提示,根据自己需要删除.git,或者作为子模块进行管理。最好不要直接git rm --cached,否则后续对主题的修改不会被追踪
  • 参考:

参考