From b7e65ebc1dcb14afbe4712c666b2f7934ca990e8 Mon Sep 17 00:00:00 2001 From: hypercross Date: Thu, 2 Apr 2026 17:32:13 +0800 Subject: [PATCH] feat: export extra functions from the loader --- src/csv-loader/loader.ts | 97 +++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 20 deletions(-) diff --git a/src/csv-loader/loader.ts b/src/csv-loader/loader.ts index 058aafb..0ba79e9 100644 --- a/src/csv-loader/loader.ts +++ b/src/csv-loader/loader.ts @@ -20,6 +20,15 @@ export interface CsvLoaderOptions { writeToDisk?: boolean; } +export interface CsvParseResult { + /** Parsed CSV data as array of objects */ + data: Record[]; + /** Generated TypeScript type definition string (if emitTypes is true) */ + typeDefinition?: string; + /** Property configurations for the CSV columns */ + propertyConfigs: PropertyConfig[]; +} + interface PropertyConfig { name: string; schema: any; @@ -78,20 +87,25 @@ export default data; `; } -export default function csvLoader( - this: LoaderContext, - content: string -): string | Buffer { - const options = this.getOptions() as CsvLoaderOptions | undefined; - const delimiter = options?.delimiter ?? ','; - 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 typesOutputDir = options?.typesOutputDir ?? ''; - const writeToDisk = options?.writeToDisk ?? false; +/** + * Parse CSV content string into structured data with schema validation. + * This is a standalone function that doesn't depend on webpack/rspack LoaderContext. + * + * @param content - CSV content string (must have at least headers + schema row + 1 data row) + * @param options - Parsing options + * @returns CsvParseResult containing parsed data and optional type definitions + */ +export function parseCsv( + content: string, + options: CsvLoaderOptions = {} +): CsvParseResult { + const delimiter = options.delimiter ?? ','; + 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, { delimiter, @@ -152,10 +166,54 @@ export default function csvLoader( 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, + 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 - if (emitTypes) { + if (emitTypes && result.typeDefinition) { const context = this.context || ''; // Get relative path from context, normalize to forward slashes let relativePath = this.resourcePath.replace(context, ''); @@ -169,18 +227,17 @@ export default function csvLoader( const outputPath = typesOutputDir ? path.join(typesOutputDir, dtsFileName) : dtsFileName; - const dtsContent = generateTypeDefinition(this.resourcePath, propertyConfigs); if (writeToDisk) { // Write directly to disk (useful for dev server) const absolutePath = path.join(this.context || process.cwd(), typesOutputDir || '', dtsFileName); fs.mkdirSync(path.dirname(absolutePath), { recursive: true }); - fs.writeFileSync(absolutePath, dtsContent); + fs.writeFileSync(absolutePath, result.typeDefinition); } else { // 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)};`; }