Aller au contenu

Générateur de Générateur Nx

Ajoute un Nx Generator à un projet TypeScript, pour vous aider à automatiser des tâches répétitives comme la création de composants ou l’application de structures de projet spécifiques.

Vous pouvez générer un générateur de deux manières :

  1. Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
  2. Ouvrez la console Nx dans VSCode
  3. Cliquez sur Generate (UI) dans la section "Common Nx Commands"
  4. Recherchez @aws/nx-plugin - ts#nx-generator
  5. Remplissez les paramètres requis
    • Cliquez sur Generate
    Paramètre Type Par défaut Description
    project Requis string - Projet TypeScript auquel ajouter le générateur. Nous recommandons d'utiliser le générateur ts#nx-plugin pour le créer.
    name Requis string - Nom du générateur
    description string - Une description de votre générateur
    directory string - Le répertoire dans le dossier source du projet de plugin où ajouter le générateur (par défaut : <name>)

    Le générateur créera les fichiers suivants dans le project spécifié :

    • Répertoiresrc/<name>/
      • schema.json Schéma des entrées de votre générateur
      • schema.d.ts Types TypeScript pour votre schéma
      • generator.ts Implémentation de base du générateur
      • generator.spec.ts Tests pour votre générateur
      • README.md Documentation pour votre générateur
    • generators.json Configuration Nx pour définir vos générateurs
    • package.json Créé ou mis à jour pour ajouter une entrée “generators”
    • tsconfig.json Mis à jour pour utiliser CommonJS

    Modification du projet

    Ce générateur mettra à jour le project sélectionné pour utiliser CommonJS, car les Nx Generators ne prennent actuellement en charge que CommonJS (voir cette issue GitHub pour le support ESM).

    Sélectionnez votre projet local nx-plugin lors de l’exécution du générateur ts#nx-generator, et spécifiez un nom ainsi qu’un répertoire et une description optionnels.

    Le fichier schema.json définit les options acceptées par votre générateur. Il suit le format JSON Schema avec des extensions spécifiques à Nx.

    Un fichier schema.json a la structure de base suivante :

    {
    "$schema": "https://json-schema.org/schema",
    "$id": "YourGeneratorName",
    "title": "Your Generator Title",
    "description": "Description of what your generator does",
    "type": "object",
    "properties": {
    // Vos options de générateur ici
    },
    "required": ["requiredOption1", "requiredOption2"]
    }

    Voici un exemple simple avec quelques options basiques :

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

    Vous pouvez personnaliser les invites affichées lors de l’exécution de votre générateur via la CLI en ajoutant la propriété x-prompt :

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

    Pour les options booléennes, vous pouvez utiliser une invite oui/non :

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

    Pour les options avec un ensemble fixe de choix, utilisez enum afin que les utilisateurs puissent sélectionner l’une des options.

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

    Un modèle courant consiste à permettre aux utilisateurs de sélectionner parmi les projets existants dans l’espace de travail :

    "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 propriété x-dropdown: "projects" indique à Nx de peupler le menu déroulant avec tous les projets de l’espace de travail.

    Vous pouvez configurer des options pour qu’elles soient passées comme arguments positionnels lors de l’exécution du générateur depuis la ligne de commande :

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

    Cela permet aux utilisateurs d’exécuter votre générateur comme nx g your-generator my-component au lieu de nx g your-generator --name=my-component.

    Utilisez la propriété x-priority pour indiquer quelles options sont les plus importantes :

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

    Les options peuvent avoir des priorités de "important" ou "internal". Cela aide Nx à ordonner les propriétés dans l’extension VSCode Nx et la CLI Nx.

    Vous pouvez fournir des valeurs par défaut pour les options :

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

    Pour plus de détails sur les schémas, consultez la documentation Nx sur les options de générateur.

    Avec schema.json, le générateur crée un fichier schema.d.ts qui fournit des types TypeScript pour les options de votre générateur :

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

    Cette interface est utilisée dans l’implémentation de votre générateur pour fournir la sécurité des types et l’autocomplétion du code :

    import { YourGeneratorSchema } from './schema';
    export default async function (tree: Tree, options: YourGeneratorSchema) {
    // TypeScript connaît les types de toutes vos options
    const { name, directory = 'src/components', withTests = true } = options;
    // ...
    }

    Après avoir créé le nouveau générateur comme ci-dessus, vous pouvez écrire votre implémentation dans generator.ts.

    Un générateur est une fonction qui modifie un système de fichiers virtuel (le Tree), en lisant et en écrivant des fichiers pour effectuer les changements souhaités. Les changements du Tree ne sont écrits sur le disque qu’une fois que le générateur a terminé son exécution, sauf s’il est exécuté en mode “dry-run”. Un générateur vide ressemble à ceci :

    export const myGenerator = async (tree: Tree, options: MyGeneratorSchema) => {
    // Utilisez le tree pour appliquer des modifications
    };
    export default myGenerator;

    Voici quelques opérations courantes que vous pourriez vouloir effectuer dans votre générateur :

    // Lire un fichier
    const content = tree.read('path/to/file.ts', 'utf-8');
    // Écrire un fichier
    tree.write('path/to/new-file.ts', 'export const hello = "world";');
    // Vérifier si un fichier existe
    if (tree.exists('path/to/file.ts')) {
    // Faire quelque chose
    }

    Vous pouvez générer des fichiers avec l’utilitaire generateFiles de @nx/devkit. Cela vous permet de définir des templates en syntaxe EJS et de substituer des variables.

    import { generateFiles, joinPathFragments } from '@nx/devkit';
    // Générer des fichiers depuis des templates
    generateFiles(
    tree,
    joinPathFragments(__dirname, 'files'), // Répertoire de templates
    'path/to/output', // Répertoire de sortie
    {
    // Variables à remplacer dans les templates
    name: options.name,
    nameCamelCase: camelCase(options.name),
    nameKebabCase: kebabCase(options.name),
    // Ajoutez plus de variables selon les besoins
    },
    );

    Vous pouvez utiliser GritQL pour rechercher et transformer de manière déclarative le code source dans vos générateurs. GritQL prend en charge plusieurs langages, notamment TypeScript, JavaScript, Python, HCL (Terraform) et plus encore — vous pouvez donc utiliser la même syntaxe de motif dans toute votre pile technologique.

    Le Nx Plugin for AWS expose deux assistants :

    • applyGritQL(tree, filePath, pattern) — applique un motif de réécriture GritQL à un fichier et renvoie Promise<boolean> indiquant si des modifications ont été effectuées
    • matchGritQL(tree, filePath, pattern) — vérifie si un motif GritQL correspond quelque part dans un fichier et renvoie Promise<boolean>
    import { applyGritQL, matchGritQL } from '@aws/nx-plugin/sdk/utils/ast';
    // Remplacer un appel de fonction
    await applyGritQL(
    tree,
    'src/app.ts',
    '`console.log($msg)` => `logger.info($msg)`',
    );
    // Ajouter un élément à un tableau uniquement s'il n'est pas déjà présent
    await applyGritQL(
    tree,
    'src/plugins.ts',
    '`plugins: [$items]` => `plugins: [$items, myPlugin()]` where { $items <: not contains `myPlugin` }',
    );
    // Vérifier si un motif existe avant d'effectuer des modifications
    if (!(await matchGritQL(tree, filePath, '`import { Auth } from "./auth"`'))) {
    // Ajouter l'import
    }

    Les motifs GritQL fonctionnent également sur des fichiers non-TypeScript. Préfixez votre motif avec language <name> pour cibler d’autres langages :

    // Python : remplacer les instructions print par des appels de logging
    await applyGritQL(
    tree,
    'src/handler.py',
    'language python\n`print($msg)` => `logger.info($msg)`',
    );

    Les motifs GritQL utilisent des extraits de code délimités par des backticks avec des $metavariables comme caractères génériques. Utilisez => pour les réécritures et les clauses where pour les conditions.

    import { addDependenciesToPackageJson } from '@nx/devkit';
    // Ajouter des dépendances au package.json
    addDependenciesToPackageJson(
    tree,
    {
    'new-dependency': '^1.0.0',
    },
    {
    'new-dev-dependency': '^2.0.0',
    },
    );
    import { formatFilesInSubtree } from '@aws/nx-plugin/sdk/utils/format';
    // Formater tous les fichiers qui ont été modifiés
    await formatFilesInSubtree(tree, 'optional/path/to/format');
    import { readJson, updateJson } from '@nx/devkit';
    // Lire un fichier JSON
    const packageJson = readJson(tree, 'package.json');
    // Mettre à jour un fichier JSON
    updateJson(tree, 'tsconfig.json', (json) => {
    json.compilerOptions = {
    ...json.compilerOptions,
    strict: true,
    };
    return json;
    });

    Extension d’un générateur du Nx Plugin for AWS

    Section intitulée « Extension d’un générateur du Nx Plugin for AWS »

    Vous pouvez importer des générateurs du Nx Plugin for AWS et les étendre ou les composer comme vous le souhaitez. Par exemple, vous pourriez vouloir créer un générateur qui s’appuie sur un projet TypeScript :

    import { tsProjectGenerator } from '@aws/nx-plugin/sdk/ts';
    export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
    const callback = await tsProjectGenerator(tree, { ... });
    // Étendez le générateur de projet TypeScript ici
    // Retournez le callback pour vous assurer que les dépendances sont installées.
    // Vous pouvez encapsuler le callback si vous souhaitez effectuer des opérations supplémentaires dans le callback du générateur.
    return callback;
    };

    Vous pouvez utiliser et étendre les générateurs que nous utilisons pour les clients et hooks TypeScript de manière similaire à ce qui précède :

    import { openApiTsClientGenerator } from '@aws/nx-plugin/sdk/open-api';
    export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
    await openApiTsClientGenerator(tree, { ... });
    // Ajoutez des fichiers supplémentaires ici
    };

    Nous exposons également une méthode qui vous permet de construire une structure de données pouvant être utilisée pour itérer sur les opérations dans une spécification OpenAPI et donc instrumenter votre propre génération de code, par exemple :

    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'), // Répertoire de templates
    'path/to/output', // Répertoire de sortie
    data,
    );
    };

    Ce qui vous permet ensuite d’écrire des templates tels que :

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

    Consultez le code source sur GitHub pour des exemples de templates plus complexes.

    Vous pouvez exécuter votre générateur de deux manières :

    1. Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
    2. Ouvrez la console Nx dans VSCode
    3. Cliquez sur Generate (UI) dans la section "Common Nx Commands"
    4. Recherchez @my-project/nx-plugin - my-generator
    5. Remplissez les paramètres requis
      • Cliquez sur Generate

      Les tests unitaires pour les générateurs sont simples à implémenter. Voici un modèle typique :

      import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
      import { yourGenerator } from './generator';
      describe('your generator', () => {
      let tree;
      beforeEach(() => {
      // Créer un arbre d'espace de travail vide
      tree = createTreeWithEmptyWorkspace();
      // Ajouter tous les fichiers qui devraient déjà exister dans l'arbre
      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 () => {
      // Exécuter le générateur
      await yourGenerator(tree, {
      name: 'test',
      // Ajouter d'autres options requises
      });
      // Vérifier que les fichiers ont été créés
      expect(tree.exists('src/test/file.ts')).toBeTruthy();
      // Vérifier le contenu du fichier
      const content = tree.read('src/test/file.ts', 'utf-8');
      expect(content).toContain('export const test');
      // Vous pouvez également utiliser des snapshots
      expect(tree.read('src/test/file.ts', 'utf-8')).toMatchSnapshot();
      });
      it('should update existing files', async () => {
      // Exécuter le générateur
      await yourGenerator(tree, {
      name: 'test',
      // Ajouter d'autres options requises
      });
      // Vérifier que les fichiers existants ont été mis à jour
      const content = tree.read('src/existing-file.ts', 'utf-8');
      expect(content).toContain('import { test } from');
      });
      it('should handle errors', async () => {
      // S'attendre à ce que le générateur lève une erreur dans certaines conditions
      await expect(
      yourGenerator(tree, {
      name: 'invalid',
      // Ajouter des options qui devraient provoquer une erreur
      }),
      ).rejects.toThrow('Expected error message');
      });
      });

      Points clés pour tester les générateurs :

      • Utilisez createTreeWithEmptyWorkspace() pour créer un système de fichiers virtuel
      • Configurez tous les fichiers prérequis avant d’exécuter le générateur
      • Testez à la fois la création de nouveaux fichiers et les mises à jour de fichiers existants
      • Utilisez des snapshots pour du contenu de fichier complexe
      • Testez les conditions d’erreur pour vous assurer que votre générateur échoue gracieusement

      Vous pouvez également utiliser ts#nx-generator pour créer un générateur dans @aws/nx-plugin.

      Lorsque ce générateur est exécuté dans notre dépôt, il générera les fichiers suivants pour vous :

      • Répertoirepackages/nx-plugin/src/<name>/
        • schema.json Schéma des entrées de votre générateur
        • schema.d.ts Types TypeScript pour votre schéma
        • generator.ts Implémentation du générateur
        • generator.spec.ts Tests pour votre générateur
      • Répertoiredocs/src/content/docs/guides/
        • <name>.mdx Page de documentation pour votre générateur
      • packages/nx-plugin/generators.json Mis à jour pour inclure votre générateur

      Vous pouvez ensuite commencer à implémenter votre générateur.