插件体系 #
一个文档工具的扩展能力,通常从很小的需求开始。
也许你只是想加一个本地搜索。它需要先读取所有 Markdown,按标题切分内容,再生成搜索索引。到这里,事情都发生在构建过程中。
接着,网站里还要有搜索按钮和结果列表。它要响应键盘操作,跟随当前语言,并在读者选择结果后交给 Router 打开页面。这些需求又依赖 Vue 和浏览器能力。
从使用者的角度看,这始终只是一个功能:搜索。但在许多静态站点生成器里,它已经被拆成了几段需要分别接线的代码。
KawaPress 的插件体系,就是从这里开始设计的。
痛点:配置散落在多处 #
VitePress 提供了不少实用的扩展入口。你可以在配置中调整 Markdown,加入 Vite Plugin,也可以使用构建 Hook。到了最终网站,主题还能通过 enhanceApp 安装 Vue 插件和组件。
这些入口都很好用,也让 VitePress 保持了简单和灵活。但如果搜索同时用到了它们,你就要去好几个地方配置同一个功能,还要记住这些配置是怎样连在一起的。
功能只改 Markdown 时,这并不麻烦。等它还要生成数据、注册组件、导入样式和处理浏览器交互,插件作者就得自己把这些代码接起来,使用者也要照着说明,在配置和主题入口里分别安装。
所以,真正缺少的不是另一个 Hook,而是一种能告诉框架“这些代码都属于同一个插件”的办法。
KawaPress 插件规范定义 #
生成索引和显示搜索界面,本来就应该在不同的地方完成。KawaPress 不会把它们硬塞进同一个运行环境,而是把它们装进同一个插件包:
@kawapress/plugin-search
├─ Generator Plugin
│ └─ 扫描 Markdown,生成搜索索引
└─ Runtime Plugin
└─ 注册搜索组件、样式和交互KawaPress 把整个搜索能力称为一个 Plugin。这个 Plugin 可以包含两个互相配合的部分。
Generator Plugin 参与构建生成过程 #
Generator Plugin 在 KawaPress 读取配置、编译 Markdown 和构建站点时工作。它可以:
- 修改站点配置;
- 扩展 Markdown 语法;
- 调整每个页面的数据;
- 接入 Vite Plugin 或修改 Vite 配置。
提示容器只需要改变生成的 HTML,因此只用 Generator Plugin 就够了。本地搜索需要在这里生成索引,代码高亮也需要在这里把源码变成最终标记。
Runtime Plugin 参与页面运行环境 #
Runtime Plugin 在 KawaPress 创建 Vue App 和 Vue Router 时工作。它可以:
- 安装普通 Vue 插件;
- 注册全局组件;
- 导入运行时样式;
- 添加导航守卫和其他 Router 行为。
服务端渲染和浏览器都会加载 Runtime Plugin。搜索界面、代码复制和主题布局,都需要这一部分。使用浏览器 API 前,请先阅读 SSR 兼容性。
发布约定 #
一个发布到 npm 的 Plugin 使用默认入口公开 Generator Plugin。需要运行侧时,再公开一个 ./runtime-plugin 入口。KawaPress 从站点配置中的 Generator Plugin 取得它的名字,再检查同名包是否公开了 ./runtime-plugin。存在时,运行入口会被自动加载。
只参与构建的 Plugin 不需要公开 ./runtime-plugin。只参与页面运行环境的 Plugin 仍然需要默认入口,用 Generator Plugin 声明自己的稳定身份;它的 setup() 可以不注册任何 Hook。
站点始终只导入默认入口:
import searchPlugin from '@kawapress/plugin-search'
const plugins = [
searchPlugin(),
]不要让使用者再导入 @kawapress/plugin-search/runtime-plugin。生成与运行虽然发生在不同阶段,对外仍然是一次安装和一次配置。
KawaPress Plugin 推荐使用 TypeScript 开发,并直接发布 .ts、.vue 和 CSS 源码,不需要先运行一次打包工具。KawaPress 使用的 Vite 会完成最终的语法转换、依赖解析和浏览器构建。
具体的 package.json 写法、各入口的代码与发布检查,请阅读插件开发。
默认配置应当是一个足够好的产品 #
搜索、代码高亮、提示容器和主题都能独立安装以后,组合会更自由。但如果每个站点都要从零选择和连接它们,简单的文档站反而要先写一长串配置。
Preset 就是为了解决这个问题。
Preset 提供一套默认配置,并把多个 Plugin 组合成可以直接开始的完整体验:
Preset
├─ 默认配置
└─ 一组有明确顺序的 Plugin
├─ 主题
├─ Markdown 能力
├─ 搜索
└─ 其他默认能力nagi(凪)就是 KawaPress 内置的 Preset。它准备好文档布局、代码高亮、提示容器、搜索、UnoCSS 等能力,所以站点只需要很少的配置就能开始:
import { nagi } from 'kawapress/nagi'
export default nagi({
title: 'My Docs',
})这里没有一个拥有特殊权限的“官方主题层”。nagi 的界面由 Runtime Plugin 安装,它组合的每项能力也都是普通 Plugin。官方自己使用的,就是第三方可以使用的公开入口。
在 Preset 上继续添加插件 #
使用 nagi 以后,仍然可以继续加入独立能力。下面的配置保留 nagi 的全部默认体验,同时增加 MathJax:
import mathjaxPlugin from '@kawapress/plugin-mathjax'
import { nagi } from 'kawapress/nagi'
export default nagi({
plugins: [
mathjaxPlugin(),
],
})Preset 内置的 Plugin 会先加入,你在 plugins 中填写的内容随后加入。数组顺序就是执行顺序:后面的 Plugin 能看到前面已经完成的修改,也可以有意覆盖它们。
KawaPress 不使用数字优先级,也不在背后猜测插件依赖。配置文件展示的顺序,就是能力实际组合的顺序。
如果要设计完全不同的界面,可以不使用 Preset,直接从 defineConfig() 开始:
import { defineConfig } from 'kawapress'
import themePlugin from 'my-kawapress-theme'
import markdownPlugin from 'my-markdown-plugin'
export default defineConfig({
plugins: [
themePlugin(),
markdownPlugin(),
],
})自定义主题 Plugin 需要注册 KawaPress 渲染页面所需的布局组件。更换 Preset,就是更换最外层的配置函数;配置中没有另一份需要叠加维护的 presets 列表。
开始编写插件 #
本篇解释了 KawaPress 为什么这样组织插件。实际编写时,请阅读插件开发。那里按扩展位置列出了六个生成侧方法与两个运行侧方法,并给出可以直接参考的代码。

