fix: 文档监听与 vitepress 竞态问题

This commit is contained in:
unanmed 2026-03-08 18:28:48 +08:00
parent 5a1a0fee42
commit 5550fac160
5 changed files with 93 additions and 72 deletions

View File

@ -1,59 +1,64 @@
import fs from 'fs-extra';
import path from 'path';
import chokidar from 'chokidar';
import { basename, join, resolve } from 'node:path';
import { DefaultTheme } from 'vitepress';
import { readdir, stat, writeFile } from 'node:fs/promises';
const apiDir = path.resolve('./docs/api');
const sidebarConfigPath = path.resolve('./docs/.vitepress/apiSidebar.ts');
const apiDir = resolve('./docs/api');
const sidebarConfigPath = resolve('./docs/.vitepress/apiSidebar.ts');
const weight: Record<string, number> = {
主页: 10,
函数: 5
};
function generateSidebar(): void {
export async function generateSidebar(): Promise<void> {
const sidebar: DefaultTheme.SidebarItem[] = [
{ text: '目录', link: '/api/' }
];
// 遍历 api 目录,查找 package 目录
const packages = fs
.readdirSync(apiDir)
.filter(pkg => fs.statSync(path.join(apiDir, pkg)).isDirectory());
const dir = await readdir(apiDir);
const packages = [];
for (const pkg of dir) {
const stats = await stat(join(apiDir, pkg));
if (stats.isDirectory()) {
packages.push(pkg);
}
}
packages.forEach(pkg => {
const pkgPath = path.join(apiDir, pkg);
const files = fs
.readdirSync(pkgPath)
.filter(file => file.endsWith('.md'));
await Promise.all(
packages.map(async pkg => {
const pkgPath = join(apiDir, pkg);
const dir = await readdir(pkgPath);
const files = dir.filter(file => file.endsWith('.md'));
const items: DefaultTheme.SidebarItem[] = files.map(file => {
const filePath = `api/${pkg}/${file}`;
const fileName = path.basename(file, '.md');
const items: DefaultTheme.SidebarItem[] = files.map(file => {
const filePath = `api/${pkg}/${file}`;
const fileName = basename(file, '.md');
return {
text:
fileName === 'index'
? '主页'
: fileName === 'functions'
? '函数'
: fileName,
link: `/${filePath.replace(/\\/g, '/')}` // 兼容 Windows 路径
};
});
return {
text:
fileName === 'index'
? '主页'
: fileName === 'functions'
? '函数'
: fileName,
link: `/${filePath.replace(/\\/g, '/')}` // 兼容 Windows 路径
};
});
items.sort((a, b) => {
const titleA = a.text ?? '';
const titleB = b.text ?? '';
return (weight[titleB] ?? 0) - (weight[titleA] ?? 0);
});
items.sort((a, b) => {
const titleA = a.text ?? '';
const titleB = b.text ?? '';
return (weight[titleB] ?? 0) - (weight[titleA] ?? 0);
});
sidebar.push({
text: pkg,
collapsed: true,
items
});
});
sidebar.push({
text: pkg,
collapsed: true,
items
});
})
);
// 生成 sidebar.ts
const sidebarContent = `import { DefaultTheme } from 'vitepress';
@ -63,35 +68,6 @@ export default ${JSON.stringify(
null,
4
)} as DefaultTheme.SidebarItem[];`;
fs.writeFileSync(sidebarConfigPath, sidebarContent);
await writeFile(sidebarConfigPath, sidebarContent);
console.log('✅ Sidebar 配置已更新');
}
// 初次运行
generateSidebar();
// 监听文件变动
chokidar
.watch(apiDir, { ignoreInitial: true })
.on('add', filePath => {
console.log(`📄 文件新增: ${filePath}`);
generateSidebar();
})
.on('unlink', filePath => {
console.log(`❌ 文件删除: ${filePath}`);
generateSidebar();
})
.on('addDir', dirPath => {
console.log(`📁 目录新增: ${dirPath}`);
generateSidebar();
})
.on('unlinkDir', dirPath => {
console.log(`📁 目录删除: ${dirPath}`);
generateSidebar();
})
.on('raw', (event, path, details) => {
if (event === 'rename') {
console.log(`🔄 文件或文件夹重命名: ${path}`);
generateSidebar();
}
});

View File

@ -1,7 +1,39 @@
import { defineConfig } from 'vitepress';
import { defineConfig, Plugin } from 'vitepress';
import { MermaidMarkdown, MermaidPlugin } from 'vitepress-plugin-mermaid';
import api from './apiSidebar';
import { join } from 'path';
import { generateSidebar } from './api';
function listenSidebar(): Plugin {
return {
name: 'sidebar-listen',
configureServer(server) {
server.watcher
.on('add', filePath => {
console.log(`📄 文件新增: ${filePath}`);
generateSidebar();
})
.on('unlink', filePath => {
console.log(`❌ 文件删除: ${filePath}`);
generateSidebar();
})
.on('addDir', dirPath => {
console.log(`📁 目录新增: ${dirPath}`);
generateSidebar();
})
.on('unlinkDir', dirPath => {
console.log(`📁 目录删除: ${dirPath}`);
generateSidebar();
})
.on('raw', (event, path, _) => {
if (event === 'rename') {
console.log(`🔄 文件或文件夹重命名: ${path}`);
generateSidebar();
}
});
}
};
}
// https://vitepress.dev/reference/site-config
export default defineConfig({
@ -157,7 +189,7 @@ export default defineConfig({
},
vite: {
// @ts-expect-error 类型错误
plugins: [MermaidPlugin()],
plugins: [MermaidPlugin(), listenSidebar()],
optimizeDeps: {
include: ['mermaid']
},

5
docs/.vitepress/init.ts Normal file
View File

@ -0,0 +1,5 @@
import { generateSidebar } from './api';
(() => {
generateSidebar();
})();

View File

@ -12,7 +12,7 @@
"build:packages": "vue-tsc --noEmit && tsx script/build-packages.ts",
"build:game": "tsx script/declare.ts && vue-tsc --noEmit && tsx script/build-game.ts",
"build:lib": "vue-tsc --noEmit && tsx script/build-lib.ts",
"docs:dev": "concurrently -k -n SIDEBAR,VITEPRESS -c blue,green \"tsx docs/.vitepress/api.ts\" \"vitepress dev docs\"",
"docs:dev": "tsx docs/.vitepress/init.ts && vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs",
"pack:template": "tsx script/pack-template.ts"

View File

@ -7,5 +7,13 @@
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts", "script/**/*.ts", "mota.config.ts"]
"include": [
"vite.config.ts",
"script/**/*.ts",
"docs/**/*.ts",
"docs/.vitepress/api.ts",
"docs/.vitepress/config.ts",
"docs/.vitepress/apiSidebar.ts",
"docs/.vitepress/init.ts"
]
}