由于Hexo中使用了
bluebird这个Promise库,会导致代码较难理解
本文会省略一些和issue无关的代码
最近看到了Hexo的issue #4976,其中提到了大文件的CSS在生成的过程中可能会丢失部分代码。本人感觉这个问题非常有意思,于是自己也尝试了一下大文件的CSS,没想到也成功复现了这个问题。
 先说结论:问题应该出现在post.js中的escapeAllSwigTags()函数。由于压缩过的CSS中可能出现诸如{#main 这样的语句,而这样的语句会在这个函数中被当成swig模板进行处理,导致了代码的丢失。
 解决方法:在_config.yml的skip_render中添加CSS的相对路径
 以下为排查的过程和部分源码的分析:
hexo-cli
hexo中所输入的命令实际运行的是hexo/bin/hexo文件:
| 1 | 
 | 
hexo文件中直接导入hexo-cli模块,查看hexo-cli入口点:
| 1 | { | 
即入口点为hexo/node_modules/hexo-cli/lib/hexo.js,其模块导出:
| 1 | module.exports = entry; | 
查看entry()函数:
 其输入两个参数cwd和args。在该函数中调用了loadModule()函数,并将其返回结果赋值给hexo,接着调用其init()函数
| 1 | function entry(cwd = process.cwd(), args) { | 
查看loadModule()函数,在该函数中创建Hexo对象并返回:
| 1 | function loadModule(path, args) { | 
总结:hexo-cli中创建Hexo对象,并调用其init()函数
hexo初始化
查看hexo入口点:
| 1 | { | 
即入口点为hexo/lib/hexo/index.js,其模块导出:
| 1 | module.exports = Hexo; | 
先查看Hexo类的构造函数,在该函数中主要为属性赋值,初始化配置文件;同时初始化数据库,绑定查询方法
| 1 | constructor(base = process.cwd(), args = {}) { | 
由于在hexo-cli调用了Hexo类中的init()函数,查看该函数:
| 1 | init() { | 
至此,Hexo初始化完成,可以开始执行用户输入的指令
generate
hexo/lib/plugins/console用于处理用户输入的指令
 hexo/lib/plugins/console/index.js是该模块的入口,该模块用于向对应的extend中注册模块,以下以generate命令为例:
| 1 | module.exports = function(ctx) { | 
查看同目录下的generate.js模块:
 其创建了Generater对象,并调用了this.load()函数,由于this就是Hexo对象,所以相当于调用了Hexo对象中的load()函数
| 1 | function generateConsole(args = {}) { | 
查看Hexo类中的load()函数:
 该函数首先调用load_database.js中的loadDatabase模块,先检查是否在根目录下存在db.json数据库文件,如果有则进行读取,否则直接返回
 由于hexo将需要处理的文件分成了source(\source目录下的文件)和theme(\themes目录下的文件),所以分别需要对这两个部分执行process()函数进行预处理
 在异步调用结束后,需要生成的文件已经被存入了hexo对象中的database属性中,等待被生成。此时执行mergeCtxThemeConfig()函数进行配置的融合,并调用_generate()函数用于执行生成前和生成后的过滤器(filter)
 由于CSS文件位于\source目录下,所以CSS文件会在this.source.process()中被处理
| 1 | load(callback) { | 
由于issue中所提到的CSS文件属于soruce,所以只需要研究this.source.process()
processor
查看hexo/lib/box/index.js中的process()函数:
 重点在于最后的return语句,通过_readDir()函数读取文件到数据库中,再使用过滤器处理被删除的文件。而issue中提到的问题正是在将文件读取到数据库中发生的
| 1 | process(callback) { | 
查看同文件下_readDir()函数:
 函数比较简单,即递归读取特定目录下所有文件,检查其状态并使用_processFile()函数进行处理
 读取文件本身不存在问题,问题出在对读取出来数据的处理上
| 1 | _readDir(base, prefix = '') { | 
查看同文件下_processFile函数:
 bluebird的使用使得代码较难理解,大意就是对于每个path,判断其是否匹配processor中的pattern。如果匹配,则执行processor中的process()函数,并将结果返回
| 1 | _processFile(type, path) { | 
注意,这里的this是hexo对象中的source而非theme,所以查看hexo/lib/hexo/source.js:
| 1 | class Source extends Box { | 
继续查看hexo/lib/extend/processor.js:
 可以知道最终processors中的处理器就是那些初始化时被注册的处理器(和console一样)
| 1 | class Processor { | 
继续查看hexo/lib/plugins/processor/index.js:
 可以知道asset、data和post三个处理器被成功注册,而CSS文件是归属于asset进行处理的
| 1 | module.exports = ctx => { | 
继续查看hexo/lib/plugins/processor/asset.js:
 CSS文件的renderable是true,所以会进入processPage()函数中
| 1 | module.exports = ctx => { | 
继续查看processPage()函数:
 在该函数中,主要是读取对应文件,进行一定的处理后将结果存入数据库的Page模型中
| 1 | function processPage(ctx, file) { | 
filter
process()函数已经完成,此时回到load()函数中:
 开始执行_generate()函数
| 1 | load(callback) { | 
查看同目录下的_generate()函数:
 基本上就是先运行before_generate过滤器,接着运行_runGenerators调用生成器进行生成,最后运行after_generate过滤器
| 1 | _generate(options = {}) { | 
问题出现在before_generate过滤器之中,查看hexo/lib/plugins/filter/before_generate/render_post.js:
 在该过滤器中,对于Post模型和Page模型分别调用render()函数对post进行处理(如转义)
| 1 | function renderPostFilter(data) { | 
查看hexo/lib/hexo/post.js中的render()函数:
 在该函数中,首先运行before_post_render过滤器,接着在对文件进行转义操作后使用渲染器对markdown等进行渲染,最后运行after_post_render过滤器
 问题就出在escapeAllSwigTags()函数中。由于压缩过的CSS中可能出现诸如{#main这样的语句,而这样的语句会在这个函数中被当成swig模板进行处理,导致文件丢失部分代码
| 1 | render(source, data = {}, callback) { | 
 
  
 
说些什么吧!