Saltearse al contenido

Generador de Generadores de Nx

Agrega un Generador de Nx a un proyecto TypeScript, para ayudarte a automatizar tareas repetitivas como la creación de componentes o la implementación de estructuras de proyecto específicas.

Uso

Generar un Generador

Puedes generar un generador de dos formas:

  1. Instale el Nx Console VSCode Plugin si aún no lo ha hecho
  2. Abra la consola Nx en VSCode
  3. Haga clic en Generate (UI) en la sección "Common Nx Commands"
  4. Busque @aws/nx-plugin - ts#nx-generator
  5. Complete los parámetros requeridos
    • Haga clic en Generate

    Opciones

    Parámetro Tipo Predeterminado Descripción
    pluginProject Requerido string - TypeScript project to add the generator to. We recommend creating a ts#project in a top-level 'tools' directory.
    name Requerido 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>)

    Resultado del Generador

    El generador creará los siguientes archivos de proyecto dentro del pluginProject especificado:

    • Directorysrc/<name>/
      • schema.json Esquema para la entrada de tu generador
      • schema.d.ts Tipos TypeScript para tu esquema
      • generator.ts Implementación base del generador
      • generator.spec.ts Pruebas para tu generador
    • generators.json Configuración de Nx para definir tus generadores
    • package.json Creado o actualizado para agregar una entrada “generators”
    • tsconfig.json Actualizado para usar CommonJS

    Este generador actualizará el pluginProject seleccionado para usar CommonJS, ya que los Generadores de Nx actualmente solo admiten CommonJS (consulta este issue de GitHub para el soporte de ESM).

    Generadores Locales

    Selecciona tu proyecto local nx-plugin cuando ejecutes el generador ts#nx-generator, y especifica un nombre junto con un directorio y descripción opcionales.

    Definiendo el Esquema

    El archivo schema.json define las opciones que acepta tu generador. Sigue el formato JSON Schema con extensiones específicas de Nx.

    Estructura Básica

    Un archivo schema.json tiene la siguiente estructura básica:

    {
    "$schema": "https://json-schema.org/schema",
    "$id": "YourGeneratorName",
    "title": "Your Generator Title",
    "description": "Description of what your generator does",
    "type": "object",
    "properties": {
    // Tus opciones de generador van aquí
    },
    "required": ["requiredOption1", "requiredOption2"]
    }

    Ejemplo Simple

    Aquí un ejemplo simple con algunas opciones básicas:

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

    Prompts Interactivos (CLI)

    Puedes personalizar los prompts mostrados al ejecutar tu generador mediante la CLI agregando la propiedad x-prompt:

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

    Para opciones booleanas, puedes usar un prompt de sí/no:

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

    Selecciones en Menú Desplegable

    Para opciones con un conjunto fijo de opciones, usa enum para que los usuarios puedan seleccionar una de ellas.

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

    Menú Desplegable de Selección de Proyecto

    Un patrón común es permitir que los usuarios seleccionen entre proyectos existentes en el 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 propiedad x-dropdown: "projects" indica a Nx que llene el menú con todos los proyectos del workspace.

    Argumentos Posicionales

    Puedes configurar opciones para que se pasen como argumentos posicionales al ejecutar el generador desde la línea de comandos:

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

    Esto permite a los usuarios ejecutar tu generador como nx g your-generator my-component en lugar de nx g your-generator --name=my-component.

    Estableciendo Prioridades

    Usa la propiedad x-priority para indicar qué opciones son más importantes:

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

    Las opciones pueden tener prioridades de "important" o "internal". Esto ayuda a Nx a ordenar las propiedades en la extensión VSCode de Nx y en la CLI de Nx.

    Valores Predeterminados

    Puedes proporcionar valores predeterminados para las opciones:

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

    Más Información

    Para más detalles sobre esquemas, consulta la documentación de Opciones de Generador de Nx.

    Tipos TypeScript con schema.d.ts

    Junto con schema.json, el generador crea un archivo schema.d.ts que proporciona tipos TypeScript para las opciones de tu generador:

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

    Esta interfaz se usa en la implementación de tu generador para proporcionar seguridad de tipos y autocompletado:

    import { YourGeneratorSchema } from './schema';
    export default async function (tree: Tree, options: YourGeneratorSchema) {
    // TypeScript conoce los tipos de todas tus opciones
    const { name, directory = 'src/components', withTests = true } = options;
    // ...
    }

    Implementando un Generador

    Después de crear el nuevo generador como se indicó anteriormente, puedes escribir tu implementación en generator.ts.

    Un generador es una función que muta un sistema de archivos virtual (Tree), leyendo y escribiendo archivos para realizar los cambios deseados. Los cambios del Tree solo se escriben en disco una vez que el generador termina de ejecutarse, a menos que se ejecute en modo “dry-run”.

    Aquí hay algunas operaciones comunes que podrías realizar en tu generador:

    Lectura y Escritura de Archivos

    // Leer un archivo
    const content = tree.read('path/to/file.ts', 'utf-8');
    // Escribir un archivo
    tree.write('path/to/new-file.ts', 'export const hello = "world";');
    // Verificar si un archivo existe
    if (tree.exists('path/to/file.ts')) {
    // Hacer algo
    }

    Generación de Archivos desde Plantillas

    import { generateFiles, joinPathFragments } from '@nx/devkit';
    // Generar archivos desde plantillas
    generateFiles(
    tree,
    joinPathFragments(__dirname, 'files'), // Directorio de plantillas
    'path/to/output', // Directorio de salida
    {
    // Variables para reemplazar en plantillas
    name: options.name,
    nameCamelCase: camelCase(options.name),
    nameKebabCase: kebabCase(options.name),
    // Agrega más variables según sea necesario
    },
    );

    Manipulación de AST (Árbol de Sintaxis Abstracta) de TypeScript

    Recomendamos instalar TSQuery para ayudar con la manipulación de AST.

    import { tsquery } from '@phenomnomnominal/tsquery';
    import * as ts from 'typescript';
    // Ejemplo: Incrementar número de versión en un archivo
    // Parsear el contenido del archivo en un AST de TypeScript
    const sourceFile = tsquery.ast(tree.read('path/to/version.ts', 'utf-8'));
    // Encontrar nodos que coincidan con el selector
    const nodes = tsquery.query(
    sourceFile,
    'VariableDeclaration:has(Identifier[name="VERSION"]) NumericLiteral',
    );
    if (nodes.length > 0) {
    // Obtener el nodo del literal numérico
    const numericNode = nodes[0] as ts.NumericLiteral;
    // Obtener el número de versión actual e incrementarlo
    const currentVersion = Number(numericNode.text);
    const newVersion = currentVersion + 1;
    // Reemplazar el nodo en el AST
    const result = tsquery.replace(
    sourceFile,
    'VariableDeclaration:has(Identifier[name="VERSION"]) NumericLiteral',
    () => ts.factory.createNumericLiteral(newVersion),
    );
    // Escribir el contenido actualizado en el árbol
    tree.write(
    'path/to/version.ts',
    ts
    .createPrinter({
    newLine: ts.NewLineKind.LineFeed,
    })
    .printNode(ts.EmitHint.Unspecified, result, sourceFile),
    );
    }

    Agregar Dependencias

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

    Formatear Archivos Generados

    import { formatFiles } from '@nx/devkit';
    // Formatear todos los archivos modificados
    await formatFiles(tree);

    Leer y Actualizar Archivos JSON

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

    Ejecutando Tu Generador

    Puedes ejecutar tu generador de dos formas:

    1. Instale el Nx Console VSCode Plugin si aún no lo ha hecho
    2. Abra la consola Nx en VSCode
    3. Haga clic en Generate (UI) en la sección "Common Nx Commands"
    4. Busque @my-project/nx-plugin - my-generator
    5. Complete los parámetros requeridos
      • Haga clic en Generate

      Probando Tu Generador

      Las pruebas unitarias para generadores son sencillas de implementar. Aquí un patrón típico:

      import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
      import { yourGenerator } from './generator';
      describe('your generator', () => {
      let tree;
      beforeEach(() => {
      // Crear un árbol de workspace vacío
      tree = createTreeWithEmptyWorkspace();
      // Agregar archivos que deban existir previamente
      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 () => {
      // Ejecutar el generador
      await yourGenerator(tree, {
      name: 'test',
      // Agregar otras opciones requeridas
      });
      // Verificar creación de archivos
      expect(tree.exists('src/test/file.ts')).toBeTruthy();
      // Verificar contenido de archivo
      const content = tree.read('src/test/file.ts', 'utf-8');
      expect(content).toContain('export const test');
      // También puedes usar snapshots
      expect(tree.read('src/test/file.ts', 'utf-8')).toMatchSnapshot();
      });
      it('should update existing files', async () => {
      // Ejecutar el generador
      await yourGenerator(tree, {
      name: 'test',
      // Agregar otras opciones requeridas
      });
      // Verificar actualización de archivos existentes
      const content = tree.read('src/existing-file.ts', 'utf-8');
      expect(content).toContain('import { test } from');
      });
      it('should handle errors', async () => {
      // Esperar que el generador lance un error en ciertas condiciones
      await expect(
      yourGenerator(tree, {
      name: 'invalid',
      // Agregar opciones que causen un error
      }),
      ).rejects.toThrow('Expected error message');
      });
      });

      Puntos clave para probar generadores:

      • Usa createTreeWithEmptyWorkspace() para crear un sistema de archivos virtual
      • Configura archivos prerrequisitos antes de ejecutar el generador
      • Prueba tanto la creación de nuevos archivos como actualizaciones a existentes
      • Usa snapshots para contenido de archivos complejos
      • Prueba condiciones de error para asegurar un fallo controlado

      Contribuyendo Generadores a @aws/nx-plugin

      También puedes usar ts#nx-generator para crear un generador dentro de @aws/nx-plugin.

      Cuando este generador se ejecuta en nuestro repositorio, generará los siguientes archivos:

      • Directorypackages/nx-plugin/src/<name>/
        • schema.json Esquema para la entrada de tu generador
        • schema.d.ts Tipos TypeScript para tu esquema
        • generator.ts Implementación del generador
        • generator.spec.ts Pruebas para tu generador
      • Directorydocs/src/content/docs/guides/
        • <name>.mdx Página de documentación para tu generador
      • packages/nx-plugin/generators.json Actualizado para incluir tu generador

      Podrás entonces comenzar a implementar tu generador.