这篇文章上次修改于 205 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

今天晚上直播 Hexo 主题初入门,主要目标是将我的 Typecho 老牌主题 Single 移植 Hexo,也就是 GitHub 上我新开的 Hingle 仓库。什么?你竟然不知道我在 B 站直播写代码?还不 赶紧关注

打开 Hexo 官网,进入眼帘的第一步,就是首先安装需要的命令行工具。

一键安装

简单几条命令即可完成,hexo-cli 可以理解成方便你初始化和管理一个 Hexo 博客的命令行工具。

yarn global add hexo-cli
hexo init Hexo-Playground
cd Hexo-Playground
hexo server

如果过程出现错误,那大部分是因为 GitHub 访问不上导致的(阿里云的 Windows 服务器上死都拿不到,挂全局代理也不行,太坑了,也不知道为什么)

EJS

编写 Hexo 模板使用的是 EJS 语法,模板里面主要用到的写法,大概有这些:

<%= date() %> // 转义输出表达式返回的内容(纯文本)
<%- paginator() %> // 直接输出表达式返回的内容(一般是 HTML)

// JS 逻辑块(不得不说,这种写法下只能把 else 和两括号放一行了)
<% if (true) { %>
<% } else { %>
<% } %>

虽然没特别学习过 EJS,但总体来说和 PHP 还是大同小异,并不是很困难。

主题架构

由于没有任何 Hexo 的开发经验,于是就从官方的文档一步一步开始看起。定位到 Hexo 目录的 themes,创建一个文件夹,这里以 hingle 为例。官方文档 的描述我就不详细扯了,以下是我对主题结构的个人理解和汇总:

如果你写过 Next.JS 项目,可以把 layout.ejs 理解成 _document.tsx 生成网站根结构。它放在 layouts 文件夹里面,是不是只看官方文档的话就会很懵逼呢?
- source(前端静态文件)
- languages(国际化,暂无需求先忽略)
- layouts(布局相关模板)
--- _partial(内容切片)
------ head.ejs(页头、CSS、Link 等标签)
------ header.ejs(页头,各种菜单等)
------ footer.ejs(页尾和 JS)
--- layout.ejs(放 HTML,根架构)
--- post.ejs(文章模版)
--- page.ejs(页面模板)
--- index.ejs(首页模版)
--- archive.ejs(归档页面模板)

这个架构我感觉 官方文档 也没详细说清楚,尤其是 layouts 文件夹里面要有个 layout.ejs 这个情况,我还是参考了 官方模板 才得出的结论。

期间我还错误的将 layouts.ejs 的内容放到了 index.ejs 里面(虽说刚开始也能用,但其实后续加了模板之后你会发现是错的)。

不得不说,官方中文文档的质量真的很一般。

引入主题资源

在输出页面之前,我需要引入主题的样式和 JS,分别放在 Header 和 Footer 里面,使用内置函数 css()script() 函数引入即可,支持字符串或数组形式。

<!-- Head -->
<head>
  <meta charset="UTF-8">
  <title><%= page.title %></title>
  <%- css("static/kico.css", "static/hingle.css") %>
</head>
<!-- Footer -->
<%- js(["static/kico.js", "static/hingle.js"]) %>

在我这个架构里面,head.ejs 输出 head 标签的内容,header.ejs 里面输出 header,是我比较常用的方式。虽说内置的 partial() 函数可以传参数实现「模块化」管理,实现各个小模块的切片(很像 React/Vue 的组件概念),但我现阶段还是以化繁为简为主,前期方便理解一些。

变量替换

随后就是各个页面的函数替代工作,需要将 PHP 的输出换成对应的 EJS 语法。Hexo 的核心变量我们经常用到的,大概就这些:

名称用途
site网站变量
page页面变量(包括文章)
config网站配置(Hexo 路径下的 _config.yml)
theme主题配置(使用主题路径下的 _config.yml)

首页

首先是网站首页,这个页面主要是将博文进行遍历和输出,这里使用 page.posts 全局变量完成。这个变量返回多个子对象,可以拿到文章的 title categories path excerpt date 等属性。

首页才有 page.posts 对象,如果是文章页面,这个对象估计就是不存在的了,所以需要注意判断和使用
  • config.title 站点名称
  • config.description 站点介绍

page 变量在不同页面情况下的值不相同,这个需要注意。这块 官网文档 都有详细说明,对照着来就行。

文章页

文章页有一处用户自定义的选项,叫做「作者信息」。这块需要使用到主题 theme 变量,这需要将主题路径的 _config.yml 配置起来才行。

author: 'Paul'
author_avatar: ''
author_text: ''

对应的输出:

<% if (theme.author && theme.author_text) { %>
    <section class="post-author">
    <% if (theme.author_avatar) { %>
        <figure class="author-avatar">
            <img src="<%- theme.author_avatar %>" alt="<%= theme.author %>" />
        </figure>
    <% } %>
        <div class="author-info">
            <h4><%= theme.author %></h4>
            <p><%- theme.author_text %></p>
        </div>
    </section>
<% } %>

代码片段

代码片段,就是前面提到的 _partial 文件夹下的 Header Footer 了。这里面用到的属性基本上和前面提到的大同小异,但是有一些功能是不能直接用变量获取,需要一个叫做「辅助函数」的操作才能实现。

辅助函数

一些属性无法直接使用,需要配合“辅助函数”完成。最简单的例子就是你博客主页的文章分页器,这玩意儿总不可能自己遍历一次数组吧,DOM 结构自己拼一次也挺麻烦。这就是它存在的意义了。在我的 Hingle 主题里面,初步用到了这些函数:

  • list_posts 列出博文(List 的函数都是 A 链接形式)
  • list_categories 列出文章所属分类(支持传对象,不传就是全部,可以去掉列表结构)
  • list_tags 列出文章包含的标签
  • paginator 输出分页器(无法自定义结构)
  • tagcloud 输出标签云,随机大小
  • url_for 输出地址(空就是站点地址,带相对链接就转换为绝对链接)
  • url_for() 输出站点首页链接
  • url_for(post.path) 输出文章链接
  • date(new Date(), "YYYY.MM.DD") 格式化输出当前时间

它们可以帮你生成一个 ul 列表,或是一个全是 a 的序列等等,这些函数的使用情形放在侧边栏、页脚一类的地方比较合适,比你用 for 遍历是要方便不少。更多辅助函数,可以看看 官方文档
上的其他说明。

文档的大坑

直播过程中踩了个大坑,就是我文章页面底部位置需要调用上下两篇间隔文章,看文档的属性是 page.prevpage.next 没错,但是上面写着这个东西返回 string 或者 null

<%= page.prev %>

我用 EJS 直接输出它们是报错的,我尝试用 console.log 打印出来才发现这两个的实际返回类型是 Object,对象确实会更符合开发的使用情形,反倒字符串拿不到齐全有效的信息。

这个是 Hingle 模板里面完全复刻 Single 的实现方法:

<% if( page.prev !== undefined) { %>
    <li>上一篇: <a href="<%= url_for(page.prev.path) %>"><%= page.prev.title %></a></li>
<% } else { %>
    <li>上一篇: 看完啦 (つд⊂)</li>
<% } %>
<% if( page.next !== undefined) { %>
    <li>下一篇: <a href="<%= url_for(page.next.path) %>"><%= page.next.title %></a></li>
<% } else { %>
    <li>下一篇: 看完啦 (つд⊂)</li>
<% } %>

这样一搞我倒是发现了 Hexo 的 Debug 方法了,console.log 会打印在控制台里面。但是官方文档写错返回类型这个问题,对新手开发者难免还是不太友好。看起来官网是一个 Git 仓库,或许我可以提交一个 PR 改进一下?

尚未解决的问题

代码高亮

目前发现文章的代码块默认采用了 Highlight.JS,且修改 Hexo 项目下的 _config.yml 配置文件并重启服务之后依旧无效,无法关闭,具体原因不明。主要是这块和之前的 CSS 有些冲突,这才想让我先关闭取消的。

更新:使用以下配置即可关闭 Hightlight.JS 仅启用 PrismJS
highlight:
  enable: false # 关闭 Highlight
  line_number: true
  auto_detect: false
  tab_replace: ''
  wrap: true
  hljs: false
prismjs:
  enable: true # 开启 Prism
  preprocess: true
  line_number: true
  tab_replace: ''

如果修改后代码高亮并未生效,可尝试执行 hexo clean 命令修复这个问题。

评论功能

在 Hexo 平台下的评论支持也是一个没踩过的坑,还得后期看看其他人的解决方案...

后续

我会陆陆续续把 Single 的逻辑进行移植,也可能会进行过程直播,希望大家持续关注!如果你也认为 Single 是一款值得移植的优秀主题,那么不妨去 GitHub 仓库下为我 点个赞 支持一下吧!

录播

直播折腾过程有 录播,信息量较大,感觉整出来也是给各位看笑话的...