项目概况

我的插件

2026-01-293 min read未分类
vitepress

My Plugin

vitepress 使用 vite 构建的,所以我们可以根据 vite 的插件来实现。

VitePluginAutoSidebar

自动构建侧边栏,如本页面侧边栏。

  • 侧边栏发生变化自动重启
  • 文章发生变化不自动重启,提高速度。

使用方法

  • category: 侧边栏目录
text
1--- 2title: Plugin 3editLink: true 4navbar: true 5category: 'VitePress' 6---
text
1import { defineConfig } from 'vitepress'; 2import { demoblockPlugin, demoblockVitePlugin } from 'vitepress-theme-demoblock'; 3import nav from './nav'; 4import { tocPlugin } from '@mdit-vue/plugin-toc'; 5import suggestionHighlightPlugin from './theme/custom-markdown.cjs'; 6import { pagefindPlugin } from 'vitepress-plugin-pagefind'; 7import VitePluginAutoSidebar from './plugin/VitePluginAutoSidebar'; 8import { resolve } from 'path'; 9 10// https://vitepress.dev/reference/site-config 11export default defineConfig({ 12 vite: { 13 plugins: [demoblockVitePlugin(), VitePluginAutoSidebar( 14 [ 15 resolve(__dirname, '../post/java'), 16 resolve(__dirname, '../post/guide'), 17 resolve(__dirname, '../post/design'), 18 resolve(__dirname, '../post/test'), 19 resolve(__dirname, '../components'), 20 resolve(__dirname, '../post/scm'), 21 resolve(__dirname, '../post/project'), 22 resolve(__dirname, '../post/ai'), 23 resolve(__dirname, '../post/other'), 24 resolve(__dirname, '../post/python'), 25 ], 26 ), pagefindPlugin({ 27 btnPlaceholder: '搜索', 28 placeholder: '搜索文档', 29 emptyText: '空空如也', 30 heading: '共: {{searchResult}} 条结果', 31 })] 32 33 ... 34 }); 35

实现代码

text
1import { buildSidebars } from './sidebar'; 2import { ViteDevServer } from 'vite'; 3 4// https://www.vitejs.net/guide/api-plugin.html#config 5 6type Cache = { 7 fileName: string, 8 title: string, 9 category: string 10} 11 12const fileNameCache = new Map<string, Cache>(); 13 14const titleCache = new Map<string, Cache>(); 15 16 17function removeFileExtension(filePath) { 18 // 标准化路径分隔符为斜杠(/) 19 const normalizedPath = filePath.replace(//g, '/'); 20 21 // 分割路径为数组 22 const parts = normalizedPath.split('/'); 23 24 // 获取文件名 25 let fileName = parts.pop(); 26 27 // 使用正则表达式匹配文件名中的后缀部分(假设后缀是由点(.)和至少一个字符组成) 28 const extensionMatch = fileName.match(/.([^.]+)$/); 29 30 // 如果匹配到后缀,则移除它 31 if (extensionMatch) { 32 fileName = fileName.slice(0, -extensionMatch[0].length); 33 } 34 35 return fileName; 36} 37 38function rebuildSide(config: any, paths: string[]) { 39 let autoSidebars = buildSidebars(paths); 40 Object.keys(autoSidebars).forEach(key => { 41 const sideList = autoSidebars[key]; 42 for (let i = 0; i < sideList.length; i++) { 43 const side = sideList[i]; 44 // 第一个目录打开,其他目录收缩 45 if (i > 0) { 46 side['collapsed'] = true; 47 } 48 const side2List = side['items']; 49 side2List.forEach((side2) => { 50 const category = side['text']; 51 const fileName = removeFileExtension(side2['link']); 52 const title = side2['text']; 53 const cache = { 54 category, title, fileName, 55 }; 56 fileNameCache.set(fileName, cache); 57 titleCache.set(title, cache); 58 }); 59 } 60 }); 61 config.vitepress.site.themeConfig.sidebar = autoSidebars; 62 return config; 63} 64 65export default function VitePluginAutoSidebar(paths: []) { 66 return { 67 name: 'VitePluginAutoSidebar', 68 // 新增 69 config(config) { 70 return rebuildSide(config, paths); 71 }, 72 configureServer: ({ watcher, restart }: ViteDevServer) => { 73 const fsWatcher = watcher.add('*.md'); 74 fsWatcher.on('all', async (event, filePath) => { 75 if (event === 'addDir') return; 76 if (event === 'unlinkDir') return; 77 if (event == 'add') return; 78 let fileName = removeFileExtension(filePath); 79 console.log(`fs Watcher[${filePath}]:${event}:fileName:${fileName}`); 80 if (event === 'unlink' || event === 'change') { 81 let cache = fileNameCache.get(fileName); 82 try { 83 // 如果根据文件名 84 if (!titleCache.get(cache.title)) { 85 await restart(); 86 } 87 } catch { 88 console.log('update sidebar failed'); 89 } 90 } 91 }); 92 }, 93 }; 94} 95

Markdown 插件

使用方法

js
1import { defineConfig } from 'vitepress'; 2import suggestionHighlightPlugin from './theme/custom-markdown.cjs'; 3// https://vitepress.dev/reference/site-config 4export default defineConfig({ 5 markdown: { 6 lineNumbers: true, 7 config: (md) => { 8 md.use(suggestionHighlightPlugin); 9 }, 10 }, 11 })

实现原理

= 红色高亮 =

  • 语法:
    • *= 红色高亮 =*
js
1// custom-markdown.js 2module.exports = function suggestionPlugin(md) { 3 // 使用 markdown-it 内置的 parser 规则注册新的 inline 规则 4 md.inline.ruler.before('emphasis', 'suggestion', function (state, silent) { 5 const start = state.pos; 6 const markerStart = '*='; 7 const markerEnd = '=*'; 8 9 // 检查当前位置是否为 *= 起始标记 10 if (state.src.startsWith(markerStart, start)) { 11 const end = state.src.indexOf(markerEnd, start + markerStart.length); 12 if (end === -1) return false; // 如果没有找到 =* 结束标记,返回 false 13 14 if (!silent) { 15 // 插入 suggestion_open token 16 const token = state.push('suggestion_open', 'span', 1); 17 token.attrs = [['style', 'color: #e64b7d;font-weight: bold;background-color: var(--vp-code-bg);border-radius: 4px; 18' + 19 ' padding: 3px 6px;']]; 20 21 // 获取 *= 和 =* 之间的内容 22 const contentToken = state.push('text', '', 0); 23 contentToken.content = state.src.slice(start + markerStart.length, end).trim(); 24 25 // 插入 suggestion_close token 26 state.push('suggestion_close', 'span', -1); 27 } 28 29 // 更新位置到结束标记之后 30 state.pos = end + markerEnd.length; 31 return true; 32 } 33 34 return false; 35 }); 36 37 // 渲染 suggestion_open 标签 38 md.renderer.rules.suggestion_open = function (tokens, idx) { 39 return `<span style="${tokens[idx].attrs[0][1]}">`; 40 }; 41 42 // 渲染 suggestion_close 标签 43 md.renderer.rules.suggestion_close = function () { 44 return '</span>'; 45 }; 46}; 47