Hexo

Hexo - A fast, simple & powerful blog framework

hexojs/hexo: A fast, simple & powerful blog framework, powered by Node.js.

How Hexo Load Plugins

hexo/lib/hexo/load_plugins.ts at master · hexojs/hexo

Load Modules: package.jsonhexo- 开头和 hexo-theme- 开头的。

Load Scripts: ${theme_dir}/scripts/scripts 目录下所有的文件。这些文件都可以直接访问全局变量 hexo

Hexo API

Tag (Plugin)

Tag | Hexo

A tag allows users to quickly and easily insert snippets into their posts.

Inline Tag vs. Block Tag

注册:

1
2
3
4
5
6
7
8
9
10
11
12
// inline tag
hexo.extend.tag.register(
'foo',
args => { ... }
);

// block tag with `end`
hexo.extend.tag.register(
'bar',
(args, content) => { ... },
{ ends: true } // or simply `true`
);

.md 中使用:

1
2
3
4
5
6
7
<!-- inline tag, no `end` -->
{% foo arg1 arg2 %}

<!-- block tag, need `end` -->
{% bar arg1 arg2 %}
markdown content
{% endbar %}

Async Tag

如果 Tag 的处理逻辑中有 async 函数调用,可以将其注册为 async Tag:

1
2
3
4
5
hexo.extend.tag.register(
'foo',
args => { ... },
{ async: true }
);

处理参数 args

比如 foo Tag 支持这样的语法(有必填参数,可选参数,可选的命名参数):

1
{% foo param1 [param2] [opt1:val1] [opt2:val2] %}

通过 hexo.args.map() 将传入的 args 转成参数信息对象:

1
2
3
4
hexo => args => {
args = hexo.args.map(args, ['opt1', 'opt2'], ['key1', 'key2']);
let { key1, key2 = 'param2', opt1 = 'val1', opt2 = 'val2' } = args;
};

访问 Hexo 实例

如果是 scripts 目录里的文件,就可以直接访问全局变量 hexo

1
2
3
hexo.extend.tag.register('foo', args => {
// `hexo` is accessible
});

如果分开,则:

lib/foo.js
1
2
3
module.exports = hexo => args => {
// `hexo` is accessible
};
index.js
1
hexo.extend.tag.register('foo', require("./lib/foo")(hexo));

访问当前 Article (Post/Page) 信息

this 即为当前文件的信息,如:

1
2
3
4
5
6
7
8
module.exports = hexo => function (args) {
const {
source, // file path (relative to `source/`)
title, // article title
_content, // original content
content // HTML-rendered content
} = this;
};

Caution

这里 function (args) { ... } 不能写成 args => { ... },因为 arrow functions ignore this.

Filters

Filter | Hexo

A filter is used to modify some specified data. Hexo passes data to filters in sequence and the filters then modify the data one after the other. This concept was borrowed from WordPress.

Hexo 内置的 filter type,filter 被调用时,this 就是 Hexo 实例(不要用 arrow functions)。

Type markdown-it:renderer

Provided by hexo-renderer-markdown-it.

hexojs/hexo-renderer-markdown-it: Markdown-it is a Markdown parser, done right. A faster and CommonMark compliant alternative for Hexo.

This plugin overrides some default behaviors of how markdown-it plugin renders the markdown into html, to integrate with the Hexo ecosystem. It is possible to override this plugin too, without resorting to forking the whole thing.

自定义 filter 的调用逻辑为:

1
this.hexo.execFilterSync('markdown-it:renderer', this.parser, { context: this });

其中 thisclass Renderer 的实例,this.hexo 即为 Hexo 实例,this.parserclass MarkdownIt 的实例。

自定义 filter 的例子:

lib/foo.js
1
2
3
4
module.exports = function (parser) {
const { hexo } = this;
// parser === this.parser
};

Events

Events | Hexo

Hexo inherits from EventEmitter. Use the on method to listen for events emitted by Hexo, and use the emit method to emit events. For more information, refer to the Node.js API documentation.

Hexo Processing Flow

use pnpm hexo generate --debug to output debug level logs.

Add demo filters:

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
const filterTypes = [
'before_post_render',
'after_post_render',
'before_exit',
'before_generate',
'after_generate',
'template_locals',
'after_init',
'new_post_path',
'post_permalink',
'after_render',
'after_clean',
'server_middleware',
'markdown-it:renderer'
];

filterTypes.forEach(filterType => {
hexo.extend.filter.register(filterType, function(data, options = {}) {
let source = data?.source;
if (!source) {
if (filterType === 'post_permalink') {
source = data;
}
}

hexo.log.info(`[Demo] Filter for ${filterType} called: ${source}`);
return data;
}, 20);
});

Add demo event handlers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const events = [
'deployBefore',
'deployAfter',
'exit',
'generateBefore',
'generateAfter',
'new',
'processBefore',
'processAfter',
'ready'
];

events.forEach(event => {
hexo.on(event, function() {
hexo.log.info(`[Demo] Event ${event} emitted`);
});
});

Add demo tag plugin:

1
2
3
4
5
6
7
const foobar = ctx => function (args) {
const { source } = this;
ctx.log.info(`[Demo] Foobar tag with args "${args}" called in source file: ${source}`);
return '<div class="foobar-tag">Foobar tag called with args: ' + args.join(' ') + '</div>';
};

hexo.extend.tag.register('foobar', foobar(hexo));

“hexo generate”

  1. Database
    • [D] "Writing database to .../db.json"
  2. Initialization
    • [D] "Hexo version: 8.1.1"
    • [D] "Workding directory: ..."
    • [D] "Config loaded: .../_config.yml"
    • "Validating config"
    • [D] "Second Theme Config loaded: .../_config.stellar.yml"
    • [D] "Plugin loaded: ..." (per hexo-* and @*/hexo-* dependency)
    • [D] "Script loaded: ..." (per scripts/**/*.js)
    • [D] "Script loaded: ..." (per {theme}/scripts/**/*.js)
    • after_init filter
    • ready event
  3. Process Source Files (hexo.source and hexo.theme) (see Box | Hexo)
    • "Start processing"
    • [D] "Processed: ..."
      • post_permalink filter (per _posts .md and asset file)
  4. Content (Posts and Pages) Rendering
    • generateBefore event
    • [D] "Rendering file: ... (per .css, .js, and .json file)
    • post_permalink filter (per _posts .md file)
    • before_post_render filter (per .md file)
    • [D] "Rendering post: ..." (per .md file)
    • markdown-it:renderer filter (per .md file)
    • Tag plugins (markdown-it:renderer filter might be called if tag plugin calls renderer)
    • Together:
      • after_post_render filter (per .md file)
      • "hexo-esbuild: processed ..." (per .css file)
  5. Content Generation
    • before_generate filter
    • registered generators
      • post_permalink filter (per _posts .md file, when "Generator: post")
    • post_permalink filter (per _posts asset file❔)
    • template_locals filter (called repeatedly)
    • generateAfter event
    • after_generate filter
    • "Files loaded in d.dd s"
  6. HTML Rendering
    • [D] "Rendering HTML xxx: ....html
      • markdown-it:renderer filter (called repeatedly, for 正文之外的部分,如 side bar, footer)
  7. HTML Generation
    • Together:
      • Generated: ... (outputting files)
      • "hexo-esbuild: processed ..." (per .js, .styl file)
    • "n files generated in d.dd s"
  8. Database
    • [D] "Database saved"
  9. Exit
    • before_exit filter
    • exit event

“hexo server”

The server starting:

  1. Database
    • Same with hexo generate
  2. Initialization
    • Same with hexo generate
  3. server_middleware filter
  4. Process Source Files (hexo.source and hexo.theme) (see Box | Hexo)
    • Same with hexo generate
  5. Content (Posts and Pages) Rendering
    • Same with hexo generate
  6. From where:
    • "正在获取图片长宽比。首次可能耗时较久,请耐心等待..."
    • "[image-ratios.json] 生成完成"
  7. Content Generation
    • Same with hexo generate
  8. Server Started
    • "Hexo is running at http://localhost:4000/ . Press Ctrl+C to stop."
    • [D] "Database saved"
    • before_exit filter
    • exit event
  9. Generation Again
    • generateBefore event
    • Same with about “Content Generation”

When browsing a page:

  1. HTML Rendering
    • Same with hexo generate
  2. HTML Accessing
    • "GET /... 200 d.ddd ms - - "
      • "hexo-esbuild: processed ..." (per .js, .styl file)

When a post or page modified:

  1. Process Source Files
    • [D] "Processed: ..." (for the modified file)
      • post_permalink filter (for the modified file, if in _posts)
  2. Content (Posts and Pages) Rendering
    • Same with hexo generate, but only for the modified file
  3. Content Generation
    • Same with hexo generate

Stopping the server:

  1. Goodbye
    • "Have a nice day" or "Bye!" or similar message, twice
  2. Exit
    • Same with hexo generate

“hexo clean”

  1. Initialization
    • Same with hexo generate
  2. Clean
    • "Deleted database."
    • "Deleted public folder."
  3. Exit
    • Same with hexo generate

关于 Syntax Highlighting

Syntax Highlighting | Hexo

Hexo 内置了 highlight.js 和 prismjs 两种 syntax highlight libraries,都支持 server-side 和 browser-side 渲染。默认使用 highlight.js。

注意处理的顺序。

内置的 syntax highlight 是通过 before_post_render filter 注入的:

1
filter.register('before_post_render', require('./backtick_code_block')(ctx));

https://github.com/hexojs/hexo/blob/master/lib/plugins/filter/before_post_render/index.ts

因为 hexo generate 的 processing flow 是:

  • Content (Posts and Pages) Rendering
    • before_post_render filter
    • [D] "Rendering post: ..." (per .md file)
    • Tag plugins
    • after_post_render filter

即,在 renderer(如 hexojs/hexo-renderer-markdown-it)处理之前就已经完成了 fenced code 的 syntax highlighting 渲染。

所以直接引入 markdown-it 的 插件 @mdit/plugin-snippet,可以将 asset 文件中的代码注入到 markdown,但并不会被 Hexo 内置的 syntax highlighting 处理,而只是得到普通的 <pre><code>...</code></pre> 片段。

自定义的 snippet tag 是手动调用了 Hexo 内置的 syntax highlighting(参考了 Hexo 自带的 include_code tag):

1
2
3
4
return hexo.extend.highlight.exec(hexo.config.syntax_highlighter, {
context: hexo,
args: [code, options]
});