如何编写 VuePress 插件
VuePress 2 的插件本质上是一个返回插件对象的函数。本页通过示例讲解插件的常见写法与生命周期钩子,帮助你在模板基础上扩展自己的功能。
本模板的所有插件都在 .vuepress/configs/plugins.ts 中集中注册。写好插件后,在该文件的 getPlugins() 数组里加一行即可。
1. 插件的基本形态
一个插件就是一个函数,接收 options 与 app,返回一个插件对象:
import type { Plugin } from 'vuepress/core'
export interface MyPluginOptions {
foo?: string
}
// 推荐用 definePlugin 获得更好的类型提示
import { definePlugin } from 'vuepress/core'
export default definePlugin<MyPluginOptions>((options = {}, app) => {
return {
name: 'vuepress-plugin-my',
// 插件逻辑写在下面这些钩子里
}
})name:插件名(建议以vuepress-plugin-或@org/vuepress-plugin-开头)。multiple:是否允许同时注册多次(默认false)。- 其余字段都是可选的钩子,按需使用。
2. 常用钩子一览
| 钩子 | 触发时机 | 典型用途 |
|---|---|---|
extendsMarkdown(md) | Markdown 解析器初始化时 | 扩展 Markdown 语法(自定义规则、容器) |
extendsPageData(page) | 每篇页面数据生成时 | 给页面附加自定义数据(如阅读时长) |
onPrepared(app) | 站点数据准备完成后 | 写入临时文件、修改 head |
onGenerated(app) | 整个站点渲染完成后 | 后处理 dist/ 产物、生成额外文件 |
alias | 打包解析阶段 | 覆盖/替换某个组件路径 |
define | 打包解析阶段 | 向客户端注入全局常量 |
clientConfigFile | 客户端初始化 | 注册全局组件、全局 UI、路由钩子 |
globalUIComponents | 客户端初始化 | 注册全局 UI 组件(如悬浮按钮) |
extendsBundlerOptions | 打包器配置阶段 | 修改 Vite 配置、排除依赖 |
3. 示例一:给每页附加「阅读时长」
利用 extendsPageData 给每篇页面计算阅读时长,结果会出现在 page.data.readingTime:
import { definePlugin } from 'vuepress/core'
interface ReadingTimeOptions {
wordsPerMinute?: number
}
export const readingTimePlugin = definePlugin<ReadingTimeOptions>((options = {}, app) => {
const wpm = options.wordsPerMinute ?? 200
return {
name: 'vuepress-plugin-reading-time',
extendsPageData(page) {
const words = page.content.split(/\s+/).filter(Boolean).length
const minutes = Math.max(1, Math.ceil(words / wpm))
return { readingTime: { words, minutes } }
},
}
})在 Markdown 或组件中即可读取:
<template>
<span>预计阅读 {{ $frontmatter.readingTime?.minutes }} 分钟</span>
</template>4. 示例二:注入客户端全局组件 + UI
通过 clientConfigFile 注册一个全局组件,并用 globalUIComponents 把它挂为全局 UI:
// my-plugin/index.ts
import { getDirname, path } from 'vuepress/utils'
import { definePlugin } from 'vuepress/core'
import type { Plugin } from 'vuepress/core'
const __dirname = getDirname(import.meta.url)
export const myUiPlugin = definePlugin((options = {}, app): Plugin => ({
name: 'vuepress-plugin-my-ui',
// 向客户端注入配置常量(客户端代码可直接引用 __MY_OPTION__)
define: {
__MY_OPTION__: options,
},
// 客户端增强文件
clientConfigFile: path.resolve(__dirname, './client.js'),
// 注册为全局 UI 组件(始终渲染,如回到顶部按钮、公告栏)
globalUIComponents: ['MyGlobalComp'],
}))// my-plugin/client.js
import { defineClientConfig } from 'vuepress/client'
import MyComp from './components/MyGlobalComp.vue'
export default defineClientConfig({
enhance({ app }) {
// 注册为可复用的全局组件
app.component('MyComp', MyComp)
},
// 作为全局 UI 挂载(与上面的 globalUIComponents 二选一即可)
rootComponents: [MyComp],
})5. 示例三:扩展 Markdown 语法
extendsMarkdown 可以拿到 markdown-it 实例,自定义渲染规则:
import { definePlugin } from 'vuepress/core'
export const upperStrongPlugin = definePlugin((options, app) => ({
name: 'vuepress-plugin-upper-strong',
extendsMarkdown(md) {
// 让 **加粗** 渲染为带 class 的 <strong>
md.renderer.rules.strong_open = (tokens, idx, options, env, self) =>
self.renderToken(tokens, idx, options).replace('<strong>', '<strong class="custom-strong">')
},
}))6. 示例四:生成额外产物(onGenerated)
站点渲染完成后,可以在 dist/ 里生成额外文件,例如写入一份构建信息:
import { definePlugin } from 'vuepress/core'
export const buildInfoPlugin = definePlugin((options, app) => ({
name: 'vuepress-plugin-build-info',
async onGenerated(app) {
const fs = await import('node:fs/promises')
const outDir = app.dir.dest()
const info = {
pages: app.pages.length,
base: app.siteData.base,
generatedAt: new Date().toISOString(),
}
await fs.writeFile(path.resolve(outDir, 'build-info.json'), JSON.stringify(info, null, 2))
},
}))7. 在模板里注册你的插件
把你写好的插件放进项目(例如 .vuepress/plugins/my-plugin.ts),然后在 .vuepress/configs/plugins.ts 引入并加入数组:
import { myUiPlugin } from '../plugins/my-plugin'
export const getPlugins = (isProd: boolean): unknown[] => {
return [
// ……已有插件
myUiPlugin({/* options */}),
].filter(Boolean)
}