feat: add TypeScript type definition generation for csv-loader

- Add emitTypes option (default true) to generate .d.ts files
- Add typesOutputDir option for custom output directory
- Generate interface based on CSV schema definitions
- Export RowType for explicit type imports

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
hyper 2026-03-31 15:19:03 +08:00
parent bf15adb1c0
commit 69b419256c
2 changed files with 98 additions and 5 deletions

View File

@ -41,6 +41,8 @@ module.exports = {
bom: true, // 处理 BOM (默认 true)
comment: '#', // 忽略 # 开头的注释行 (默认 '#')
trim: true, // 修剪表头和值的前后空格 (默认 true)
// emitTypes: false, // 禁用类型定义生成 (默认 true)
// typesOutputDir: 'types', // 类型文件输出目录 (可选)
},
},
},
@ -49,16 +51,35 @@ module.exports = {
};
```
### Generated TypeScript Types
`emitTypes: true`loader 会自动生成 `.d.ts` 类型定义文件:
```typescript
// data.schema.d.ts
export interface data_schema {
name: string;
age: number;
active: boolean;
scores: number[];
}
export type RowType = data_schema;
declare const data: data_schema[];
export default data;
```
### Importing in TypeScript
```typescript
import data from './data.schema.csv';
// data = [
// { name: "Alice", age: 30, active: true, scores: [90, 85, 95] },
// { name: "Bob", age: 25, active: false, scores: [75, 80, 70] }
// ]
```
// TypeScript 会自动推断类型:
// data: { name: string; age: number; active: boolean; scores: number[] }[]
// 如果启用了 emitTypes也可以显式导入类型:
// import type { RowType } from './data.schema.csv';
## Options
@ -70,6 +91,8 @@ import data from './data.schema.csv';
| `bom` | boolean | `true` | Handle byte order mark |
| `comment` | string \| `false` | `#` | Comment character (set `false` to disable) |
| `trim` | boolean | `true` | Trim headers and values |
| `emitTypes` | boolean | `true` | Generate TypeScript declaration file (.d.ts) |
| `typesOutputDir` | string | `''` | Output directory for generated type files (relative to output path) |
## Schema Syntax

View File

@ -1,6 +1,8 @@
import type { LoaderContext } from '@rspack/core';
import { parse } from 'csv-parse/sync';
import { parseSchema, createValidator, parseValue } from '../index.js';
import type { Schema } from '../types.js';
import * as path from 'path';
export interface CsvLoaderOptions {
delimiter?: string;
@ -9,6 +11,10 @@ export interface CsvLoaderOptions {
bom?: boolean;
comment?: string | false;
trim?: boolean;
/** Generate TypeScript declaration file (.d.ts) */
emitTypes?: boolean;
/** Output directory for generated type files (relative to output path) */
typesOutputDir?: string;
}
interface PropertyConfig {
@ -18,6 +24,57 @@ interface PropertyConfig {
parser: (valueString: string) => unknown;
}
/**
* Convert a schema to TypeScript type string
*/
function schemaToTypeString(schema: Schema): string {
switch (schema.type) {
case 'string':
return 'string';
case 'number':
return 'number';
case 'boolean':
return 'boolean';
case 'array':
if (schema.element.type === 'tuple') {
const tupleElements = schema.element.elements.map(schemaToTypeString);
return `[${tupleElements.join(', ')}]`;
}
return `${schemaToTypeString(schema.element)}[]`;
case 'tuple':
const tupleElements = schema.elements.map(schemaToTypeString);
return `[${tupleElements.join(', ')}]`;
default:
return 'unknown';
}
}
/**
* Generate TypeScript interface for the CSV data
*/
function generateTypeDefinition(
resourceName: string,
propertyConfigs: PropertyConfig[]
): string {
const interfaceName = path.basename(resourceName, path.extname(resourceName))
.replace(/[^a-zA-Z0-9_$]/g, '_')
.replace(/^(\d)/, '_$1');
const properties = propertyConfigs
.map((config) => ` ${config.name}: ${schemaToTypeString(config.schema)};`)
.join('\n');
return `export interface ${interfaceName} {
${properties}
}
export type RowType = ${interfaceName};
declare const data: ${interfaceName}[];
export default data;
`;
}
export default function csvLoader(
this: LoaderContext<CsvLoaderOptions>,
content: string
@ -29,6 +86,8 @@ export default function csvLoader(
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 records = parse(content, {
delimiter,
@ -90,5 +149,16 @@ export default function csvLoader(
});
const json = JSON.stringify(objects, null, 2);
// Emit type definition file if enabled
if (emitTypes) {
const dtsContent = generateTypeDefinition(this.resourcePath, propertyConfigs);
const relativePath = this.resourcePath.replace(this.context, '');
const dtsFileName = relativePath.replace(/\.csv$/, '.d.ts');
const outputPath = path.join(typesOutputDir, dtsFileName);
this.emitFile?.(outputPath, dtsContent);
}
return `export default ${json};`;
}