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)
|
bom: true, // 处理 BOM (默认 true)
|
||||||
comment: '#', // 忽略 # 开头的注释行 (默认 '#')
|
comment: '#', // 忽略 # 开头的注释行 (默认 '#')
|
||||||
trim: true, // 修剪表头和值的前后空格 (默认 true)
|
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
|
### Importing in TypeScript
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import data from './data.schema.csv';
|
import data from './data.schema.csv';
|
||||||
|
|
||||||
// data = [
|
// TypeScript 会自动推断类型:
|
||||||
// { name: "Alice", age: 30, active: true, scores: [90, 85, 95] },
|
// data: { name: string; age: number; active: boolean; scores: number[] }[]
|
||||||
// { name: "Bob", age: 25, active: false, scores: [75, 80, 70] }
|
|
||||||
// ]
|
// 如果启用了 emitTypes,也可以显式导入类型:
|
||||||
```
|
// import type { RowType } from './data.schema.csv';
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
|
|
@ -70,6 +91,8 @@ import data from './data.schema.csv';
|
||||||
| `bom` | boolean | `true` | Handle byte order mark |
|
| `bom` | boolean | `true` | Handle byte order mark |
|
||||||
| `comment` | string \| `false` | `#` | Comment character (set `false` to disable) |
|
| `comment` | string \| `false` | `#` | Comment character (set `false` to disable) |
|
||||||
| `trim` | boolean | `true` | Trim headers and values |
|
| `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
|
## Schema Syntax
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import type { LoaderContext } from '@rspack/core';
|
import type { LoaderContext } from '@rspack/core';
|
||||||
import { parse } from 'csv-parse/sync';
|
import { parse } from 'csv-parse/sync';
|
||||||
import { parseSchema, createValidator, parseValue } from '../index.js';
|
import { parseSchema, createValidator, parseValue } from '../index.js';
|
||||||
|
import type { Schema } from '../types.js';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
export interface CsvLoaderOptions {
|
export interface CsvLoaderOptions {
|
||||||
delimiter?: string;
|
delimiter?: string;
|
||||||
|
|
@ -9,6 +11,10 @@ export interface CsvLoaderOptions {
|
||||||
bom?: boolean;
|
bom?: boolean;
|
||||||
comment?: string | false;
|
comment?: string | false;
|
||||||
trim?: boolean;
|
trim?: boolean;
|
||||||
|
/** Generate TypeScript declaration file (.d.ts) */
|
||||||
|
emitTypes?: boolean;
|
||||||
|
/** Output directory for generated type files (relative to output path) */
|
||||||
|
typesOutputDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PropertyConfig {
|
interface PropertyConfig {
|
||||||
|
|
@ -18,6 +24,57 @@ interface PropertyConfig {
|
||||||
parser: (valueString: string) => unknown;
|
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(
|
export default function csvLoader(
|
||||||
this: LoaderContext<CsvLoaderOptions>,
|
this: LoaderContext<CsvLoaderOptions>,
|
||||||
content: string
|
content: string
|
||||||
|
|
@ -29,6 +86,8 @@ export default function csvLoader(
|
||||||
const bom = options?.bom ?? true;
|
const bom = options?.bom ?? true;
|
||||||
const comment = options?.comment === false ? undefined : (options?.comment ?? '#');
|
const comment = options?.comment === false ? undefined : (options?.comment ?? '#');
|
||||||
const trim = options?.trim ?? true;
|
const trim = options?.trim ?? true;
|
||||||
|
const emitTypes = options?.emitTypes ?? true;
|
||||||
|
const typesOutputDir = options?.typesOutputDir ?? '';
|
||||||
|
|
||||||
const records = parse(content, {
|
const records = parse(content, {
|
||||||
delimiter,
|
delimiter,
|
||||||
|
|
@ -90,5 +149,16 @@ export default function csvLoader(
|
||||||
});
|
});
|
||||||
|
|
||||||
const json = JSON.stringify(objects, null, 2);
|
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};`;
|
return `export default ${json};`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue