Salta ai contenuti

Nx Generator Generator

Aggiunge un Nx Generator a un progetto TypeScript, per aiutarti ad automatizzare attività ripetitive come lo scaffolding di componenti o l’applicazione di particolari strutture di progetto.

Puoi generare un generator in due modi:

Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-generator
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-generator --dry-run
ParametroTipoPredefinitoDescrizione
project Obbligatoriostring-Progetto TypeScript a cui aggiungere il generatore. Si consiglia di utilizzare il generatore ts#nx-plugin per crearlo.
name Obbligatoriostring-Nome del generatore
description string-Una descrizione del tuo generatore
directory string-La directory all'interno della cartella sorgente del progetto plugin in cui aggiungere il generatore (predefinito: <name>)
preferInstallDependencies booleantrueSe preferire l'installazione delle dipendenze dopo l'esecuzione del generatore. Impostare su false per rimandare l'installazione quando si eseguono più generatori in batch (l'installazione viene comunque eseguita se necessaria affinché i generatori successivi possano calcolare il grafo dei progetti Nx); installare una volta alla fine.

Il generator creerà i seguenti file di progetto all’interno del project specificato:

  • Directorysrc/<name>/
    • schema.json Schema per l’input del tuo generator
    • schema.d.ts Tipi TypeScript per il tuo schema
    • generator.ts Implementazione stub del generator
    • generator.spec.ts Test per il tuo generator
    • README.md Documentazione per il tuo generator
  • generators.json Configurazione Nx per definire i tuoi generator
  • package.json Creato o aggiornato per aggiungere una voce “generators”
  • tsconfig.json Aggiornato per utilizzare CommonJS

Modifica del Progetto

Questo generator aggiornerà il project selezionato per utilizzare CommonJS, poiché gli Nx Generator supportano solo CommonJS al momento (fare riferimento a questa issue su GitHub per il supporto ESM).

Seleziona il tuo progetto nx-plugin locale quando esegui il generator ts#nx-generator, e specifica un nome e una directory e descrizione opzionali.

Il file schema.json definisce le opzioni che il tuo generator accetta. Segue il formato JSON Schema con estensioni specifiche di Nx.

Un file schema.json ha la seguente struttura base:

{
"$schema": "https://json-schema.org/schema",
"$id": "YourGeneratorName",
"title": "Your Generator Title",
"description": "Description of what your generator does",
"type": "object",
"properties": {
// Your generator options go here
},
"required": ["requiredOption1", "requiredOption2"]
}

Ecco un esempio semplice con alcune opzioni di base:

{
"$schema": "https://json-schema.org/schema",
"$id": "ComponentGenerator",
"title": "Create a Component",
"description": "Creates a new React component",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Component name",
"x-priority": "important"
},
"directory": {
"type": "string",
"description": "Directory where the component will be created",
"default": "src/components"
},
"withTests": {
"type": "boolean",
"description": "Whether to generate test files",
"default": true
}
},
"required": ["name"]
}

Puoi personalizzare i prompt visualizzati quando esegui il tuo generator tramite CLI aggiungendo la proprietà x-prompt:

"name": {
"type": "string",
"description": "Component name",
"x-prompt": "What is the name of your component?"
}

Per le opzioni booleane, puoi utilizzare un prompt sì/no:

"withTests": {
"type": "boolean",
"description": "Whether to generate test files",
"x-prompt": "Would you like to generate test files?"
}

Per le opzioni con un insieme fisso di scelte, usa enum in modo che gli utenti possano selezionare una delle opzioni.

"style": {
"type": "string",
"description": "The styling approach to use",
"enum": ["css", "scss", "styled-components", "none"],
"default": "css"
}

Un pattern comune è permettere agli utenti di selezionare tra i progetti esistenti nel workspace:

"project": {
"type": "string",
"description": "The project to add the component to",
"x-prompt": "Which project would you like to add the component to?",
"x-dropdown": "projects"
}

La proprietà x-dropdown: "projects" indica a Nx di popolare il menu a tendina con tutti i progetti nel workspace.

Puoi configurare le opzioni per essere passate come argomenti posizionali quando esegui il generator dalla riga di comando:

"name": {
"type": "string",
"description": "Component name",
"x-priority": "important",
"$default": {
"$source": "argv",
"index": 0
}
}

Questo permette agli utenti di eseguire il tuo generator come nx g your-generator my-component invece di nx g your-generator --name=my-component.

Usa la proprietà x-priority per indicare quali opzioni sono più importanti:

"name": {
"type": "string",
"description": "Component name",
"x-priority": "important"
}

Le opzioni possono avere priorità di "important" o "internal". Questo aiuta Nx a ordinare le proprietà nell’estensione VSCode di Nx e nella CLI di Nx.

Puoi fornire valori predefiniti per le opzioni:

"directory": {
"type": "string",
"description": "Directory where the component will be created",
"default": "src/components"
}

Per maggiori dettagli sugli schemi, fare riferimento alla documentazione delle Opzioni dei Generator di Nx.

Insieme a schema.json, il generator crea un file schema.d.ts che fornisce i tipi TypeScript per le opzioni del tuo generator:

export interface YourGeneratorSchema {
name: string;
directory?: string;
withTests?: boolean;
}

Questa interfaccia viene utilizzata nell’implementazione del tuo generator per fornire type safety e completamento del codice:

import { YourGeneratorSchema } from './schema';
export default async function (tree: Tree, options: YourGeneratorSchema) {
// TypeScript knows the types of all your options
const { name, directory = 'src/components', withTests = true } = options;
// ...
}

Dopo aver creato il nuovo generator come sopra, puoi scrivere la tua implementazione in generator.ts.

Un generator è una funzione che muta un filesystem virtuale (il Tree), leggendo e scrivendo file per apportare le modifiche desiderate. Le modifiche dal Tree vengono scritte su disco solo una volta che il generator termina l’esecuzione, a meno che non venga eseguito in modalità “dry-run”. Un generator vuoto appare come segue:

export const myGenerator = async (tree: Tree, options: MyGeneratorSchema) => {
// Use the tree to apply changes
};
export default myGenerator;

Ecco alcune operazioni comuni che potresti voler eseguire nel tuo generator:

// Read a file
const content = tree.read('path/to/file.ts', 'utf-8');
// Write a file
tree.write('path/to/new-file.ts', 'export const hello = "world";');
// Check if a file exists
if (tree.exists('path/to/file.ts')) {
// Do something
}

Puoi generare file con l’utility generateFiles da @nx/devkit. Questo ti permette di definire template nella sintassi EJS e sostituire variabili.

import { generateFiles, joinPathFragments } from '@nx/devkit';
// Generate files from templates
generateFiles(
tree,
joinPathFragments(import.meta.dirname, 'files'), // Template directory
'path/to/output', // Output directory
{
// Variables to replace in templates
name: options.name,
nameCamelCase: camelCase(options.name),
nameKebabCase: kebabCase(options.name),
// Add more variables as needed
},
);

Puoi utilizzare GritQL per cercare e trasformare in modo dichiarativo il codice sorgente nei tuoi generator. GritQL supporta più linguaggi tra cui TypeScript, JavaScript, Python, HCL (Terraform) e altri — quindi puoi utilizzare la stessa sintassi di pattern in tutto il tuo stack.

Il Nx Plugin for AWS espone due helper:

  • applyGritQL(tree, filePath, pattern) — applica un pattern di riscrittura GritQL a un file e restituisce Promise<boolean> indicando se sono state apportate modifiche
  • matchGritQL(tree, filePath, pattern) — verifica se un pattern GritQL corrisponde in qualsiasi punto di un file e restituisce Promise<boolean>
import { applyGritQL, matchGritQL } from '@aws/nx-plugin/sdk/utils/ast';
// Replace a function call
await applyGritQL(
tree,
'src/app.ts',
'`console.log($msg)` => `logger.info($msg)`',
);
// Add an element to an array only if not already present
await applyGritQL(
tree,
'src/plugins.ts',
'`plugins: [$items]` => `plugins: [$items, myPlugin()]` where { $items <: not contains `myPlugin` }',
);
// Check if a pattern exists before making changes
if (!(await matchGritQL(tree, filePath, '`import { Auth } from "./auth"`'))) {
// Add the import
}

I pattern GritQL funzionano anche su file non TypeScript. Prefissa il tuo pattern con language <name> per targetizzare altri linguaggi:

// Python: replace print statements with logging calls
await applyGritQL(
tree,
'src/handler.py',
'language python\n`print($msg)` => `logger.info($msg)`',
);

I pattern GritQL utilizzano snippet di codice delimitati da backtick con $metavariables come wildcard. Usa => per le riscritture e clausole where per le condizioni.

import { addDependenciesToPackageJson } from '@nx/devkit';
// Add dependencies to package.json
addDependenciesToPackageJson(
tree,
{
'new-dependency': '^1.0.0',
},
{
'new-dev-dependency': '^2.0.0',
},
);
import { formatFilesInSubtree } from '@aws/nx-plugin/sdk/utils/format';
// Format all files that were modified
await formatFilesInSubtree(tree, 'optional/path/to/format');
import { readJson, updateJson } from '@nx/devkit';
// Read a JSON file
const packageJson = readJson(tree, 'package.json');
// Update a JSON file
updateJson(tree, 'tsconfig.json', (json) => {
json.compilerOptions = {
...json.compilerOptions,
strict: true,
};
return json;
});

Puoi importare generator dal Nx Plugin for AWS ed estenderli o comporli come desideri, ad esempio potresti voler creare un generator che si basa su un progetto TypeScript:

import { tsProjectGenerator } from '@aws/nx-plugin/sdk/ts';
export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
const callback = await tsProjectGenerator(tree, { ... });
// Extend the TypeScript project generator here
// Return the callback to ensure dependencies are installed.
// You can wrap the callback if you wish to perform additional operations in the generator callback.
return callback;
};

Puoi utilizzare ed estendere i generator che usiamo per i client e gli hook TypeScript in modo simile a quanto sopra:

import { openApiTsClientGenerator } from '@aws/nx-plugin/sdk/open-api';
export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
await openApiTsClientGenerator(tree, { ... });
// Add additional files here
};

Esponiamo anche un metodo che ti permette di costruire una struttura dati che può essere utilizzata per iterare sulle operazioni in una specifica OpenAPI e quindi strumentare la tua generazione di codice, per esempio:

import { buildOpenApiCodeGenerationData } from '@aws/nx-plugin/sdk/open-api.js';
export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
const data = await buildOpenApiCodeGenerationData(tree, 'path/to/spec.json');
generateFiles(
tree,
joinPathFragments(import.meta.dirname, 'files'), // Template directory
'path/to/output', // Output directory
data,
);
};

Che poi ti permette di scrivere template come:

files/my-operations.ts.template
export const myOperationNames = [
<%_ allOperations.forEach((op) => { _%>
'<%- op.name %>',
<%_ }); _%>
];

Fare riferimento al codebase su GitHub per template di esempio più complessi.

Puoi eseguire il tuo generator in due modi:

Terminal window
pnpm nx g @my-project/nx-plugin:my-generator
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
Terminal window
pnpm nx g @my-project/nx-plugin:my-generator --dry-run

Gli unit test per i generator sono semplici da implementare. Ecco un pattern tipico:

import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { yourGenerator } from './generator.js';
describe('your generator', () => {
let tree;
beforeEach(() => {
// Create an empty workspace tree
tree = createTreeWithEmptyWorkspace();
// Add any files that should already exist in the tree
tree.write(
'project.json',
JSON.stringify({
name: 'test-project',
sourceRoot: 'src',
}),
);
tree.write('src/existing-file.ts', 'export const existing = true;');
});
it('should generate expected files', async () => {
// Run the generator
await yourGenerator(tree, {
name: 'test',
// Add other required options
});
// Check that files were created
expect(tree.exists('src/test/file.ts')).toBeTruthy();
// Check file content
const content = tree.read('src/test/file.ts', 'utf-8');
expect(content).toContain('export const test');
// You can also use snapshots
expect(tree.read('src/test/file.ts', 'utf-8')).toMatchSnapshot();
});
it('should update existing files', async () => {
// Run the generator
await yourGenerator(tree, {
name: 'test',
// Add other required options
});
// Check that existing files were updated
const content = tree.read('src/existing-file.ts', 'utf-8');
expect(content).toContain('import { test } from');
});
it('should handle errors', async () => {
// Expect the generator to throw an error in certain conditions
await expect(
yourGenerator(tree, {
name: 'invalid',
// Add options that should cause an error
}),
).rejects.toThrow('Expected error message');
});
});

Punti chiave per testare i generator:

  • Usa createTreeWithEmptyWorkspace() per creare un filesystem virtuale
  • Configura tutti i file prerequisiti prima di eseguire il generator
  • Testa sia la creazione di nuovi file che gli aggiornamenti ai file esistenti
  • Usa gli snapshot per contenuti di file complessi
  • Testa le condizioni di errore per assicurarti che il tuo generator fallisca in modo elegante

Puoi anche utilizzare ts#nx-generator per creare lo scaffolding di un generator all’interno di @aws/nx-plugin.

Quando questo generator viene eseguito nel nostro repository, genererà i seguenti file per te:

  • Directorypackages/nx-plugin/src/<name>/
    • schema.json Schema per l’input del tuo generator
    • schema.d.ts Tipi TypeScript per il tuo schema
    • generator.ts Implementazione del generator
    • generator.spec.ts Test per il tuo generator
  • Directorydocs/src/content/docs/guides/
    • <name>.mdx Pagina di documentazione per il tuo generator
  • packages/nx-plugin/generators.json Aggiornato per includere il tuo generator
  • packages/nx-plugin/sdk/<prefix>.ts Aggiornato per esporre il tuo generator dall’SDK (per i generator ts# e py#)

Puoi quindi iniziare a implementare il tuo generator.