目录:
  1. 前言
    1. 实验环境
      1. 细则说明
        1. 理解Hexo
          1. 找到文件进入调试模式
            1. hexo-cli解读
              1. 目录结构:
              2. 代码分析
            2. hexo解读
              1. 目录结构:
              2. 代码分析
            3. NodeJS语法摘要
            4. 总结

              Hexo源码阅读

              阅读时间:全文 2106 字,预估用时 11 分钟
              创作日期:2018-02-26
              文章标签:
              上篇文章:vim的可配置项
               
              BEGIN

              前言

              由于开发新网站, 涉及前后端博客需要改版, 增加留言、统计等配合后端及数据库的自定义功能, 但博客用惯了Hexo, 之前也写了一年博客挂在github page上, 因此决定修改hexo的next主题满足自己的需求.

              实验环境

              • 执行hexo version返回如下:
              hexo: 3.4.2
              hexo-cli: 1.0.4
              os: Linux 4.13.0-36-generic linux x64
              http_parser: 2.7.0
              node: 9.1.0
              v8: 6.2.414.32-node.8
              uv: 1.15.0
              zlib: 1.2.11
              ares: 1.13.0
              modules: 59
              nghttp2: 1.25.0
              openssl: 1.0.2m
              icu: 59.1
              unicode: 9.0
              cldr: 31.0.1
              tz: 2017b
              • 辅助工具: google-chrome, vim

              细则说明

              1. 关于NodeJS的调试方法参见NodeJS代码的debug
              2. 实验博客为可正常运行的, 路径为: /project/blog/
              3. require('modname')方法查找模块的过程如下:
              1. 参数为文件名
              modname -> modname.js -> modname.json -> modname.node
              2. 参数为路径地址
              package.json(main) -> index.js -> index.json -> index.node

              理解Hexo

              用过Hexo写博客的都知道, Hexo是高度可定制的, 通过_config.yml配置关键信息, 可通过下载主题, 体验社区里优秀的博客界面. 但核心还是在于Hexo本身, 生成博客文章等.

              找到文件进入调试模式

              1. 查找Hexo路径: which hexo 返回路径 /home/zxl/.nvm/versions/node/v9.1.0/bin/hexo, 查看文件代码如下(关注第一行):
              #!/usr/bin/env node
              'use strict';
              require('hexo-cli')();
              1. 启用调试模式执行文件: /usr/bin/env node debug /home/zxl/.nvm/versions/node/v9.1.0/bin/hexo g
              2. 单步执行找到hexo-cli入口: /home/zxl/.nvm/versions/node/v9.1.0/lib/node_modules/hexo/node_modules/hexo-cli/lib/hexo.js
              3. 具体执行命令的文件入口: /project/blog/node_modules/hexo/lib/hexo/index.js
              4. 引入的模块
                • chalk 🔗: 让命令行输出更个性化, 如使用chalk.blue('hello world!'), 则输出hello world!
                • tildify 🔗: 将绝对路径的家目录替换成~, 如tildify('/home/zxl/haha')返回~/haha
                • bluebird 🔗: 一个实现promise语法的库.
                • minimist 🔗: 参数解析库, 如hexo g解析成{ _: [], g: true }
                • hexo-fs 🔗: 文件与目录管理工具库库
                • Lodash 🔗: JavaScript实用工具库
                • hexo-log 🔗: 日志输出功能
                • warehouse 🔗: 一个基于json格式数据结构的文本型数据库
                • abbrev-js 🔗: 切割字符串产生切割串与字符的对应关系, 用户参数简称的生成
                • resolve 🔗: 根据模块名返回模块的入口文件绝对路径, 即模块中package.json中main的值
                • hexo-util 🔗: Hexo实用工具集
                • pretty-hrtime 🔗: 时间戳格式化输出

              hexo-cli解读

              作用: 执行hexo构建的命令行工具

              目录结构:

              .
              ├── lib
              │   ├── console
              │   │   ├── help.js             # 用于注册help参数及功能
              │   │   ├── index.js            # 用于注册参数及功能
              │   │   ├── init.js             # 注册init参数及功能
              │   │   └── version.js          # 注册version参数及功能
              │   ├── context.js              # 初始赋值hexo变量的上下文
              │   ├── extend
              │   │   └── console.js          # 用于初始构造上下文时的命令仓库
              │   ├── find_pkg.js             # 检查执行命令的目录是否为博客根目录
              │   ├── goodbye.js              # 执行完退出时的交互
              │   └── hexo.js                 # hexo-cli的入口
              ├── package.json

              代码分析

              文件: /home/zxl/.nvm/versions/node/v9.1.0/lib/node_modules/hexo/node_modules/hexo-cli/lib/hexo.js

              向外提供的函数: entry

              作用: 执行控制

              function entry(cwd, args): cwd值为路径, args值为传入的参数

              // 目录/project/blog/下执行命令: hexo g -d
              cwd = cwd || process.cwd();
              args = camelCaseKeys(args || minimist(process.argv.slice(2)));
              // 此时cwd = '/project/blog', args = {_: [g], d: true}
              var hexo = new Context(cwd, args); // 用预定义上下文实例化了一个hexo对象, 此对象用于之后所有操作, 加载hexo模块时值会被被替换掉
              
              // 主要代码中清理无用代码进行解读
              return findPkg(cwd, args).then(function(path) { // 检查执行命令的目录是否为博客根目录, 如果是返回根目录路径, 此时path = '/project/blog'
                  return loadModule(path, args).catch(function() {
                      process.exit(2);
              	});
              }).then(function(mod) {
                  if (mod) hexo = mod; // 将实例化的Hexo模块赋值给hexo变量
                  require('./console')(hexo); // 赋予参数help, init, version的功能
                  return hexo.init(); // 初始化hexo, 赋予参数clean, config, deploy, generate, list, migrate, new, publish, render的功能
              }).then(function() {
                  return hexo.call(cmd, args).then(function() { // 调用最终的执行函数, 此时cmd = 'g', args = { _: [], d: true }
                      return hexo.exit(); // 任务完成后的清理工作
                  })
              })
              function loadModule(path, args) { // 用于加载模块, 传入的path = '/project/blog'
                  return Promise.try(function() {
                      var modulePath = pathFn.join(path, 'node_modules', 'hexo'); // 此时modulePath = '/project/blog/node_modules/hexo'
                      var Hexo = require(modulePath); // 载入模块
                      return new Hexo(path, args); // 实例化模块, 进入hexo解读的Hexo方法查看具体实现
                  })
              }

              hexo解读

              hexo放着主要工程文件, 供命令行工具hexo-cli引入调用, 是hexo-cli里定义的功能的延时与拓展.

              请联结上面的hexo-cli解读一起阅读

              目录结构:

              .
              ├── box
              │   ├── file.js
              │   └── index.js
              ├── extend                         # 用于注册命令
              │   ├── console.js                 # 注册命令的方法, 其中有两个私有属性: alias = {'h': 'help', 'he', 'help', ...}, store = {'help': [Function], 'generator': [Function], ...}
              │   ├── generator.js
              │   ├── index.js                   # 入口文件
              │   └── ......
              ├── hexo                           # 主功能区
              │   ├── default_config.js          # hexo中必须的默认配置, 同_config.yml
              │   ├── index.js
              │   ├── load_config.js             # 加载配置文件_config.yml和默认的config进行整合, 更新theme, source, public路径
              │   ├── load_database.js
              │   ├── load_plugins.js            # 用于加载package.json中dependencies和devDependencies字段下的依赖包, 并执行代码
              │   ├── locals.js
              │   ├── multi_config_path.js
              │   ├── post.js
              │   ├── register_models.js
              │   ├── render.js                  # 执行渲染处理, 如render.render({configPath}) 读入_config.yml的内容
              │   ├── router.js                  # 路由配置
              │   ├── scaffold.js
              │   ├── source.js
              │   └── update_package.js          # 根据安装的包信息更新package.json文件的版本号
              ├── models                         # 定义数据结构供hexo自建的json数据库调用
              │   ├── index.js
              │   ├── post.js
              │   └── ......
              ├── plugins                        # 命令及命令的作用在此文件夹下定义
              │   ├── console/index.js           # 注册命令
              │   ├── generator                  # 命令对应的具体实现函数
              │   └── ......

              代码分析

              目录: /project/blog/node_modules/hexo/lib/

              默认文件: ./hexo/index.js, 默认文件提供的默认对象: Hexo

              作用: 命令功能实现

              ./hexo/index.js

              1. function Hexo(base = process.cwd(), args = {}): base值为路径, args值为输入的参数
              function Hexo(base = process.cwd(), args = {}) { // 此时base = '/project/blog', args = { _: [ g ], d: true }
                  const mcp = multiConfigPath(this); // 执行hexo config命令时会用到
                  this.base_dir = base + sep; // this.base_dir = '/project/blog/'
                  this.source_dir = pathFn.join(base, 'source') + sep; // this.source_dir = '/project/blog/source/'
                  this.plugin_dir = pathFn.join(base, 'node_modules') + sep; // this.plugin_dir = '/project/blog/node_modules/'
                  this.script_dir = pathFn.join(base, 'scripts') + sep; // this.script_dir = '/project/blog/scripts/'
                  this.scaffold_dir = pathFn.join(base, 'scaffolds') + sep; // this.scaffold_dir = '/project/blog/scaffold_dir'
                  this.theme_dir = pathFn.join(base, 'themes', defaultConfig.theme) + sep; // this.theme_dir = '/project/blog/themes/landscape/'
                  this.theme_script_dir = pathFn.join(this.theme_dir, 'scripts') + sep; // this.theme_script_dir = '/project/blog/themes/landscape/scripts/'
                  this.env = { // 任务进程的环境变量
                      version: pkg.version,
                      ......
                  }
                  this.extend = {
                      console: new extend.Console(),
                      generator: new extend.Generator(),
                      ......
                  }
                  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.database = new Database({ // 实例化文本型数据库引擎, 载入数据
                      version: dbVersion,
                      path: pathFn.join(base, 'db.json') // '/project/blog/db.json'
                  });
                  registerModels(this); // 定义数据结构
                  this.source = new Source(this); //
                  this.theme = new Theme(this); //
                  this.locals = new Locals(this); //
                  this._bindLocals(); // 数据库相关操作
              }
              1. Hexo.prototype.init = function(){}
              Hexo.prototype.init = function() {
                  require('../plugins/console')(this);
                  require('../plugins/generator')(this); // 注册命令
                  ......
                  return Promise.each([ // 加载模块并执行, 具体作用可以看上面的目录结构下对应的文件分析
                      'update_package',
                      'load_config',
                      'load_plugins'
                  ], name => require(`./${name}`)(self)).then(() => self.execFilter('after_init', null, {context: self})).then(() => {
                      self.emit('ready');
                  });
              }
              1. Hexo.prototype.call = function(name, args, callback) {}: name为命令, args为参数
              Hexo.prototype.call = function(name, args, callback) { // 接于hexo-cli解读, 此时: name = g, args = { _: [ ], d: true }
                  return new Promise((resolve, reject) => {
                      const c = self.extend.console.get(name); // 返回命令的全称对应下的处理函数, 如g全称为generator, 反对对应generator的处理函数
                      c.call(self, args).then(resolve, reject); // 执行命令, 此时要回到init函数中查看注册命令处理函数的代码
                  })
              }

              ./plugins/console/generate.js

              1. function generateConsole(args = {}) {}: args为参数列表
              function generateConsole(args = {}) {
                  let start = process.hrtime(); // 记录开始时间
                  const generatingFiles = {};
                  function generateFile(path) {
                      // 生成文件
                      writeFile(path, true);
                  }
                  function writeFile(path, force) {
                      // 写入文件
                      ...
                  }
                  function deleteFile(path) {}
                  function wrapDataStream(dataStream, options) {}
                  function firstGenerate() {
                      // 生成文件入口
                      ...
                      return Promise.all([
                          Promise.map(routeList, generateFile),
                          Promise.filter(publicFiles, path => !~routeList.indexOf(path)).map(deleteFile)
                      ])
                  }
                  if (args.w || args.watch) {
                      // 检查参数是否带watch, 带则启动watch模式, 监听文件变化
                      ...
                  }
                  return this.load().then(firstGenerate).then(() => {
                      if (args.d || args.deploy) {
                          return self.call('deploy', args);
                      }
                  });
              }
              function pipeStream() {}
              function CacheStream() {}

              NodeJS语法摘要

              1. 返回执行命令的目录: process.cwd()
              2. 通过events系统模块发起事件监听及触发事件
              const EventEmitter = require('events');
              
              class MyEmitter extends EventEmitter {}
              
              const myEmitter = new MyEmitter();
              myEmitter.on('event', () => {
                console.log('an event occurred!');
              });
              myEmitter.emit('event');

              总结

              经过几天源码阅读, 这个项目的很多部分值得我们学习, 作者也很厉害, 感谢作者为技术进步造福, 项目不是很大, 很适合阅读学习.

              需要跟进理解:

              1. 项目架构
              2. 自建数据库
              3. 日志系统
              4. 文件生成
              FINISH
              上篇文章:vim的可配置项

              随机文章
              人生倒计时
              default