Hexo 插件开发

眼馋 The Verge 的图片轮播功能,在有序图集中轮播比组图要好用,但是 Hexo 并没有相关插件,于是照猫画虎自己用 Splide 摸了一个适用于 NexT 主题的图片轮播插件,顺便记录下插件从开发到发布的全过程。

版本说明:

  • Hexo:7.3.0
  • npm:10.8.2

创建

  1. 首先创建一个文件夹来存放插件文件。
    • 文件夹名称必须以hexo-开头,如hexo-splide-carousel,否则Hexo不会识别这个插件。
    • 文件夹不必放在博客目录中。
    • 建议搭配git使用,比如在GitHub中新建一个同名仓库并克隆到本地。
  2. 在终端中执行npm init命令来初始化项目,跟随引导完成package.json的创建,package.json用于记录插件的名称、版本、依赖等信息,没想好或者不明白的选项保持默认即可,后续可以修改。
    • 有关package.json中各项的作用,详见官方文档 package.json
      • 示例:
        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
        {
        "name": "hexo-splide-carousel",
        "version": "1.2.1",
        "description": "A package for Hexo blogs using the Next theme, provides image carousel and zoom functionality using Splide and medium-zoom libraries.",
        "main": "index.js",
        "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
        },
        "keywords": [
        "hexo",
        "theme-next",
        "plugin",
        "splide",
        "carousel",
        "medium-zoom"
        ],
        "author": "Siriusq",
        "homepage": "https://github.com/Siriusq/hexo-splide-carousel",
        "bugs": "https://github.com/Siriusq/hexo-splide-carousel/issues",
        "repository": {
        "type": "git",
        "url": "git://github.com:Siriusq/hexo-splide-carousel.git"
        },
        "license": "MIT"
        }
    • 懒得在这里一项一项填写的话就直接输入npm init -y,这个命令会直接生成一个默认的package.json
  3. 初始化完毕后,文件夹中会出现index.jspackage.json两个文件,index.js是插件的入口文件。

开发

这部分内容比较长,是我的插件中相关文件的说明,仅供参考,毕竟我也是第一次开发Hexo的插件,难免出现纰漏。

在开发前建议完整阅读官方文档

我的插件属于标签类的,Hexo在生成静态文件的过程中,如果检测到markdown文件中存在相应的标签(如 {% splide %}),就会将标签及其中的内容转换为我们指定的html结构。

如果你的插件类型不同,可以到下面的网站寻找类似插件,照猫画虎。

文件结构

我的插件结构如下,并不是很规范,比如splide-init.js我就不知道应该放在哪里,因为我也没找到相关的规范,就仿照了其他看起来文件结构比较规范的插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
hexo-splide-carousel
├─index.js
├─package.json
├─LICENSE
├─README.md
├─.npmignore
├─lib
│ ├─scripts
│ │ ├─splide-init.js
│ │ └─tags
│ │ └─splide-tag.js
│ └─assets
│ └─splide-custom.css
└─ReadmeAssets
├─CNREADME.md
└─preview.jpg

index.js

前面已经说过,这个文件是插件的入口文件,也就是主文件,它在我插件中的功能为:

  • 注册标签
  • 引入模块
  • 向页面中注入js脚本及css样式

注册标签

标签插件可以帮助开发者在文章中快速插入内容。相关文档:标签插件(Tag)

我为插件注册了两个标签:splidesc

1
2
3
4
5
6
7
8
9
10
11
12
13
// 注册 Hexo 自定义标签及其简写
const splideTag = require('./lib/scripts/tags/splide-tag')(hexo);
hexo.extend.tag.register(
"splide",
splideTag,
{ ends: true },
);

hexo.extend.tag.register(
"sc",
splideTag,
{ ends: true },
);
  • const splideTag = require('./lib/scripts/tags/splide-tag')(hexo);
    • require('./lib/scripts/tags/splide-tag'):这里我将标签函数作为模块单独引入,方便后期修改,模块的路径为./lib/scripts/tags/splide-tag.js
    • (hexo):如果你的标签函数中调用了hexo的配置文件等,就需要传递hexo实例到函数中。
  • hexo.extend.tag.register用于标签注册。
    • 第一个参数为标签名称,也就是在markdown文件中使用的标签。
    • 第二个参数为标签函数,因为我把它作为模块引入了,所以这里很简洁。
      • 如果你的标签函数比较简单,也可以直接填入函数function (args, content) { // ... },
    • 第三个参数是标签选项,Hexo共提供了两个选项,ends和async,两者默认都是false状态。
      • { ends: true },表示使用结束标签。反映在md文件中,就是使用end标签名称来表示标签结束。

加载文件

首先加载Hexo提供的文件IO模块,详细用法请查看hexo-fs

1
2
const fs = require('fs');
const path = require('path');

然后通过fs模块加载其他文件

1
2
3
// 加载 lib 中的文件
const splideInit = fs.readFileSync(path.resolve(__dirname, "./lib/scripts/splide-init.js"), { encoding: "utf8" });
const customStyle = fs.readFileSync(path.resolve(__dirname, "./lib/assets/splide-custom.css"), { encoding: "utf8" });

自定义配置

我在插件中开放了一些自定义功能,比如选择依赖使用的CDN供应商,调整组件的样式等,为了让用户更方便的进行调整,就需要在Hexo的配置文件中添加这些配置选项。

需要注意_config.yml中使用的缩进均为两个空格。

下面以自定义CDN为例:

1
2
3
# _config.yml
splide:
cdn: unpkg # 可选项:unpkg, cdnjs, jsdelivr

然后就可以在index.js中读取配置。

1
const cdn = hexo.config.splide.cdn || 'unpkg';

为了防止用户将选项留空,建议设置一个默认选项,|| 'unpkg'就表示如果配置文件中未指定CDN,则使用默认CDN供应商unpkg。

注入器

注入器被用于将静态代码片段注入生成的 HTML 的 <head><body> 中。 Hexo 将在 after_render:html 过滤器之前完成注入。相关文档:注入器(Injector)

前面我们只是读取了js与css文件,但是它们的代码需要被注入到生成的 HTML 中才能生效。以注入自定义样式为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 前面引入的css样式
const customStyle = fs.readFileSync(path.resolve(__dirname, "./lib/assets/splide-custom.css"), { encoding: "utf8" });

// 将样式注入到 HTML 中
hexo.extend.injector.register(
"head_begin",
() => {
return `
<style type="text/css">${customStyle}</style>
`;
},
"default"
);
  • 第一个参数是注入代码的位置。
    • head_begin: 注入在 <head> 之后(默认)
    • head_end: 注入在 </head> 之前
    • body_begin: 注入在 <body> 之后
    • body_end: 注入在 </body> 之前
  • 第二个参数是需要注入的代码片段。
  • 第三个参数是注入代码的页面范围。
    • default: 注入到每个页面(默认值)
    • home: 只注入到主页
    • post: 只注入到文章页面
    • page: 只注入到独立页面
    • archive: 只注入到归档页面
    • category: 只注入到分类页面
    • tag: 只注入到标签页面

splide-tag.js

./lib/scripts/tags/splide-tag.js就是前面作为模块引入的标签函数,文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 生成html结构
module.exports = hexo => function (args, content) {
// 从Hexo配置中获取全局默认设置
const globalSplideOptions = hexo.config.splide.options || {};
// 解析标签中的局部设置,比如 {% splide type:'loop' autoplay:true %}
const splideSettings = args.reduce((acc, arg) => {
const [key, value] = arg.split(':');
acc[key] = value;
return acc;
}, {});
// Markdown 中的每行图片
const lines = content.trim().split('\n');

// 省略内容

// 生成最终 HTML,如果至少有一张图片有简介,才生成简介部分以及id
return `
<div class="carousel-container">
// 省略内容
</div>
`;
};

标签函数会传入两个参数:argscontent。在md文件中,我的标签结构为:

1
2
3
{% splide type:'loop' autoplay:true %}
![alt](url)
{% endsc %}
  • args 包含传入标签插件的参数,即上面的type:'loop' autoplay:true
  • content 是标签插件所覆盖的内容,即![alt](url)

其他文件

splide-init.js的作用为监听页面事件,并判断是否初始化轮播组件。
splide-custom.css会应用我自定义的样式,覆盖部分Splide的默认样式。

本地测试

在开发过程中我们需要频繁的进行测试,来确保插件正常工作。

  1. 首先在博客项目中安装我们的插件,安装方法有多种,包括但不限于:
    • (推荐)在博客根目录执行命令npm install 插件的路径来安装我们的插件,这个命令相当于创建了一个链接,我们在插件项目的文件夹中对文件做出的改动会实时同步到博客根目录/node_modules/插件名中。
    • 如果你不打算上传插件的话,也可以直接将插件文件夹移动到./node_modules路径下,然后手动在博客根目录下的./package.json中的dependencies下添加我们的插件及其版本,如"hexo-splide-carousel": "^1.0.0",。如果是最后一行,记得在前一行末尾添加逗号并删除自身的逗号。
  2. 如果你的插件有自定义配置项,记得在Hexo的配置文件_config.yml中添加这些配置项。
  3. 然后就是老一套的hexo clhexo s测试了。

发布

在插件制作完成后,我们可以选择将它上传到npm,这样其他人也可以安装使用我们制作的插件。

  1. 创建README文件,介绍插件的作用以及使用方法,更多内容详见官方文档 About package README files
  2. 如果有需要忽略的文件,可以创建.npmignore文件,写法与.gitignore类似,一些常见文件如.git文件夹以及README文件是默认忽略的,不需要添加到.npmignore文件中。详见官方文档 Keeping files out of your Package
  3. 完善package.json
  4. npm官网注册一个账号。
  5. 在插件目录执行npm login,然后回车跳转到浏览器中登录账号。
  6. 执行npm publish,然后回车跳转到浏览器中验证身份。
  7. 在npm首页搜索自己的插件名就可以找到刚刚发布的插件了。

更新

如果在插件中发现了新的Bug或者添加了新的功能,就需要将更新后的插件再次推送到npm。

  1. 执行npm version 版本号
    • 初始化后的默认版本号是1.0.0
    • 如果更新内容主要是Bug修复,那么只需要增加最后一位数字,如1.0.1
    • 如果添加了向后兼容的新功能,则改动中间的数字,如1.1.0
    • 如果改动非常大,添加了不能向后兼容的新功能,则改动第一位数字,如2.0.0
    • 有关版本号的详细规则,请查看官方文档 About semantic versioning
  2. 执行npm publish,然后回车跳转到浏览器中验证身份,然后等待终端中的命令执行完成。

Hexo收录

插件发布后,我们还可以将插件推送到Hexo官方的插件列表中,方便更多人查找使用。

  1. Fork 一份 hexojs/site 到自己的仓库。
  2. 将 fork 后的仓库 clone 到本地。
  3. ./source/_data/plugins/中新建一个yaml文件,文件名同你的插件名。
  4. 在新建的文件中使用英语填写相关内容,示例如下:
    1
    2
    3
    4
    5
    6
    description: Server module for Hexo.
    link: https://github.com/hexojs/hexo-server
    tags:
    - official
    - server
    - console
  5. 将做出的更改推送到自己的仓库。
  6. 开启新的 pull request。
  7. 在 title 中描述做出的修改,如Plugin: add plugin hexo-splide-carousel
  8. 勾选 Check List 中的相关选项。
  9. 提交后等待 Hexo 完成相关检查即可。
  10. 最后你的插件就会出现在Hexo官网的插件列表中。

参考及引用

  1. hexo-插件开发规范@一人の境
  2. Hexo 插件开发步骤
  3. Hexo API