code-generation-template
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCode Generation & Templates
代码生成与模板
Overview
概述
Comprehensive guide to code generation techniques including template engines, AST manipulation, code scaffolding, and automated boilerplate generation for increased productivity and consistency.
本指南全面介绍代码生成技术,包括模板引擎、AST操作、代码scaffolding以及自动化boilerplate生成,旨在提升开发效率和代码一致性。
When to Use
适用场景
- Scaffolding new projects or components
- Generating repetitive boilerplate code
- Creating CRUD operations automatically
- Generating API clients from OpenAPI specs
- Building code from templates
- Creating database models from schemas
- Generating TypeScript types from JSON Schema
- Building custom CLI generators
- 搭建新项目或组件的脚手架
- 生成重复的样板代码
- 自动创建CRUD操作
- 从OpenAPI规范生成API客户端
- 通过模板构建代码
- 从数据库schema生成数据库模型
- 从JSON Schema生成TypeScript类型
- 构建自定义CLI生成器
Instructions
使用说明
1. Template Engines
1. 模板引擎
Handlebars Templates
Handlebars模板
typescript
// templates/component.hbs
import React from 'react';
export interface {{pascalCase name}}Props {
{{#each props}}
{{this.name}}{{#if this.optional}}?{{/if}}: {{this.type}};
{{/each}}
}
export const {{pascalCase name}}: React.FC<{{pascalCase name}}Props> = ({
{{#each props}}{{this.name}},{{/each}}
}) => {
return (
<div className="{{kebabCase name}}">
{/* Component implementation */}
</div>
);
};typescript
// generator.ts
import Handlebars from 'handlebars';
import fs from 'fs';
// Register helpers
Handlebars.registerHelper('pascalCase', (str: string) =>
str.replace(/(\w)(\w*)/g, (_, first, rest) =>
first.toUpperCase() + rest.toLowerCase()
)
);
Handlebars.registerHelper('kebabCase', (str: string) =>
str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
);
// Load template
const templateSource = fs.readFileSync('templates/component.hbs', 'utf8');
const template = Handlebars.compile(templateSource);
// Generate code
const code = template({
name: 'userProfile',
props: [
{ name: 'userId', type: 'string', optional: false },
{ name: 'onUpdate', type: '() => void', optional: true }
]
});
fs.writeFileSync('src/components/UserProfile.tsx', code);typescript
// templates/component.hbs
import React from 'react';
export interface {{pascalCase name}}Props {
{{#each props}}
{{this.name}}{{#if this.optional}}?{{/if}}: {{this.type}};
{{/each}}
}
export const {{pascalCase name}}: React.FC<{{pascalCase name}}Props> = ({
{{#each props}}{{this.name}},{{/each}}
}) => {
return (
<div className="{{kebabCase name}}">
{/* Component implementation */}
</div>
);
};typescript
// generator.ts
import Handlebars from 'handlebars';
import fs from 'fs';
// Register helpers
Handlebars.registerHelper('pascalCase', (str: string) =>
str.replace(/(\w)(\w*)/g, (_, first, rest) =>
first.toUpperCase() + rest.toLowerCase()
)
);
Handlebars.registerHelper('kebabCase', (str: string) =>
str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
);
// Load template
const templateSource = fs.readFileSync('templates/component.hbs', 'utf8');
const template = Handlebars.compile(templateSource);
// Generate code
const code = template({
name: 'userProfile',
props: [
{ name: 'userId', type: 'string', optional: false },
{ name: 'onUpdate', type: '() => void', optional: true }
]
});
fs.writeFileSync('src/components/UserProfile.tsx', code);EJS Templates
EJS模板
typescript
// templates/api-endpoint.ejs
import { Router } from 'express';
import { <%= modelName %>Service } from '../services/<%= kebabCase(modelName) %>.service';
const router = Router();
const service = new <%= modelName %>Service();
// GET /<%= pluralize(kebabCase(modelName)) %>
router.get('/', async (req, res) => {
try {
const items = await service.findAll();
res.json(items);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// GET /<%= pluralize(kebabCase(modelName)) %>/:id
router.get('/:id', async (req, res) => {
try {
const item = await service.findById(req.params.id);
if (!item) {
return res.status(404).json({ error: 'Not found' });
}
res.json(item);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// POST /<%= pluralize(kebabCase(modelName)) %>
router.post('/', async (req, res) => {
try {
const item = await service.create(req.body);
res.status(201).json(item);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
export default router;typescript
// Using EJS
import ejs from 'ejs';
const code = await ejs.renderFile('templates/api-endpoint.ejs', {
modelName: 'User',
kebabCase: (str: string) => str.replace(/([A-Z])/g, '-$1').toLowerCase().slice(1),
pluralize: (str: string) => str + 's'
});typescript
// templates/api-endpoint.ejs
import { Router } from 'express';
import { <%= modelName %>Service } from '../services/<%= kebabCase(modelName) %>.service';
const router = Router();
const service = new <%= modelName %>Service();
// GET /<%= pluralize(kebabCase(modelName)) %>
router.get('/', async (req, res) => {
try {
const items = await service.findAll();
res.json(items);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// GET /<%= pluralize(kebabCase(modelName)) %>/:id
router.get('/:id', async (req, res) => {
try {
const item = await service.findById(req.params.id);
if (!item) {
return res.status(404).json({ error: 'Not found' });
}
res.json(item);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// POST /<%= pluralize(kebabCase(modelName)) %>
router.post('/', async (req, res) => {
try {
const item = await service.create(req.body);
res.status(201).json(item);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
export default router;typescript
// Using EJS
import ejs from 'ejs';
const code = await ejs.renderFile('templates/api-endpoint.ejs', {
modelName: 'User',
kebabCase: (str: string) => str.replace(/([A-Z])/g, '-$1').toLowerCase().slice(1),
pluralize: (str: string) => str + 's'
});2. AST-Based Code Generation
2. 基于AST的代码生成
Using Babel/TypeScript AST
使用Babel/TypeScript AST
typescript
// ast-generator.ts
import * as ts from 'typescript';
export class TypeScriptGenerator {
// Generate interface
generateInterface(name: string, properties: Array<{ name: string; type: string; optional?: boolean }>) {
const members = properties.map(prop =>
ts.factory.createPropertySignature(
undefined,
ts.factory.createIdentifier(prop.name),
prop.optional ? ts.factory.createToken(ts.SyntaxKind.QuestionToken) : undefined,
ts.factory.createTypeReferenceNode(prop.type)
)
);
const interfaceDecl = ts.factory.createInterfaceDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier(name),
undefined,
undefined,
members
);
return this.printNode(interfaceDecl);
}
// Generate class
generateClass(name: string, properties: Array<{ name: string; type: string }>) {
const propertyDecls = properties.map(prop =>
ts.factory.createPropertyDeclaration(
[ts.factory.createToken(ts.SyntaxKind.PrivateKeyword)],
ts.factory.createIdentifier(prop.name),
undefined,
ts.factory.createTypeReferenceNode(prop.type),
undefined
)
);
const constructor = ts.factory.createConstructorDeclaration(
undefined,
properties.map(prop =>
ts.factory.createParameterDeclaration(
undefined,
undefined,
ts.factory.createIdentifier(prop.name),
undefined,
ts.factory.createTypeReferenceNode(prop.type)
)
),
ts.factory.createBlock(
properties.map(prop =>
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createThis(),
prop.name
),
ts.SyntaxKind.EqualsToken,
ts.factory.createIdentifier(prop.name)
)
)
),
true
)
);
const classDecl = ts.factory.createClassDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier(name),
undefined,
undefined,
[...propertyDecls, constructor]
);
return this.printNode(classDecl);
}
private printNode(node: ts.Node): string {
const sourceFile = ts.createSourceFile(
'temp.ts',
'',
ts.ScriptTarget.Latest,
false,
ts.ScriptKind.TS
);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
}
}
// Usage
const generator = new TypeScriptGenerator();
const interfaceCode = generator.generateInterface('User', [
{ name: 'id', type: 'string' },
{ name: 'email', type: 'string' },
{ name: 'name', type: 'string', optional: true }
]);
const classCode = generator.generateClass('UserService', [
{ name: 'repository', type: 'UserRepository' },
{ name: 'logger', type: 'Logger' }
]);typescript
// ast-generator.ts
import * as ts from 'typescript';
export class TypeScriptGenerator {
// Generate interface
generateInterface(name: string, properties: Array<{ name: string; type: string; optional?: boolean }>) {
const members = properties.map(prop =>
ts.factory.createPropertySignature(
undefined,
ts.factory.createIdentifier(prop.name),
prop.optional ? ts.factory.createToken(ts.SyntaxKind.QuestionToken) : undefined,
ts.factory.createTypeReferenceNode(prop.type)
)
);
const interfaceDecl = ts.factory.createInterfaceDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier(name),
undefined,
undefined,
members
);
return this.printNode(interfaceDecl);
}
// Generate class
generateClass(name: string, properties: Array<{ name: string; type: string }>) {
const propertyDecls = properties.map(prop =>
ts.factory.createPropertyDeclaration(
[ts.factory.createToken(ts.SyntaxKind.PrivateKeyword)],
ts.factory.createIdentifier(prop.name),
undefined,
ts.factory.createTypeReferenceNode(prop.type),
undefined
)
);
const constructor = ts.factory.createConstructorDeclaration(
undefined,
properties.map(prop =>
ts.factory.createParameterDeclaration(
undefined,
undefined,
ts.factory.createIdentifier(prop.name),
undefined,
ts.factory.createTypeReferenceNode(prop.type)
)
),
ts.factory.createBlock(
properties.map(prop =>
ts.factory.createExpressionStatement(
ts.factory.createBinaryExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createThis(),
prop.name
),
ts.SyntaxKind.EqualsToken,
ts.factory.createIdentifier(prop.name)
)
)
),
true
)
);
const classDecl = ts.factory.createClassDeclaration(
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier(name),
undefined,
undefined,
[...propertyDecls, constructor]
);
return this.printNode(classDecl);
}
private printNode(node: ts.Node): string {
const sourceFile = ts.createSourceFile(
'temp.ts',
'',
ts.ScriptTarget.Latest,
false,
ts.ScriptKind.TS
);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
}
}
// Usage
const generator = new TypeScriptGenerator();
const interfaceCode = generator.generateInterface('User', [
{ name: 'id', type: 'string' },
{ name: 'email', type: 'string' },
{ name: 'name', type: 'string', optional: true }
]);
const classCode = generator.generateClass('UserService', [
{ name: 'repository', type: 'UserRepository' },
{ name: 'logger', type: 'Logger' }
]);3. Project Scaffolding
3. 项目脚手架
Simple CLI Generator
简单CLI生成器
typescript
// cli/generate.ts
#!/usr/bin/env node
import { Command } from 'commander';
import inquirer from 'inquirer';
import fs from 'fs-extra';
import path from 'path';
const program = new Command();
program
.name('generate')
.description('Code generator CLI')
.version('1.0.0');
program
.command('component <name>')
.description('Generate a React component')
.option('-d, --dir <directory>', 'Output directory', 'src/components')
.action(async (name, options) => {
const answers = await inquirer.prompt([
{
type: 'list',
name: 'type',
message: 'Component type?',
choices: ['functional', 'class']
},
{
type: 'confirm',
name: 'typescript',
message: 'Use TypeScript?',
default: true
},
{
type: 'confirm',
name: 'test',
message: 'Generate test file?',
default: true
}
]);
await generateComponent(name, options.dir, answers);
});
program
.command('api <resource>')
.description('Generate API endpoint with controller, service, and model')
.action(async (resource) => {
await generateApiResource(resource);
});
program.parse();
async function generateComponent(name: string, dir: string, options: any) {
const componentName = pascalCase(name);
const ext = options.typescript ? 'tsx' : 'jsx';
const template = options.type === 'functional'
? getFunctionalComponentTemplate(componentName, options.typescript)
: getClassComponentTemplate(componentName, options.typescript);
const componentPath = path.join(dir, `${componentName}.${ext}`);
await fs.ensureDir(dir);
await fs.writeFile(componentPath, template);
console.log(`✓ Created ${componentPath}`);
if (options.test) {
const testTemplate = getTestTemplate(componentName, options.typescript);
const testPath = path.join(dir, `${componentName}.test.${ext}`);
await fs.writeFile(testPath, testTemplate);
console.log(`✓ Created ${testPath}`);
}
}
function getFunctionalComponentTemplate(name: string, ts: boolean): string {
if (ts) {
return `import React from 'react';
export interface ${name}Props {
// Add props here
}
export const ${name}: React.FC<${name}Props> = (props) => {
return (
<div className="${kebabCase(name)}">
<h1>${name}</h1>
</div>
);
};
`;
}
return `import React from 'react';
export const ${name} = (props) => {
return (
<div className="${kebabCase(name)}">
<h1>${name}</h1>
</div>
);
};
`;
}
async function generateApiResource(resource: string) {
const name = pascalCase(resource);
// Generate model
const modelCode = `export interface ${name} {
id: string;
createdAt: Date;
updatedAt: Date;
// Add fields here
}
`;
await fs.writeFile(`src/models/${kebabCase(resource)}.model.ts`, modelCode);
// Generate service
const serviceCode = `import { ${name} } from '../models/${kebabCase(resource)}.model';
export class ${name}Service {
async findAll(): Promise<${name}[]> {
// Implement
return [];
}
async findById(id: string): Promise<${name} | null> {
// Implement
return null;
}
async create(data: Partial<${name}>): Promise<${name}> {
// Implement
throw new Error('Not implemented');
}
async update(id: string, data: Partial<${name}>): Promise<${name}> {
// Implement
throw new Error('Not implemented');
}
async delete(id: string): Promise<void> {
// Implement
}
}
`;
await fs.writeFile(`src/services/${kebabCase(resource)}.service.ts`, serviceCode);
// Generate controller
const controllerCode = `import { Router } from 'express';
import { ${name}Service } from '../services/${kebabCase(resource)}.service';
const router = Router();
const service = new ${name}Service();
router.get('/', async (req, res) => {
const items = await service.findAll();
res.json(items);
});
router.get('/:id', async (req, res) => {
const item = await service.findById(req.params.id);
if (!item) return res.status(404).json({ error: 'Not found' });
res.json(item);
});
router.post('/', async (req, res) => {
const item = await service.create(req.body);
res.status(201).json(item);
});
router.put('/:id', async (req, res) => {
const item = await service.update(req.params.id, req.body);
res.json(item);
});
router.delete('/:id', async (req, res) => {
await service.delete(req.params.id);
res.status(204).send();
});
export default router;
`;
await fs.writeFile(`src/controllers/${kebabCase(resource)}.controller.ts`, controllerCode);
console.log(`✓ Generated API resource: ${name}`);
}typescript
// cli/generate.ts
#!/usr/bin/env node
import { Command } from 'commander';
import inquirer from 'inquirer';
import fs from 'fs-extra';
import path from 'path';
const program = new Command();
program
.name('generate')
.description('Code generator CLI')
.version('1.0.0');
program
.command('component <name>')
.description('Generate a React component')
.option('-d, --dir <directory>', 'Output directory', 'src/components')
.action(async (name, options) => {
const answers = await inquirer.prompt([
{
type: 'list',
name: 'type',
message: 'Component type?',
choices: ['functional', 'class']
},
{
type: 'confirm',
name: 'typescript',
message: 'Use TypeScript?',
default: true
},
{
type: 'confirm',
name: 'test',
message: 'Generate test file?',
default: true
}
]);
await generateComponent(name, options.dir, answers);
});
program
.command('api <resource>')
.description('Generate API endpoint with controller, service, and model')
.action(async (resource) => {
await generateApiResource(resource);
});
program.parse();
async function generateComponent(name: string, dir: string, options: any) {
const componentName = pascalCase(name);
const ext = options.typescript ? 'tsx' : 'jsx';
const template = options.type === 'functional'
? getFunctionalComponentTemplate(componentName, options.typescript)
: getClassComponentTemplate(componentName, options.typescript);
const componentPath = path.join(dir, `${componentName}.${ext}`);
await fs.ensureDir(dir);
await fs.writeFile(componentPath, template);
console.log(`✓ Created ${componentPath}`);
if (options.test) {
const testTemplate = getTestTemplate(componentName, options.typescript);
const testPath = path.join(dir, `${componentName}.test.${ext}`);
await fs.writeFile(testPath, testTemplate);
console.log(`✓ Created ${testPath}`);
}
}
function getFunctionalComponentTemplate(name: string, ts: boolean): string {
if (ts) {
return `import React from 'react';
export interface ${name}Props {
// Add props here
}
export const ${name}: React.FC<${name}Props> = (props) => {
return (
<div className="${kebabCase(name)}">
<h1>${name}</h1>
</div>
);
};
`;
}
return `import React from 'react';
export const ${name} = (props) => {
return (
<div className="${kebabCase(name)}">
<h1>${name}</h1>
</div>
);
};
`;
}
async function generateApiResource(resource: string) {
const name = pascalCase(resource);
// Generate model
const modelCode = `export interface ${name} {
id: string;
createdAt: Date;
updatedAt: Date;
// Add fields here
}
`;
await fs.writeFile(`src/models/${kebabCase(resource)}.model.ts`, modelCode);
// Generate service
const serviceCode = `import { ${name} } from '../models/${kebabCase(resource)}.model';
export class ${name}Service {
async findAll(): Promise<${name}[]> {
// Implement
return [];
}
async findById(id: string): Promise<${name} | null> {
// Implement
return null;
}
async create(data: Partial<${name}>): Promise<${name}> {
// Implement
throw new Error('Not implemented');
}
async update(id: string, data: Partial<${name}>): Promise<${name}> {
// Implement
throw new Error('Not implemented');
}
async delete(id: string): Promise<void> {
// Implement
}
}
`;
await fs.writeFile(`src/services/${kebabCase(resource)}.service.ts`, serviceCode);
// Generate controller
const controllerCode = `import { Router } from 'express';
import { ${name}Service } from '../services/${kebabCase(resource)}.service';
const router = Router();
const service = new ${name}Service();
router.get('/', async (req, res) => {
const items = await service.findAll();
res.json(items);
});
router.get('/:id', async (req, res) => {
const item = await service.findById(req.params.id);
if (!item) return res.status(404).json({ error: 'Not found' });
res.json(item);
});
router.post('/', async (req, res) => {
const item = await service.create(req.body);
res.status(201).json(item);
});
router.put('/:id', async (req, res) => {
const item = await service.update(req.params.id, req.body);
res.json(item);
});
router.delete('/:id', async (req, res) => {
await service.delete(req.params.id);
res.status(204).send();
});
export default router;
`;
await fs.writeFile(`src/controllers/${kebabCase(resource)}.controller.ts`, controllerCode);
console.log(`✓ Generated API resource: ${name}`);
}4. OpenAPI Client Generation
4. OpenAPI客户端生成
typescript
// openapi-client-generator.ts
import SwaggerParser from '@apidevtools/swagger-parser';
import { compile } from 'json-schema-to-typescript';
export class OpenAPIClientGenerator {
async generate(specPath: string, outputDir: string) {
const api = await SwaggerParser.parse(specPath);
// Generate TypeScript types from schemas
if (api.components?.schemas) {
for (const [name, schema] of Object.entries(api.components.schemas)) {
const ts = await compile(schema as any, name, {
bannerComment: ''
});
await fs.writeFile(
path.join(outputDir, 'types', `${name}.ts`),
ts
);
}
}
// Generate API client methods
for (const [path, pathItem] of Object.entries(api.paths)) {
for (const [method, operation] of Object.entries(pathItem)) {
if (['get', 'post', 'put', 'delete', 'patch'].includes(method)) {
const clientMethod = this.generateClientMethod(
method,
path,
operation as any
);
// Write to file...
}
}
}
}
private generateClientMethod(
method: string,
path: string,
operation: any
): string {
const functionName = operation.operationId || this.pathToFunctionName(method, path);
const parameters = operation.parameters || [];
return `
async ${functionName}(${this.generateParameters(parameters)}): Promise<${this.getResponseType(operation)}> {
const response = await this.request('${method.toUpperCase()}', '${path}', {
${this.generateRequestOptions(parameters)}
});
return response.json();
}
`;
}
private generateParameters(parameters: any[]): string {
return parameters
.map(p => `${p.name}${p.required ? '' : '?'}: ${this.schemaToType(p.schema)}`)
.join(', ');
}
private getResponseType(operation: any): string {
const successResponse = operation.responses['200'] || operation.responses['201'];
if (!successResponse) return 'any';
const schema = successResponse.content?.['application/json']?.schema;
return schema ? this.schemaToType(schema) : 'any';
}
private schemaToType(schema: any): string {
if (schema.$ref) {
return schema.$ref.split('/').pop();
}
if (schema.type === 'string') return 'string';
if (schema.type === 'number' || schema.type === 'integer') return 'number';
if (schema.type === 'boolean') return 'boolean';
if (schema.type === 'array') return `${this.schemaToType(schema.items)}[]`;
return 'any';
}
private pathToFunctionName(method: string, path: string): string {
const cleanPath = path.replace(/\{.*?\}/g, 'By').replace(/[^a-zA-Z0-9]/g, '');
return `${method}${cleanPath}`;
}
}typescript
// openapi-client-generator.ts
import SwaggerParser from '@apidevtools/swagger-parser';
import { compile } from 'json-schema-to-typescript';
export class OpenAPIClientGenerator {
async generate(specPath: string, outputDir: string) {
const api = await SwaggerParser.parse(specPath);
// Generate TypeScript types from schemas
if (api.components?.schemas) {
for (const [name, schema] of Object.entries(api.components.schemas)) {
const ts = await compile(schema as any, name, {
bannerComment: ''
});
await fs.writeFile(
path.join(outputDir, 'types', `${name}.ts`),
ts
);
}
}
// Generate API client methods
for (const [path, pathItem] of Object.entries(api.paths)) {
for (const [method, operation] of Object.entries(pathItem)) {
if (['get', 'post', 'put', 'delete', 'patch'].includes(method)) {
const clientMethod = this.generateClientMethod(
method,
path,
operation as any
);
// Write to file...
}
}
}
}
private generateClientMethod(
method: string,
path: string,
operation: any
): string {
const functionName = operation.operationId || this.pathToFunctionName(method, path);
const parameters = operation.parameters || [];
return `
async ${functionName}(${this.generateParameters(parameters)}): Promise<${this.getResponseType(operation)}> {
const response = await this.request('${method.toUpperCase()}', '${path}', {
${this.generateRequestOptions(parameters)}
});
return response.json();
}
`;
}
private generateParameters(parameters: any[]): string {
return parameters
.map(p => `${p.name}${p.required ? '' : '?'}: ${this.schemaToType(p.schema)}`)
.join(', ');
}
private getResponseType(operation: any): string {
const successResponse = operation.responses['200'] || operation.responses['201'];
if (!successResponse) return 'any';
const schema = successResponse.content?.['application/json']?.schema;
return schema ? this.schemaToType(schema) : 'any';
}
private schemaToType(schema: any): string {
if (schema.$ref) {
return schema.$ref.split('/').pop();
}
if (schema.type === 'string') return 'string';
if (schema.type === 'number' || schema.type === 'integer') return 'number';
if (schema.type === 'boolean') return 'boolean';
if (schema.type === 'array') return `${this.schemaToType(schema.items)}[]`;
return 'any';
}
private pathToFunctionName(method: string, path: string): string {
const cleanPath = path.replace(/\{.*?\}/g, 'By').replace(/[^a-zA-Z0-9]/g, '');
return `${method}${cleanPath}`;
}
}5. Database Model Generation
5. 数据库模型生成
typescript
// prisma-schema-generator.ts
export class PrismaSchemaGenerator {
generateModel(table: DatabaseTable): string {
return `model ${pascalCase(table.name)} {
${table.columns.map(col => this.generateField(col)).join('\n')}
${this.generateRelations(table.relations)}
${this.generateIndexes(table.indexes)}
}
`;
}
private generateField(column: Column): string {
const optional = !column.required ? '?' : '';
const unique = column.unique ? ' @unique' : '';
const defaultValue = column.default ? ` @default(${column.default})` : '';
return ` ${column.name} ${this.mapType(column.type)}${optional}${unique}${defaultValue}`;
}
private mapType(sqlType: string): string {
const typeMap: Record<string, string> = {
'varchar': 'String',
'text': 'String',
'integer': 'Int',
'bigint': 'BigInt',
'boolean': 'Boolean',
'timestamp': 'DateTime',
'date': 'DateTime',
'json': 'Json'
};
return typeMap[sqlType.toLowerCase()] || 'String';
}
private generateRelations(relations: Relation[]): string {
return relations.map(rel => {
if (rel.type === 'hasMany') {
return ` ${rel.name} ${rel.model}[]`;
} else if (rel.type === 'belongsTo') {
return ` ${rel.name} ${rel.model} @relation(fields: [${rel.foreignKey}], references: [id])`;
}
return '';
}).join('\n');
}
}typescript
// prisma-schema-generator.ts
export class PrismaSchemaGenerator {
generateModel(table: DatabaseTable): string {
return `model ${pascalCase(table.name)} {
${table.columns.map(col => this.generateField(col)).join('\n')}
${this.generateRelations(table.relations)}
${this.generateIndexes(table.indexes)}
}
`;
}
private generateField(column: Column): string {
const optional = !column.required ? '?' : '';
const unique = column.unique ? ' @unique' : '';
const defaultValue = column.default ? ` @default(${column.default})` : '';
return ` ${column.name} ${this.mapType(column.type)}${optional}${unique}${defaultValue}`;
}
private mapType(sqlType: string): string {
const typeMap: Record<string, string> = {
'varchar': 'String',
'text': 'String',
'integer': 'Int',
'bigint': 'BigInt',
'boolean': 'Boolean',
'timestamp': 'DateTime',
'date': 'DateTime',
'json': 'Json'
};
return typeMap[sqlType.toLowerCase()] || 'String';
}
private generateRelations(relations: Relation[]): string {
return relations.map(rel => {
if (rel.type === 'hasMany') {
return ` ${rel.name} ${rel.model}[]`;
} else if (rel.type === 'belongsTo') {
return ` ${rel.name} ${rel.model} @relation(fields: [${rel.foreignKey}], references: [id])`;
}
return '';
}).join('\n');
}
}6. GraphQL Code Generation
6. GraphQL代码生成
typescript
// graphql-codegen.config.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: 'http://localhost:4000/graphql',
documents: ['src/**/*.tsx', 'src/**/*.ts'],
generates: {
'./src/generated/graphql.ts': {
plugins: [
'typescript',
'typescript-operations',
'typescript-react-apollo'
],
config: {
withHooks: true,
withComponent: false,
withHOC: false
}
},
'./src/generated/introspection.json': {
plugins: ['introspection']
}
}
};
export default config;typescript
// graphql-codegen.config.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: 'http://localhost:4000/graphql',
documents: ['src/**/*.tsx', 'src/**/*.ts'],
generates: {
'./src/generated/graphql.ts': {
plugins: [
'typescript',
'typescript-operations',
'typescript-react-apollo'
],
config: {
withHooks: true,
withComponent: false,
withHOC: false
}
},
'./src/generated/introspection.json': {
plugins: ['introspection']
}
}
};
export default config;7. Plop.js Generator
7. Plop.js生成器
typescript
// plopfile.ts
import { NodePlopAPI } from 'plop';
export default function (plop: NodePlopAPI) {
// Component generator
plop.setGenerator('component', {
description: 'React component',
prompts: [
{
type: 'input',
name: 'name',
message: 'Component name:'
},
{
type: 'list',
name: 'type',
message: 'Component type:',
choices: ['functional', 'class']
}
],
actions: [
{
type: 'add',
path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.tsx',
templateFile: 'templates/component.hbs'
},
{
type: 'add',
path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.test.tsx',
templateFile: 'templates/component.test.hbs'
},
{
type: 'add',
path: 'src/components/{{pascalCase name}}/index.ts',
template: "export { {{pascalCase name}} } from './{{pascalCase name}}';\n"
}
]
});
// API generator
plop.setGenerator('api', {
description: 'API endpoint with full stack',
prompts: [
{
type: 'input',
name: 'name',
message: 'Resource name (e.g., user, post):'
}
],
actions: [
{
type: 'add',
path: 'src/models/{{kebabCase name}}.model.ts',
templateFile: 'templates/model.hbs'
},
{
type: 'add',
path: 'src/services/{{kebabCase name}}.service.ts',
templateFile: 'templates/service.hbs'
},
{
type: 'add',
path: 'src/controllers/{{kebabCase name}}.controller.ts',
templateFile: 'templates/controller.hbs'
},
{
type: 'add',
path: 'src/routes/{{kebabCase name}}.routes.ts',
templateFile: 'templates/routes.hbs'
}
]
});
}typescript
// plopfile.ts
import { NodePlopAPI } from 'plop';
export default function (plop: NodePlopAPI) {
// Component generator
plop.setGenerator('component', {
description: 'React component',
prompts: [
{
type: 'input',
name: 'name',
message: 'Component name:'
},
{
type: 'list',
name: 'type',
message: 'Component type:',
choices: ['functional', 'class']
}
],
actions: [
{
type: 'add',
path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.tsx',
templateFile: 'templates/component.hbs'
},
{
type: 'add',
path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.test.tsx',
templateFile: 'templates/component.test.hbs'
},
{
type: 'add',
path: 'src/components/{{pascalCase name}}/index.ts',
template: "export { {{pascalCase name}} } from './{{pascalCase name}}';\n"
}
]
});
// API generator
plop.setGenerator('api', {
description: 'API endpoint with full stack',
prompts: [
{
type: 'input',
name: 'name',
message: 'Resource name (e.g., user, post):'
}
],
actions: [
{
type: 'add',
path: 'src/models/{{kebabCase name}}.model.ts',
templateFile: 'templates/model.hbs'
},
{
type: 'add',
path: 'src/services/{{kebabCase name}}.service.ts',
templateFile: 'templates/service.hbs'
},
{
type: 'add',
path: 'src/controllers/{{kebabCase name}}.controller.ts',
templateFile: 'templates/controller.hbs'
},
{
type: 'add',
path: 'src/routes/{{kebabCase name}}.routes.ts',
templateFile: 'templates/routes.hbs'
}
]
});
}Best Practices
最佳实践
✅ DO
✅ 建议
- Use templates for repetitive code patterns
- Generate TypeScript types from schemas
- Include tests in generated code
- Follow project conventions in templates
- Add comments to explain generated code
- Version control your templates
- Make templates configurable
- Generate documentation alongside code
- Validate inputs before generating
- Use consistent naming conventions
- Keep templates simple and maintainable
- Provide CLI for easy generation
- 为重复代码模式使用模板
- 从schema生成TypeScript类型
- 在生成的代码中包含测试
- 在模板中遵循项目约定
- 添加注释说明生成的代码
- 对模板进行版本控制
- 使模板可配置
- 随代码一起生成文档
- 生成前验证输入
- 使用一致的命名约定
- 保持模板简单易维护
- 提供CLI以便于生成
❌ DON'T
❌ 不建议
- Over-generate (avoid unnecessary complexity)
- Generate code that's hard to maintain
- Forget to validate generated code
- Hardcode values in templates
- Generate code without documentation
- Create generators for one-off use cases
- Mix business logic in templates
- Generate code without formatting
- Skip error handling in generators
- Create overly complex templates
- 过度生成(避免不必要的复杂性)
- 生成难以维护的代码
- 忘记验证生成的代码
- 在模板中硬编码值
- 生成无文档的代码
- 为一次性使用场景创建生成器
- 在模板中混入业务逻辑
- 生成未格式化的代码
- 在生成器中跳过错误处理
- 创建过于复杂的模板
Common Patterns
常见模式
Pattern 1: CRUD Generator
模式1:CRUD生成器
typescript
export function generateCRUD(entityName: string) {
return {
model: generateModel(entityName),
service: generateService(entityName),
controller: generateController(entityName),
routes: generateRoutes(entityName),
tests: generateTests(entityName)
};
}typescript
export function generateCRUD(entityName: string) {
return {
model: generateModel(entityName),
service: generateService(entityName),
controller: generateController(entityName),
routes: generateRoutes(entityName),
tests: generateTests(entityName)
};
}Pattern 2: Migration Generator
模式2:迁移生成器
typescript
export function generateMigration(name: string, changes: SchemaChange[]) {
return {
up: generateUpMigration(changes),
down: generateDownMigration(changes)
};
}typescript
export function generateMigration(name: string, changes: SchemaChange[]) {
return {
up: generateUpMigration(changes),
down: generateDownMigration(changes)
};
}Pattern 3: Factory Generator
模式3:工厂生成器
typescript
export function generateFactory(model: Model) {
return `export const create${model.name} = (overrides?: Partial<${model.name}>): ${model.name} => ({
${model.fields.map(f => `${f.name}: ${getDefaultValue(f)}`).join(',\n ')},
...overrides
});`;
}typescript
export function generateFactory(model: Model) {
return `export const create${model.name} = (overrides?: Partial<${model.name}>): ${model.name} => ({
${model.fields.map(f => `${f.name}: ${getDefaultValue(f)}`).join(',\n ')},
...overrides
});`;
}Tools & Resources
工具与资源
- Plop: Micro-generator framework
- Yeoman: Scaffolding tool
- Hygen: Code generator with templates
- GraphQL Code Generator: Generate code from GraphQL
- Prisma: Database ORM with code generation
- OpenAPI Generator: Generate clients from OpenAPI
- json-schema-to-typescript: Generate TS types
- TypeScript Compiler API: AST manipulation
- Plop: 微型生成器框架
- Yeoman: 脚手架工具
- Hygen: 基于模板的代码生成器
- GraphQL Code Generator: 从GraphQL生成代码
- Prisma: 带代码生成功能的数据库ORM
- OpenAPI Generator: 从OpenAPI生成客户端
- json-schema-to-typescript: 生成TS类型
- TypeScript Compiler API: AST操作工具