使用 Hexo,痛骂 Hexo,理解 Hexo,成为 Hexo。

这篇文章是用来记录我阅读 Hexo 源代码的过程和分析。

上一篇文章 中,我们了解了 Hexo 源代码中的入口文件,并且在没有讲太多细节的情况下过了一遍 Hexo 运行的流程。

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Hexo extends events_1.EventEmitter {
constructor(base = process.cwd(), args = {}) { ... }
_bindLocals() { ... }
init() { ... }
call(name, args, callback) { ... }
model(name, schema) { ... }
resolvePlugin(name, basedir) { ... }
loadPlugin(path, callback) { ... }
_showDrafts() { ... }
load(callback) { ... }
watch(callback) { ... }
unwatch() { ... }
_generateLocals() { ... }
_runGenerators() { ... }
_routerRefresh(runningGenerators, useCache) { ... }
_generate(options = {}) { ... }
exit(err) { ... }
execFilter(type, data, options) { ... }
execFilterSync(type, data, options) { ... }
}

简单来说是:

  1. 初始化了各种目录路径、设置环境变量、初始化各种扩展,设置配置、日志、渲染器、路由等。
  2. _bindLocals 方法将数据库中的数据绑定到 locals 对象上。
  3. init 方法初始化 Hexo,加载插件和配置。
  4. call 方法调用控制台命令。
  5. model 方法创建或获取数据库模型。
  6. resolvePluginloadPlugin 方法用于解析和加载插件。
  7. loadwatch 方法加载数据并处理源文件,watch 方法还会设置文件监听。
  8. _generate 方法生成静态文件。
  9. exit 方法退出程序,执行清理工作。

这次,我们来了解一下扩展(extend)。请配合 官方文档 食用。

扩展

extend\index.js

js
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
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tag = exports.Renderer = exports.Processor = exports.Migrator = exports.Injector = exports.Highlight = exports.Helper = exports.Generator = exports.Filter = exports.Deployer = exports.Console = void 0;
var console_1 = require("./console");
Object.defineProperty(exports, "Console", { enumerable: true, get: function () { return __importDefault(console_1).default; } });
var deployer_1 = require("./deployer");
Object.defineProperty(exports, "Deployer", { enumerable: true, get: function () { return __importDefault(deployer_1).default; } });
var filter_1 = require("./filter");
Object.defineProperty(exports, "Filter", { enumerable: true, get: function () { return __importDefault(filter_1).default; } });
var generator_1 = require("./generator");
Object.defineProperty(exports, "Generator", { enumerable: true, get: function () { return __importDefault(generator_1).default; } });
var helper_1 = require("./helper");
Object.defineProperty(exports, "Helper", { enumerable: true, get: function () { return __importDefault(helper_1).default; } });
var syntax_highlight_1 = require("./syntax_highlight");
Object.defineProperty(exports, "Highlight", { enumerable: true, get: function () { return __importDefault(syntax_highlight_1).default; } });
var injector_1 = require("./injector");
Object.defineProperty(exports, "Injector", { enumerable: true, get: function () { return __importDefault(injector_1).default; } });
var migrator_1 = require("./migrator");
Object.defineProperty(exports, "Migrator", { enumerable: true, get: function () { return __importDefault(migrator_1).default; } });
var processor_1 = require("./processor");
Object.defineProperty(exports, "Processor", { enumerable: true, get: function () { return __importDefault(processor_1).default; } });
var renderer_1 = require("./renderer");
Object.defineProperty(exports, "Renderer", { enumerable: true, get: function () { return __importDefault(renderer_1).default; } });
var tag_1 = require("./tag");
Object.defineProperty(exports, "Tag", { enumerable: true, get: function () { return __importDefault(tag_1).default; } });
//# sourceMappingURL=index.js.map

这段代码需要在技术层面来讲解。它一个 TypeScript 编译后的 JavaScript 模块,主要用于导出多个类或函数。

用于处理 ES 模块的默认导出的辅助函数:

js
1
2
3
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};

这行代码将当前模块标记为 ES 模块:

js
1
Object.defineProperty(exports, "__esModule", { value: true });

预先声明了所有将要导出的属性:

js
1
exports.Tag = exports.Renderer = exports.Processor = exports.Migrator = exports.Injector = exports.Highlight = exports.Helper = exports.Generator = exports.Filter = exports.Deployer = exports.Console = void 0;

接下来的代码块都遵循类似的模式,例如:

js
1
2
var console_1 = require("./console");
Object.defineProperty(exports, "Console", { enumerable: true, get: function () { return __importDefault(console_1).default; } });

这些代码从其他文件导入模块,然后将它们重新导出。使用 Object.defineProperty 来定义 getter 函数,这样可以实现延迟加载。

简单而言,这段代码的主要目的是将多个相关的模块集中到一个文件中导出,方便其他部分的代码统一导入这些模块。这种模式在大型项目中很常见,可以简化导入语句并提高代码的组织性。

控制台 | Console

控制台是 Hexo 与开发者之间沟通的桥梁。它注册并描述了可用的控制台命令。

js
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const bluebird_1 = __importDefault(require("bluebird"));
const abbrev_1 = __importDefault(require("abbrev"));
class Console {
constructor() {
this.store = {};
this.alias = {};
}
/**
* Get a console plugin function by name
* @param {String} name - The name of the console plugin
* @returns {StoreFunction} - The console plugin function
*/
get(name) {
name = name.toLowerCase();
return this.store[this.alias[name]];
}
list() {
return this.store;
}
register(name, desc, options, fn) {
if (!name)
throw new TypeError('name is required');
if (!fn) {
if (options) {
if (typeof options === 'function') {
fn = options;
if (typeof desc === 'object') { // name, options, fn
options = desc;
desc = '';
}
else { // name, desc, fn
options = {};
}
}
else {
throw new TypeError('fn must be a function');
}
}
else {
// name, fn
if (typeof desc === 'function') {
fn = desc;
options = {};
desc = '';
}
else {
throw new TypeError('fn must be a function');
}
}
}
if (fn.length > 1) {
fn = bluebird_1.default.promisify(fn);
}
else {
fn = bluebird_1.default.method(fn);
}
const c = fn;
this.store[name.toLowerCase()] = c;
c.options = options;
c.desc = desc;
this.alias = (0, abbrev_1.default)(Object.keys(this.store));
}
}
module.exports = Console;
//# sourceMappingURL=console.js.map

Console 类是 Hexo 的控制台命令管理器。

导入的依赖中,bluebird 已经在 上一篇文章 中提过了;abbrev 是用于生成命令别名的包。

npm/abbrev-js - GitHub

在构造函数中,storealias 对象被初始化,分别存储命令和别名。

  • get 方法通过名称获取控制台插件函数。它会将输入的名称转换为小写,并通过别名查找实际的命令。
  • list 方法返回所有存储的命令。
  • register 方法用于注册新的控制台命令。
    • 参数包含:
      • name:命令的名称,必须。
      • desc:命令的描述,可选。
      • options:命令的选项,可选。
      • fn:命令的函数,必须(也必须是一个函数)。
    • 使用 bluebird 将命令函数 fn 转换为 Promise,接着将命令存储在 store 对象中,并更新别名。

Console 类的主要作用是管理 Hexo 的控制台命令。它允许注册新命令,检索已存在的命令,并处理命令别名。

根据官网文档的演示,可以这么用:

js
1
2
3
hexo.extend.console.register(name, desc, options, function(args){
// ...
});

关于 args 参数,它是通过 Minimist 库解析命令行参数后得到的对象。

minimistjs/minimist - GitHub