自定义
本页讲解如何给博客加自己的东西:自定义 JS/CSS、注入点机制、自定义 tag 插件,以及主题配置的覆盖方式。改代码属于"开发级"操作,动手前先读「安装与主题配置」篇了解目录结构,改完按「部署」篇选择发布方式。
核心原则:能不改主题源码就不改。优先用 inject_point 注入和配置覆盖,主题升级(子模块
themes/matery/)时才不会被冲突拖累。
1. 自定义 JS/CSS
用途:给全站或单页追加脚本与样式,最常见的一类定制。
方式一:放 source/ 下,页面里引用(无需改主题源码):
<!-- 文章/页面正文里直接引用 -->
<link rel="stylesheet" href="/js/my-custom.css">
<script src="/js/my-custom.js" defer></script>- 文件放
source/js/、source/css/等任意目录,构建时原样复制到public/对应路径 - 注意
_config.yml的skip_render段:source/下的部分目录(如nav/、docs/、live2d/)会跳过渲染,自定义 HTML 放这些目录时保持原样;普通.js/.css不受影响 - 生产环境走 CDN 时,静态资源 URL 由主题配置
cdn段决定(见「全局配置」篇),自定义文件直接/js/xxx.js引用即可,走站点自身域名
方式二:注入点全局注入(推荐,见下节)——脚本/样式会在所有页面生效,且不污染主题文件。
2. inject_point 注入机制
用途:在不修改主题模板的前提下,向页面的固定位置插入 HTML/JS/CSS。主题从 NexT 继承了一套注入系统,layout/layout.ejs 中实际使用了 3 个视图注入点:
<!-- layout.ejs -->
<%- inject_point('bodyBegin') %> <!-- <body> 开头 -->
<%- inject_point('header') %> <!-- 页头位置 -->
<%- inject_point('bodyEnd') %> <!-- </body> 之前 -->怎么用:注入内容不是从主题配置加载的 HTML 字符串,而是由 hexo 脚本通过 theme_inject filter 注册到运行时注册表——scripts/events/lib/injects.js 在生成前执行 hexo.execFilterSync('theme_inject', injects),把结果写入 theme.config.injects;inject_point helper(scripts/helpers/engine.js)渲染时读取该注册表并输出。
以主题真实写法(scripts/filters/default-injects.js)为模板,自定义注入放在主题 scripts/ 下任意新文件(如 scripts/filters/my-injects.js):
// themes/matery/scripts/filters/my-injects.js
'use strict';
const path = require('path');
hexo.extend.filter.register('theme_inject', function(injects) {
// file(name, 文件路径):注册模板文件(name 无扩展名时取文件扩展名,路径相对 hexo 根目录)
injects.bodyEnd.file('my-custom', path.join(hexo.theme_dir, 'layout/_partial/my-custom.ejs'));
// raw(name, 内容字符串):直接注册 HTML/JS 内容
injects.bodyEnd.raw('my-analytics', '<script src="/js/my-custom.js" defer></script>');
}, -99); // -99 = 最先执行,与 default-injects.js 保持一致模板文件 layout/_partial/my-custom.ejs 的内容会被原样渲染进 <%- inject_point('bodyEnd') %> 所在位置。injects.<point>.file(name, path, locals, options, order) 第三个参数起依次为 locals/options/order,order 控制同一点多个注入的排序。
bodyEnd是最常用的注入点:追加全局脚本、统计代码、悬浮组件- 主题定义了 12 个视图注入点(
headEnd/header/bodyBegin/bodyEnd/footer/postMetaTop/postMarkdownBegin/postMarkdownEnd/postCopyright/postComments/pageComments/linksComments),12 个点均已在模板中调用:headEnd(head.ejs)、footer(footer.ejs)、postMetaTop/postMarkdownBegin/postMarkdownEnd/postCopyright/postComments(post-detail.ejs)、bodyBegin/header/bodyEnd(layout.ejs)、pageComments(bb/contact/msg)、linksComments(friends)。未在模板中调用的仅postMetaBottom/postLeft/postRight(无注入时返回空字符串,评论页用inject_point('pageComments') || partial('_partial/comments')做回退) - 样式注入点(
variable/mixin/style)是另一套机制:注册的是.styl文件路径,由 Stylus 编译期注入,与视图注入点不同,不要混用 - 注入内容原样输出,写
<script>时建议带defer(见「性能优化」篇 LCP 分层原则)
3. 自定义 tag 插件
用途:在 Markdown 里用短代码生成复杂 HTML。主题内置 12 个 tag 插件(note/tabs/timeline/mermaid/pdf/github-card 等,用法见「Tag 插件」篇),不够用时可自己写。
实现:在 themes/matery/scripts/tags/ 下新增文件,导出一个 Hexo tag 函数:
// themes/matery/scripts/tags/mybox.js
'use strict';
function mybox(args, content) {
const title = args.join(' ') || '提示';
return `<div class="my-box"><strong>${title}</strong><div class="my-box-body">${hexo.render.renderSync({ text: content, engine: 'markdown' })}</div></div>`;
}
hexo.extend.tag.register('mybox', mybox, { ends: true });文章中使用:
{% mybox 注意事项 %}
这里写**任意 Markdown**,会被渲染为卡片内容。
{% endmybox %}{ ends: true }表示需要{% endmybox %}闭合标签content默认是原始文本,需要渲染 Markdown 时用hexo.render.renderSync(注意这是一个 Hexo 内部 API,仅在构建时执行)- 文件命名即插件名(
mybox.js→{% mybox %}),改完必须hexo clean && hexo generate(见「常见问题」篇缓存不生效) - 想全局可用且不动主题源码:把文件放进主题
scripts/是唯一方式(主题是子模块,改动需双仓库提交,见「部署」篇)
4. 主题配置覆盖(模板注入机制)
用途:主题配置在 CI 构建时被动态生成——userConfig/_config.tmp.yml 是模板,构建时复制为 themes/matery/_config.yml 并替换占位符。改主题配置必须改模板,直接改 themes/matery/_config.yml 会被覆盖。
模板占位符(tools/cicd.sh 用 sed 替换):
| 占位符 | 替换为 | 示例 |
|---|---|---|
{cdnPathVersion} | jsDelivr 带版本路径 | https://cdn.jsdelivr.net/gh/appotry/hexo@1.1 |
{cdnPathLatest} | latest CDN 路径 | https://cdn.jsdelivr.net/gh/appotry/hexo@latest |
{urlVersion} | 文件版本号 | ?v=12.19 |
{cdnUrl} / {mediaUrl} / {resUrl} | 各 CDN 域名 | https://cfblog.17lai.site |
自定义配置项的完整链路(以加一个"我的开关"为例):
- 模板
userConfig/_config.tmp.yml加键:myFeature: enable: true text: 你好 - Stylus 里读(
themes/matery/source/css/下):$my-feature-color = theme-config("myFeature.text", "默认值") - 模板里读(
layout/下 EJS):<% if (theme.myFeature.enable) { %> <div><%= theme.myFeature.text %></div> <% } %> - 发布:改配置后
tools/cicd.sh -r "特性"递增版本号(配置属于代码侧改动,见「部署」篇发布流程选择)
注意:userConfig/_config.tmp.yml 同时控制 CI 开关——all_minifier 压缩开关、图片 URL 处理(imgurl.sh --weserv2githubpage)、PWA Service Worker 版本号(userConfig/sw.tmp.js 生成 source/sw.js)都在构建流水线里处理,改模板前先 grep 确认键名没有被构建脚本占用。
附:自定义速查表
| 需求 | 方式 | 文件位置 |
|---|---|---|
| 单页脚本/样式 | Markdown 里 <link>/<script> 引用 | source/js/、source/css/ |
| 全站脚本/样式 | inject_point 注入 | themes/matery/scripts/filters/ |
| 新短代码 | tag 插件 | themes/matery/scripts/tags/ |
| 主题配置项 | 模板加键 + Stylus/EJS 读取 | userConfig/_config.tmp.yml |
| 全局 HTML 片段 | 注入点或 _partial/ 模板 | 配置或 themes/matery/layout/_partial/ |