This commit is contained in:
2026-06-08 04:19:23 +08:00
parent 8351498071
commit f2d8ad0de2
152 changed files with 2362 additions and 2227 deletions

View File

@@ -0,0 +1,254 @@
import * as fs from "fs";
import * as path from "path";
/**
* 默认测试 / story / example / fixture / type 文件匹配规则
* 仅用于“识别”,不直接决定是否排除
*/
const DEFAULT_TEST_FILE_PATTERNS: RegExp[] = [
// =========================
// Test / Spec
// =========================
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
/\.(test|spec)\.(ts|tsx|js|jsx)\?.*$/, // query param safe
// =========================
// E2E / Playwright / Cypress
// =========================
/\.(cy|playwright|e2e)\.(ts|tsx|js|jsx)$/,
// =========================
// Storybook
// =========================
/\.(story|stories)\.(ts|tsx|js|jsx|mdx)$/,
// =========================
// Snapshot / Mock / Fixture
// =========================
/\.snap$/,
/\.mock\.(ts|tsx|js|jsx)$/,
/\.fixture\.(ts|tsx|js|jsx)$/,
// =========================
// Type declarations
// =========================
/\.d\.ts$/,
// =========================
// Examples / Demos / Docs
// =========================
/\.(example|demo|docs)\.(ts|tsx|js|jsx)$/,
];
/**
* 测试 / 辅助目录(目录级排除)
*/
const DEFAULT_TEST_DIR_PATTERNS: RegExp[] = [
/[/\\](__tests__|tests|test|spec|__mocks__|__fixtures__|__snapshots__)[\/\\]/,
/[/\\](cypress|playwright|e2e|__e2e__|__playwright__)[\/\\]/,
/[/\\](story|stories|\.storybook)[\/\\]/,
/[/\\](types|type|typings|interfaces)[\/\\]/,
/[/\\](examples|example|demo|demos|docs)[\/\\]/,
];
type Config = {
outputDir: string;
outputFile: string;
scanDirs: string[];
preamble?: string[];
importPrefix?: string;
entryFilePatterns?: RegExp[];
barrelFirstMode?: boolean;
includeFilePatterns?: RegExp[];
excludeFilePatterns?: RegExp[];
excludeDirPatterns?: RegExp[];
};
const tsConfig: Config = {
outputDir: "src",
outputFile: "index.ts",
scanDirs: ["src"],
importPrefix: "export * from",
barrelFirstMode: true,
preamble: ["import './index.css';"],
entryFilePatterns: [/^index\.(js|ts|jsx|tsx)$/],
includeFilePatterns: [/\.(js|ts|jsx|tsx)$/],
excludeFilePatterns: DEFAULT_TEST_FILE_PATTERNS,
excludeDirPatterns: DEFAULT_TEST_DIR_PATTERNS,
};
const cssConfig: Config = {
outputDir: "src",
outputFile: "index.css",
scanDirs: ["src"],
importPrefix: "@import",
barrelFirstMode: false,
entryFilePatterns: [/^index\.(css)$/],
includeFilePatterns: [/\.(css)$/],
excludeFilePatterns: DEFAULT_TEST_FILE_PATTERNS,
excludeDirPatterns: DEFAULT_TEST_DIR_PATTERNS,
};
function isEntryFile(fileName: string, config: Config): boolean {
const regExps = config.entryFilePatterns;
if (!regExps || regExps.length === 0) return false;
return regExps.some((regExp) => regExp.test(fileName));
}
function isIncludeFile(fileName: string, config: Config): boolean {
const regExps = config.includeFilePatterns;
if (!regExps || regExps.length === 0) return false;
return regExps.some((regExp) => regExp.test(fileName));
}
function isExcludeFile(fileName: string, config: Config): boolean {
const regExps = config.excludeFilePatterns;
if (!regExps || regExps.length === 0) return false;
return regExps.some((regExp) => regExp.test(fileName));
}
function isExcludeDir(filePath: string, config: Config): boolean {
const regExps = config.excludeDirPatterns;
if (!regExps || regExps.length === 0) return false;
return regExps.some((regExp) => regExp.test(filePath));
}
function isValidDir(filePath: string, config: Config): boolean {
if (isExcludeDir(filePath, config)) {
return false;
}
return true;
}
function isValidFile(fileName: string, config: Config): boolean {
if (isIncludeFile(fileName, config) && !isExcludeFile(fileName, config)) {
return true;
}
return false;
}
const buildExportStatement = (filePath: string, config: Config) => {
// 文件夹(不是文件)与目标文件的相对路径
let exportFilePath = path.relative(config.outputDir, filePath);
// 去除扩展名,并且转化成正斜杠
const exportFilePathWithoutExt = path
.join(
path.dirname(exportFilePath),
path.basename(exportFilePath, path.extname(exportFilePath)),
)
.replace(/\\/g, "/");
// 加上 ./ 前缀
return `${config.importPrefix} './${exportFilePathWithoutExt}';`;
};
function isOutputEntry(filePath: string, config: Config) {
const outputFilePath = path.resolve(config.outputDir, config.outputFile);
const targetFilePath = path.resolve(filePath);
return outputFilePath === targetFilePath;
}
function generateExports(dirPath: string, config: Config): string[] {
const fileNames = fs.readdirSync(dirPath);
const exports: string[] = [];
// =========================
// 逻辑分支Barrel First Mode
// =========================
if (config.barrelFirstMode) {
// 查找当前目录有没有 index.ts
const entryFileName = fileNames.find((fileName) => {
const filePath = path.join(dirPath, fileName);
return fs.statSync(filePath).isFile() && isEntryFile(fileName, config);
});
// 如果有
if (entryFileName) {
const entryFilePath = path.join(dirPath, entryFileName);
// 如果是 outputFile 自身,则跳过
if (isOutputEntry(entryFilePath, config)) {
console.log(`⏭️ 跳过自身入口文件: ${entryFilePath}`);
} else {
exports.push(buildExportStatement(entryFilePath, config));
}
}
}
// =========================
// 逻辑分支index.ts 不存在,则继续正常遍历与递归
// =========================
fileNames.forEach((fileName) => {
const filePath = path.join(dirPath, fileName);
const stat = fs.statSync(filePath);
// 情况1是文件且通过了校验
if (
stat.isFile() &&
isValidFile(fileName, config) &&
!isEntryFile(fileName, config)
) {
exports.push(buildExportStatement(filePath, config));
}
// 情况2是文件夹且通过了校验递归扫描子文件夹
else if (stat.isDirectory() && isValidDir(filePath, config)) {
const subExports = generateExports(filePath, config);
exports.push(...subExports);
}
});
return exports;
}
function genIndexFile(config: Config) {
// 确保输出目录存在,如果不存在就递归创建
if (!fs.existsSync(config.outputDir)) {
fs.mkdirSync(config.outputDir, { recursive: true });
}
const allExports: string[] = [];
// 遍历所有需要扫描的根目录
config.scanDirs.forEach((scanDir) => {
// 只有当目录真实存在时才进行扫描
if (fs.existsSync(scanDir)) {
const exports = generateExports(scanDir, config);
allExports.push(...exports);
} else {
console.warn(`⚠️ 警告:扫描目录不存在,已跳过 -> ${scanDir}`);
}
});
// 拼接最终的文件内容:前言 + 导出语句(使用 Set 自动去重)
const fileContent = [
...(config.preamble ?? []),
...Array.from(new Set(allExports)),
].join("\n");
// 将内容写入到最终的 index.ts 文件中
const outputFilePath = path.join(config.outputDir, config.outputFile);
fs.writeFileSync(outputFilePath, fileContent, "utf-8");
console.log(`✨ 成功生成入口文件: ${outputFilePath}`);
}
// ================= 脚本执行入口 =================
try {
console.log("🚀 开始扫描并生成入口文件...");
genIndexFile(tsConfig);
genIndexFile(cssConfig);
console.log("✅ 脚本执行完毕!");
} catch (error) {
console.error("❌ 脚本执行失败:", error);
process.exit(1); // 如果报错,让进程以非 0 状态码退出
}