vitepress:编写vite插件实现vitepress自动sidebar

有用过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',
    },
  },
})

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值