Salta ai contenuti

Generatore di Generatori Nx

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

Utilizzo

Generare un Generatore

Puoi generare un generatore in due modi:

  1. Installa il Nx Console VSCode Plugin se non l'hai già fatto
  2. Apri la console Nx in VSCode
  3. Clicca su Generate (UI) nella sezione "Common Nx Commands"
  4. Cerca @aws/nx-plugin - ts#nx-generator
  5. Compila i parametri richiesti
    • Clicca su Generate

    Opzioni

    Parametro Tipo Predefinito Descrizione
    pluginProject Obbligatorio string - TypeScript project to add the generator to. We recommend creating a ts#project in a top-level 'tools' directory.
    name Obbligatorio string - Generator name
    description string - A description of your generator
    directory string - The directory within the plugin project's source folder to add the generator to (default: <name>)

    Output del Generatore

    Il generatore creerà i seguenti file all’interno del pluginProject specificato:

    • Directorysrc/<name>/
      • schema.json Schema per l’input del generatore
      • schema.d.ts Tipi TypeScript per lo schema
      • generator.ts Implementazione stub del generatore
      • generator.spec.ts Test per il generatore
    • generators.json Configurazione Nx per definire i generatori
    • package.json Creato o aggiornato con una voce “generators”
    • tsconfig.json Aggiornato per usare CommonJS

    Questo generatore aggiornerà il pluginProject selezionato per utilizzare CommonJS, poiché i generatori Nx supportano attualmente solo CommonJS (fai riferimento a questa issue GitHub per il supporto ESM).

    Generator Locali

    Consigliamo di generare prima un progetto TypeScript dedicato per tutti i tuoi generatori utilizzando il generatore ts#project. Ad esempio:

    1. Installa il Nx Console VSCode Plugin se non l'hai già fatto
    2. Apri la console Nx in VSCode
    3. Clicca su Generate (UI) nella sezione "Common Nx Commands"
    4. Cerca @aws/nx-plugin - ts#project
    5. Compila i parametri richiesti
      • name: nx-plugin
      • directory: tools
    6. Clicca su Generate

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

    Definizione dello Schema

    Il file schema.json definisce le opzioni accettate dal generatore. Segue il formato JSON Schema con estensioni specifiche di Nx.

    Struttura Base

    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": {
    // Le opzioni del generatore vanno qui
    },
    "required": ["requiredOption1", "requiredOption2"]
    }

    Esempio Semplice

    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"]
    }

    Prompt Interattivi (CLI)

    Puoi personalizzare i prompt visualizzati durante l’esecuzione del generatore via CLI aggiungendo la proprietà x-prompt:

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

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

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

    Selezione a Tendina

    Per opzioni con un set fisso di scelte, usa enum per permettere agli utenti di selezionare un’opzione.

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

    Selezione Progetto a Tendina

    Un pattern comune è permettere agli utenti di selezionare da 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 la tendina con tutti i progetti nel workspace.

    Argomenti Posizionali

    Puoi configurare opzioni da passare come argomenti posizionali quando si esegue il generatore da riga di comando:

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

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

    Impostazione Priorità

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

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

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

    Valori Predefiniti

    Puoi fornire valori predefiniti per le opzioni:

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

    Ulteriori Informazioni

    Per maggiori dettagli sugli schemi, consulta la documentazione Nx Generator Options.

    Tipi TypeScript con schema.d.ts

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

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

    Questa interfaccia è usata nell’implementazione del generatore per garantire type safety e completamento del codice:

    import { YourGeneratorSchema } from './schema';
    export default async function (tree: Tree, options: YourGeneratorSchema) {
    // TypeScript conosce i tipi di tutte le opzioni
    const { name, directory = 'src/components', withTests = true } = options;
    // ...
    }

    Ogni volta che modifichi schema.json, devi aggiornare schema.d.ts per corrispondere. Questo include:

    • Aggiungere o rimuovere proprietà
    • Cambiare i tipi delle proprietà
    • Rendere proprietà obbligatorie o opzionali (usa il suffisso ? per proprietà opzionali)

    L’interfaccia TypeScript deve riflettere accuratamente la struttura definita nel tuo JSON schema.

    Implementazione di un Generatore

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

    Un generatore è una funzione che modifica un filesystem virtuale (Tree), leggendo e scrivendo file per apportare le modifiche desiderate. Le modifiche dal Tree vengono scritte su disco solo al termine dell’esecuzione del generatore, a meno che non sia eseguito in modalità “dry-run”.

    Ecco alcune operazioni comuni che potresti voler eseguire nel generatore:

    Lettura e Scrittura di File

    // Leggi un file
    const content = tree.read('path/to/file.ts', 'utf-8');
    // Scrivi un file
    tree.write('path/to/new-file.ts', 'export const hello = "world";');
    // Verifica se un file esiste
    if (tree.exists('path/to/file.ts')) {
    // Fai qualcosa
    }

    Generazione File da Template

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

    import { generateFiles, joinPathFragments } from '@nx/devkit';
    // Genera file da template
    generateFiles(
    tree,
    joinPathFragments(__dirname, 'files'), // Directory template
    'path/to/output', // Directory di output
    {
    // Variabili da sostituire nei template
    name: options.name,
    nameCamelCase: camelCase(options.name),
    nameKebabCase: kebabCase(options.name),
    // Aggiungi altre variabili se necessario
    },
    );

    Manipolazione AST (Abstract Syntax Tree) TypeScript

    Puoi usare il metodo tsAstReplace esposto dal Nx Plugin for AWS per sostituire parti di un abstract syntax tree TypeScript.

    import { tsAstReplace } from '@aws/nx-plugin/sdk/utils/ast';
    import * as ts from 'typescript';
    // Esempio: Incrementa numero di versione in un file
    tsAstReplace(
    tree,
    'path/to/version.ts',
    'VariableDeclaration:has(Identifier[name="VERSION"]) NumericLiteral',
    (node: ts.NumericLiteral) =>
    ts.factory.createNumericLiteral(Number(node.text) + 1));

    Puoi testare i selettori online nel TSQuery Playground.

    Aggiunta Dipendenze

    import { addDependenciesToPackageJson } from '@nx/devkit';
    // Aggiungi dipendenze a package.json
    addDependenciesToPackageJson(
    tree,
    {
    'new-dependency': '^1.0.0',
    },
    {
    'new-dev-dependency': '^2.0.0',
    },
    );

    Se aggiungi dipendenze a un package.json, puoi installarle per l’utente come parte del callback del generatore:

    import { installPackagesTask } from '@nx/devkit';
    // I generatori restituiscono un callback che può eseguire task post-generazione, come installare dipendenze
    return () => {
    installPackagesTask(tree);
    };

    Formattazione File Generati

    import { formatFilesInSubtree } from '@aws/nx-plugin/sdk/utils/format';
    // Formatta tutti i file modificati
    await formatFilesInSubtree(tree, 'optional/path/to/format');

    Lettura e Aggiornamento File JSON

    import { readJson, updateJson } from '@nx/devkit';
    // Leggi un file JSON
    const packageJson = readJson(tree, 'package.json');
    // Aggiorna un file JSON
    updateJson(tree, 'tsconfig.json', (json) => {
    json.compilerOptions = {
    ...json.compilerOptions,
    strict: true,
    };
    return json;
    });

    Estensione di un Generatore dal Nx Plugin for AWS

    Puoi importare generatori dal Nx Plugin for AWS ed estenderli o combinarli come desideri. Ad esempio, potresti creare un generatore 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, { ... });
    // Estendi qui il generatore di progetto TypeScript
    // Restituisci il callback per assicurare l'installazione delle dipendenze.
    // Puoi wrappare il callback se desideri eseguire operazioni aggiuntive nel callback del generatore.
    return callback;
    };

    Generator OpenAPI

    Puoi usare ed estendere i generatori che utilizziamo per client e 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, { ... });
    // Aggiungi file aggiuntivi qui
    };

    Esponiamo anche un metodo che ti permette di costruire una struttura dati utilizzabile per iterare sulle operazioni in una specifica OpenAPI e quindi strumentare la tua generazione di codice, ad 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(__dirname, 'files'), // Directory template
    'path/to/output', // Directory di output
    data,
    );
    };

    Il che ti permette di scrivere template come:

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

    Fai riferimento al codice sorgente su GitHub per esempi di template più complessi.

    Esecuzione del Generatore

    Puoi eseguire il generatore in due modi:

    1. Installa il Nx Console VSCode Plugin se non l'hai già fatto
    2. Apri la console Nx in VSCode
    3. Clicca su Generate (UI) nella sezione "Common Nx Commands"
    4. Cerca @my-project/nx-plugin - my-generator
    5. Compila i parametri richiesti
      • Clicca su Generate

      Se non vedi il tuo generatore nell’interfaccia UI del plugin VSCode, puoi aggiornare il tuo Nx Workspace con:

      Terminal window
      pnpm nx reset

      Test del Generatore

      I test unitari per i generatori sono semplici da implementare. Ecco un pattern tipico:

      import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
      import { yourGenerator } from './generator';
      describe('your generator', () => {
      let tree;
      beforeEach(() => {
      // Crea un workspace tree vuoto
      tree = createTreeWithEmptyWorkspace();
      // Aggiungi file che devono esistere nel tree
      tree.write(
      'project.json',
      JSON.stringify({
      name: 'test-project',
      sourceRoot: 'src',
      }),
      );
      tree.write('src/existing-file.ts', 'export const existing = true;');
      });
      it('dovrebbe generare i file attesi', async () => {
      // Esegui il generatore
      await yourGenerator(tree, {
      name: 'test',
      // Aggiungi altre opzioni richieste
      });
      // Verifica che i file siano stati creati
      expect(tree.exists('src/test/file.ts')).toBeTruthy();
      // Controlla il contenuto del file
      const content = tree.read('src/test/file.ts', 'utf-8');
      expect(content).toContain('export const test');
      // Puoi usare anche gli snapshot
      expect(tree.read('src/test/file.ts', 'utf-8')).toMatchSnapshot();
      });
      it('dovrebbe aggiornare file esistenti', async () => {
      // Esegui il generatore
      await yourGenerator(tree, {
      name: 'test',
      // Aggiungi altre opzioni richieste
      });
      // Verifica che i file esistenti siano stati aggiornati
      const content = tree.read('src/existing-file.ts', 'utf-8');
      expect(content).toContain('import { test } from');
      });
      it('dovrebbe gestire gli errori', async () => {
      // Aspettati che il generatore sollevi un errore in certe condizioni
      await expect(
      yourGenerator(tree, {
      name: 'invalid',
      // Aggiungi opzioni che dovrebbero causare un errore
      }),
      ).rejects.toThrow('Messaggio di errore atteso');
      });
      });

      Punti chiave per testare i generatori:

      • Usa createTreeWithEmptyWorkspace() per creare un filesystem virtuale
      • Imposta file prerequisiti prima di eseguire il generatore
      • Testa sia la creazione di nuovi file che l’aggiornamento di file esistenti
      • Usa snapshot per contenuti di file complessi
      • Testa condizioni di errore per assicurarti che il generatore fallisca correttamente

      Contribuire ai Generatori per @aws/nx-plugin

      Puoi usare ts#nx-generator anche per scaffoldare un generatore all’interno di @aws/nx-plugin.

      Quando questo generatore viene eseguito nel nostro repository, genererà i seguenti file:

      • Directorypackages/nx-plugin/src/<name>/
        • schema.json Schema per l’input del generatore
        • schema.d.ts Tipi TypeScript per lo schema
        • generator.ts Implementazione del generatore
        • generator.spec.ts Test per il generatore
      • Directorydocs/src/content/docs/guides/
        • <name>.mdx Pagina di documentazione per il generatore
      • packages/nx-plugin/generators.json Aggiornato per includere il generatore

      Potrai quindi iniziare a implementare il tuo generatore.

      Per una guida più approfondita su come contribuire al Nx Plugin for AWS, consulta il tutorial qui.