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:
parent
bf15adb1c0
commit
69b419256c
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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};`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue