// plugins/vite-plugin-gen-index-css.ts import type { Plugin } from "vite"; import fs from "fs"; import path from "path"; import { globSync } from "glob"; interface Config { targetDirs: string[]; includeExtensions: string[]; importSyntax: "@import"; importSyntaxTail?: string; excludeFilePattern: RegExp[]; excludeDirs: string[]; warnDuplicateTailwindImport: boolean; indexFileName: string; } const CONFIG: Config = { targetDirs: ["src"], includeExtensions: [".css"], importSyntax: "@import", excludeFilePattern: [/index\.css/, /index\.scss/, /\.(test|spec)\./], excludeDirs: [ "__tests__", "tests", "story", "stories", "types", "node_modules", "dist", "build", ], warnDuplicateTailwindImport: true, indexFileName: "index.css", }; const normalizePath = (p: string) => p.replace(/\\/g, "/"); function isValidFile(filePath: string, config: Config): boolean { const filenameWithExt = filePath.split(/[\\/]/).pop()!; const shouldExcludeFile = config.excludeFilePattern.some((p) => p.test(filenameWithExt), ); if (shouldExcludeFile) return false; const normalized = normalizePath(filePath); const shouldExcludeDir = config.excludeDirs.some((dir) => normalized.includes(`/${dir}/`), ); if (shouldExcludeDir) return false; const ext = path.extname(filePath); if (!config.includeExtensions.includes(ext)) return false; if (config.warnDuplicateTailwindImport) { try { const content = fs.readFileSync(filePath, "utf-8"); if ( content.includes('@import "tailwindcss"') || content.includes("@import 'tailwindcss'") ) { console.warn( `[gen-index-css] ${filePath} 含有重复的 @import "tailwindcss",建议删除`, ); } } catch { // ignore } } return true; } function generateIndexFile(config: Config) { const [targetDir] = config.targetDirs; const dirPath = path.resolve(process.cwd(), targetDir); const searchPattern = path.resolve(dirPath, "**", "*.*"); const allFiles = globSync(searchPattern, { nodir: true, absolute: true, windowsPathsNoEscape: true, dot: false, follow: true, }); const validFiles = allFiles.filter((f) => isValidFile(f, config)); console.log(`✅ 有效 CSS 文件数量: ${validFiles.length}`); validFiles.sort(); const importStatements = validFiles.map((file) => { const relPath = path.relative(dirPath, file); const importPath = "./" + relPath.replace(/\\/g, "/"); return `${config.importSyntax} '${importPath}';`; }); const indexContent = ` @import "tailwindcss"; ${importStatements.join("\n")} `.trim(); const indexFilePath = path.resolve(dirPath, config.indexFileName); // ✅ 内容比对,防止无限 rebuild if (fs.existsSync(indexFilePath)) { const old = fs.readFileSync(indexFilePath, "utf8"); if (old === indexContent) return; } fs.writeFileSync(indexFilePath, indexContent, "utf8"); console.log(`✅ 成功生成 ${config.indexFileName}: ${indexFilePath}`); } export function genIndexCssPlugin(): Plugin { return { name: "vite-plugin-gen-index-css", apply: "build", buildStart() { try { generateIndexFile(CONFIG); } catch (err) { const msg = err instanceof Error ? err.message : String(err); this.error(`[gen-index-css] failed: ${msg}`); throw err; } }, handleHotUpdate({ file }) { if (normalizePath(file).endsWith(`/src/${CONFIG.indexFileName}`)) { return []; } }, }; }