先前使用的Hexo主题略显花哨,为了让博客更学术范一点,准备在2024年伊始换一个新的主题。这个过程中遇到不少问题,其中之一便是如何在Hexo中自定义页面。

目录

  1. 前言
  2. Hexo的渲染逻辑
    1. Hexo框架
    2. 目录结构
    3. 渲染过程
    4. 主页面的生成
    5. 其他页面的生成
  3. 自定义“友链”页面

前言

与Hexo的邂逅已有5年之久,期间换过不少主题。虽然总会遇到一些问题,但大多都在Github的教程以及各种issue的帮助下迎刃而解。每当看到一个焕然一新的主题,心中不免会产生一丝成就感。但说来惭愧,这么多年一直都对Hexo的配置浅尝辄止,就连最核心的页面生成过程也是一知半解。

本次换了一个全新的主题——tranquilpeak。这个主题的UI风格很对我的胃口,美中不足的是它支持的特性并不是很丰富。例如,它不支持添加友情链接。本着不想抛弃那为数不多的“友谊”,我决定自己添加一个“友链”页面。

Hexo的渲染逻辑

Hexo框架

众所周知,Hexo是典型的静态博客框架。它需要用户先将本地的Markdown文件转化为静态的HTML网页,然后部署到服务器上以供他人访问。在实际访问过程中,服务器只需要负责返回一个个静态的网页即可,并不需要进行额外的存储和逻辑运算。

因此,如何根据用户编写的Markdown文档生成精致的HTML页面是Hexo需要解决的关键问题。Hexo网站内容的生成过程主要涉及三个核心部分:

  1. Hexo渲染引擎:这是Hexo框架的核心。它在主题的指示下,负责将Markdown文件转换为静态的HTML页面。
  2. Markdown数据:用户编写的博客内容。
  3. UI:Hexo的各种第三方主题。

一句话总结三者的关系就是:渲染引擎根据主题设定的UI样式将用户编写的Markdown文档渲染成HTML页面(包括html、css和js三件套)。

目录结构

接下来,我将根据Hexo项目的目录结构对Hexo页面的生成过程进行详细的介绍。一般而言,Hexo项目的目录主要包含以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.
├── public
├── source
│   ├── _drafts
│   ├── _posts
│   ├── archives
│   ├── categories
│   └── tags
├── themes
│   └── demo_theme
│ ├── languages
│ ├── scripts
│ ├── layout
│ │ ├── archive.ejs
│ │ ├── category.ejs
│ │ ├── index.ejs
│ │ ├── layout.ejs
│ │ ├── page.ejs
│ │ ├── post.ejs
│ │ └── tag.ejs
│ ├── source
│ └── _config.yml
└── _config.yml

渲染过程

  • _config.yml:Hexo的配置文件。
  • public:存放Hexo生成得到的网页文件。部署时只需要将该目录上传到服务器,用户在浏览器端访问的也是public中的文件。
  • source:存放用户数据,如用户创建的Markdown文档。
  • themes:存放若干个Hexo主题。
    • languages:国际化,用于多语言配置,里面定义了配置文件中变量到具体语言的映射。
    • layout:用于存放主题的模板文件,决定了网站内容的呈现方式。
    • scripts:script源码。
    • source:资源文件夹,存放除了模板以外的CSS、JavaScript文件等。
    • _config.yml:相应主题的配置文件。

主页面的生成

Hexo的页面布局主要由layout/layout.ejs控制,每个模板都默认使用layout布局。具体而言,layout.ejs通过body变量将模板内容添加到生成文件的指定位置,例如:

1
2
3
4
5
6
7
8
<!-- layout.ejs -->
<!DOCTYPE html>
<html>
<body>
<p>This is the layout file</p>
<p><%- body %></p>
</body>
</html>
1
2
<!-- index.ejs -->
This is the index page

经过Hexo引擎处理,会在public文件夹中生成一个index.html文件,其内容为:

1
2
3
4
5
6
7
8
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<body>
<p>This is the layout file</p>
<p>This is the index page</p>
</body>
</html>

通过访问http://your-site/,浏览器就会加载public/index.html文件。

更多详细内容可参考Hexo-Templates

其他页面的生成

上面展示了网站主页(index.html)的生成过程,其他页面的生成也是大同小异。例如,我们希望新建一个分类页面,来展示所有的分类项。

1、首先,需要在用户数据文件夹(source)为分类页面新建一个index.md文件。下面这个命令会在source文件夹下新建一个category/index.md文件。

1
$ hexo new page category

2、为分类页面新建一个布局文件,即themes/demo_theme/layout/category.ejs

1
2
<!-- category.ejs -->
This is the category page

3、将分类页面与相应的布局文件进行关联,这一过程只需要在category/index.mdFront-matter中指定布局即可。

1
2
3
4
---
title: Hello World
layout: category
---

layout: category指代的便是layout/category.ejs文件。这样Hexo引擎就知道需要根据指定的布局处理该Markdown文件。

值得注意的是,分类模板生成的内容同样需要经过layout/layout.ejs,即替换其中的<%- body %>

所以,最终分类页面会得到以下内容:

1
2
3
4
5
6
7
8
<!-- public/category/index.html -->
<!DOCTYPE html>
<html>
<body>
<p>This is the layout file</p>
<p>This is the category page</p>
</body>
</html>

该页面可以通过http://your-site/category/访问。

自定义“友链”页面

回归到本文的主线任务,为博客自定义一个“友链”页面,用来展示他人网站的链接等。其原理与分类页面的创建相同,只需要将上述的category替换为friends即可。

得到的文件内容如下:

1
2
3
4
5
6
7
8
<!-- public/friends/index.html -->
<!DOCTYPE html>
<html>
<body>
<p>This is the layout file</p>
<p>This is the friends.ejs page</p>
</body>
</html>

当然,这仅仅是一个“友链”页面的演示,并没有将实际的数据渲染到该页面。下面,我们需要加载数据来充实该页面。

“友链”信息是相对规则的数据,通常包含他人网站的标题、简介、链接和favicon等。因此,我们可以将这些数据都存放到一个特定的json文件中,然后在friends.ejs中读取并展示数据。

1、需要借助Hexo的数据文件(Data Files)。具体而言,在source文件下新建一个文件——_data/friends.json,并添加需要的“友链”数据。

1
2
3
4
5
6
7
8
9
10
11
[{
"favicon": "http://blog.xandery.top/assets/images/favicon.jpg",
"name": "梅雪殇",
"introduction": "梅须逊雪三分白,雪却输梅一段殇",
"url": "http://blog.xandery.top/"
}, {
"favicon": "https://hexo.io/icon/favicon-196x196.png",
"name": "Hexo",
"introduction": "A fast, simple & powerful blog framework",
"url": "https://hexo.io/"
}]

2、修改friends.ejs文件,读取_data/friends.json文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div>
<div>
<h1><%= page.title %></h1>
</div>
<div>
<% if (site.data && site.data.friends) { %>
<% var friends = site.data.friends; %>
<div>
<% for (var i = 0, len = friends.length; i < len; i++) { %>
<% var friend = friends[i]; %>
<div>
<h3><%= friend.name %></h3>
<p>
<%= friend.introduction %>
<span>
<a href="<%- url_for(friend.url) %>"> Go </a>
</span>
</p>
</div>
<% } %>
</div>
<% } %>
</div>
</div>

简洁起见,上述ejs代码仅仅读取了json数据,并进行了展示,没有添加任何css样式。代码中,涉及Hexo定义的一些变量(Variables),这里稍作解释。

  • page: 全局变量,针对该页面的内容以及front-matter中定义的变量,如page.title
  • site: 全局变量
    • site.posts: 包含了站点全部的文章对象。
    • site.categories: 包含了站点全部的分类对象。
    • site.tags: 包含了站点全部的标签对象。
    • site.data: 包含了站点全部的数据文件对象。