Hexo+NexT(六):手把手教你编写一个Hexo过滤器插件
Hexo
+NexT
介绍到这里,我认为已经可以很好地完成任务了。它所提供的一些基础功能及配置,都已经进行了讲解。你已经可以随心所欲地配置一个自己的博客环境,然后享受码字的乐趣。
把博客托管到Github上,是个很好的想法,没有自己空间的博主肯定很欢迎。其实文章编译之后,他就是一个非常简单的静态网站。部署的目的就是简单的把静态网站文件夹拷贝到Github的一个仓库里,然后把这个仓库当作一个网站文件夹,仅此而已,非常简单。所以,没有讲的价值。
但是,作为一个Coder
,研究了Hexo
,总得来点真本事,提出一个方案,解决一个痛点,然后实现它。
痛点当然有,每次用Typora
码文章,习惯对文中图片所见即所得,无奈,Typora
对图片的处理方式,Hexo
不认可,转换之后url
错乱,无法识别。所以,我希望Typora
和Hexo
用统一的方式处理图片,在Typora
中和Hexo
编译之后都可以正常显示。
没有人解决,我就想解决它。
欢迎访问博主主页www.Guide2IT.com
1. Typora的图片和NexT的资源文件的统一
在Typora
中,图片可以采用相对位置保存,并且可以用文章文件名进行灵活定制。如果我们在Typora
中,把图片的保存位置指定为与文章同名的文件夹,那么跟NexT
提供的资源文件夹就不谋而合了。
在Typora
中,把图片的存储位置设置为./${filename}
,见图。
在NexT
的主题配置文件中,打开资源文件夹功能,Hexo
编译时会把资源文件夹下的资源对象,根据引用它的页面而赋予相应的url
。
post_asset_folder: true
如果,我们把这两者统一起来,在markdown文章中我们能够在文章编译为html之前,实现这样的转换
![img](postname/sample.jpg) => {%asset_img sample.jpg%}
那就幸福了:在Typora
下采用![img](postname/sample.jpg)
使用图片,享受所见即所得,在编译过程中转化为资源文件,自动获得,正确的url
,鱼与熊掌兼得,完美。
2. 解决思路
2.1 了解Hexo运作模式
研究Hexo
的项目结构,主要研究页面的编译过程,也就是Hexo g
命令是如何执行的。
根据Hexo
的概述,Hexo
项目的执行过程如下:
- 初始化
- 载入文件
- 执行指令
- 结束
第一步:初始化
初始化阶段,会创建Hexo
实例,各种配置,各种插件,各种扩展全部就位,就等待载入文章进行处理。
Hexo
通过项目包管理文件package.json
引入各种插件扩展。
第二步:载入文件
载入source
下所有的文章及样式、脚本等资源。如有指令,则可以监控该文件下面文件的变化。
第三步:执行指令
执行控制台指令,根据指令执行相应的命令。
第四步:退出
2.2 着手点
需要达成的目的,主要在编译页面的过程中,也就是主要在渲染render
阶段。
从Hexo
的源代码中固然可以找到蛛丝马迹,但是这太麻烦了,速度也不快。有没有其他的方式。
换换思路,研究下Hexo
提供的API,突然发现,其中的扩展是这样的。
基本上所有的扩展都能够望文生义,最有可能入手的地方就是Filter
过滤器。
把它的定义摆上来:
hexo.extend.filter.register(type, function(data){
}, priority);
type
是类型,表示过滤器的类型,过滤器的类型是什么意思?好吧,看看有什么类型before_post_render
、after_post_render
、before_exit
、before_generate
,这就是过滤器的插入时机啊。function(data)
是回调函数,这个很好地理解,其中的data
是什么,回头再说。priority
,type
是过滤器的插入时机,如果在同一时机插入多个过滤器,那么就由priority
来决定执行先后顺序,`priority值小就先执行。
重点在render
在上面的过滤器类型(就是过滤器的插入点)中,有一个重要的类型是before_post_render
,意思就是在渲染之前执行过滤器。查一下Hexo
的API,渲染的过程如下:
- 执行
before_post_render
过滤器- 使用 Markdown 或其他渲染器渲染(根据扩展名而定)
- 使用 Nunjucks 渲染
- 执行
after_post_render
过滤器
好啊,那么我们拿before_post_render
来尝试一下。
2.3 编写一个过滤器
找一个例子学习一下
从https://hexo.io/plugins/
里面找一个简单的过滤器例子,发现它就是一个特别简单的Node
的包。比如过滤器插件hexo-filter-auto-spacing
mmhy,它的文件清单如下:
- lib
- renderer.js
- README.md
- index.js
- package.json
其中有用的也就是package.json
和index.js
。而package.json
也就是典型的Node
包文件,它的输出对象由main
字段指定,本例中main
字段指向index
,也就是我们的index.js
文件。
看一下index.js
内容
var assign = require('deep-assign');
var renderer = require('./lib/renderer');
hexo.extend.filter.register('before_post_render', renderer.render, 9);
再看一下/lib/renderer.js
的内容
var reg = /(\s*)(```) *(.*?) *\n?\[hide\]([\s\S]+?)\s*(\2)(\n+|$)/g;
function ignore(data) {
var source = data.source;
var ext = source.substring(source.lastIndexOf('.')).toLowerCase();
return ['.js', '.css', '.html', '.htm'].indexOf(ext) > -1;
}
exports.render = function (data) {
if (!ignore(data)) {
data.content = data.content
.replace(reg, function (raw, start, startQuote, lang, content, endQuote, end) {
return start + end;
});
}
};
太简单了,对于上面这个例子,就是实现了过滤器的定义
hexo.extend.filter.register(type, function(data){
}, priority);
照猫画虎
与Hexo
项目文件并排新建一个文件node_modules
,并在里面新建项目hexo-image2asset
。结构如下:
├─guide2it-blog
│ ├─node_modules
│ ├─public
│ ├─scaffolds
│ ├─source
│ │ ├─about
│ │ │ └─index
│ │ ├─categories
│ │ ├─images
│ │ ├─tags
│ │ └─_posts
│ │ ├─2019-04-19-01测试插件.md
│ │ └─2019-04-19-01测试插件
│ │ └─guide2it.jpg
│ ├─themes
│ │ └─next
└─node_modules
└─hexo-image2asset
├─package.json
└─index.js
至于为什么要这样,这都是血的教训。对于Node
项目,新建模块应该在/guide2it-blog/node_modules
下面,我之前也是这样建立的,后来因为莫名奇妙的问题,采用万能的修复大法delete node_modules & npm install
之后,我的hexo-image2asset
项目找不到了,驾鹤西去了。
而我把hexo-image2asset
按上述方式布置,它也在Node项目的搜索路径上,也可以避免万能修复大法重蹈覆辙。
探究data的数据结构
为了弄清楚回调函数中data
的结构,我决定用一个例子来测试。
请看2019-04-19-01测试插件.md
的内容
---
内容略
---
测试hexo-image2asset插件
下面我要加入一张图片了。
![测试](2019-04-19-01测试插件/guide2it.jpg)
然后我编写index.js
,内容如下:
var deal_image=function(data){
console.log(data);
}
hexo.extend.filter.register('before_post_render', deal_image, 9);
执行hexo g
激发渲染过程。
Document {
layout: 'post',
title: '测试插件',
date: moment("2019-03-05T09:00:00.000"),
_content:
'\n测试hexo-image2asset插件\n\n下面我要加入一张图片了。\n\n![测试](2019-04-19-01测试插件/guide2it.jpg)',
source: '_posts/2019-04-19-01测试插件.md',
raw:
'---\nlayout: post\ntitle: \'测试插件\'\ndate: 2019/3/5 09:00:00\ncategory: [\'博客\',\'Hexo\']\ntags: [\'博客\',\'Hexo\',\'NexT\']\n---\n\n测试hexo-image2asset插件\n\n下面我要加入一张图片了。\n\n![测试](2019-04-19-01测试插件/guide2it.jpg)',
slug: '01测试插件',
published: true,
updated: moment("2019-04-21T01:15:15.699"),
comments: true,
photos: [],
link: '',
_id: 'cjuprkojw0001o4d4cbawzsgo',
path: [Getter],
permalink: [Getter],
full_source: [Getter],
asset_dir: [Getter],
tags: [Getter],
categories: [Getter],
content:
'\n测试hexo-image2asset插件\n\n下面我要加入一张图片了。\n\n![测试](2019-04-19-01测试插件/guide2it.jpg)',
site: { data: {} } }
原来这个data
是一个Document
,它的内容及结构如上所示。跟内容相关的主要有三个字段_content
、content
和raw
,raw
表示原始文章,_content
这种带前缀_
的一般是内部属性,不能动,那么就动content
的内容。
按照资源对象的格式要求,应该把
![测试](2019-04-19-01测试插件/guide2it.jpg)
转换为
{%asset_img guide2it.jpg 测试%}
转换图片对象为资源对象
这个需要采用正则表达式来全局转换,被转换的字符串中有文章名字,这个需要首先找出来。
已知source形如_posts/2019-04-19-01测试插件.md
,那么文件名应该是,找到最右边的/
,其后的字符串,去掉.md
。
建立正则表达式来进行替换,把[]
内的内容用()
确定为$1
,把图片文件名用()
定义为$2
,最终的正则表达式如下。
插件的index.js
完整内容如下。
var deal_image = function(data) {
var reverseSource = data.source.split("").reverse().join("");
var fileName = reverseSource.substring(3, reverseSource.indexOf("/")).split("").reverse().join("");
var regExp = RegExp("!\\[([^\\f\\n\\r\\t\\v\\[\\]]+)\\]\\(" + fileName +
'\\/([^\\\\\\/\\:\\*\\?\\"\\<\\>\\|\\,\\)]+)\\)');
data.content = data.content.replace(regExp, "{%asset_img $2 %}","g");
return data;
}
hexo.extend.filter.register('before_post_render', deal_image, 9);
这里有个bug,替换对象为"{%asset_img $2 $1 %}"
时,如果正则匹配的%1
是纯数字,则它被解释为图片宽度,这好像就离题了。所以暂时把$1
去掉。
Hexo+NexT(六):手把手教你编写一个Hexo过滤器插件的更多相关文章
- 手把手教你编写一个具有基本功能的shell(已开源)
刚接触Linux时,对shell总有种神秘感:在对shell的工作原理有所了解之后,便尝试着动手写一个shell.下面是一个从最简单的情况开始,一步步完成一个模拟的shell(我命名之为wshell) ...
- 手把手教你编写一个简单的PHP模块形态的后门
看到Freebuf 小编发表的用这个隐藏于PHP模块中的rootkit,就能持久接管服务器文章,很感兴趣,苦无作者没留下PoC,自己研究一番,有了此文 0×00. 引言 PHP是一个非常流行的web ...
- 只有20行Javascript代码!手把手教你写一个页面模板引擎
http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...
- iOS回顾笔记(05) -- 手把手教你封装一个广告轮播图框架
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...
- PWA入门:手把手教你制作一个PWA应用
摘要: PWA图文教程 原文:PWA入门:手把手教你制作一个PWA应用 作者:MudOnTire Fundebug经授权转载,版权归原作者所有. 简介 Web前端的同学是否想过学习app开发,以弥补自 ...
- R数据分析:跟随top期刊手把手教你做一个临床预测模型
临床预测模型也是大家比较感兴趣的,今天就带着大家看一篇临床预测模型的文章,并且用一个例子给大家过一遍做法. 这篇文章来自护理领域顶级期刊的文章,文章名在下面 Ballesta-Castillejos ...
- Viper 微服务框架 编写一个hello world 插件-02
1.Viper是什么? Viper 是.NET平台下的Anno微服务框架的一个示例项目.入门简单.安全.稳定.高可用.全平台可监控.底层通讯可以随意切换thrift grpc. 自带服务发现.调用链追 ...
- 用Python手把手教你搭一个Transformer!
来源商业新知网,原标题:百闻不如一码!手把手教你用Python搭一个Transformer 与基于RNN的方法相比,Transformer 不需要循环,主要是由Attention 机制组成,因而可以充 ...
- [swift实战入门]手把手教你编写2048(一)
苹果设备越来越普及,拿着个手机就想捣鼓点啥,于是乎就有了这个系列,会一步一步教大家学习swift编程,学会自己做一个自己的app,github地址:https://github.com/scarlet ...
随机推荐
- VS2010中新控件的编程------颜色按钮类和颜色对话框
(1) 颜色按钮类和颜色对话框 1) 颜色对话框 MFC提供了颜色对话框类CMFCColorDialog进行颜色的选择,系统可以利用DoModal()调用,然后选择相应的颜色. CMFCCo ...
- 解决:insert Vodafone sim card,open the mms read report,when receive the read report,cann't download..
insert Vodafone sim card,open the mms read report,when receive the read report,cann't download the m ...
- VS2015编译环境下CUDA安装配置
CUDA下载 CUDA是NVIDIA推出的通用并行计算架构,该架构使GPU能够解决复杂的计算问题,CUDA只支持NVIDIA自家的显卡,过旧的版本型号也不被支持. 下载地址:https://devel ...
- WPF中动态加载XAML中的控件
原文:WPF中动态加载XAML中的控件 using System; using System.Collections.Generic; using System.Linq; using System. ...
- 在webapi中使用swagger
1 在webapi项目下安装swagger,包名 Swashbuckle.AspNetCore 2 在webapi的startup.cs文件中添加swagger服务 /// <summary&g ...
- Wireshark非标准分析port无流量
Wireshark非标准分析port无流量 2.2.2 非标准分析port无流量Wireshark非标准分析port流量 应用程序执行使用非标准port号总是网络分析专家最关注的.关注该应用程序是否 ...
- C#获取windows 10的下载文件夹路径
Windows没有为“下载”文件夹定义CSIDL,并且通过Environment.SpecialFolder枚举无法使用它. 但是,新的Vista 知名文件夹 API确实使用ID定义它FOLDERID ...
- CMMI 能力成熟度模型集成
关于CMMI的过程域,请参考 CMMI能力成熟度模型集成的过程区域 1.CMMI/SPCA概述 CMM是“能力成熟度模型(Capability Maturity Model)”的英文简写,该模型由美国 ...
- UVA - 825Walking on the Safe Side(dp)
id=19217">称号: UVA - 825Walking on the Safe Side(dp) 题目大意:给出一个n * m的矩阵.起点是1 * 1,终点是n * m.这个矩阵 ...
- 关于Qt的事件循环以及QEventLoop的简单使用(QEventLoop::quit()能够终止事件循环,事件循环是可以嵌套的)
http://www.cnblogs.com/-wang-cheng/p/4973021.html 1.一般我们的事件循环都是由exec()来开启的,例如下面的例子: 1 QCoreApplicato ...