From b7e65ebc1dcb14afbe4712c666b2f7934ca990e8 Mon Sep 17 00:00:00 2001 From: hypercross Date: Thu, 2 Apr 2026 17:32:13 +0800 Subject: [PATCH 1/2] 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)};`; } From 16d88d610827b5df7f77d7bc22f66b0a8a802dd7 Mon Sep 17 00:00:00 2001 From: hypercross Date: Thu, 2 Apr 2026 17:34:11 +0800 Subject: [PATCH 2/2] build: update dist files for loader export --- dist/csv-loader/loader.d.mts | 37 +++++++++++++++++++++- dist/csv-loader/loader.d.ts | 37 +++++++++++++++++++++- dist/csv-loader/loader.js | 60 ++++++++++++++++++++++++++---------- dist/csv-loader/loader.mjs | 55 +++++++++++++++++++++++---------- tsconfig.json | 5 +-- 5 files changed, 156 insertions(+), 38 deletions(-) diff --git a/dist/csv-loader/loader.d.mts b/dist/csv-loader/loader.d.mts index 607e045..7d40ed6 100644 --- a/dist/csv-loader/loader.d.mts +++ b/dist/csv-loader/loader.d.mts @@ -14,6 +14,41 @@ interface CsvLoaderOptions { /** Write .d.ts files to disk (useful for dev server) */ writeToDisk?: boolean; } +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; + validator: (value: unknown) => boolean; + parser: (valueString: string) => unknown; +} +/** + * 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 + */ +declare function parseCsv(content: string, options?: CsvLoaderOptions): CsvParseResult; +/** + * 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 + */ +declare function csvToModule(content: string, options?: CsvLoaderOptions): { + js: string; + dts?: string; +}; declare function csvLoader(this: LoaderContext, content: string): string | Buffer; -export { type CsvLoaderOptions, csvLoader as default }; +export { type CsvLoaderOptions, type CsvParseResult, csvToModule, csvLoader as default, parseCsv }; diff --git a/dist/csv-loader/loader.d.ts b/dist/csv-loader/loader.d.ts index 607e045..7d40ed6 100644 --- a/dist/csv-loader/loader.d.ts +++ b/dist/csv-loader/loader.d.ts @@ -14,6 +14,41 @@ interface CsvLoaderOptions { /** Write .d.ts files to disk (useful for dev server) */ writeToDisk?: boolean; } +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; + validator: (value: unknown) => boolean; + parser: (valueString: string) => unknown; +} +/** + * 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 + */ +declare function parseCsv(content: string, options?: CsvLoaderOptions): CsvParseResult; +/** + * 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 + */ +declare function csvToModule(content: string, options?: CsvLoaderOptions): { + js: string; + dts?: string; +}; declare function csvLoader(this: LoaderContext, content: string): string | Buffer; -export { type CsvLoaderOptions, csvLoader as default }; +export { type CsvLoaderOptions, type CsvParseResult, csvToModule, csvLoader as default, parseCsv }; diff --git a/dist/csv-loader/loader.js b/dist/csv-loader/loader.js index b5fd892..9f36c26 100644 --- a/dist/csv-loader/loader.js +++ b/dist/csv-loader/loader.js @@ -30,7 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru // src/csv-loader/loader.ts var loader_exports = {}; __export(loader_exports, { - default: () => csvLoader + csvToModule: () => csvToModule, + default: () => csvLoader, + parseCsv: () => parseCsv }); module.exports = __toCommonJS(loader_exports); var import_sync = require("csv-parse/sync"); @@ -435,17 +437,14 @@ declare const data: Table; export default data; `; } -function csvLoader(content) { - const options = this.getOptions(); - const delimiter = options?.delimiter ?? ","; - const quote = options?.quote ?? '"'; - const escape = options?.escape ?? "\\"; - const bom = options?.bom ?? true; - const comment = options?.comment === false ? void 0 : options?.comment ?? "#"; - const trim = options?.trim ?? true; - const emitTypes = options?.emitTypes ?? true; - const typesOutputDir = options?.typesOutputDir ?? ""; - const writeToDisk = options?.writeToDisk ?? false; +function parseCsv(content, options = {}) { + const delimiter = options.delimiter ?? ","; + const quote = options.quote ?? '"'; + const escape = options.escape ?? "\\"; + const bom = options.bom ?? true; + const comment = options.comment === false ? void 0 : options.comment ?? "#"; + const trim = options.trim ?? true; + const emitTypes = options.emitTypes ?? true; const records = (0, import_sync.parse)(content, { delimiter, quote, @@ -499,8 +498,31 @@ function csvLoader(content) { }); return obj; }); - const json = JSON.stringify(objects, null, 2); + const result = { + data: objects, + propertyConfigs + }; if (emitTypes) { + result.typeDefinition = generateTypeDefinition("", propertyConfigs); + } + return result; +} +function csvToModule(content, options = {}) { + const result = parseCsv(content, options); + const json = JSON.stringify(result.data, null, 2); + const js = `export default ${json};`; + return { + js, + dts: result.typeDefinition + }; +} +function csvLoader(content) { + const options = this.getOptions(); + const emitTypes = options?.emitTypes ?? true; + const typesOutputDir = options?.typesOutputDir ?? ""; + const writeToDisk = options?.writeToDisk ?? false; + const result = parseCsv(content, options); + if (emitTypes && result.typeDefinition) { const context = this.context || ""; let relativePath = this.resourcePath.replace(context, ""); if (relativePath.startsWith("\\") || relativePath.startsWith("/")) { @@ -509,14 +531,18 @@ function csvLoader(content) { relativePath = relativePath.replace(/\\/g, "/"); const dtsFileName = `${relativePath}.d.ts`; const outputPath = typesOutputDir ? path.join(typesOutputDir, dtsFileName) : dtsFileName; - const dtsContent = generateTypeDefinition(this.resourcePath, propertyConfigs); if (writeToDisk) { 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 { - this.emitFile?.(outputPath, dtsContent); + this.emitFile?.(outputPath, result.typeDefinition); } } - return `export default ${json};`; + return `export default ${JSON.stringify(result.data, null, 2)};`; } +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + csvToModule, + parseCsv +}); diff --git a/dist/csv-loader/loader.mjs b/dist/csv-loader/loader.mjs index 2800a64..59a5c64 100644 --- a/dist/csv-loader/loader.mjs +++ b/dist/csv-loader/loader.mjs @@ -401,17 +401,14 @@ declare const data: Table; export default data; `; } -function csvLoader(content) { - const options = this.getOptions(); - const delimiter = options?.delimiter ?? ","; - const quote = options?.quote ?? '"'; - const escape = options?.escape ?? "\\"; - const bom = options?.bom ?? true; - const comment = options?.comment === false ? void 0 : options?.comment ?? "#"; - const trim = options?.trim ?? true; - const emitTypes = options?.emitTypes ?? true; - const typesOutputDir = options?.typesOutputDir ?? ""; - const writeToDisk = options?.writeToDisk ?? false; +function parseCsv(content, options = {}) { + const delimiter = options.delimiter ?? ","; + const quote = options.quote ?? '"'; + const escape = options.escape ?? "\\"; + const bom = options.bom ?? true; + const comment = options.comment === false ? void 0 : options.comment ?? "#"; + const trim = options.trim ?? true; + const emitTypes = options.emitTypes ?? true; const records = parse(content, { delimiter, quote, @@ -465,8 +462,31 @@ function csvLoader(content) { }); return obj; }); - const json = JSON.stringify(objects, null, 2); + const result = { + data: objects, + propertyConfigs + }; if (emitTypes) { + result.typeDefinition = generateTypeDefinition("", propertyConfigs); + } + return result; +} +function csvToModule(content, options = {}) { + const result = parseCsv(content, options); + const json = JSON.stringify(result.data, null, 2); + const js = `export default ${json};`; + return { + js, + dts: result.typeDefinition + }; +} +function csvLoader(content) { + const options = this.getOptions(); + const emitTypes = options?.emitTypes ?? true; + const typesOutputDir = options?.typesOutputDir ?? ""; + const writeToDisk = options?.writeToDisk ?? false; + const result = parseCsv(content, options); + if (emitTypes && result.typeDefinition) { const context = this.context || ""; let relativePath = this.resourcePath.replace(context, ""); if (relativePath.startsWith("\\") || relativePath.startsWith("/")) { @@ -475,17 +495,18 @@ function csvLoader(content) { relativePath = relativePath.replace(/\\/g, "/"); const dtsFileName = `${relativePath}.d.ts`; const outputPath = typesOutputDir ? path.join(typesOutputDir, dtsFileName) : dtsFileName; - const dtsContent = generateTypeDefinition(this.resourcePath, propertyConfigs); if (writeToDisk) { 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 { - this.emitFile?.(outputPath, dtsContent); + this.emitFile?.(outputPath, result.typeDefinition); } } - return `export default ${json};`; + return `export default ${JSON.stringify(result.data, null, 2)};`; } export { - csvLoader as default + csvToModule, + csvLoader as default, + parseCsv }; diff --git a/tsconfig.json b/tsconfig.json index 6885de3..ce93329 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,8 +12,9 @@ "declarationMap": true, "sourceMap": true, "outDir": "./dist", - "rootDir": "./src" + "rootDir": "./src", + "types": ["node"] }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "src/csv-loader"] + "exclude": ["node_modules", "dist"] }