玩命加载中 . . .

主页 归档 分类 标签

hexo深入再学习

感谢

hexo设计模式探索

node之proce模块

minimist命令行引擎

Object.setPrototypeOf原型组合

模板引擎

nodejs vm模块

简书-个人学习

主题文件夹功能探索

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));
var cmd=args._.shift();
var hexo=new Hexo();
hexo.init();
hexo.call(cmd,args);

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){
EventEmitter.call(this);
this.public_dir=path.join(base,'public');
this.source_dir=path.join(base,'source');
...
this.extend={
console:new extend.Console();
generator:new extend.Generator();
processor:new extend.Processor();
renderer:new extend.Renderer();
...
}
...
//等同于Object.setPrototypeOf(Hexo.prototype,EventEmitter.prototype);
require('util').inherits(Hexo,EventEmitter);
}

换句话说,扩展对象是个容器,一个事件注册机。

接下来要做的是Hexo初始化阶段,加载Hexo内置插件,不断增加扩容工作,以渲染引擎为例,向extend.renderer注册渲染过程处理函数,在其它模块中就可以很方便的从hexo的上下文去调用渲染。

Hexo.prototype.init=function(){
//加载内部插件
require('plugins/console')(this);
require('plugins/generator')(this);
require('plugins/processor')(this);
require('plugins/renderer')(this);
...
};
//plugins/renderer注册渲染函数
module.exports=function(){
var renderer=hexo.extend.renderer;
renderer.register('swig','html',require('./swig'));
renderer.register('ejs','html',require('./ejs'));
renderer.register('yml','josn',require('./yaml'));
}
//调用渲染器 
module.exprots=function(hexo){
var renderer=hexo.extend.renderer;
return renderer.get('ejs');
}

hexo 还能加载外部插件,通过 npm 的方式存放在 node_module 或目录 script 里面,巧妙的是,插件内部无效引入 hexo 对象,可以直接hexo变量来访问上下文,正是由于框架采用 Node 中 vm 模块(Virtual Machine) 模块来加载 js 文件,相当于模板引擎实现原理中的new Function或者 eval 来解析并执行字符串代码。

//加载外部插件
Hexo.prototype.loadPlugin=function(path){
fs.readFile(path).then(function(script){
script=`(function(hexo){`+script+'})';
});
retutn vm.runInThisContext(script,path)(this)
}

Hexo编译模块设计

预期用户命令接口

$ hexo generate

首先往 Hexo 扩展对象 Console 中注册 ==generate== 函数

console.register('generate','Generate static files',{
options:[
{name:'-d,--deloy',desc:'Deploy after generated'},
{name:'-f,--force',desc:'Force regenerate'},
{name:'-w,--watch',dec:'Watch file changes'},
]
},require('/generate'))

generate 函数用于生成目标文件夹,从 Hexo 路由模块中取得所由需要生成目标文件的路径,调用fs输出文件,在此之前,首先对源文件进行预处理,把路径写入路由。由于Hexo本身设计的特点,源文件又分为内容和主题两部分,分别为放在 source 和theme文件夹中,所以得调用 process 函数分别对它们进行预处理。

function generate(hexo){
hexo.source.process();
hexo.theme.process();
routerList.forEach(path=>writeFile(path));
}

Hexo 抽象出一层公用模块用来管理所有处理器,命名为Box,相当于一个容器,统一管理处理器的添加删除执行监控,并分别为source和theme创建实列,Box原型如下:

function Box(base){
this.base=base;
this.processors=[];
}
Box.prototype.addProcessor=function(pattern,fn){
this.processors.push({
pattern,
process:fn,
});
};
Box.prototype.process=function(callback){
this.processors.forEach(processor=>processor.process());
}

有了Box容器,接下来要做的就是往容器注入处理器,同样用插件形式往扩展对象extend中注册句柄,在注入到 Box 容器

module.exports=function(hexo){
var {processor}=hexo.extend;
var obj=require('./asset')(hexo);
processer.register(obj.pattern,obj.process);
...
};

以markdown文件的处理为例,成功匹配到文件扩展名,调用hexo-font-matter利用正则表达式匹配解析文件,分离顶部元素据与主题内容,类似于gray-matter,把元数据与内容key/value的形式转换为一个js对象。

module.exports=function(hexo){
return {
patter:/\.md/,
process:function(path){
readFile(path,function(err,content){
var data=require('hexo-front-matter')(content);
data.source=path;
data.raw=content;
return data;
});
}
}
}
//markdown文件
---
title:hello,
layout:home
---
#hexo
A fast,simple&porwerful blog framework

解析成=>

{
title:'hello',
layout:'home',
_content:'# Hexo\n fast,simple&porwerful blog framework',
source:'README.md',
raw:`---\ntitile:hello\n---# Hexo\n fast,simple&porwerful blog framework`
}

下一步,Hexo定义过滤器的概念(Filter)的概念,借鉴于Wordpress,用于在模板渲染前后修改具体数据,也可以把它看成钩子,例如使用 marked 编译 markdown 文件内容。

hexo.execFilter('before_generate',function(data)){
hexo.render.render({
text:data._content,
path:data.source,
engine:data.engine
});
};

转换后增加一条 content 属性,带有标签与类名的markdown html 片段。

{
title:'hello',
layout:'home',
_content:'# Hexo\n fast,simple&porwerful blog framework',
content:'<h1 id="Hexo"><a href="#Hexo" class="headerlink" title="Hexo">Hexo</a></h1><p>fast,simple&porwerful blog framework</p>'
source:'README.md',
raw:`---\ntitile:hello\n---# Hexo\n fast,simple&porwerful blog framework`
}

得到页面数据后,进入模板引擎渲染阶段,Hexo 本身并不带模板引擎,需要借助第三方库,如ejs,并通过一个适配器,把原接口转换为需求接口,向扩展对象extend.render中注册模板解析函数。

hexok.extend.renderer.register('ejs','html',function(data,locals){
require('ejs').render(data,locals)
});

模板引擎解析后的函数在hexo.theme对象中, 以文件名作为key,后续渲染只需要layout就能找到指定的渲染函数,注入locals变量(上面markdown解析后的js对象+扩展对象extend.helper定义变量,函数),生成最终文本字符串。

var view=hexo.theme.getView(data.layout);
view.render(lcoals);

最后通过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 -->
index
<!-- layout.ejs -->
<html>
<body>
<%- body %>
</body>
</html>

生成结果:

<!DOCTYPE html>
<html>
<body>
index
</body>
</html>

每个模板都默认使用 layout 布局,你可在文章的前置申明指定布局。比如 post 或者 page 或者设为 false 来关闭布局功能。如果不填默认是 post ,这个在_config.yml中设置默认值,你甚至可以在布局中中再使用其它布局来建立嵌套布局。

Pratials 局部模板

局部模板可以让你在不同的模板中分享相同的组件,例如Header,Footer,Sidebar侧边栏等,可以利用局部模版功能将各个组件分割独立的文件,便于维护。

<!-- partial/header.ejs-->
<h1 id="logo">
<%= config.title %>
</h1>
<!-- index.ejs-->
<div id="content">
Home page
</div>

生成就是:

<h1 id="logo">
My Site
</h1>
<div id="content">
Home page
</div>

这个很容易理解,就是将局部模块里的内容,原封不动的拷贝到引用它的文件中。

Local Variables 本地变量

这里的变量指的是针对局部模板来使用的,你可以在局部模块中指定局部变量来进行传递。例如:

<!--partial/header.ejs-->
<h1 id="logo">
<%- title %>
</h1>
<!--index.ejs-->
<%- partial('partial/header',{title:'Hello,World'}) %>
<div id="content">
Home page
</div>

生成后就是:

<h1 id="logo">
Hello World
</h1>
<div id="content">
Home page
</div>

这个也不难理解,就是传递变量的过程。

Optimization 最优化

假如你的主题特别复杂,因为要生成的文件过于庞大,这样导致hexo 生成性能下降,除了优化主题外,你可以通过 Fragment Caching 局部缓存的功能来处理。它主要的功能是缓存局部的内容,减少文件查询。

常用在哪些变动很少的模块。例如Header、Footer。

<%- fragment_cache('header',function(){
retutn `<header></header>`
});

如果使用局部模板的话,如下:

<%- 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当前页的URL
  • page.posts当前页的文章ge
  • page.prev前一页页码,如果为第一页,该值为0
  • page.prev_link前一页URL,如果为第一页,则为“
  • page.next_link后一页URL,如果为最后一页,则为”
  • page.path当前不含根路径网址,通常在主题中使用 url_for(page.path)

归档页 archive

index布局相同,但是新增如下变量

  • archivetrue
  • year归档年份
  • month归档月份

分类页 category

index 布局相同,但是新增如下变量

  • category分类名称

标签页

index 布局相同,但是新增如下变量

  • tag标签名称

帮助函数

帮助函数用于在模板中快速插入内容

url
  1. url_for返回一个带root路径的,用法 <%- url_for(path) %>

  2. relative_url返回from相对的to路径,用法 <%- relative_url %>

  3. 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
  1. 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">
  2. 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>
  3. link_to 插入链接。用法:<%- link_to(path, [text], [options]) %>options参数有:

    • external 在新窗口中打开链接,默认值为false

    • class Class名称,也就是html标签a的class名

    • id ID名,也就是html标签a的id名

<%- link_to('http://www.google.com') %>
// <a href="http://www.google.com" title="http://www.google.com">http://www.google.com</a>

<%- link_to('http://www.google.com', 'Google') %>
// <a href="http://www.google.com" title="Google">Google</a>

<%- link_to('http://www.google.com', 'Google', {external: true, class: "link"}) %>
// <a href="http://www.google.com" title="Google" target="_blank" rel="external" class="link">Google</a>
// <a href="http://www.google.com" title="Google" target="_blank" rel="external" class="link">Google</a>
  1. 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') %>
// <a href="mailto:a@abc.com" title="a@abc.com">a@abc.com</a>

<%- mail_to('a@abc.com', 'Email') %>
// <a href="mailto:a@abc.com" title="Email">Email</a>

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]) %>,参数有:titletype(默认值为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使每行的字符串长度不超过 lengthlength 预设为 80。<%- word_wrap(str, [length]) %>
  • truncate移除超过 length 长度的字符串。<%- truncate(text, length) %>
模板
  • partial载入其它模板文件,你可在 locals设定变量。<%- partial(layout,[locals],[options]) %>
参数描述默认值
cache缓存,使用Fragment cachefalse
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);
//配置项参考原文即可
hexo.init().then(funciton(){
//...
});

载入资源文件

都负责 source文件夹和主题资源文件,

后者还监听文件变动。

具体如何运行可以查看hexo-cli作者也暂时也是以广度为主。

hexo.load().then(funciton(){});
hexo.watch().then(function(){});

执行指令

call对象,第一个参数Console的名字,第二个是选项

hexo.call('generate', {}).then(function(){
// ...
});

结束

当指令完毕后,请执行 exit 方法让 Hexo 退出结束前的准备工作(如存储资料库)。

hexo.call('generate').then(function(){
return hexo.exit();
}).catch(function(err){
return hexo.exit(err);
});

事件

这个主要事件钩子。

hexo.on("周期名",function(..args){})

网站变量(site)

默认变量不再叙述

模板中用site.xxx

你也可以添加自己的局部变量给模板使用✌,api设计有面向对象的味道。

具体看官网就,可以了。

在你的脚本文件夹里scripts

hexo.locals.set("youvar",()=>"value");

在你的模板文件夹里 layout

<%= site.yourvar %>

路由

这个官方文档很作。路径变量这些都没有明说是什么形式。

我摸索了一段时间。

官方教程顺序是倒过来的,所以先setget。难道说,这就是错误教学的魅力,§( ̄▽ ̄)§,不愧是作者大人,返璞归真。

至于,其它,要摸索还要一段时间,所以嘛,坑都帮你找出来,就不必这么多。

盒(Box)

就是处理特定文件夹的文件容器,说的比较唬人。

官方教程里的box指的是new hexo.Box()的是实例化。

渲染

这个我还没深入。

新建文章

可以生成文章,配合控制台指令,就是生成文章。

脚手架

也差不多,手动建其实更好。

控制台

控制参数不要写-h,它不会生效。

这个bug困惑我许久,他或许就是help命令。

过滤器

它的功能主要是文本转换到输出文件夹的过程。

这个我有一篇详细的介绍,这里主要有趣的是是官方加入的本地服务器中间件到过滤器。

看风格有点像epress服务器的代码,然而并不是。

生成器

这个东西就要细说了。生成器的内容是由网站变量的上下文对象locals

更新路由这一块,暂时不明白,官方文档的内容太少了,或者我不是从0开始搭建的,用了cli确实方便和快速了不少,但是确实留给我知识盲区。

分页归档,hexo-pageination就是用来分页处理的,它会生成页码数据。

抱歉,突然发现昨天乱动模块出现一个小bug,文章被连续生成3次,页码增大了3倍。

为什么会这样呢?我也不太清楚,我清理了配置,关掉服务,重新生成,它成功复原了。

这个问题和记忆化在一起,使我定位有点模糊,我猜测是文章渲染的时候数据的时候裂变成3个。

不管怎么样花了不到十分钟就解决了,还是挺开心的,本来以为要深入模块排查。

终于,知道为什么posts变量那么多奇奇怪怪的方法。看这里官方文档,

hexo post 变量介绍

没有细究,方法太多了,还好作者有用法注释,虽然不是那么容易理解,捂脸,我的自动缩进没有写好注释,有空加回去。

辅助函数

这个方面,我在做更新页面的时候简单的查看了,this,做了一个更新页面的检测功能。

我又做了一个可以为虚拟列表提供的参数。

注入器

用于注入代码,用法看官方文档即可

迁移器

暂时搞不懂,需要查看源码或者官网的话,官网介绍很少,从只言片语中只能得知是用命令行启动的。我暂时还无法触发,接下来有机会可能去学习一下吧。

处理器

第一个是用来匹配文件路径,第二个是获取传入的Box文件参数

_File {
source: 'D:\\Project\\myBlog\\source\\about\\index.md',
path: 'about/index.md',
params: [ 'about', index: 0, input: 'about/index.md', groups: undefined ],
type: 'skip'
}

渲染器

主要用来修改原始文件的映射处理。比如ejs,md.

自定义标签

采用hexo的代码块进行替换。

我的自定义tag
{% fireman 我的自定义tag %}
hexo.extend.tag.register("fireman",fucntion(args,content){
return `<strong>${args[0]}</strong>`
})

终于基本是写完了,整体有个大致了解。

然而如果要深入还有很多的东西值得我去细究的东西。

---------------- The End and I hope sunshines on the outside. ----------------
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022 Ma
  • 访问人数: | 浏览次数:

      请我喝杯咖啡吧~

      支付宝
      微信