有用过vitepress的老表都知道,配置sidebar、nav这些是相当繁琐的一件事。nav还算好的,毕竟你的网页不可能有太多的nav。但是sidebar就头疼了,随着文档的增多,我们有必要将其自动化。
下面是我写的一个自动生成sidebar的nodejs脚本0.0.1,本来是想做成插件的,类似于vite-plugin-pages这种,奈何实力不允许。等我研究完vite和rollup再进行改造吧。
博客源码:GitHub - jyj1202/docs: 项目文档
2023.11.10更新
之前看了一下vite-plugin-page源码,并且发现vitepress已经有人写过自动生成sidebar的插件了,于是仿照着一下,写了一个自己版本的vite-plugin-vitepress-auto-sidebar插件,支持dev和build环境。由于已经有现成插件了,且我的插件配置代码就是抄的现成的,所以就没打算发布成插件。
主要改动是在运用vite的configureServer钩子这块,开发时使用watcher监听文件变化,监听到变换,立即重启开发服务器,重新修改sidebar配置项。
下面贴出核心代码:
vite-plugin-vitepress-autoSidebar插件代码:
/**
* 只有目录可以作为sidebar;
* 作为sidebar的目录必须以"-sidebar"结尾,并且只有最外层以-sidebar结尾的目录才会作为sidebar,子目录忽略
* 如果文件或目录以"-hide"结尾,则不会出现在sidebar中
* 如果目录名为"xxx-hide-sidebar",则仍然会作为sidebar,对应sidebar名称为"xxx-hide"
* 如果目录名为"xxx-sidebar-hide",则不作为sidebar
*/
// const fs = require('fs').promises;
// const path = require('path');
import * as fs from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import path from 'path';
import type { Plugin, ViteDevServer } from 'vite';
import type { UserConfig, FolderInfo, AutoSidebarOption } from "./type";
import type { DefaultTheme} from 'vitepress/types/default-theme.d.ts';
const __filename = fileURLToPath(import.meta.url);
console.log(__filename);
const __dirname = path.dirname(__filename);
function generateSidebar(items: FolderInfo[], sidebar={}): DefaultTheme.Sidebar {
items.forEach(item => {
const { items: subItems, isSidebar, path} = item
if (isSidebar) {
sidebar[`/${path}/`] = [item]
} else if (subItems) {
generateSidebar(subItems, sidebar)
}
})
return sidebar
}
async function getFolderInfo(root: string, absoluteDir: string): Promise<FolderInfo> {
const foldernameWithoutExt = getNameWithoutExt(absoluteDir)
const items: FolderInfo[] = []
const isSidebar = foldernameWithoutExt.endsWith('-sidebar')
const relativeDir = path.relative(root, absoluteDir).replace(/\\/g, '/');
const folderInfo: FolderInfo = {
text: foldernameWithoutExt.replace(/-sidebar$/, ''),
isSidebar,
path: relativeDir,
collapsed: true,
items,
}
const files = await fs.readdir(absoluteDir);
for (const filename of files) {
const filePath = path.join(absoluteDir, filename);
const stat = await fs.stat(filePath);
const fileNameWithoutExt = getNameWithoutExt(filename)
// 如果是隐藏文件,则跳过当前循环
if (fileNameWithoutExt.endsWith('-hide')) {
continue
}
// 如果是目录,则递归
if (stat.isDirectory()) {
items.push(await getFolderInfo(root, filePath))
continue
}
// 如果后缀名是.md(忽略大小写),则需要添加link属性
if (path.extname(filename).toLowerCase() === '.md') {
// 如果是index.md,则向folderInfo上添加link属性,否则向folderInfo.items添加
if (fileNameWithoutExt.toLowerCase() === 'index') {
folderInfo.link = `/${relativeDir}/index.html`
} else {
items.push({
text: fileNameWithoutExt,
link: `/${relativeDir}/${fileNameWithoutExt}.html`
})
}
}
}
return folderInfo
}
function getNameWithoutExt(filePath: string) {
const fileName = path.basename(filePath);
const fileNameWithoutExt = path.parse(fileName).name;
return fileNameWithoutExt;
}
/* 默认导出一个函数,具体配置参考:https://cn.vitejs.dev/guide/api-plugin.html#simple-examples */
export default function autoGenerateSidebar(options: AutoSidebarOption): Plugin {
console.log(options);
const { docRoot } = options
const root = path.resolve(__dirname, docRoot)
return {
name: 'vite-plugin-vitepress-autoSidebar',
configureServer ({
watcher,
restart
}: ViteDevServer) {
const fsWatcher = watcher.add('*.md');
fsWatcher.on('all', async (event, path) => {
if (event !== 'change') {
console.log(`${event} ${path}`);
try {
await restart();
// ws.send({
// type: 'full-reload',
// })
console.log('update sidebar...');
} catch {
console.log(`${event} ${path}`);
console.log('update sidebar failed');
}
}
});
},
async config(config) {
const folderInfo = await getFolderInfo(root, root);
(config as UserConfig).vitepress.site.themeConfig.sidebar = folderInfo.items ? generateSidebar(folderInfo.items) : {}
return config
}
}
}
config.ts中使用:
export default defineConfig({
// lang: 'en-US',
title: 'JYJ\'s Doc',
description: 'jyj的文档',
head: [['link', { rel: 'icon', href: '/docs/favicon.ico' }]],
// https://vitepress.dev/guide/deploy#setting-a-public-base-path
base: '/docs/',
// config source directory
srcDir: 'src',
ignoreDeadLinks: true,
themeConfig: {
siteTitle: 'JYJ的文档',
logo: '/logo.svg',
nav,
sidebar: [],
search: {
provider: 'local'
}
},
/* 这块配置vite插件*/
vite: {
plugins: [
sidebar({ docRoot: '../src' })
]
}
});
最后再提一下打包插件的事,可以使用vite直接打包,但是要使用库模式
下面是示例代码,详细配置参考vite文档:
import { resolve } from 'path'
import { defineConfig } from 'vite'
export default defineConfig({
build: {
lib: {
// Could also be a dictionary or array of multiple entry points
entry: resolve(__dirname, './index.ts'),
name: 'vite-plugin-auto-sidebar',
// the proper extensions will be added
fileName: 'vite-plugin-auto-sidebar',
},
},
})