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 - TypeScript project to add the generator to. We recommend using the ts#nx-plugin generator to create this.
    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>)

    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

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

    Manipolazione AST (Abstract Syntax Tree) TypeScript

    Sezione intitolata “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 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));
    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.