Salta ai contenuti

Generatore di Generatori Nx

Aggiunge un Nx Generator a un progetto TypeScript, per aiutarti ad automatizzare task ripetitivi come la creazione di componenti o l’imposizione di strutture di progetto specifiche.

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
    Parametro Tipo Predefinito Descrizione
    project Obbligatorio string - Progetto TypeScript a cui aggiungere il generatore. Si consiglia di utilizzare il generatore ts#nx-plugin per crearlo.
    name Obbligatorio string - 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>)

    Il generatore creerà i seguenti file all’interno del project 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
      • README.md Documentazione del generatore
    • generators.json Configurazione Nx per definire i generatori
    • package.json Creato o aggiornato con una voce “generators”
    • tsconfig.json Aggiornato per utilizzare CommonJS

    Modifica del progetto

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

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

    Il file schema.json definisce le opzioni accettate dal generatore. 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 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 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?"
    }

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

    Un pattern comune è permettere agli utenti di selezionare tra 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.

    Puoi configurare opzioni da passare come argomenti posizionali quando si esegue il generatore dalla CLI:

    "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.

    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.

    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, consulta la documentazione Nx Generator Options.

    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 viene utilizzata 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;
    // ...
    }

    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”. Un generatore vuoto appare così:

    export const myGenerator = async (tree: Tree, options: MyGeneratorSchema) => {
    // Usa il tree per applicare modifiche
    };
    export default myGenerator;

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

    // 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
    }

    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
    },
    );

    Puoi utilizzare GritQL per cercare e trasformare in modo dichiarativo il codice sorgente nei tuoi generatori. GritQL supporta più linguaggi tra cui TypeScript, JavaScript, Python, HCL (Terraform) e altri — così puoi utilizzare la stessa sintassi di pattern su 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> che indica se sono state apportate modifiche
    • matchGritQL(tree, filePath, pattern) — verifica se un pattern GritQL corrisponde in qualsiasi punto del file e restituisce Promise<boolean>
    import { applyGritQL, matchGritQL } from '@aws/nx-plugin/sdk/utils/ast';
    // Sostituisci una chiamata di funzione
    await applyGritQL(
    tree,
    'src/app.ts',
    '`console.log($msg)` => `logger.info($msg)`',
    );
    // Aggiungi un elemento a un array solo se non già presente
    await applyGritQL(
    tree,
    'src/plugins.ts',
    '`plugins: [$items]` => `plugins: [$items, myPlugin()]` where { $items <: not contains `myPlugin` }',
    );
    // Verifica se un pattern esiste prima di apportare modifiche
    if (!(await matchGritQL(tree, filePath, '`import { Auth } from "./auth"`'))) {
    // Aggiungi l'import
    }

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

    // Python: sostituisci statement print con chiamate logging
    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 wildcards. Usa => per le riscritture e clausole where per le condizioni.

    import { addDependenciesToPackageJson } from '@nx/devkit';
    // Aggiungi dipendenze a package.json
    addDependenciesToPackageJson(
    tree,
    {
    'new-dependency': '^1.0.0',
    },
    {
    'new-dev-dependency': '^2.0.0',
    },
    );
    import { formatFilesInSubtree } from '@aws/nx-plugin/sdk/utils/format';
    // Formatta tutti i file modificati
    await formatFilesInSubtree(tree, 'optional/path/to/format');
    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;
    });

    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 progetti 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;
    };

    Puoi utilizzare ed estendere i generatori che usiamo 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,
    );
    };

    Questo ti permette di scrivere template come:

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

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

    Puoi eseguire il tuo 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

      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 già 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('should generate expected files', 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 snapshot
      expect(tree.read('src/test/file.ts', 'utf-8')).toMatchSnapshot();
      });
      it('should update existing files', 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('should handle errors', 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('Expected error message');
      });
      });

      Punti chiave per testare i generatori:

      • Usa createTreeWithEmptyWorkspace() per creare un filesystem virtuale
      • Configura 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 garantire un fallimento controllato

      Puoi anche usare ts#nx-generator 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.