// plugins/vite-plugin-gen-index-ts.ts import type { Plugin } from "vite"; import fs from "fs"; import path from "path"; import { globSync } from "glob"; interface Config { targetDirs: string[]; includeExtensions: string[]; excludeKeywords: { dirs: string[]; fileSuffixes: string[]; filePatterns: RegExp[]; }; } const CONFIG: Config = { targetDirs: ["src"], includeExtensions: [".ts", ".tsx", ".vue"], excludeKeywords: { dirs: ["__tests__", "tests", "story", "stories", "types"], fileSuffixes: [".d.ts"], filePatterns: [ /^index\.(ts|tsx|js|jsx)$/, /\.(test|spec)\./, /\.(story|stories)\./, ], }, }; const normalizePath = (p: string) => p.replace(/\\/g, "/"); const isInExcludeDir = (filePath: string) => { const normalized = normalizePath(filePath); return CONFIG.excludeKeywords.dirs.some((dir) => normalized.includes(`/${dir}/`), ); }; const isExcludeSuffix = (filePath: string) => CONFIG.excludeKeywords.fileSuffixes.some((suffix) => filePath.endsWith(suffix), ); const isMatchExcludePattern = (fileName: string) => CONFIG.excludeKeywords.filePatterns.some((pattern) => pattern.test(fileName)); function isValidFile(filePath: string): boolean { const fileName = filePath.split(/[\\/]/).pop()!; if (isInExcludeDir(filePath)) return false; if (isExcludeSuffix(filePath)) return false; if (isMatchExcludePattern(fileName)) return false; const ext = path.extname(filePath); return CONFIG.includeExtensions.includes(ext); } function generateIndexFile(dirPath: string) { const searchPattern = path.resolve(dirPath, "**", "*.*"); const allFiles = globSync(searchPattern, { nodir: true, absolute: true, windowsPathsNoEscape: true, dot: false, follow: true, }); const validFiles = allFiles.filter(isValidFile); if (validFiles.length === 0) return; validFiles.sort(); const exportStatements = validFiles.map((file) => { const relPath = path.relative(dirPath, file); const importPath = `./${relPath .replace(/\.[^.]+$/, "") .replace(/\\/g, "/")}`; return `export * from '${importPath}';`; }); const indexContent = ` import './index.css'; ${exportStatements.join("\n")} `.trim(); const indexFilePath = path.resolve(dirPath, "index.ts"); // ✅ 内容比对,避免无限 rebuild if (fs.existsSync(indexFilePath)) { const old = fs.readFileSync(indexFilePath, "utf8"); if (old === indexContent) return; } fs.writeFileSync(indexFilePath, indexContent, "utf8"); } export function genIndexTsPlugin(): Plugin { return { name: "vite-plugin-gen-index-ts", apply: "build", buildStart() { const [targetDir] = CONFIG.targetDirs; if (!targetDir) { this.error("CONFIG.targetDirs is empty"); return; } const absTargetDir = path.resolve(process.cwd(), targetDir); try { generateIndexFile(absTargetDir); } catch (err) { const msg = err instanceof Error ? err.message : String(err); this.error(`[gen-index-ts] failed: ${msg}`); throw err; } }, handleHotUpdate({ file }) { if (file.replace(/\\/g, "/").endsWith("/src/index.ts")) { return []; } }, }; }