感谢
主题文件夹功能探索
layout
ejs文件或者其他模板文件。
条件函数使用
<%= is_post()?"class1":"class2" %> |
HEXO设计模式笔记
静态网站生成器以及api文档。
框架特色
nodejs运行环境,速度块,扩展能力强,插件丰富,主题开发,一键部署。
盲点
process.argv
process.argv返回一个包含所有启动Nodejs的参数,第一个参数为process.execPath,第二个为当前执行js文件的路径,其它为其它命令行参数。
minimist
轻量级nodejs命令行解析引擎。
var args=minimist(process.argv.slice(2)); |
store
The store means a db in program.
作者的意思大概是事件逻辑存储在store,依靠store进行耦合工作。
句柄
函数的意思,或者说可以通过它来执行某些逻辑。
模板引擎
node vm
v8虚拟机,无安全机制,所以使用者自行担保安全机制。
命令行模式
在命令行模块,Hexo选择使用minimist来解析命令行参数得到一个js对象,并建立一个Hexo实例并初始化,最后通过实例对象call方法传递命令行指令。
Hexo入口模块设计
采用构造-原型组合定义类,采用组合继承的方式继承Node的EventEmitter模块,相对简单的完成on与emit的发布订阅。
实例化阶段,保存所编译文件的存放路径,输出路径,以及其它脚本,插件,主题等所处的路径,保存环境变量,即命令行参数,版本信息号等基本信息。
创建扩展对象按不同功能进行分类,作用是创建store,用于注册句柄,获取句柄。
扩展类型控制台(Console)、部署器(Deployer)、过滤器(Filter)、生成器(Generator)、辅助函数(Helper)、处理器(Processor)、渲染引擎(Renderer)
function Hexo(base,args){ |
换句话说,扩展对象是个容器,一个事件注册机。
接下来要做的是Hexo初始化阶段,加载Hexo内置插件,不断增加扩容工作,以渲染引擎为例,向extend.renderer注册渲染过程处理函数,在其它模块中就可以很方便的从hexo的上下文去调用渲染。
Hexo.prototype.init=function(){ |
//plugins/renderer注册渲染函数 |
//调用渲染器 |
hexo 还能加载外部插件,通过 npm 的方式存放在 node_module 或目录 script 里面,巧妙的是,插件内部无效引入 hexo 对象,可以直接hexo变量来访问上下文,正是由于框架采用 Node 中 vm 模块(Virtual Machine) 模块来加载 js 文件,相当于模板引擎实现原理中的new Function或者 eval 来解析并执行字符串代码。
//加载外部插件 |
Hexo编译模块设计
预期用户命令接口
$ hexo generate |
首先往 Hexo 扩展对象 Console 中注册 ==generate== 函数
console.register('generate','Generate static files',{ |
generate 函数用于生成目标文件夹,从 Hexo 路由模块中取得所由需要生成目标文件的路径,调用fs输出文件,在此之前,首先对源文件进行预处理,把路径写入路由。由于Hexo本身设计的特点,源文件又分为内容和主题两部分,分别为放在 source 和theme文件夹中,所以得调用 process 函数分别对它们进行预处理。
function generate(hexo){ |
Hexo 抽象出一层公用模块用来管理所有处理器,命名为Box,相当于一个容器,统一管理处理器的添加删除执行监控,并分别为source和theme创建实列,Box原型如下:
function Box(base){ |
有了Box容器,接下来要做的就是往容器注入处理器,同样用插件形式往扩展对象extend中注册句柄,在注入到 Box 容器
module.exports=function(hexo){ |
以markdown文件的处理为例,成功匹配到文件扩展名,调用hexo-font-matter利用正则表达式匹配解析文件,分离顶部元素据与主题内容,类似于gray-matter,把元数据与内容key/value的形式转换为一个js对象。
module.exports=function(hexo){ |
//markdown文件 |
解析成=>
{ |
下一步,Hexo定义过滤器的概念(Filter)的概念,借鉴于Wordpress,用于在模板渲染前后修改具体数据,也可以把它看成钩子,例如使用 marked 编译 markdown 文件内容。
hexo.execFilter('before_generate',function(data)){ |
转换后增加一条 content 属性,带有标签与类名的markdown html 片段。
{ |
得到页面数据后,进入模板引擎渲染阶段,Hexo 本身并不带模板引擎,需要借助第三方库,如ejs,并通过一个适配器,把原接口转换为需求接口,向扩展对象extend.render中注册模板解析函数。
hexok.extend.renderer.register('ejs','html',function(data,locals){ |
模板引擎解析后的函数在hexo.theme对象中, 以文件名作为key,后续渲染只需要layout就能找到指定的渲染函数,注入locals变量(上面markdown解析后的js对象+扩展对象extend.helper定义变量,函数),生成最终文本字符串。
var view=hexo.theme.getView(data.layout); |
最后通过Node fs 模块将内容输出public目标。
回顾所有的工作流程,可以看作是
cli=>hexo.init=>plugin.load=>process=>filter=>render=>generate
扩展阅读
此外,Hexo还有很多优秀的设计模式
数据库系统
Hexo 引入 json 数据库 warehouse ,也是作者自己开发的一个数据库驱动,api 和 Mongoose 相差无几。在架构中角色充当一个中介者,存储临时数据,或者持久化数据数据存储,如博客的发表时间,还可以作为缓存层,比对文件修改时间,跳过无修改文件的编译过程,减少二次编译的时间。
异步方案
在大量异步回调文件会造成代码阅读性下降。
Hexo 引入 Promise 库 bindBrid, 内置丰富 API,
很方便的处理异步的流程控制,如使用 Promise.promisify(require(‘fs’).readFile)可以原生 fs 模块包装成一个promise对象。
通用日志模块
把Log划分为六个级别,’TRACE’,’DEBUG’,’INFO,’WARN’,’FATAL’,不同级别输出不同的格式与颜色(chalk) ,并提供命令含接口,如果带有–debug字段,则Log自动降级为’TRACE’。
Hexo
变量
_config.yml
markdown.md Front-matter
多语言支持
主题
全局设置
语言
模板
基本就是网站布局,如果你想要亲手制作一个Hexo的主题,那么就要深入了解这块内容。每一个主题至少都要包含index模板,下面是各页面对应的模板名称:
- index 首页
- post 文章
- page 分页
- archive 归档
- category 分类归档
- tag 标签归档
Layouts 布局
如果页面结构类似,模板之间都包含相同结构,可以使用布局功能让模板共享相同结构。一个布局文件必须要能包含显示body变量内容,如此一来模板内容才能显示。
<!-- index.ejs --> |
生成结果:
|
每个模板都默认使用 layout 布局,你可在文章的前置申明指定布局。比如 post 或者 page 或者设为 false 来关闭布局功能。如果不填默认是 post ,这个在_config.yml中设置默认值,你甚至可以在布局中中再使用其它布局来建立嵌套布局。
Pratials 局部模板
局部模板可以让你在不同的模板中分享相同的组件,例如Header,Footer,Sidebar侧边栏等,可以利用局部模版功能将各个组件分割独立的文件,便于维护。
<!-- partial/header.ejs--> |
生成就是:
<h1 id="logo"> |
这个很容易理解,就是将局部模块里的内容,原封不动的拷贝到引用它的文件中。
Local Variables 本地变量
这里的变量指的是针对局部模板来使用的,你可以在局部模块中指定局部变量来进行传递。例如:
<!--partial/header.ejs--> |
生成后就是:
<h1 id="logo"> |
这个也不难理解,就是传递变量的过程。
Optimization 最优化
假如你的主题特别复杂,因为要生成的文件过于庞大,这样导致hexo 生成性能下降,除了优化主题外,你可以通过 Fragment Caching 局部缓存的功能来处理。它主要的功能是缓存局部的内容,减少文件查询。
常用在哪些变动很少的模块。例如Header、Footer。
<%- fragment_cache('header',function(){ |
如果使用局部模板的话,如下:
<%- partial('header',{},{cache:true}); |
但是需要注意的是,如果开启了relativet1_link
参数的话,就不要使用局部缓存的功能,因为relative link在每个页面可能不同。
变量
全局变量
site
网站变量site.posts
所有文章site.pages
所有分类site.categories
所有分类site.tags
所有标签page
针对改页面的内容以及申明前置所设定的变量config
配置文件中的变量theme
配置文件中的变量_
lodash函数库path
当前页面的路径(不含根路径)url
页面完整env
环境变量
页面变量
Page 这里指的是 hexo new page
创建的页面
page.title
文章标题page.date
文章建立日期page.updated
文章更新日期page.comments
留言是否开启page.layout
布局名称page.content
文章完整内容page.excerpt
文章摘要page.more
除了摘要的其它内容page.source
文章原始路径page.full_source
文章完整原始路径page.path
文章网址(不含根路径),通常在主题中使用url_for(page.path)
page.permalink
文章永久网址page.prev
上一章,如果此为第一篇文章则为null
page.next
下一章,如果此为最后一篇文章则为null
page.raw
文章的原始内容page.photos
文章的照片(用于相册)page.link
文章的外链(用于链接文章)
Post这里指的是文章页面,与page布局相同,添加如下变量:
page.pulished
文章非草稿为true
page.categories
文章分类page.tags
文章的标签
首页 index
page.per_page
每一页显示的文章数page.total
文章数量page.current
当前页码page.current_url
当前页的URLpage.posts
当前页的文章gepage.prev
前一页页码,如果为第一页,该值为0page.prev_link
前一页URL,如果为第一页,则为“page.next_link
后一页URL,如果为最后一页,则为”page.path
当前不含根路径网址,通常在主题中使用url_for(page.path)
归档页 archive
与index
布局相同,但是新增如下变量
archive
为true
year
归档年份month
归档月份
分类页 category
与 index
布局相同,但是新增如下变量
category
分类名称
标签页
与 index
布局相同,但是新增如下变量
tag
标签名称
帮助函数
帮助函数用于在模板中快速插入内容
url
url_for
返回一个带root路径的,用法<%- url_for(path) %>
relative_url
返回from相对的to路径,用法<%- relative_url %>
gravater
插入Gravatar图片,用法:<%- gravagtar(email,[size]) %>
<%- gravatar('a@abc.com') %>
// http://www.gravatar.com/avatar/b9b00e66c6b8a70f88c73cb6bdb06787
<%- gravatar('a@abc.com',40) %>
// http://www.gravatar.com/avatar/b9b00e66c6b8a70f88c73cb6bdb06787?s=40
HTML
css
载入CSS文件。用法:<% css(path,...) %>
,path
可以是数据或者字符串,如果path
开头不是/
或者任何协议,则会自动加上根路径,如果没有.css
扩展名,也会自动加上。<%- css('style.css') %>
// <link rel="stylesheet" href="/style.css" type="text/css">
<%- css(['style.css', 'screen.css']) %>
// <link rel="stylesheet" href="/style.css" type="text/css">
// <link rel="stylesheet" href="/screen.css" type="text/css">js
载入 JavaScript 文件。用法:<%- js(path, ...) %>
,path
可以是数组或字符串,如果path
开头不是/
或任何协议,则会自动加上根路径;如果后面没有加上.js
扩展名的话,也会自动加上。<%- js('script.js') %>
// <script type="text/javascript" src="/script.js"></script>
<%- js(['script.js', 'gallery.js']) %>
// <script type="text/javascript" src="/script.js"></script>
// <script type="text/javascript" src="/gallery.js"></script>link_to
插入链接。用法:<%- link_to(path, [text], [options]) %>
,options
参数有:external
在新窗口中打开链接,默认值为falseclass
Class名称,也就是html标签a的class名id
ID名,也就是html标签a的id名
<%- link_to('http://www.google.com') %> |
// <a href="http://www.google.com" title="Google" target="_blank" rel="external" class="link">Google</a> |
mail_to
插入电子邮件链接。用法:<%- mail_to(path, [text], [options])%>
,options
参数有:
class
Class名称,也就是html标签a的class名id
ID名,也就是html标签a的id名subject
邮件主题cc
抄送(CC)bcc
密送(BCC)body
邮件内容
<%- mail_to('a@abc.com') %> |
5.image_tag
插入图片。用法:<%- image_tag(path, [options]) %>
,options
参数有:
class
Class名称,也就是html标签a的class名id
ID名,也就是html标签a的id名alt
替代文字width
宽度height
高度
基本上就是img
标签里的属性值。
6.favicon_tag
插入favicon。用法:<%- favicon_tag(path) %>
7.feed_tag
插入feed链接。用法:<%- feed_tag(path, [options]) %>
,参数有:title
和type
(默认值为atom)
条件函数
is_current
判断path
是否符合目前页面的网址。<%- is_current(path, [strict]) %>
is_home
判断目前是否为首页。is_post
检查目前是否为文章。<%- is_post() %>
is_archive
检查目前是否为存档页面。<%- is_archive() %>
is_year
检查目前是否为年度归档页面。<%- is_year() %>
is_month
检查目前是否为月度归档页面。<%- is_month() %>is_category
检查目前是否为分类归档页面。<%- is_category() %>is_tag
检查目前是否为标签归档页面。<%- is_tag() %>
字符串处理
trim
清楚字符串开头和结尾的空格。<%- trim(string) %>
strip_html
清楚字符串中HTML标签。<%- strip_html(string) %>
titlecase
把字符串转换为Title case。<%- titlecase(string) %>
markdown
使用 Markdown 解析字符串。<%- markdown(string) %>
render
解析字符串。<%- render(str, engine, [options]) %>
word_wrap
使每行的字符串长度不超过length
。length
预设为 80。<%- word_wrap(str, [length]) %>
truncate
移除超过length
长度的字符串。<%- truncate(text, length) %>
模板
partial
载入其它模板文件,你可在locals
设定变量。<%- partial(layout,[locals],[options]) %>
参数 | 描述 | 默认值 |
---|---|---|
cache | 缓存,使用Fragment cache | false |
only | 限制局部变量,在模板中只能使用 locals 中设定的变量 | false |
fragment_cache
局部缓存。它储存局部内容,下次使用时就能直接使用缓存。<%- fragment_cache(id, fn);
日期和时间
date
插入的格式化的日期,Unix,ISO字符串,Date对象或Moment对象。
format
默认为 date_format
配置信息。
<%- date(Date.now(),'YYYY/M/D) %>
date_xml
插入XML格式的日期。数据格式和调用同上。
time
/full_date
列表
list_categories
插入分类列表,<%- list_categories([categories], [options]) %>
list_tags
list_archives
lsit_posts
tagclound
想要学习paginator
插入分页连接search_form
插入谷歌搜索number_format
open_graph
toc
本地化
插件
注意
上面提到的所有Options
参数一定要按照json
的格式来书写;
2022-06-23再度学习
概述(overview)
初始化
let hexo=new require('hexo')(process.cwd(),options); |
载入资源文件
都负责 source
文件夹和主题资源文件,
后者还监听文件变动。
具体如何运行可以查看hexo-cli
作者也暂时也是以广度为主。
hexo.load().then(funciton(){}); |
执行指令
call
对象,第一个参数Console
的名字,第二个是选项
hexo.call('generate', {}).then(function(){ |
结束
当指令完毕后,请执行 exit
方法让 Hexo 退出结束前的准备工作(如存储资料库)。
hexo.call('generate').then(function(){ |
事件
这个主要事件钩子。
hexo.on("周期名",function(..args){}) |
网站变量(site)
默认变量不再叙述
模板中用site.xxx
你也可以添加自己的局部变量给模板使用✌,api
设计有面向对象的味道。
具体看官网就,可以了。
在你的脚本文件夹里scripts
里
hexo.locals.set("youvar",()=>"value"); |
在你的模板文件夹里 layout
<%= site.yourvar %> |
路由
这个官方文档很作。路径变量这些都没有明说是什么形式。
我摸索了一段时间。
官方教程顺序是倒过来的,所以先set
后get
。难道说,这就是错误教学的魅力,§( ̄▽ ̄)§,不愧是作者大人,返璞归真。
至于,其它,要摸索还要一段时间,所以嘛,坑都帮你找出来,就不必这么多。
盒(Box)
就是处理特定文件夹的文件容器,说的比较唬人。
官方教程里的box指的是new hexo.Box()
的是实例化。
渲染
这个我还没深入。
新建文章
可以生成文章,配合控制台指令,就是生成文章。
脚手架
也差不多,手动建其实更好。
控制台
控制参数不要写-h
,它不会生效。
这个bug
困惑我许久,他或许就是help
命令。
过滤器
它的功能主要是文本转换到输出文件夹的过程。
这个我有一篇详细的介绍,这里主要有趣的是是官方加入的本地服务器中间件到过滤器。
看风格有点像epress
服务器的代码,然而并不是。
生成器
这个东西就要细说了。生成器的内容是由网站变量的上下文对象locals
更新路由这一块,暂时不明白,官方文档的内容太少了,或者我不是从0开始搭建的,用了cli
确实方便和快速了不少,但是确实留给我知识盲区。
分页归档,hexo-pageination
就是用来分页处理的,它会生成页码数据。
抱歉,突然发现昨天乱动模块出现一个小bug,文章被连续生成3次,页码增大了3倍。
为什么会这样呢?我也不太清楚,我清理了配置,关掉服务,重新生成,它成功复原了。
这个问题和记忆化在一起,使我定位有点模糊,我猜测是文章渲染的时候数据的时候裂变成3个。
不管怎么样花了不到十分钟就解决了,还是挺开心的,本来以为要深入模块排查。
终于,知道为什么posts
变量那么多奇奇怪怪的方法。看这里官方文档,
没有细究,方法太多了,还好作者有用法注释,虽然不是那么容易理解,捂脸,我的自动缩进没有写好注释,有空加回去。
辅助函数
这个方面,我在做更新页面的时候简单的查看了,this
,做了一个更新页面的检测功能。
我又做了一个可以为虚拟列表提供的参数。
注入器
用于注入代码,用法看官方文档即可
迁移器
暂时搞不懂,需要查看源码或者官网的话,官网介绍很少,从只言片语中只能得知是用命令行启动的。我暂时还无法触发,接下来有机会可能去学习一下吧。
处理器
第一个是用来匹配文件路径,第二个是获取传入的Box
文件参数
_File { |
渲染器
主要用来修改原始文件的映射处理。比如ejs
,md
.
自定义标签
采用hexo
的代码块进行替换。
{% fireman 我的自定义tag %} |
hexo.extend.tag.register("fireman",fucntion(args,content){ |
终于基本是写完了,整体有个大致了解。
然而如果要深入还有很多的东西值得我去细究的东西。