关于 Hexo 如何开发主题包的教程在已经是大把的存在了,这里就不在赘述了。这边文章主要讲的是作为一个主题的开发者,如何让你的主题具有更好的扩展性,在用户自定义修改主题后,能够更加平易升级主题。

问题所在

Hexo 提供两种方式安装主题包:

  • 直接在 themes 目录下直接存放主题包文件,这种方式方便用户自己魔改主题,魔改后升级主题会比较困难
  • 通过 npm 安装主题包,这种方式更加方便用户升级主题,但是不易扩展

当用户想要自定义修改主题时,基本上只能通过第一种方式安装,然后通过修改 源代码 形式去修改主题。这样带来的问题就是,当主题修复一些 bug 或者主题迭代 N 个版本后,用户想升级主题时就会变的比较麻烦。

有没有能让用户方便升级,又能提供一定个性化的能力的?答案是有的,那就是通过 npm 方式分发主题包,然后通过一些魔法,让其有一定的扩展能力,这篇文章就来讲解如何实现它。

模板

在 Hexo 中,主题的模板决定的网站页面程序的方式,当你不同页面结构很相似时候,可以通过布局(Layout)去复用相同的结构,而相似的部分可以抽离成通用局部模板,通过使用 Partial 去加载,以达到模板复用的效果。

这就是 Hexo 在开发主题处理模板复用的方式,可把一个个局部模板理解为一个个独立的组件,哪里需要是就在哪里加载它。如果说把用户想替换某一个局部模板,然后让用户提供一个新的模板,然后我们去加载这个新的模板,那是不是达到在用户不修改源代码情况下对主题进行个性话的扩展呢。

Partial

要想知道 Hexo 是如果加载局部模板的,我们翻看下 Hexo 源码里 Partial 的实现(/plugins/helper/partial.js),可以看到当通过调用 ctx.theme 获取到对应的 view,然后调用 render 渲染的。

const { dirname, join } = require("path");

module.exports = (ctx) =>
function partial(name, locals, options = {}) {
const viewDir = this.view_dir;
const currentView = this.filename.substring(viewDir.length);
const path = join(dirname(currentView), name); // 根据当前路径找到,局部模板路径
const view = ctx.theme.getView(path) || ctx.theme.getView(name); // 根据路径去匹配 view
const viewLocals = { layout: false };
// Partial don't need layout
viewLocals.layout = false;
return view.renderSync(viewLocals);
};

Hexo 对文件处理分为两种,一种是 source 目录文件处理,一种是对主题包里文件处理。在辅助函数注册里可以看 ctx 其实就是 hexo 运行时的实例,上面的 ctx.theme 就是主题文件处理的 Box。通过 Hexo 提供 api 可以看到,它不仅提供了 getView,还提供了 setViewremoveView 方法。

然后翻看 setView 代码,可以看到当你重新设置一个新的 view 时,它会覆盖掉已有的 view。也就是说我们可以直接覆盖主题里的 局部模板


setView(path, data) {
const ext = extname(path);
const name = path.substring(0, path.length - ext.length);
this.views[name] = this.views[name] || {};
const views = this.views[name]; views[ext] = new this.View(path, data);
}

修改示例

我们以覆盖 hexo-theme-async 为示例,在生成前钩子 generateBefore 里,覆盖掉主题里默认的侧栏模板。

hexo.on("generateBefore", () => {
hexo.theme.setView("_partial/sidebar/index.ejs", "<div>111</div>");
});

运行起来会发现侧栏模板已经替换成我们写的 111 了。

主题实现

通过上面方式确实可以达到覆盖主题默认模板能力,但是让用户直接修改会很不友好,需要自己去看主题中局部模板的路径信息,并且还需要自己编写加载文件内容,覆盖主题默认模板逻辑。

我们可以将这部分操作内置进入主题内,然后只需要让用户编写自己的模板,以及告诉我们需要替换对应模板即可。大致流程如下:

我们还可以提供默认配置,简化通过路径覆盖

可以通过在配置中配置好主题中使用的局部模板,类似这样,将主题中使用的局部模板以配置形式展示。

layout:
path: layout
# layout
main: _partial/main
header: _partial/header
banner: _partial/banner
sidebar: _partial/sidebar/index
footer: _partial/footer

然后在加载局部模板时,直接读取配置的信息,当用户覆盖掉了 layout.header 时候,主题就会自动使用新的模板了。

<%- partial(theme.layout.header) %>

模板加载实现

根据上面配置,约定 layout.path 配置指向目录为用存在模板目录,以便可以自定义存放路径。

layout:
path: layout

首先就是根据配置获取模板存在的绝对路径,可以根据 hexo 实例,获取到根目录,拼接出完整路径位置。

const { resolve } = require("path");
const layoutDir = resolve(hexo.base_dir, hexo.theme.config.layout.path);

然后是对文件目录的监听,这个可以直接使用 hexo-fs ,避免安装额外的依赖包,提供了新增、删除、修改、文件夹变动的监听,可以针对不同事件做出不同操作。

const { watch } = require("hexo-fs");

watch(layoutDir, {
persistent: true,
awaitWriteFinish: {
stabilityThreshold: 200,
},
}).then((watcher) => {
watcher.on("add", (path) => /** 设置模板 */);
watcher.on("change", (path) => /** 设置模板 */);
watcher.on("unlink", (path) => /** 移除模板 */);
watcher.on("addDir", (path) => /** 添加文件夹,递归遍历设置模板 */);
});

因为我们上面是通过配置去加载模板的,所有为了避免用户自定义的模板名称会与主题的模板名称冲突,导致覆盖了主题的模板,我们可以在使用时增加一个约定的前缀,避免重名。我们对设置模板进行简单封装

const setView = (fullpath) => {
const path = "async" + fullpath.replace(layoutDir, ""); // 约定固定前缀为 async
hexo.theme.setView(path, readFileSync(fullpath, { encoding: "utf8" }));
};

上面处理方式,用户自定义模板,可以正常加载使用的,但是当自定义的模板又引入了其他模板时会存在一个问题,在有的模板引擎中会出现路径不正常。通过查看 view 实例信息,可以看到其指向目录是在 node_modules,而实际上是存在根目录的。

翻看 view 源码可以看到 source 是获取的 this._theme.base ,而 this._theme.base 往上找就 theme_dir,也就是主题存放的目录,最后又通过 renderer.compile 设置模板渲染到,导致传入 path 不正确。

知道了原因我对上面代码进行修正,设置后重新获取到 view,然后手动根据路径信息。

const setView = (fullpath) => {
const path = "async" + fullpath.replace(layoutDir, ""); // 约定固定前缀为 async
hexo.theme.setView(path, readFileSync(fullpath, { encoding: "utf8" })); const view = hexo.theme.getView(path);
view.source = fullpath; // 修正原文件路径
view._precompile(); // 重新调用渲染器的初始化
};

然后将上面操作,放置在在 Hexo 的 generateBefore 中:

const { resolve } = require("path");
const { watch, readdirSync, statSync } = require("hexo-fs"); hexo.on("generateBefore", () => {
const layoutDir = resolve(hexo.base_dir, hexo.theme.config.layout.path); const setView = (fullpath) => {
const path = "async" + fullpath.replace(layoutDir, ""); // 约定固定前缀为 async
hexo.theme.setView(path, readFileSync(fullpath, { encoding: "utf8" }));
const view = hexo.theme.getView(path);
view.source = fullpath; // 修正原文件路径
view._precompile(); // 重新调用渲染器的初始化
}; watch(layoutDir, {
persistent: true,
awaitWriteFinish: {
stabilityThreshold: 200,
},
}).then((watcher) => {
watcher.on("add", (path) => setView(path));
watcher.on("change", (path) => setView(path));
watcher.on("unlink", (path) => {
const path = "async" + path.replace(layoutDir, "");
hexo.theme.removeView(path);
});
watcher.on("addDir", (path) => loadDir(path));
}); const loadDir = (base) => {
let dirs = readdirSync(base);
dirs.forEach((path) => {
const fullpath = resolve(base, path);
const stats = statSync(fullpath);
if (stats.isDirectory()) {
loadDir(fullpath);
} else if (stats.isFile()) {
setView(fullpath);
}
});
}; loadDir(layoutDir);
});

到此主要功能以及实现了,其他待优化项这里就不描述了,可以看看完整实现源码。

使用示例

以为 hexo-theme-async 为例,在根目录新建 layout 目录,然后添加 sidebar.ejs 文件,结构如下:

┌── blog
│ └── layout
│ └── sidebar.ejs
│ └── scaffolds
│ └── source
│ └── themes

sidebar.ejs 添加一点内容

<div>111</div>

然后在 _config.async.yml 中修改 layout 配置,替换掉默认 sidebar 模板。

layout:
sidebar: async/sidebar

运行起来后,可以看到效果和 修改示例 中的效果一样,但是简化了用户使用。

结语

通过上面方式,可以在使用 npm 安装主题时,也支持自定义替换部分区域,来个性化的目的,当主题版本迭代升级后,也更方便用户更新升级。

完整实现源码可以参考 hexo-theme-async 中源码。

Hexo 主题开发之自定义模板的更多相关文章

  1. Hexo主题开发

    序章 想要一个自己的知识管理系统,用了 Hexo ,但是没有发现自己心仪的主题,就自己做了一个.本文记录了制作的全过程.本人编码功底和前端知识并不是特别雄厚,希望能由此文引出各路大神的兴趣,以后制作出 ...

  2. WordPress 主题开发 - (十三) Archive模板 待翻译

    What archive.php does (and all its related templates) is show posts based on a select criteria. A da ...

  3. WordPress 主题开发 - (八) Head模板 待翻译

    THE WORDPRESS THEME HEADER TEMPLATE Now we get into the nitty-gritty: building up your header.php an ...

  4. Struts2的模板和主题theme及自定义theme的使用

    Struts2的模板和主题theme及自定义theme 标签: struts2 2016-03-29 11:22 190人阅读 评论(0) 收藏 举报  分类: javaweb(8)  Struts2 ...

  5. h5 录音 自动生成proto Js语句 UglifyJS-- 对你的js做了什么 【原码笔记】-- protobuf.js 与 Long.js 【微信开发】-- 发送模板消息 能编程与会编程 vue2入坑随记(二) -- 自定义动态组件 微信上传图片

    得益于前辈的分享,做了一个h5录音的demo.效果图如下: 点击开始录音会先弹出确认框: 首次确认允许后,再次录音不需要再确认,但如果用户点击禁止,则无法录音: 点击发送 将录音内容发送到对话框中.点 ...

  6. pythonDjango开发-自定义模板标签

    1.在APP同级目录新建文件夹'templatetags' 并在文件夹下创建__init__.py和定义标签用的customer.py文件 2.在customer.py文件中定义自定义标签 from ...

  7. hexo干货系列:(二)hexo主题下载及配置

    前言 上一篇文章介绍了hexo+gitHub简易搭建属于自己的个人独立博客,但是主题是默认的landscape,略显简单,今天的教程推荐Jacman主题. Jacman是一款为Hexo打造的一款扁平化 ...

  8. 黄聪:《跟黄聪学WordPress主题开发》

    又一个作品完成!<跟黄聪学Wordpress主题开发>,国内最好的Wordpress主题模版开发视频教程!! 目录预览: WordPress官方源文件层式结构讲解 WordPress数据库 ...

  9. 从零开始制作 Hexo 主题

    原文地址:从零开始制作 Hexo 主题 · Ahonn 写在前面 本文将会从零开始开发一个简单的博客主题.样式主要参考 Hexo theme 中的 Noise 主题. 开始之前你需要了解: 模板引擎  ...

  10. wordpress 主题开发

    https://yusi123.com/3205.html https://themeshaper.com/2012/10/22/the-themeshaper-wordpress-theme-tut ...

随机推荐

  1. Vue【原创】千位符输入框(不仅只是过滤器哦)

    最近和一个做金融的朋友讨论到千位符输入的问题,后来一想貌似自己项目中也会经常碰到金额数字这种输入框,要么自己做一个吧. 首先肯定要有一个正则表达式,也就是过滤器的方案里面常用的正则: 1 filter ...

  2. CTFshow misc1-10

    小提示:需要从图片上提取flag文字,可以通过截图翻译或者微信发送图片,这两个的ai图像识别挺好用的. misc1: 解压打开就能看见flag,提取出来就行 misc2: 记事本打开,看见 ng字符, ...

  3. sql-labs--Less-1--Error based-Single quotes

    sql="SELECT * FROM users WHERE id='id' LIMIT 0,1"; 打开第一关,我们看到如下界面,上面写着Please input the ID ...

  4. containerd镜像拉取配置

    背景: 公司要求部署最一套新版的k8s系统来部署生产应用,说实话很头疼.因为k8s自1.23版本之后就用不docker作为容器的默认运行时了,而是采用的containerd,这就带来了一系列的问题.没 ...

  5. STL容器:map

    map 可以当作特殊的数组来使用,在数组开不下,或者数组下标不是整数的时候使用 map 就很方便,比如统计字符串的出现个数,统计 int 范围内的数的出现次数等等. 映射是指两个集合之间的元素的相互对 ...

  6. mooc第五单元《管理组织》单元测试

    第五单元<管理组织>单元测试     返回 本次得分为:30.00/50.00, 本次测试的提交时间为:2020-08-30, 如果你认为本次测试成绩不理想,你可以选择 再做一次 . 1 ...

  7. 数字逻辑笔记 全加器全减器8421BCD转余3

    二进制全加器 全减器 十进制加法 8421BCD转余3码

  8. Mach-O Inside: 命令行工具集 otool objdump od 与 dwarfdump

    1 otool otool 命令行工具用来查看 Mach-O 文件的结构. 1.1 查看文件头 otool -h -v 文件路径 -h选项表明查看 Mach-O 文件头. -v 选项表明将展示的内容进 ...

  9. Java技术_基础技术(0003)_类执行顺序详解+实例(阿里面试题)+详细讲解+流程图

    类加载机制 加载.验证.准备.初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的 ...

  10. 如何将word格式的文档转换成markdown格式的文档

    如何将word格式的文档转换成markdown格式的文档 如何将word格式的文档转换成markdown格式的文档 前言 A. 介绍Markdown和Word格式文档 什么是Markdown? Mar ...