前言
笔者维护的 VitePress 博客主题在近1年多的时间里集成了非常多功能,不少用户希望将里面的部分功能分离出来,方便在其它 VitePress 站点也可以独立使用。
其中分离的第一个组件类型的就是呼声较高的 公告插件。
最终效果如下:
接下来将介绍一下用法,讲解一下实现原理,提供一个插件模板供大家快速开发同类型插件。
如何使用
只需要 2 步:
- 安装插件
pnpm/npm/yarn
均可,笔者比较偏好 pnpm
pnpm add vitepress-plugin-announcement
- 配置插件
引入插件在 .vitepress/config.ts
VitePress 配置文件中
import { defineConfig } from 'vitepress' import { AnnouncementPlugin } from 'vitepress-plugin-announcement' export default defineConfig({ vite: { // ↓↓↓↓↓ plugins: [ AnnouncementPlugin({ title: '标题', body: [ { type: 'text', content: '文本内容' }, { type: 'image', src: '图片' } ], footer: [ { type: 'button', content: '按钮', link: 'https://sugarat.top' }, ], }) ] // ↑↑↑↑↑ } })
目前支持 文本/图片/按钮
三种类型。
实现原理
这里只阐述关键点,详细部分将计划单独拆一篇文章进行讲解(完整的搭配插件模板 0 - 1 实现)
注入自定义组件
在 VitePress 官方文档自定义板块 中可以了解到,
VitePress 入口的组件是在 .vitepress/theme/index
中导出的 Layout.vue
组件。
同时 VitePress 默认主题的 Layout 已经提供了许多可以直接使用的插槽,于是乎,咱们可以把组件直接放入这些位置即可。
VitePress 是由 Vite 驱动,所以按照 Vite 插件的思路去拓展即可。
这里利用插件的 transform 钩子 去处理。
const pluginOps = { name: 'vitepress-plugin-announcement', transform(code, id) { // 具体处理逻辑 } }
将我们的组件插入到默认主题的 Layout.vue 里即可。
期望插入后的代码如下
<script setup lang="ts"> import Announcement from './Announcement.vue' // [!code ++] </script> <template> <div> <slot name="layout-top" /> <Announcement /> // [!code ++] </div> </template>
下面是具体的处理逻辑,简单字符串替换插入我们需要的内容即可
const pluginOps = { name: 'vitepress-plugin-announcement', enforce: 'pre', transform(code, id) { // 筛选出 Layout 文件 if (id.endsWith('vitepress/dist/client/theme-default/Layout.vue')) { // 插入自定义组件调用代码 const slotPosition = '<slot name="layout-top" />' let transformResult = code.replace(slotPosition, `${slotPosition}<Announcement/>`) // 导入自定义组件导入代码 const setupPosition = '<script setup lang="ts">' transformResult = transformResult.replace(setupPosition, `${setupPosition}nimport Announcement from './Announcement.vue'`) return transformResult } } }
同时通过 enforce 参数控制插件顺序,这里需要尽可能的提前执行,处理源代码。
接下来就是处理 import
的导入,如果不做处理,Layout.vue
里是找不到这些组件,就一定会报错。
import Announcement from './Announcement.vue' // [!code ++]
于是这里,再利用 config 钩子,传入 resolve.alias 配置 即可。
// 构造插件里实际组件的路径 const aliasComponentFile = `${getDirname()}/components/Announcement.vue` const pluginOps = { config() { return { resolve: { alias: { './Announcement.vue': aliasComponentFile } } } } }
至此自定义组件的注入就算是完成了,接下来介绍如何将外部的参数传入到组件里。
插件配置传递
方式有很多种,笔者这里采用改动最小,使用较为广泛的一种 虚拟模块 。
官方示例里已经介绍了如何使用,这里不做赘述。
组件中导入配置代码
<script lang="ts" setup> import announcementOptions from 'virtual:announcement-options' </script>
插件中处理虚拟模块逻辑。
import { stringify } from 'javascript-stringify' function AnnouncementPlugin(options) { const virtualModuleId = 'virtual:announcement-options' const resolvedVirtualModuleId = ` ${virtualModuleId}` const pluginOps = { name: 'vitepress-plugin-announcement', enforce: 'pre', resolveId(id) { if (id === virtualModuleId) { return resolvedVirtualModuleId } }, load(this, id) { if (id === resolvedVirtualModuleId) { // 虚拟模块处理 return `export default ${stringify(options)}` } }, } return pluginOps }
tips:由于 JSON.stringify 默认不会处理函数,这里就偷懒用一下社区的库 javascript-stringify 进行处理。
如果要用 JSON.stringify 处理,需要传入第二个 replacer
参数,对函数做特殊处理。
至此,关键的两步就算搞定了,剩余的工作就是具体组件的样式和交互实现开发了,按常规 Vue 组件开发即可。
插件模板介绍
在开发插件的过程中,笔者把此类基于 slot 位置注入的插件分离了一个模板 vitepress-plugin-slot-inject-template
目录结构
├── scripts # 构建相关脚本,无特殊需求可以不用修改 | ├── copyComponents.mjs | └── watchAndCopy.mjs ├── src | ├── components # 组件实现 | ├── index.ts # 插件入口 | ├── type.ts # 插件配置参数类型定义 | └── util.ts # 插件使用的工具函数 ├── README.md ├── package.json └── tsconfig.json
参考了 VitePress 默认主题中部分实现。
使用步骤
- 插件入口
src/index.ts
,修改一下插件&组件命名信息和组件插入位置。 - 按照实际需求编写组件实现。
- 完善 README.md 使用文档后发布 npm 包即可。
最后
样式实现参考了 reco-1.x 主题 中的 @vuepress-reco/vuepress-plugin-bulletin-popover 插件
后续继续按照 vitepress 插件开发计划 推进,将开发过程内容整理成教程与插件模板。