发布于 

【hexo专栏】hexo文章内容区域的自定义组件

背景

我们再开发hexo主题的时候,用ejs等其他模版来开发。但在hexo的文章中,如果想要自定义一些插件,比如下面这样的一个小组件,就需要使用hexo的extends扩展开发了。

文章中上面的语法是:

1
{% copy npm install @fedfans/stone -g left: true %}

然后目前是通过 hexo 官方文档里面的扩展Extensions的Tag实现。

语法:

1
2
3
hexo.extend.tag.register(name, function(args, content){
// ...
}, options);

官网文档地址:

然后就想着这个是如何实现的呢?

实现原理

打开源码,在hexo/lib/extend/tag.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
const { Environment } = require('nunjucks');

....

class Tag {
constructor() {
this.env = new Environment(null, {
autoescape: false
});
}

register(name, fn, options) {
if (!name) throw new TypeError('name is required');
if (typeof fn !== 'function') throw new TypeError('fn must be a function');

if (options == null || typeof options === 'boolean') {
options = { ends: options };
}

let tag;

if (options.async) {
if (fn.length > 2) {
fn = Promise.promisify(fn);
} else {
fn = Promise.method(fn);
}

if (options.ends) {
tag = new NunjucksAsyncBlock(name, fn);
} else {
tag = new NunjucksAsyncTag(name, fn);
}
} else if (options.ends) {
tag = new NunjucksBlock(name, fn);
} else {
tag = new NunjucksTag(name, fn);
}

this.env.addExtension(name, tag);
}

unregister(name) {
...
}

render(str, options = {}, callback) {
...
}
}

发现文章部分是通过nunjucks来实现的,所以关于这块我们可以打开nunjucks的文档来看看。

它的文档相比ejs就更完善一些了。

官方的文档默认是相对更底层一些,hexo做了一层封装。

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
class NunjucksTag {
constructor(name, fn) {
this.tags = [name];
this.fn = fn;
}

parse(parser, nodes, lexer) {
const node = this._parseArgs(parser, nodes, lexer);

return new nodes.CallExtension(this, 'run', node, []);
}

_parseArgs(parser, nodes, lexer) {
const tag = parser.nextToken();
const node = new nodes.NodeList(tag.lineno, tag.colno);
const argarray = new nodes.Array(tag.lineno, tag.colno);

let token;
let argitem = '';

while ((token = parser.nextToken(true))) {
if (token.type === lexer.TOKEN_WHITESPACE || token.type === lexer.TOKEN_BLOCK_END) {
if (argitem !== '') {
const argnode = new nodes.Literal(tag.lineno, tag.colno, argitem.trim());
argarray.addChild(argnode);
argitem = '';
}

if (token.type === lexer.TOKEN_BLOCK_END) {
break;
}
} else {
argitem += token.value;
}
}

node.addChild(argarray);

return node;
}

run(context, args) {
return this._run(context, args, '');
}

_run(context, args, body) {
return Reflect.apply(this.fn, context.ctx, [args, body]);
}
}

const trimBody = body => {
return stripIndent(body()).replace(/^\n?|\n?$/g, '');
};

class NunjucksBlock extends NunjucksTag {
parse(parser, nodes, lexer) {
const node = this._parseArgs(parser, nodes, lexer);
const body = this._parseBody(parser, nodes, lexer);

return new nodes.CallExtension(this, 'run', node, [body]);
}

_parseBody(parser, nodes, lexer) {
const body = parser.parseUntilBlocks(`end${this.tags[0]}`);

parser.advanceAfterBlockEnd();
return body;
}

run(context, args, body) {
return this._run(context, args, trimBody(body));
}
}

class NunjucksAsyncTag extends NunjucksTag {
parse(parser, nodes, lexer) {
const node = this._parseArgs(parser, nodes, lexer);

return new nodes.CallExtensionAsync(this, 'run', node, []);
}

run(context, args, callback) {
return this._run(context, args, '').then(result => {
callback(null, result);
}, callback);
}
}

class NunjucksAsyncBlock extends NunjucksBlock {
parse(parser, nodes, lexer) {
const node = this._parseArgs(parser, nodes, lexer);
const body = this._parseBody(parser, nodes, lexer);

return new nodes.CallExtensionAsync(this, 'run', node, [body]);
}

run(context, args, body, callback) {
// enable async tag nesting
body((err, result) => {
// wrapper for trimBody expecting
// body to be a function
body = () => result || '';

this._run(context, args, trimBody(body)).then(result => {
callback(err, result);
});
});
}
}

总结

目前类似Docusaurus、dumi、vuepress等都是基于markdown来写文章,然后他们是通过直接写react组件或者vue组件来实现文章内部加入有更多交互行为的组件。而hexo这边则通过nunjucks的自定义tag这样的方式来实现。

Hexo专栏

目前 Hexo 系列形成了一个小专栏,欢迎读者继续阅读: Hexo专栏地址


如果你有什么意见和建议,可以点击: 反馈地址 进行反馈。