CLI启动
有种流行的做法是把cli和实现分离,比如grunt-cli和grunt。hexo也是采取这种方式,hexo-cli专门处理命令行,hexo才是具体的实现。可以像bash一样执行hexo-cli的命令
启动脚本
#!/usr/bin/env node
'use strict';
require('../lib')();
搜索路径,初始化Hexo
上面的脚本,实际上执行的是lib/index.js,核心代码如下。为了方便阅读,省略了与流程无关的代码:
findPkg(cwd, args).then(function(path){
if(!path){
return runCLICommand(args);
}
var modulePath = pathFn.join(path, 'node_modules', 'hexo');
return fs.exists(modulePath).then(function(exist){
if (!exist){
return process.exit(1);
}
var Hexo = require(modulePath);
hexo = new Hexo(path, args);
log = hexo.log;
return hexo.init().then(runHexoCommand);
});
首先,Hexo大量使用了bluebird,包括上面代码中的fs也不是node核心模块的fs,而是经过promise化的API,所以习惯了callback风格的人可能会看得晕头转向,怎么全是各种return。本文就不介绍bluebird了,基本上就是前一个function执行完之后,会进入下一个then方法
findPkg具体的代码不展开了,目的是从cwd(当前目录,也就是执行hexo xxx命令的目录)递归向上查找package.json里是否包含Hexo属性,如果有的话,就把此目录作为Hexo的根目录
如果找不到根目录,就执行hexo-cli自带的3个基础命令(init, help, version);如果找到了根目录,就require hexo module,然后实例化,调用init函数,最后执行具体的命令,如new,generate等
cli用到的模块
hexo-cli思路很简单,麻雀虽小五脏俱全,读它的源代码也很有意思。比较有收获的是了解了几个库的用法
var minimist = require('minimist');
var abbrev = require('abbrev');
var tildify = require('tildify');
var chalk = require('chalk');
minimist
minimist是命令行处理的组件,比如下面这个命令:
-- --
会处理成:
{ _: [ 'init', 'blog' ], verbose: true, cwd: '/usr/local/' }
以–开头的参数,会处理成key/value;其他的参数会以数组的形式放到里。后续可以很容易地从中按顺序取出参数,或者判断某参数是否存在
abbrev
abbrev也是个命令行处理组件:
var commands = ["generate", "init", "help"];
var shorthands = abbrev(commands);
会得到:
{ g: 'generate',
ge: 'generate',
gen: 'generate',
gene: 'generate',
gener: 'generate',
genera: 'generate',
generat: 'generate',
generate: 'generate',
h: 'help',
he: 'help',
hel: 'help',
help: 'help',
i: 'init',
in: 'init',
ini: 'init',
init: 'init' }
可以方便用户输入命令
tildify
tildify可以把用户的目录处理成~
var path = "/Users/apple/git_local/";
var short = tildify(path);
似乎没什么用
chalk
chalk可以给stdout增加文字特效,比如改变文字颜色,增加下划线等
Hexo执行
执行构造函数
从hexo-cli的这行代码开始,转入hexo执行:
var Hexo = require(modulePath);
hexo = new Hexo(path, args);
标准的javascript OO编程的惯例,设置了一大堆this.xxx = xxx,后续把这个实例作为参数传递,可以通过this.xxx取到实例变量
这里跟插件机制有关的地方,是以下的代码:
var extend = require('../extend');
this.extend = {
console: new extend.Console(),
deployer: new extend.Deployer(),
filter: new extend.Filter(),
generator: new extend.Generator(),
helper: new extend.Helper(),
migrator: new extend.Migrator(),
processor: new extend.Processor(),
renderer: new extend.Renderer(),
tag: new extend.Tag()
};
这段代码的变量名取得不太好,容易误导。其实this.extend和extend是完全不同的东西
extend是一个局部变量,extend.Console,extend.Generator等,是构造函数:
function Console(){
this.store = {};
this.alias = {};
}
Console.prototype.register = function(name, desc, options, fn){
};
而this.extend.console是用上述构造函数创建的实例,后续通过register函数来注册插件,它本身也是插件的容器,执行的时候从内部的store取出插件(通常是一个function)执行
执行init方法
接下来这行代码是核心,使hexo初始化,包括加载内部插件,外部插件,都是在init函数里完成的:
hexo.init()
init函数的核心代码如下,省略了与插件机制无关的部分:
require('../plugins/console')(this);
require('../plugins/filter')(this);
require('../plugins/generator')(this);
require('../plugins/helper')(this);
require('../plugins/processor')(this);
require('../plugins/renderer')(this);
require('../plugins/tag')(this);
require('./load_plugins')(this);
上述的代码是注册插件,包括hexo自带的核心内部插件,和开发者扩展的外部插件。具体注册的流程下面再说
执行具体命令
hexo初始化之后,就开始执行具体命令,省略无关代码:
function runHexoCommand(){
var cmd = args._.shift();
return hexo.call(cmd, args);
}
注意hexo就是前面实例化的hexo对象,call函数不是Function的call,而是hexo定义的call函数:
Hexo.prototype.call = function (name, args, callback) {
};
注册插件
插件分为内部插件和外部插件,内部插件是hexo自带的,外部插件是其他开发者的扩展
注册内部插件
hexo已经提供了核心的插件,在plugins目录里,会注册到对应的模块上。比如console的插件,会注册到hexo.extend.console这个对象上,内部用store存储。
另外,hexo的模块在extend目录里,针对不同的扩展点进行了分离。比如console模块的插件,是用hexo.extend.console注册的;generator模块的插件,是用hexo.extend.generator注册的。所以每个模块可以实现不同的注册逻辑,后续也有不同的执行逻辑
以下是console模块注册插件的核心代码;
module.exports = function(ctx){
var console = ctx.extend.console;
console.register('clean', 'Removed generated files and cache.', require('./clean'));
// 以下类似,省略
};
其中require(“./clean”)得到了一个function,hexo的插件最终都是一个个function,在特定的时机被调用。这里的this指的是hexo的实例,后面会说
module.exports = cleanConsole;
function cleanConsole(args){
return Promise.all([
deleteDatabase(this),
deletePublicDir(this)
]);
}
看下register代码的实现,省略了非核心的部分:
Console.prototype.register = function(name, desc, options, fn){
var c = this.store[name.toLowerCase()] = fn
c.options = options
c.desc = desc
this.alias = abbrev(Object.keys(this.store))
}
主要就是把插件保存在store里,同时用abbrev设置了一些shorthands
注册外部插件
注册外部插件的代码在load_plugins.js里,外部插件指的是不在hexo核心里,通过npm install的扩展插件,命名规则是必须以hexo-开头。这样的module会被hexo框架识别为hexo外部插件,尝试加载
module.exports = function(ctx){
if (!ctx.env.init || ctx.env.safe){
return;
}
return Promise.all([
loadModules(ctx),
loadScripts(ctx)
]);
};
然后是loadModules函数:
function loadModules(ctx){
var packagePath = pathFn.join(ctx.base_dir, 'package.json');
var pluginDir = ctx.plugin_dir;
return fs.exists(packagePath).then(function(exist){
if (!exist) return [];
return fs.readFile(packagePath).then(function(content){
var json = JSON.parse(content);
var deps = json.dependencies || {};
return Object.keys(deps);
});
}).filter(function(name){
if (name.substring(0, 5) !== 'hexo-') return false;
var path = pathFn.join(pluginDir, name);
return fs.exists(path);
}).map(function(name){
var path = require.resolve(pathFn.join(pluginDir, name));
return ctx.loadPlugin(path).then(function(){
ctx.log.debug('Plugin loaded: %s', chalk.magenta(name));
}, function(err){
ctx.log.error({err: err}, 'Plugin load failed: %s', chalk.magenta(name));
});
});
}
从package.json的dependencies里找到所有hexo-开头的模块,然后在node-modules目录里找到对应的模块,将path作为参数调用loadPlugin函数
然后是loadPlugin函数,真正的注册逻辑都在这个函数里:
var Module = require('module');
var vm = require('vm');
Hexo.prototype.loadPlugin = function (path, callback) {
var self = this;
return fs.readFile(path).then(function (script) {
// Based on: https://github.com/joyent/node/blob/v0.10.33/src/node.js
var module = new Module(path);
module.filename = path;
module.paths = Module._nodeModulePaths(path);
function require(path) {
return module.require(path);
}
require.resolve = function (request) {
return Module._resolveFilename(request, module);
};
require.main = process.mainModule;
require.extensions = Module._extensions;
require.cache = Module._cache;
script = '(function(exports, require, module, __filename, __dirname, hexo){' +
script + '});';
var fn = vm.runInThisContext(script, path);
return fn(module.exports, require, module, path, pathFn.dirname(path), self);
}).nodeify(callback);
};
上面这段代码有一个比较特别的地方,用到了node提供的Module和vm模块,这样通过hexo框架require的文件,都可以通过hexo变量访问到hexo的实例,从而能够访问hexo上的各种属性,如log,env等。这种做法很巧妙,令hexo的插件可以直接访问到hexo变量,又没有添加很多限制,值得学习
最后用我写的一个CSDN migrator为例,看下外部插件的写法:
hexo.extend.migrator.register('csdn', function(args){
var username = args._.shift();
});
migrator插件需要注册到hexo.extend.migrator模块下。Hexo框架支持的扩展点已经设计好了,就是extend目录下的那几个,分别都有注册和调用的逻辑。第三方插件应该注册在哪个模块下,需要查看官方文档说明,才能被正确地注册上,以及在正确的时机被调用
调用插件
所有插件的调用,都是从runHexoCommand开始的:
function runHexoCommand(){
var cmd = args._.shift();
return hexo.call(cmd, args);
}
进入Hexo的call方法,这里不是Function的call方法,省略无关代码后,核心代码只有2行:
Hexo.prototype.call = function (name, args, callback) {
var c = self.extend.console.get(name);
c.call(this, args);
};
根据用户输入的第一个命令,从hexo.extend.console中找到对应的console插件,并调用。以以下命令为例:
$ hexo migrate csdn xxxxxx
首先会调用hexo.extend.console上的migrate插件,而migrate插件只是迁移的入口,它内部又会调用具体的migrator插件来完成逻辑。由于调用的形式一般是plugin.call(hexo, args),所以插件内部的this一般来说指的都是hexo实例:
var type = args._.shift();
var migrators = this.extend.migrator.list();
if(!migrators[type]){
var help = '';
help += type.magenta + ' migrator plugin is not installed.\n\n';
help += 'Installed migrator plugins:\n';
help += ' ' + Object.keys(migrators).join(', ') + '\n\n';
help += 'For more help, you can check the online docs: ' + chalk.underline('http://hexo.io/');
console.log(help);
return;
}
return migrators[type].call(this, args);
再以clean为例,也是类似的:
$ hexo clean
function cleanConsole(args){
return Promise.all([
deleteDatabase(this),
deletePublicDir(this)
]);
}
只是clean没有设计任何扩展点,所以内部就完成了所有清理逻辑,没有再调用其他的插件
<script type="text/javascript">
$(function () {
$('pre.prettyprint code').each(function () {
var lines = $(this).text().split('\n').length;
var $numbering = $('<ul/>').addClass('pre-numbering').hide();
$(this).addClass('has-numbering').parent().append($numbering);
for (i = 1; i <= lines; i++) {
$numbering.append($('<li/>').text(i));
};
$numbering.fadeIn(1700);
});
});
</script>
相关推荐
hexo插件:Hexo插件
一个Hexo插件,可以帮助您生成豆瓣书籍,电影,音乐和游戏的内容.zip
Hexo插件可将新帖子的URL提交给Google,Bing,百度搜索引擎,以提高网站收集的质量和速度。 这三大搜索引擎已经占据了全球搜索引擎市场97%的份额(包括使用bing索引的Yahoo,ecosia等)。 以后,我可以添加向...
[PREVIEW] hexo插件渲染器webpack版本4。来自 安装 $ npm install hexo-renderer-webpack4 --save 选项 您可以在_config.yml或主题的_config.yml配置此插件。 webpack : entry : ' themes/my-theme/source/js/app...
为Hexo嵌入Steam标签插件。 演示 入门 安装 npm install hexo-tag-steam --save 用法 {% steam https://store.steampowered.com/app/286000/Tooth_and_Tail/ %} 还是这样? {% steam 286000 %} 定制 该嵌入具有....
Hexo Typora插件 六o :red_heart: Typora monorepo 配套 解决使用post_asset_folder: true时出现的路径问题post_asset_folder: true 执照 麻省理工学院
hexo插件es模块捆绑器。 安装 $ npm install hexo-plugin-rollup --save 使用 入口 # _config.yml rollup : entry : index.js 或者 # _config.yml rollup : entry : - index.js - lib.js 入口文件的根路径 站点...
hexo插件可帮助您在每次部署新帖子时自动通知读者新帖子更新。 订阅的读者可以收到有关您最新帖子的浏览器通知。 通知将包含标题和摘录。 单击它会将读者带到您的最新帖子。 提醒:如果您选择不接收通知,则直到15...
首先,这是Universe中用于hexo的最佳后加密插件(但是其他插件呢?) 适用于撰写帖子,但又不想让所有人阅读的帖子。 因此,某些页面需要密码才能访问这些加密的帖子。 除hexo之外,在wordpress,emlog或其他博客...
一个Hexo插件,在你的文章中插入b站的视频卡片,样式模仿和借鉴自b站。 Install 安装 npm i hexo-bilibili-card Preview You can click to see the preview website. 点击 看预览哦~ Usage In your markdown file: ...
六面体 通过标签插件将和嵌入到帖子/页面中。 方程在Hexo(服务器端)中呈现,因此不需要浏览器端javascript库,应将其删除。 CSS样式表是默认包含的,但可以轻松替换。安装$ npm i hexo-math --save 需要Hexo 5+...
hexo-related-posts是Hexo静态网站生成器的插件,可使用TF / IDF算法生成相关的帖子列表。 通过建议相关内容来增加用户在您的网站上花费的时间。 支持几种不同的语言,包括英语,法语,俄语,意大利语,日语和...
一个在 页面中嵌入豆瓣个人主页的小插件. 安装 $ npm install hexo-douban --save 配置 将下面的配置写入站点的配置文件 _config.yml 里(不是主题的配置文件). douban: user: mythsman builtin: false book: ...
hexo-jade-starter, 使用 Jade 和less实现Hexo的入门主题 使用 Jade 和 LESS 实现的 Hexo插件的入门主题。 这个主题对于任何想使用 Jade 创建Hexo主题的人来说都是一个起点。这里主题包括用于开发 helper的Hashgrid ...
渐进式Web应用程序(PWA)插件。 hexo-pwa让Hexo网站具有这两种功能。 -用户可以将您的网站添加到移动主屏幕 -使您的网站可以离线使用 安装 $ npm install --save hexo-pwa 选件 您可以在_config.yml配置此插件。...
建立hexo博客的必须,有些电脑hexo_init 命令很久出不来,这里直接给出压缩包。
但是装完插件后发现,首页分页URL地址是index**.html这种格式,和我原来hexo博客分页URL不符(hexo博客首页分页格式是page/数字/ 这样的分类url格式,wordpress也是),于是手动改成page/页数/ 这种常见的url分页...
六角 3.xx安装$ npm install hexo-asset-pipeline --save配置在_config.yml添加以下代码段。 为 HTML、CSS、Js 和图像启用过滤器的最小配置。 asset_pipeline : revisioning : enable : true clean_css : enable : ...
后来觉得 hexo 用起来更 geek,便移植到 hexo。 因为 hexo 开发有极大的自由度,这个主题也变得更加完善。内置三种主题样式,三种 Markdown 样式(仍在逐渐增加);背景,各种组件颜色亦或图标都能轻松自定义;集成...
hexo-generator-问题本插件将文章发布到github指定的仓库,每篇文章作为一个issue。安装 npm install hexo-generator-issues --save跑步该插件在执行hexo generate或hexo g时生效。 该插件会比较本地文件和posts....