mm
This commit is contained in:
254
packages/css/scripts/gen-index.ts
Normal file
254
packages/css/scripts/gen-index.ts
Normal 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 状态码退出
|
||||
}
|
||||
Reference in New Issue
Block a user