feat: export extra functions from the loader

This commit is contained in:
hypercross 2026-04-02 17:32:13 +08:00
parent cf55295ce7
commit b7e65ebc1d
1 changed files with 77 additions and 20 deletions

View File

@ -20,6 +20,15 @@ export interface CsvLoaderOptions {
writeToDisk?: boolean; writeToDisk?: boolean;
} }
export interface CsvParseResult {
/** Parsed CSV data as array of objects */
data: Record<string, unknown>[];
/** Generated TypeScript type definition string (if emitTypes is true) */
typeDefinition?: string;
/** Property configurations for the CSV columns */
propertyConfigs: PropertyConfig[];
}
interface PropertyConfig { interface PropertyConfig {
name: string; name: string;
schema: any; schema: any;
@ -78,20 +87,25 @@ export default data;
`; `;
} }
export default function csvLoader( /**
this: LoaderContext<CsvLoaderOptions>, * Parse CSV content string into structured data with schema validation.
content: string * This is a standalone function that doesn't depend on webpack/rspack LoaderContext.
): string | Buffer { *
const options = this.getOptions() as CsvLoaderOptions | undefined; * @param content - CSV content string (must have at least headers + schema row + 1 data row)
const delimiter = options?.delimiter ?? ','; * @param options - Parsing options
const quote = options?.quote ?? '"'; * @returns CsvParseResult containing parsed data and optional type definitions
const escape = options?.escape ?? '\\'; */
const bom = options?.bom ?? true; export function parseCsv(
const comment = options?.comment === false ? undefined : (options?.comment ?? '#'); content: string,
const trim = options?.trim ?? true; options: CsvLoaderOptions = {}
const emitTypes = options?.emitTypes ?? true; ): CsvParseResult {
const typesOutputDir = options?.typesOutputDir ?? ''; const delimiter = options.delimiter ?? ',';
const writeToDisk = options?.writeToDisk ?? false; const quote = options.quote ?? '"';
const escape = options.escape ?? '\\';
const bom = options.bom ?? true;
const comment = options.comment === false ? undefined : (options.comment ?? '#');
const trim = options.trim ?? true;
const emitTypes = options.emitTypes ?? true;
const records = parse(content, { const records = parse(content, {
delimiter, delimiter,
@ -152,10 +166,54 @@ export default function csvLoader(
return obj; return obj;
}); });
const json = JSON.stringify(objects, null, 2); const result: CsvParseResult = {
data: objects,
propertyConfigs,
};
if (emitTypes) {
result.typeDefinition = generateTypeDefinition('', propertyConfigs);
}
return result;
}
/**
* Generate JavaScript module code from CSV content.
* Returns a string that can be used as a module export.
*
* @param content - CSV content string
* @param options - Parsing options
* @returns JavaScript module code string
*/
export function csvToModule(
content: string,
options: CsvLoaderOptions = {}
): { js: string; dts?: string } {
const result = parseCsv(content, options);
const json = JSON.stringify(result.data, null, 2);
const js = `export default ${json};`;
return {
js,
dts: result.typeDefinition,
};
}
export default function csvLoader(
this: LoaderContext<CsvLoaderOptions>,
content: string
): string | Buffer {
const options = this.getOptions() as CsvLoaderOptions | undefined;
const emitTypes = options?.emitTypes ?? true;
const typesOutputDir = options?.typesOutputDir ?? '';
const writeToDisk = options?.writeToDisk ?? false;
const result = parseCsv(content, options);
// Emit type definition file if enabled // Emit type definition file if enabled
if (emitTypes) { if (emitTypes && result.typeDefinition) {
const context = this.context || ''; const context = this.context || '';
// Get relative path from context, normalize to forward slashes // Get relative path from context, normalize to forward slashes
let relativePath = this.resourcePath.replace(context, ''); let relativePath = this.resourcePath.replace(context, '');
@ -169,18 +227,17 @@ export default function csvLoader(
const outputPath = typesOutputDir const outputPath = typesOutputDir
? path.join(typesOutputDir, dtsFileName) ? path.join(typesOutputDir, dtsFileName)
: dtsFileName; : dtsFileName;
const dtsContent = generateTypeDefinition(this.resourcePath, propertyConfigs);
if (writeToDisk) { if (writeToDisk) {
// Write directly to disk (useful for dev server) // Write directly to disk (useful for dev server)
const absolutePath = path.join(this.context || process.cwd(), typesOutputDir || '', dtsFileName); const absolutePath = path.join(this.context || process.cwd(), typesOutputDir || '', dtsFileName);
fs.mkdirSync(path.dirname(absolutePath), { recursive: true }); fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
fs.writeFileSync(absolutePath, dtsContent); fs.writeFileSync(absolutePath, result.typeDefinition);
} else { } else {
// Emit to in-memory filesystem (for production build) // Emit to in-memory filesystem (for production build)
this.emitFile?.(outputPath, dtsContent); this.emitFile?.(outputPath, result.typeDefinition);
} }
} }
return `export default ${json};`; return `export default ${JSON.stringify(result.data, null, 2)};`;
} }