【hexo专栏】hexo的render模块的工作原理
背景
我们看到hexo的源码中有一个render模块,这个模块的作用是什么呢?我们先来看看它的文档。
用途
hexo框架中有很多类似 .ejs
, .yml
, .styl
这样的文件,然后在框架中或者扩展中想要把这些文件做一个转换:
1 2 3
| hexo.render.render({path: 'path/to/file.swig'}).then(function(result){ });
|
在渲染时,无需指定特定的engine,Hexo 会自动根据扩展名猜测所要使用的渲染引擎,当然您也可以使用 engine 指定。
使用方式
然后我们在注册这个扩展的时候,我们可以用同步和异步两种模式:
异步模式:
1 2 3 4 5 6 7 8 9 10 11 12 13
| var stylus = require('stylus');
hexo.extend.renderer.register('styl', 'css', function(data, options, callback){ stylus(data.text).set('filename', data.path).render(callback); });
hexo.extend.renderer.register('styl', 'css', function(data, options){ return new Promise(function(resolve, reject){ resolve('test'); }); });
|
同步模式
1 2 3 4 5 6
| var ejs = require('ejs');
hexo.extend.renderer.register('ejs', 'html', function(data, options){ options.filename = data.path; return ejs.render(data.text, options); }, true);
|
原理
我们看到在hexo的源码位置: lib/extend/renderer.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
| class Renderer { constructor() { this.store = {}; this.storeSync = {}; }
list(sync) { return sync ? this.storeSync : this.store; }
get(name, sync) { const store = this[sync ? 'storeSync' : 'store'];
return store[getExtname(name)] || store[name]; }
isRenderable(path) { return Boolean(this.get(path)); }
isRenderableSync(path) { return Boolean(this.get(path, true)); }
getOutput(path) { const renderer = this.get(path); return renderer ? renderer.output : ''; }
register(name, output, fn, sync) { if (!name) throw new TypeError('name is required'); if (!output) throw new TypeError('output is required'); if (typeof fn !== 'function') throw new TypeError('fn must be a function');
name = getExtname(name); output = getExtname(output);
if (sync) { this.storeSync[name] = fn; this.storeSync[name].output = output;
this.store[name] = Promise.method(fn); } else { if (fn.length > 2) fn = Promise.promisify(fn); this.store[name] = fn; }
this.store[name].output = output; this.store[name].compile = fn.compile; } }
|
相当于它内部有一个store维护了一个扩展名和对应的渲染函数的映射关系,然后我们在注册的时候,就是往这个store中添加一个映射关系。
然后当我们调用hexo.render.render
的时候,我们看到hexo对象上面的render是一个Render
类对象:
1 2 3 4 5 6 7
| class Hexo extends EventEmitter{ constructor(....){ ... this.render = new Render(this); .... } }
|
然后我们再看一下这个Render
类:
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
| class Render { constructor(ctx) { this.context = ctx; this.renderer = ctx.extend.renderer; }
isRenderable(path) { return this.renderer.isRenderable(path); }
isRenderableSync(path) { return this.renderer.isRenderableSync(path); }
getOutput(path) { return this.renderer.getOutput(path); }
getRenderer(ext, sync) { return this.renderer.get(ext, sync); }
getRendererSync(ext) { return this.getRenderer(ext, true); }
render(data, options, callback) { if (!callback && typeof options === 'function') { callback = options; options = {}; }
const ctx = this.context; let ext = '';
let promise;
if (!data) return Promise.reject(new TypeError('No input file or string!'));
if (data.text != null) { promise = Promise.resolve(data.text); } else if (!data.path) { return Promise.reject(new TypeError('No input file or string!')); } else { promise = readFile(data.path); }
return promise.then(text => { data.text = text; ext = data.engine || getExtname(data.path); if (!ext || !this.isRenderable(ext)) return text;
const renderer = this.getRenderer(ext); return Reflect.apply(renderer, ctx, [data, options]); }).then(result => { result = toString(result, data); if (data.onRenderEnd) { return data.onRenderEnd(result); }
return result; }).then(result => { const output = this.getOutput(ext) || ext; return ctx.execFilter(`after_render:${output}`, result, { context: ctx, args: [data] }); }).asCallback(callback); }
renderSync(data, options = {}) { if (!data) throw new TypeError('No input file or string!');
const ctx = this.context;
if (data.text == null) { if (!data.path) throw new TypeError('No input file or string!'); data.text = readFileSync(data.path); }
if (data.text == null) throw new TypeError('No input file or string!');
const ext = data.engine || getExtname(data.path); let result;
if (ext && this.isRenderableSync(ext)) { const renderer = this.getRendererSync(ext); result = Reflect.apply(renderer, ctx, [data, options]); } else { result = data.text; }
const output = this.getOutput(ext) || ext; result = toString(result, data);
if (data.onRenderEnd) { result = data.onRenderEnd(result); }
return ctx.execFilterSync(`after_render:${output}`, result, { context: ctx, args: [data] }); } }
|
这个就比较好理解,大概就是根据类型,获取对应的渲染函数,然后调用渲染函数,最后返回渲染结果。
总结
这样hexo
的扩展性就相对比较好了,比如前端有什么sass、less,然后比如变成css后,又想要Compress这个文件内容。另外我们也知道hexo
的配置文件是yaml格式,那内部相当于是hexo.render.render({path: '${path.yml}'})
比如转成json,然后做配置文件的合并什么的了。 最终实际的渲染,变成了对应的npm包的逻辑了,我们主要把文件的类型和渲染引擎的对应关系在Map里面建立好就好。
Hexo专栏
目前 Hexo
系列形成了一个小专栏,欢迎读者继续阅读: Hexo专栏地址