基于Jekyll搭建个人博客 wu-kan

最开始我是在CSDN上写博客的。博客写了一年多,访问量勉强破万,感觉其中至少有三成是自己贡献的。而且 CSDN 上的广告是越来越多,网页编辑器也是越来越不好用。

十一月份,在青岛的 ICPC 区域赛失利之后,想要重新开始,和过去告别。也从此转移自己写博客的阵地。考虑过转投暂时还比较干净而且可以部分定义页面的博客园,但是最终还是决定在Github Pages上搭建属于自己的博客。

然后,翻了一些网上的教程,用Hexo框架和NexT主题搭了一个比较素雅的符合个人审美的博客。然而,Hexo 搭建的博客需要先在本地生成博客页面然后再上传到服务器上,每次发博客都需要在电脑重新生成页面并上传,感觉没有那么方便。在不同电脑上想要发博客更是十分困难,感觉如果胡乱操作后本地部署的文件发生了一定的错乱,恢复起来会十分麻烦。

后来发现,Github Pages 原生支持Jekyll,因此如果只需要推送文章可以直接在 github 的仓库里做。于是决定在期末考试后用 Jekyll 重新搭建个人博客。原来的博客不再维护。

构思

目前博客的 demo 如下:

demo

想搭建一个足够干净、页面几乎不要有多余元素的博客,同时博客的功能还要足够丰富,满足我以下的需求:

  • 用 Markdown 写博客
  • 支持 Latex 公式
  • 支持 mermaid 等插件
  • 个性化 Live2D
  • 分享一些我在其他网站的个人账号
  • 移动端适配
  • 足够快的加载速度
  • 访问量统计,评论互动
  • 需求是会逐渐增长的!

其中,Live2D 是我某次访问别人的博客初次见到的,感觉非常吸引人。最终的目标是去掉网页上所有多余的元素,只剩下博文和 Live2D 看板娘,看板娘本身还可以作为博客访问的导航,感觉会十分有新意。

搭建过程(不定期更新)

我目前读的专业是计科超算方向,所以软件工程专业相关方面的基础几乎为 0😌,大部分的内容都是参考网上教程慢慢摸索的,假如对某些过程有意见和建议欢迎向我提出!

开发环境

我的博客使用 bundle 作为版本管理!如果你的环境里已经有 bundle 了,可以跳过此步!

这个学期选了一门 Linux 相关的公选课,感觉还不错,因此直接把开发环境是搭建在 Windows Subsystem for Linux(简称 WSL)上了,Windows 下也可以直接访问其中的环境,详见这篇博客

sudo apt install -y ruby ruby-dev make gcc g++ zlib1g-dev
# gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/
sudo gem install bundler
# bundle config mirror.https://rubygems.org https://gems.ruby-china.com

jekyll 博客是基于 ruby 语言开发的,要先将 ruby 装进来。此外,国内访问一些资源可能会很慢,推荐去掉上面的注释,使用国内镜像。

在本地运行博客

依次执行下述指令,可以自动下载并部署我博客的所有依赖。

git clone https://github.com/wu-kan/wu-kan.github.io
cd wu-kan.github.io
bundle install

如果不想下载我的博文和图片,可以根据这个issue,使用 svn 代替 git,从而快速下载必要的文件。

svn checkout https://github.com/wu-kan/wu-kan.github.io/trunk/ --depth=files

github 上的 jekyll 和普通的 jekyll 是有一点点区别的,比如说只支持特定的插件。我希望与 github 保持兼容,因此配置的依赖是直接引入了 github 版本的 jekyll,安装时间略久。

我在一个全新的 Linux 中安装了一遍,需要的依赖已经在上面的「开发环境中列出来的」。如果bundle install在你的机器上还是报错,要看报错信息,缺啥装啥。

运行下述指令,检验(我自己做的时候遇到了一个很奇怪的问题jekyll 3.8.5 | Error: Operation not permitted @ apply2files,重开 WSL 终端就好了):

bundle exec jekyll s

由于我的博客已经达到了三百篇以上,在本地生成网页的速度有些慢,在我的机器上要跑三分钟(关掉配置项中的侧边栏总字数统计就快了,可达 25s)。

    Server address: http://127.0.0.1:4000/
  Server running... press ctrl-c to stop.

看到如上内容时就成功了,本地访问http://127.0.0.1:4000/即可查看效果。

定制

我把博客实现的的插件基本上都封装起来了,可以直接在浏览器按 F12 查看我是怎么引入的。如果你想增加新的渲染规则,可以新建_layouts目录,将你自己的渲染规则放进去,见目录结构 -Jekyll

加入 Live2D 看板娘

参考了:

由于 live2d 的协议是 GPL,这里我不把它的源码拷贝到自己的博客项目了,直接用链接引入。在 html 的任意位置,或是我博客对应的append选项下插入下述 html 代码:

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/gh/Dreamer-Paul/Pio@2.4/static/pio.min.css"
/>
<script
  defer="defer"
  src="https://cdn.jsdelivr.net/gh/Dreamer-Paul/Pio@2.4/static/l2d.min.js"
></script>
<script
  defer="defer"
  src="https://cdn.jsdelivr.net/gh/Dreamer-Paul/Pio@2.4/static/pio.min.js"
  onload='
    if (screen.width > screen.height)
    {
      let pio_container = document.createElement("div");
      pio_container.classList.add("pio-container");
      pio_container.classList.add("right");
      document.body.insertAdjacentElement("beforeend", pio_container);
      let pio_action = document.createElement("div");
      pio_action.classList.add("pio-action");
      pio_container.insertAdjacentElement("beforeend", pio_action);
      let pio_canvas = document.createElement("canvas");
      pio_canvas.id = "pio";
      pio_canvas.style.width = "14rem";
      pio_canvas.width = "600";
      pio_canvas.height = "800";
      pio_container.insertAdjacentElement("beforeend", pio_canvas);
      let pio = new Paul_Pio({
        "mode": "draggable",
        "hidden": true,
        "content": {
          "skin": ["要换成我的朋友吗?", "让她放个假吧~"],
          "custom": [{
            "selector": "a",
            "type": "link",
          }, {
            "selector": ".sidebar-toggle",
            "text": "打开侧边栏叭~"
          }, {
            "selector": ".effect-info",
            "text": "哇,你发现了什么!"
          }, {
            "selector": "#sidebar-search-input",
            "text": "想搜索什么呢?很多干货哦!"
          }, {
            "selector": "#toc",
            "text": "这是目录~"
          }, {
            "selector": ".page-title",
            "text": "这是标题~"
          }, {
            "selector": ".v",
            "text": "评论没有审核,要对自己的发言负责哦~"
          }]
        },
        "model": [
          "https:\/\/cdn.jsdelivr.net/gh/imuncle/live2d/model/33/model.2018.bls-winter.json",
          "https:\/\/cdn.jsdelivr.net/gh/imuncle/live2d/model/platelet-2/model.json",
          "https:\/\/cdn.jsdelivr.net/gh/imuncle/live2d/model/xiaomai/xiaomai.model.json",
          "https:\/\/cdn.jsdelivr.net/gh/imuncle/live2d/model/mashiro/seifuku.model.json",
          "https:\/\/cdn.jsdelivr.net/gh/imuncle/live2d/model/seele/model.json",
          "https:\/\/cdn.jsdelivr.net/gh/imuncle/live2d/model/Violet/14.json",
          "https:\/\/cdn.jsdelivr.net/gh/xiaoski/live2d_models_collection/Kobayaxi/Kobayaxi.model.json",
          "https:\/\/cdn.jsdelivr.net/gh/xiaoski/live2d_models_collection/mikoto/mikoto.model.json",
          "https:\/\/cdn.jsdelivr.net/gh/xiaoski/live2d_models_collection/uiharu/uiharu.model.json"]
      });
    }'
></script>

自定义参数见Dreamer-Paul/Pio,模型收集自imuncle/live2dxiaoski/live2d_models_collection,。

$\KaTeX$

参考:

<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css" />
<script
  src="//cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js"
  defer="defer"
></script>
<script
  src="//cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js"
  defer="defer"
  onload='renderMathInElement(document.body, { delimiters: [{left: "$$", right: "$$", display: true}, { left: "$", right: "$", display: false }, {left: "\\(", right: "\\)", display: false}, {left: "\\[", right: "\\]", display: true}]})'
></script>

不过这样仅能显示 $E=mc^2$ 这样的行间公式,行内公式是没有办法显示的(issue)。

$$
E=mc^2
$$

一种方法是引入 mathjax 进行渲染,但是这样做就失去了用 katex 的初心了。后来我发现 katex 有对应的插件可以解决这一问题。在上面代码后补充如下代码:

<script
  src="//cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/mathtex-script-type.min.js"
  defer="defer"
></script>

效果如下。

\[E=mc^2\]

可以在这个页面查看一些别的插件,比如化学方程式的插件等等,都是可以引入的。

mermaid 图

我希望自己能按照 markdown 代码块扩展的规则来写 mermaid 流程图:

```mermaid
sequenceDiagram
Alice->>John: Hello John, how are you?
loop Healthcheck
    John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
```

其效果如下。

sequenceDiagram
Alice->>John: Hello John, how are you?
loop Healthcheck
    John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts!
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!

因此需要插入如下内容(将代码块隐藏,并在原位置插入 mermaid 图):

<style>
  pre.language-mermaid,
  code.language-mermaid {
    display: none;
  }
</style>
<script
  src="https://cdn.jsdelivr.net/npm/mermaid@8.7.0/dist/mermaid.min.js"
  defer="defer"
  onload='
    for(let x of document.getElementsByClassName("language-mermaid"))
      if(x.nodeName=="CODE")
      {
        let m = document.createElement("div");
        m.classList.add("mermaid");
        m.textContent = x.textContent;
        x.parentNode.insertAdjacentElement("beforebegin", m);
      }'
></script>

prism 代码高亮

默认的代码高亮器rouge渲染出来的效果一般,同时代码行号效果非常差。这里使用了 prismjs 来进行代码渲染。它的好处是模块化程度非常高,且支持自定义插件。

这里我引入了四个插件(代码行号、动态代码高亮、工具栏、括号匹配,且自己写了一个选中全部代码的按钮)作为例子,也可以像我一样自己定制。

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/combine/npm/prismjs@1.21.0/plugins/line-numbers/prism-line-numbers.min.css,npm/prismjs@1.21.0/plugins/toolbar/prism-toolbar.min.css,npm/prismjs@1.21.0/plugins/match-braces/prism-match-braces.min.css,npm/prism-themes@1.4.0/themes/prism-nord.min.css"
/>
<script
  src="https://cdn.jsdelivr.net/combine/npm/prismjs@1.21.0/components/prism-core.min.js,npm/prismjs@1.21.0/plugins/autoloader/prism-autoloader.min.js,npm/prismjs@1.21.0/plugins/line-numbers/prism-line-numbers.min.js,npm/prismjs@1.21.0/plugins/toolbar/prism-toolbar.min.js,npm/prismjs@1.21.0/plugins/match-braces/prism-match-braces.min.js"
  async="async"
  data-autoloader-path="https://cdn.jsdelivr.net/npm/prismjs@1.21.0/components/"
  onload='
    document.body.classList.add("line-numbers","match-braces");
    Prism.plugins.toolbar.registerButton("select-code", function (env) {
      let button = document.createElement("button");
      button.textContent = "select this " + env.language;
      button.addEventListener("click", function () {
        if (document.body.createTextRange) {
          let range = document.body.createTextRange();
          range.moveToElementText(env.element);
          range.select();
        } else if (window.getSelection) {
          let selection = window.getSelection();
          let range = document.createRange();
          range.selectNodeContents(env.element);
          selection.removeAllRanges();
          selection.addRange(range);
        }
      });
      return button;
    })'
></script>

valine 评论

配置详见官方手册,说得很清楚了。引入这个插件后,会在 html 页面中寻找.v的页面元素。其中 appId 和 appKey 都要自己去申请,我的 leanCloud 开了白名单,直接使用我的是不能用的。

<div class="v">
  <i class="fas fa-spinner fa-pulse"></i>
</div>
<script
  src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"
  defer="async"
  onload='
    new Valine({
      "el": document.getElementsByClassName("v")[0],
      "appId": "9hABRddSuEkTgqLrt1VSK5B1-gzGzoHsz",
      "appKey": "NJ7RwmgrxsF7KDzlqU7YewlL",
      "placeholder": "在这里评论吧!填写邮箱可以获得 Gravatar 头像和回复通知哦",
      "requiredFields": ["nick","mail"],
      "visitor": true,
      "recordIP": true
    })'
></script>

下一步计划

详细的开发计划及建站内容详见主题详情页

这个页面得到的灵感,目标是博客上除了文章和作为导航的 Live2D 之外不出现其他的模块,尽量的干净、快速。

参考资料

大部分的参考链接已在原文给出,同时搭建本博客的过程中还参考了以下内容,感谢作者们: