@gulibs/vgrove-core
Version:
VGrove CLI - A unified development tool for React applications with auto-routes and i18n
213 lines (212 loc) • 7.05 kB
JavaScript
import path from 'path';
import fs from 'fs';
import { pathToFileURL } from 'url';
import { tmpdir } from 'os';
export function defineConfig(config) {
return config;
}
export async function loadConfig(root = process.cwd()) {
const configFile = findConfigFile(root);
if (!configFile) {
return getDefaultConfig(root);
}
try {
const config = await loadConfigFile(configFile);
return resolveConfig(config, root, configFile);
}
catch (error) {
console.error(`Failed to load config from ${configFile}:`, error);
return getDefaultConfig(root);
}
}
function findConfigFile(root) {
const configFiles = [
'vgrove.config.ts',
'vgrove.config.js',
'vgrove.config.mjs'
];
for (const file of configFiles) {
const filePath = path.resolve(root, file);
if (fs.existsSync(filePath)) {
return filePath;
}
}
return null;
}
async function loadConfigFile(configFile) {
if (configFile.endsWith('.ts')) {
return await loadTypeScriptConfig(configFile);
}
// 处理 JS/MJS 文件
const fileUrl = pathToFileURL(configFile).href;
const module = await import(fileUrl);
return module.default || module;
}
async function loadTypeScriptConfig(configFile) {
try {
const { build } = await import('esbuild');
// 获取配置文件的目录路径(用于注入 __dirname)
const configDir = path.dirname(configFile);
// 使用 esbuild 编译 TypeScript 配置文件
const result = await build({
entryPoints: [configFile],
bundle: true, // 需要打包才能处理导入
format: 'esm',
platform: 'node',
target: 'node18',
write: false, // 不写入文件,直接获取结果
external: [
// 保留 Node.js 内置模块为外部依赖
'path',
'fs',
'url',
'os',
'crypto',
'util',
'events',
'stream'
],
loader: {
'.ts': 'ts'
},
define: {
// 注入 CommonJS 环境变量,模拟 Vite 的行为
'__dirname': JSON.stringify(configDir),
'__filename': JSON.stringify(configFile)
},
logLevel: 'silent', // 静默模式,我们自己处理错误
plugins: [
{
name: 'vgrove-config-transform',
setup(build) {
build.onResolve({ filter: /@gulibs\/vgrove/ }, () => {
return { path: 'vgrove-stub', namespace: 'vgrove-stub' };
});
// 提供 defineConfig 的存根实现
build.onLoad({ filter: /.*/, namespace: 'vgrove-stub' }, () => {
return {
contents: 'export function defineConfig(config) { return config; }',
loader: 'js'
};
});
}
}
]
});
if (result.errors.length > 0) {
throw new Error(`TypeScript compilation errors:\n${result.errors.map(e => e.text).join('\n')}`);
}
// 生成临时文件名
const tempDir = tmpdir();
const tempFile = path.join(tempDir, `vgrove-config-${Date.now()}-${Math.random().toString(36).substr(2, 9)}.mjs`);
try {
// 写入编译结果到临时文件
fs.writeFileSync(tempFile, result.outputFiles[0].text);
// 导入临时文件
const fileUrl = pathToFileURL(tempFile).href;
const module = await import(fileUrl);
return module.default || module;
}
finally {
// 清理临时文件
try {
if (fs.existsSync(tempFile)) {
fs.unlinkSync(tempFile);
}
}
catch (cleanupError) {
// 忽略清理错误,但可以记录日志
console.warn(`Warning: Failed to cleanup temporary config file: ${tempFile}`);
}
}
}
catch (error) {
// 提供更友好的错误信息
if (error instanceof Error) {
if (error.message.includes('Cannot resolve')) {
throw new Error(`Failed to resolve dependencies in config file: ${configFile}\n` +
`Make sure all imported packages are installed.\n` +
`Original error: ${error.message}`);
}
else if (error.message.includes('TypeScript compilation errors')) {
throw new Error(`TypeScript compilation failed for config file: ${configFile}\n` +
`${error.message}`);
}
else {
throw new Error(`Failed to load TypeScript config file: ${configFile}\n` +
`Error: ${error.message}`);
}
}
throw error;
}
}
function resolveConfig(config, root, configFile) {
const defaultConfig = getDefaultConfig(root, configFile);
return {
root,
configFile,
vgrove: {
routes: {
...defaultConfig.vgrove.routes,
...config.vgrove?.routes
},
i18n: {
...defaultConfig.vgrove.i18n,
...config.vgrove?.i18n
},
ui: {
...defaultConfig.vgrove.ui,
...config.vgrove?.ui
},
build: {
...defaultConfig.vgrove.build,
...config.vgrove?.build
}
},
vite: {
...defaultConfig.vite,
...config.vite
}
};
}
function getDefaultConfig(root, configFile) {
return {
root,
configFile,
vgrove: {
routes: {
dirs: [{ dir: 'src/pages', baseRoute: '/' }],
routeMode: 'flat',
cache: true
},
i18n: {
basePath: 'src/locales',
extensions: ['.json', '.ts', '.js'],
localePattern: 'directory',
hmr: true,
deep: true,
exclude: [],
include: [],
debug: false,
keysOutput: 'src/i18n-keys.ts'
},
ui: {
framework: 'none'
},
build: {
target: 'es2020',
outDir: 'dist',
sourcemap: true,
minify: true
}
},
vite: {
root,
resolve: {
alias: {
'@': path.resolve(root, 'src')
}
}
}
};
}