Hexo源码剖析

来源:互联网 发布:广州易娱网络怎么样 编辑:程序博客网 时间:2024/06/16 17:44

查看对应hexo中package.json文件程序入口为

"main": "lib/hexo",

进入lib目录下hexo文件后对应文件代码为

require('hexo-cli')();

表明hexo运行时直接初始化hexo-cli模块;表明hexo和hexo-cli是强依赖关系;
hexo-cli依赖包中package.json设定

"bin": {    "hexo": "./bin/hexo"  },

表明在引入hexo-cli直接引入的为hexo-cli下bin目录下的hexo文件;并直接执行
hexo-cli下的hexo文件中

module.exports = entry;

表明在初始化时创建entry对象;
创建entry对象需要传递cmd,args两个参数,如果cmd调用时没有传值表明直接是当前进程所在路径;代码如下:

cwd = cwd || process.cwd();

针对传递的第二个参数,需要给指定设定的参数进行设定为对象的方式;具体代码为

args = camelCaseKeys(args || minimist(process.argv.slice(2)));//获取调用时传递的参数process.argv.slice(2)//把传递的参数设置为对象方式指定minimist(process.argv.slice(2))//进行针对传入的数据对象key进行设定如果不是_开头进行安装骆驼命令格式转换并且设置set\get方法camelCaseKeys(args || minimist(process.argv.slice(2)))

创建Context对象

var hexo = new Context(cwd, args);

Context对象继承EventEmitter,具体代码为

function Context(base, args) {  base = base || process.cwd();  args = args || {};  EventEmitter.call(this);  this.base_dir = base;  this.log = logger(args);  this.extend = {    console: new ConsoleExtend()  };}require('util').inherits(Context, EventEmitter);

日志通过bunyan插件进行完成相关日志操作;具体的代码如下所示:

function createLogger(options) {  options = options || {};  var streams = [];  if (!options.silent) {    streams.push({      type: 'raw',      level: options.debug ? 'trace' : 'info',      stream: new ConsoleStream(options)    });  }  if (options.debug) {    streams.push({      level: 'trace',      path: 'debug.log'    });  }  var logger = bunyan.createLogger({    name: options.name || 'hexo',    streams: streams,    serializers: {      err: bunyan.stdSerializers.err    }  });  // Alias for logger levels  logger.d = logger.debug;  logger.i = logger.info;  logger.w = logger.warn;  logger.e = logger.error;  logger.log = logger.info;  return logger;}module.exports = createLogger;

初始化对应hexo对象(Context)后开始加载路径下的包文件;并返回cmd路径

findPkg(cwd, args);//具体的源代码为:function findPkg(cwd, args) {  args = args || {};  if (args.cwd) {    cwd = pathFn.resolve(cwd, args.cwd);  }  return checkPkg(cwd);}/***进行检查包文件*/function checkPkg(path) {  //拼接package.json文件  var pkgPath = pathFn.join(path, 'package.json');  //读取package.json文件  return fs.readFile(pkgPath).then(function(content) {    var json = JSON.parse(content);    //判断如果package.json文件中含有hexo属性。则直接返回项目跟路径    if (typeof json.hexo === 'object') return path;  }).catch(function(err) {    //表明针对解析出错、读取文件出错; 路径设置为上一级目录然后进行再次执行操作    if (err && err.cause.code === 'ENOENT') {      var parent = pathFn.dirname(path);      if (parent === path) return;      return checkPkg(parent);    }    throw err;  });}

针对执行findPkg后调用then方法,把路径作为返回值;如果package.json文件中没有hexo属性,则方法返回的路径不存在;如果不存在直接进行返回;

//如果对象不存在直接返回,表明package.json文件中必须含有hexo属性    if (!path) return;

下一步把返回的项目根路径进行设置到hexo对象上的base_dir上

hexo.base_dir = path;

进行动态加载项目中依赖的hexo模块进行动态加载初始化

/**     * 动态根据cmd路径下的node_moduels文件中进行初始化hexo对象,     */    return loadModule(path, args).catch(function() {      log.error('Local hexo not found in %s', chalk.magenta(tildify(path)));      log.error('Try running: \'npm install hexo --save\'');      process.exit(2);    });    //具体的loadModule处理方法为    /** * 加载模块中指定的模块 * @param path * @param args */function loadModule(path, args) {  return Promise.try(function() {    //指定对应根路径下的node_modules下的hexo文件    var modulePath = pathFn.join(path, 'node_modules', 'hexo');    var Hexo = require(modulePath);    //创建模块下的hexo对象    return new Hexo(path, args);  });}

返回hexo对象,hexo对象通过hexo核心进行提供
hexo创建代码为

function Hexo(base, args) {  base = base || process.cwd();  args = args || {};  var mcp = new MultiConfigPath(this);  EventEmitter.call(this);  this.base_dir = base + sep;  this.public_dir = pathFn.join(base, 'public') + sep;  this.source_dir = pathFn.join(base, 'source') + sep;  this.plugin_dir = pathFn.join(base, 'node_modules') + sep;  this.script_dir = pathFn.join(base, 'scripts') + sep;  this.scaffold_dir = pathFn.join(base, 'scaffolds') + sep;  this.theme_dir = pathFn.join(base, 'themes', defaultConfig.theme) + sep;  this.theme_script_dir = pathFn.join(this.theme_dir, 'scripts') + sep;  this.env = {    args: args,    debug: Boolean(args.debug),    safe: Boolean(args.safe),    silent: Boolean(args.silent),    env: process.env.NODE_ENV || 'development',    version: pkg.version,    init: false  };  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.config = _.cloneDeep(defaultConfig);  this.log = logger(this.env);  this.render = new Render(this);  this.route = new Router();  this.post = new Post(this);  this.scaffold = new Scaffold(this);  this._dbLoaded = false;  this._isGenerating = false;  this.database = new Database({    version: dbVersion,    path: pathFn.join(base, 'db.json')  });  this.config_path = args.config ? mcp(base, args.config)                                 : pathFn.join(base, '_config.yml');  registerModels(this);  this.source = new Source(this);  this.theme = new Theme(this);  this.locals = new Locals(this);  this._bindLocals();}

把组装好的对象重新设定到hexo-cli上

.then(function(mod) {    if (mod) hexo = mod;    log = hexo.log;    //初始化对应脚手架下对应功能显示处理    require('./console')(hexo);    //真正hexo对象进行执行对应初始化方法    return hexo.init();  })

其中的mod即是返回的hexo对象。直接重新给hexo赋值;
初始化脚手架下对应日志功能;主要源码为:

module.exports = function(ctx) {  var console = ctx.extend.console;  //对应help显示的注册包括命令、描述、参数、处理对象  console.register('help', 'Get help on a command.', {}, require('./help'));  console.register('init', 'Create a new Hexo folder.', {    desc: 'Create a new Hexo folder at the specified path or the current directory.',    usage: '[destination]',    arguments: [      {name: 'destination', desc: 'Folder path. Initialize in current folder if not specified'}    ],    options: [      {name: '--no-clone', desc: 'Copy files instead of cloning from GitHub'},      {name: '--no-install', desc: 'Skip npm install'}    ]  }, require('./init'));  console.register('version', 'Display version information.', {}, require('./version'));};

进行把console需要显示的内容信息进行注册到hexo对象下的console属性对象上在后续使用时直接调用;
在进行调用设置完成console对象以后进行调用hexo中init方法;具体hexo的init方法为

/** * hexo程序执行的入口。通过hexo-cli进行执行该方法 * @returns {*} */Hexo.prototype.init = function() {  var self = this;  this.log.debug('Hexo version: %s', chalk.magenta(this.version));  this.log.debug('Working directory: %s', chalk.magenta(tildify(this.base_dir)));  // Load internal plugins  //开始加载注册相关插件  //注册控制台显示  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);  // Load config 加载配置文件;主要进行跟进名称  return Promise.each([    'update_package', // Update package.json    'load_config', // Load config    'load_plugins' // Load external plugins & scripts  ], function(name) {    return require('./' + name)(self);  }).then(function() {    return self.execFilter('after_init', null, {context: self});  }).then(function() {    // Ready to go!    self.emit('ready');  });};

此处初始化加载一些依赖的功能模块;在hexo核心包的插件文件通过加载方式为

 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);

需要依赖配置文件的加载的通过Promise方式进行加载;其中update_config主要为修改package.json文件中版本号设置同步作用;load_config文件为加载调用加载注册的render对象,其中注册的render对象主要为

renderer.register('htm', 'html', plain, true);  renderer.register('html', 'html', plain, true);  renderer.register('css', 'css', plain, true);  renderer.register('js', 'js', plain, true);  renderer.register('json', 'json', require('./json'), true);  renderer.register('swig', 'html', require('./swig'), true);  var yaml = require('./yaml');  renderer.register('yml', 'json', yaml, true);  renderer.register('yaml', 'json', yaml, true);

renderer进行针对文件类型注册指定解析方式;具体操作的逻辑为

sequenceDiagramHexo->>load_config: init()load_config->>Render: constructor()Render->>具体注册对象: call()

注册的对象调用call方法进行加载注册对象;具体处理代码为

//获取了具体配置文件了;需要进行调用hexo的render下的render方法;    configPath = path;    //返回配置文件内容数据    return ctx.render.render({path: path});

ctx中的render的对象中render方法为

Render.prototype.render = function(data, options, callback) {  if (!callback && typeof options === 'function') {    callback = options;    options = {};  }  var ctx = this.context;  var self = this;  var ext = '';  return new Promise(function(resolve, reject) {    if (!data) return reject(new TypeError('No input file or string!'));    if (data.text != null) return resolve(data.text);    if (!data.path) return reject(new TypeError('No input file or string!'));    //data.path 为项目跟路径下的_config.yml文件;    fs.readFile(data.path).then(resolve, reject);  }).then(function(text) {    data.text = text;    ext = data.engine || getExtname(data.path);    if (!ext || !self.isRenderable(ext)) return text;    //初始化时候进行获取的ext为.yml    var renderer = self.getRenderer(ext);    //调用对应初始化对象    return renderer.call(ctx, data, options);  }).then(function(result) {    result = toString(result, data);    if (data.onRenderEnd) {      return data.onRenderEnd(result);    }    return result;  }).then(function(result) {    var output = self.getOutput(ext) || ext;    return ctx.execFilter('after_render:' + output, result, {      context: ctx,      args: [data]    });  }).asCallback(callback);};

render方法执行后把项目中配置文件内容读取到;然后根据配置文件信息进行设定到hexo对象上指定的参数上;

.then(function(config) {    //返回解析内容数据;    if (!config || typeof config !== 'object') return;    ctx.log.debug('Config loaded: %s', chalk.magenta(tildify(configPath)));    config = _.merge(ctx.config, config);    ctx.config_path = configPath;    config.root = config.root.replace(/\/*$/, '/');    config.url = config.url.replace(/\/+$/, '');    ctx.public_dir = pathFn.resolve(baseDir, config.public_dir) + sep;    ctx.source_dir = pathFn.resolve(baseDir, config.source_dir) + sep;    ctx.source = new Source(ctx);    if (!config.theme) return;    config.theme = config.theme.toString();    ctx.theme_dir = pathFn.join(baseDir, 'themes', config.theme) + sep;    ctx.theme_script_dir = pathFn.join(ctx.theme_dir, 'scripts') + sep;    ctx.theme = new Theme(ctx);  });

基本配置下信息设置完毕下一步进行动态加载依赖的包文件件;具体处理逻辑代码在load_config.js文件中为

module.exports = function(ctx) {  if (!ctx.env.init || ctx.env.safe) return;  return Promise.all([    loadModules(ctx),    loadScripts(ctx)  ]);};

其中loadModules方法为匹配配置文件中的hexo_开头的依赖包然后进行动态加载依赖包,具体代码:

function loadModules(ctx) {  var pluginDir = ctx.plugin_dir;  return loadModuleList(ctx).map(function(name) {    //获取文件的路径;    var path = require.resolve(pathFn.join(pluginDir, name));    // Load plugins 动态方式进行加载插件    return ctx.loadPlugin(path).then(function() {      ctx.log.debug('Plugin loaded: %s', chalk.magenta(name));    }).catch(function(err) {      ctx.log.error({err: err}, 'Plugin load failed: %s', chalk.magenta(name));    });  });}//动态获取匹配上hexo-开头的依赖包文件function loadModuleList(ctx) {  //如果已经设置了配置文件过直接进行调用;  if (ctx.config && Array.isArray(ctx.config.plugins)) {    return Promise.resolve(ctx.config.plugins).filter(function(item) {      return typeof item === 'string';    });  }  var packagePath = pathFn.join(ctx.base_dir, 'package.json');  //插件的目录;表明为node-model路径  var pluginDir = ctx.plugin_dir;  // Make sure package.json exists  return fs.exists(packagePath).then(function(exist) {    if (!exist) return [];    // Read package.json and find dependencies    return fs.readFile(packagePath).then(function(content) {      var json = JSON.parse(content);      var deps = json.dependencies || json.devDependencies || {};      //获取依赖对象      return Object.keys(deps);    });  }).filter(function(name) {    // Ignore plugins whose name is not started with "hexo-"    //针对插件名称不包含hexo-的进行过滤掉    if (!/^hexo-|^@[^/]+\/hexo-/.test(name)) return false;    // Make sure the plugin exists  确保插件存在;    var path = pathFn.join(pluginDir, name);    return fs.exists(path);  });}

第三方在package.json中依赖的文件加载完毕,下一步进行加载对应指定目录下的配置文件:

function loadScripts(ctx) {  var baseDirLength = ctx.base_dir.length;  function displayPath(path) {    return chalk.magenta(path.substring(baseDirLength));  }  return Promise.filter([    ctx.script_dir,    ctx.theme_script_dir  ], function(scriptDir) {    // Ignore the directory if it does not exist    return scriptDir ? fs.exists(scriptDir) : false;  }).map(function(scriptDir) {    return fs.listDir(scriptDir).map(function(name) {      var path = pathFn.join(scriptDir, name);      //动态加载指定目录下的文件进行设定到      return ctx.loadPlugin(path).then(function() {        ctx.log.debug('Script loaded: %s', displayPath(path));      }).catch(function(err) {        ctx.log.error({err: err}, 'Script load failed: %s', displayPath(path));      });    });  });}

固定ctx.script_dirhe ctx.theme_script_dir文件目录下所有的文件进行动态加载;初始化相关数据全部搞定下一步动作该按照输入的命令进行执行动作了;
回到hexo-cli项目中对应代码

  //真正hexo对象进行执行对应初始化方法    return hexo.init();  }).then(function() {    var cmd = '';    //传递的参数,args 传递的参数对象例如执行的为hexo init  hexo server; hexo help    if (!args.h && !args.help) {      //表明不是帮助的页面,获取数组对象数据中获取第一个数据;      cmd = args._.shift();      if (cmd) {        //进行验证指定的命令是否存在。如果不存在直接进入对应help命令里面;        var c = hexo.extend.console.get(cmd);        if (!c) cmd = 'help';      } else {        cmd = 'help';      }    } else {      cmd = 'help';    }    /**     * 监测系统信号。如果退出,hexo相关信息进行注销掉     */    watchSignal(hexo);    //开始根据具体的传递的参数进行处理了;    return hexo.call(cmd, args).then(function() {      //调用退出方法      return hexo.exit();    }).catch(function(err) {      return hexo.exit(err).then(function() {        // `hexo.exit()` already dumped `err`        handleError(null);      });    });  }).catch(handleError);}

其他动态设定值就不进行关注。主要就是获取通过命令行中输入的参数;然后执行hexo.call方法进行执行相关动作,在call方法中根据传入的命令在之前注册的对象中查找然后创建注册对象;具体动作为:

/** * 通过对应hexo-cli中进行执行该方法; * 该方法为真正开始运行参数的方法; * * name 为cmd路径 * args具体传递参数 * @param name * @param args * @param callback * @returns {*} */Hexo.prototype.call = function(name, args, callback) {  if (!callback && typeof args === 'function') {    callback = args;    args = {};  }  var self = this;  return new Promise(function(resolve, reject) {    //console 在对应创建hexo对象时进行创建;    //extend.console对象为extend.Console()    var c = self.extend.console.get(name);    if (c) {      //调用注册对象的call方式;例如      //console.register('clean', 'Removed generated files and cache.', require('./clean'));      //其中console.get(name)则直接返回require('./clean')对应对象      //根据返回的对象进行调用构造方法并且把参数进行传递过去;      c.call(self, args).then(resolve, reject);    } else {      //不存在。直接提示对应错误;      reject(new Error('Console `' + name + '` has not been registered yet!'));    }  }).asCallback(callback);};

到此为止hexo核心功能加载处理完毕。后续具体的命令模块通过注册对象进行查找处理;