上篇文章分析了配置加载、插件加载两个事情,本篇文章将继续分析后面的流程。还是会以hexo g
为例,将会分析一下文件预处理的过程。
hexo g 入口 这一部分在hexo源码分析(一) 中已经分析了实现方式。先来看hexo g
的入口实现。
1 2 3 4 5 6 7 8 9 10 console .register ('generate' , 'Generate static files.' , {options : [ {name : '-d, --deploy' , desc : 'Deploy after generated' }, {name : '-f, --force' , desc : 'Force regenerate' }, {name : '-w, --watch' , desc : 'Watch file changes' }, {name : '-b, --bail' , desc : 'Raise an error if any unhandled exception is thrown during generation' }, {name : '-c, --concurrency' , desc : 'Maximum number of files to be generated in parallel. Default is infinity' } ] }, require ('./generate' ));
执行hexo generate
之后会去找generate的实现。
1 2 3 4 5 6 7 8 9 10 function generateConsole (args = {} ) { return this .load ().then (() => generator.firstGenerate ()).then (() => { if (generator.deploy ) { return generator.execDeploy (); } }); }
这里调用了ctx.load
,我们再去看load的实现。
文件预渲染 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 load (callback ) { return loadDatabase (this ).then (() => { this .log .info ('Start processing' ); return Promise .all ([ this .source .process (), this .theme .process () ]); }).then (() => { mergeCtxThemeConfig (this ); return this ._generate ({cache : false }); }).asCallback (callback); }
先是加载了db.json,因为每次生成文件都没有必要全部渲染生成,因此这里db.json就是存储记录了当前的整个静态页面的情况,对于一些不需要重复渲染的,就会直接跳过。其中this.source.process()
和this.theme.process()
是文件预处理。我们来看看对应的实现。
1 2 3 4 5 6 7 class Source extends Box { constructor (ctx ) { super (ctx, ctx.source_dir ); this .processors = ctx.extend .processor .list (); } }
以Source类为例,里面继承了Box,Theme同样,我们再来看Box实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 process (callback ) { const { base, Cache , context : ctx } = this ; return stat (base).then (stats => { if (!stats.isDirectory ()) return ; const relativeBase = escapeBackslash (base.substring (ctx.base_dir .length )); const cacheFiles = Cache .filter (item => item._id .startsWith (relativeBase)).map (item => item._id .substring (relativeBase.length )); return this ._readDir (base) .then (files => cacheFiles.filter (path => !files.includes (path))) .map (path => this ._processFile (File .TYPE_DELETE , path)); }).catch (err => { if (err && err.code !== 'ENOENT' ) throw err; }).asCallback (callback); }
里面先是去找了db.json中的Cache字段,Cache字段就是为了确认文件是否修改过,可以根据hash来做判断,针对没有修改过的文件,可以不做处理,仅仅对有改动的文件做处理,我们可以看一下Cache字段长什么样。
1 2 3 4 5 6 7 8 "Cache" : [ { "_id" : "source/_posts/1. hexo博客构建.md" , "hash" : "cea7c5c70883098af40c8785fbd9674bbdec5b6d" , "modified" : 1632984281348 }, ]
然后会调用_readDir
去递归查找目录下面的所有文件,并通过results依赖传递,将所有的路径返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 _readDir (base, prefix = '' ) { const results = []; return readDirWalker (base, results, this .ignore , prefix) .return (results) .map (path => this ._checkFileStatus (path)) .map (file => this ._processFile (file.type , file.path ).return (file.path )); } function readDirWalker (base, results, ignore, prefix ) { if (isIgnoreMatch (base, ignore)) return Promise .resolve (); return Promise .map (readdir (base).catch (err => { if (err && err.code === 'ENOENT' ) return []; throw err; }), async path => { const fullpath = join (base, path); const stats = await stat (fullpath); const prefixdPath = `${prefix} ${path} ` ; if (stats.isDirectory ()) { return readDirWalker (fullpath, results, ignore, `${prefixdPath} /` ); } if (!isIgnoreMatch (fullpath, ignore)) { results.push (prefixdPath); } }); }
然后来看下是如何处理文件的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 _processFile (type, path ) { return Promise .reduce (this .processors , (count, processor ) => { const params = processor.pattern .match (path); if (!params) return count; const file = new File ({ source : join (base, path), path, params, type }); return Reflect .apply (Promise .method (processor.process ), ctx, [file]) .thenReturn (count + 1 ); }, 0 ).then (count => { if (count) { ctx.log .debug ('Processed: %s' , magenta (path)); } }) }
里面非常关键的就是这个this.processors
,定义了预处理的逻辑。以Source为例,里面source/_post
一般是存放md后缀的博客文章的。而 processor的注册是在上文分析的 extends中。
processor扩展 1 2 3 4 5 6 7 8 9 10 11 12 13 module .exports = ctx => { const { processor } = ctx.extend ; function register (name ) { const obj = require (`./${name} ` )(ctx); processor.register (obj.pattern , obj.process ); } register ('asset' ); register ('data' ); register ('post' ); };
hexo-front-matter 分离 md文件 看一下里面post的大概实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 const { parse : yfm } = require ('hexo-front-matter' );function processPost (ctx, file ) { return Promise .all ([ file.stat (), file.read () ]).spread ((stats, content ) => { const data = yfm (content); }); }
只截取了很小一部分,里面一个很关键的步骤就是使用hexo-front-matter
来处理文章内容。
1 2 3 4 5 6 --- title: Hello World --- Welcome to [Hexo](https://hexo.io/)! This is your very first post. Check [documentation](https://hexo.io/docs/) for more info. If you get any problems when using Hexo, you can find the answer in [troubleshooting](https://hexo.io/docs/troubleshooting.html) or you can ask me on [GitHub](https://github.com/hexojs/hexo/issues). // ...
举个很简单的例子,以hexo默认的第一篇文章为例,里面的结构结构是上面的,会用---
来分割 yml格式的数据和下面的文章内容。hexo-front-matter
就是为了解析这个内容,最终会解析成下面的结构。
1 2 3 4 { title : 'Hello World' , _content : 'Welcome to [Hexo](https://hexo.io/)! This is your very first post. Check [documentation](https://hexo.io/docs/) for more info. If you get any problems when using Hexo, you can find the answer in [troubleshooting](https://hexo.io/docs/troubleshooting.html) or you can ask me on [GitHub](https://github.com/hexojs/hexo/issues).\n' , }
预处理的逻辑就是诸如此类,把一些需要预先处理的文件,先做第一层处理,方便后面render过程。
小结 这里简单梳理了一下文件预处理的过程,本想继续梳理一下render过程,篇幅有限,下篇文章再进行梳理。