Creating a custom hexo theme note

Despite there being an ever-growing selection of viable, aesthetically-pleasing themes available for hexo, I would feel remiss if I did not take upon myself the task of creating my own custom theme for this blog. Customization is the greatest prize for the detail-obsessed, and I would not rest until I felt satisfied that either I had succeeded in doing so or failed miserably due to the futility of the task. Having remained largely unfamiliar to preprocessors and templating engines, I admittedly felt out of my depth without further homework on the subject matters.

Hexo generates its content based on theme layouts, scripts and other resources. Creating a new theme is as simple as creating a new subdirectory under the /themes directory under your project root populated with a few basic files.

1
2
3
4
├─ _config.yml
├─ layout
├─ scripts
└─ source

Your _config.yml file will contain definitions your theme can leverage in its template files to render content conditionally or to store other theme-wide data. Changing your theme’s configuration data does not require a hexo server restart to reflect new changes. Hexo stores theme.config data separately from your site.config data, so using duplicate property names seemingly does not overwrite/override site config data.

Template files are stored in the layout directory. Each generated page at its core stems from a base layout template file stored under the layout directory. The file extension of your template files will determine which template engine hexo will use to generate your content. This site’s template files leverage EJS, but others such as Jade, Haml and Swig are also options, perhaps requiring additional installation as a plugin through the CLI.

Source files are either preprocessed or directly copied over to your public folder during generation. Scripts placed in the scripts folder are made immediately available to your site content, avoiding racing/async loading complications one might otherwise run into when including them strictly in the source directory. Your site config file contains a post_asset_folder which, if set to true, copies source material into corresponding subdirectories available to your post’s URL as opposed to being made available relative to the site config’s root.

Layouts

All template files are initially routed through your main layout template file. From there, hexo keeps an internal state of which type of page you are viewing from a short list of layouts. A non-comprehensive list of those states look like this:

  • index
  • archive
  • page
  • post
  • tag
  • category

Hexo provides several boolean helper methods to determine the current page layout.

This state ultimately determines the default layout used to route the main content body of your template. The default condition routes to your index layout. The layout contents can be accessed through your main layout template file using the hexo variable body. A simplified example layout template structure using EJS might look like the following:

layout/layout.ejs
1
2
3
4
5
6
7
8
9
!DOCTYPE html
<html>
<!-- import document head from subdir _partial/ -->
<%- partial( "_partial/head" ) %>
<body>
<!-- render layout body -->
<%- body %>
</body>
</html>

Invoking the partial() method within your template script will fetch other partial template files within your layout to be rendered. An optional object-literal parameter may be included, representing key-value pairs that will then become accessible as local variables within your partial’s script. Partials may be chained or nested to create a complex layout from small, reusable modules. Theme crafting will rely heavily on one’s creativity to leverage partials as a fundamental component to their site’s page generation.

Front matter

Page layouts are conditionally determined internally by hexo, but can also be overridden by declaring an explicit layout name within your content’s front-matter.

1
2
3
4
5
---
title: [My post's title]
date: 1970-01-01 12:00:00
layout: archives
---

This will tell hexo to generate your <%- body -> content from a layout file named archives. If no such layout file exists, hexo will fall back to its conditionally determined default layout. Front-matter local variables override all other variables that have been passed along either through the partial method as an argument.

Other information associated explicitly with your content file can also be defined in the front-matter. Two specifically supported properties relate to your content’s associated tags and categories. Hexo parses this information and associates the structural data to your content and is accessible within template script.

/source/posts/my-post.md
1
2
3
4
5
6
7
8
9
10
11
---
title: [My post's title]
date: 1970-01-01 12:00:00
layout: archives
tags:
- javascript
- prototype
- canvas
categories:
- code
---

We can then access this information using the hexo post variable within our script. Assuming we wanted to author a simple partial/block that renders a post’s tags as archive links as an example case, we can access that information as such:

layout/_partial/tags.ejs
1
2
3
4
5
6
7
8
9
10
<!-- output tag links for a post -->
<% if( post.tags && post.tags.length ){ %>
<nav class="meta-list">
<% post.tags.each(){ %>
<% function( tag ){ %>
<a href="<%= url_for( 'tags/' + tag.name ) %>"></a>#<%- tag.name %>
<% } %>
<% } %>
</nav>
<% } %>

While hexo does have rather comprehensive documentation regarding its page variable, it does little to illuminate the many properties available to the post variable. A console dump of the object can reveal further details about its properties and methods available to you, or you could comb through the source if you prefer.

Pagination

This was an admittedly difficult topic for me to find specific information about. There is little in the way of documentation explaining how to leverage hexo’s pagination module in your template scripts. I have found some basic information that may prove useful if you have also found hexo’s implementation rather frustrating.

Before you begin, make sure that you have the module installed - requiring only a simple command in your terminal.

  • npm install hexo-pagination --save

    Your site’s root config file has by default a global property setting per_page that determines the amount of posts on a given page to display before pagination is generated. Additionally you can set the name of the directory paginated pages are generated to with the pagination_dir property.

    Unfortunately this affords very little control for the author who wishes to leverage pagination under specific layout conditions. My initial research into the topic left me frustrated without answers, until I came to the realization that there were external modules that existed to handle this. The module names typically begin with “hexo-generator-“ and are followed by the layout type of which the module generates. Links to several of these modules are listed below:

    according to each module’s documentation, a new set of options now become available to us when these modules are present. Taking the archive generator module, we can include the following options under our site’s config file to control more specific generation behavior. All generator modules seem to at least have a specific per_page property that determines the number of posts displayed per page.

    1
    2
    3
    4
    5
    archive_generator:
    per_page: 8
    yearly: true
    monthly: true
    daily: false