WARNING
This article was last updated on 2022-09-12. Please note that the content may no longer be applicable.
由于 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) { |
Leave a comment