Ir al contenido

Generador de Generadores Nx

Agrega un Generador Nx a un proyecto TypeScript, para ayudarte a automatizar tareas repetitivas como el scaffolding de componentes o la aplicación de estructuras de proyecto particulares.

Puedes generar un generador de dos maneras:

Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-generator
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
Terminal window
pnpm nx g @aws/nx-plugin:ts#nx-generator --dry-run
ParámetroTipoPredeterminadoDescripción
project Requeridostring-Proyecto TypeScript al que agregar el generador. Recomendamos usar el generador ts#nx-plugin para crearlo.
name Requeridostring-Nombre del generador
description string-Una descripción de tu generador
directory string-El directorio dentro de la carpeta de código fuente del proyecto del plugin donde agregar el generador (predeterminado: <name>)
preferInstallDependencies booleantrueSi se prefiere instalar las dependencias después de que se ejecute el generador. Establecer en false para diferir la instalación al ejecutar múltiples generadores en lote (la instalación aún se ejecuta si es necesaria para que los generadores subsiguientes puedan calcular el grafo de proyectos de Nx); instalar una vez al final.

El generador creará los siguientes archivos de proyecto dentro del project dado:

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

Modificación del Proyecto

Este generador actualizará el project seleccionado para usar CommonJS, ya que los Generadores Nx solo soportan CommonJS en la actualidad (consulta este issue de GitHub para soporte ESM).

Selecciona tu proyecto local nx-plugin al ejecutar el generador ts#nx-generator, y especifica un nombre y opcionalmente un directorio y descripción.

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

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": {
// Your generator options go here
},
"required": ["requiredOption1", "requiredOption2"]
}

Aquí hay 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"]
}

Puedes personalizar los prompts mostrados al ejecutar tu generador a través de 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?"
}

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

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

Un patrón común es permitir a los usuarios seleccionar de 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" le dice a Nx que llene el desplegable con todos los proyectos en el workspace.

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.

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 propiedades en la extensión Nx VSCode y Nx CLI.

Puedes proporcionar valores predeterminados para las opciones:

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

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

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 de código:

import { YourGeneratorSchema } from './schema';
export default async function (tree: Tree, options: YourGeneratorSchema) {
// TypeScript knows the types of all your options
const { name, directory = 'src/components', withTests = true } = options;
// ...
}

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 (el 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”. Un generador vacío se ve de la siguiente manera:

export const myGenerator = async (tree: Tree, options: MyGeneratorSchema) => {
// Use the tree to apply changes
};
export default myGenerator;

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

// Read a file
const content = tree.read('path/to/file.ts', 'utf-8');
// Write a file
tree.write('path/to/new-file.ts', 'export const hello = "world";');
// Check if a file exists
if (tree.exists('path/to/file.ts')) {
// Do something
}

Puedes generar archivos con la utilidad generateFiles de @nx/devkit. Esto te permite definir plantillas en sintaxis EJS, y sustituir variables.

import { generateFiles, joinPathFragments } from '@nx/devkit';
// Generate files from templates
generateFiles(
tree,
joinPathFragments(import.meta.dirname, 'files'), // Template directory
'path/to/output', // Output directory
{
// Variables to replace in templates
name: options.name,
nameCamelCase: camelCase(options.name),
nameKebabCase: kebabCase(options.name),
// Add more variables as needed
},
);

Puedes usar GritQL para buscar y transformar código fuente de manera declarativa en tus generadores. GritQL soporta múltiples lenguajes incluyendo TypeScript, JavaScript, Python, HCL (Terraform), y más — por lo que puedes usar la misma sintaxis de patrón en toda tu pila.

El Nx Plugin for AWS expone dos helpers:

  • applyGritQL(tree, filePath, pattern) — aplica un patrón de reescritura GritQL a un archivo y devuelve Promise<boolean> indicando si se realizaron cambios
  • matchGritQL(tree, filePath, pattern) — verifica si un patrón GritQL coincide en algún lugar de un archivo y devuelve Promise<boolean>
import { applyGritQL, matchGritQL } from '@aws/nx-plugin/sdk/utils/ast';
// Replace a function call
await applyGritQL(
tree,
'src/app.ts',
'`console.log($msg)` => `logger.info($msg)`',
);
// Add an element to an array only if not already present
await applyGritQL(
tree,
'src/plugins.ts',
'`plugins: [$items]` => `plugins: [$items, myPlugin()]` where { $items <: not contains `myPlugin` }',
);
// Check if a pattern exists before making changes
if (!(await matchGritQL(tree, filePath, '`import { Auth } from "./auth"`'))) {
// Add the import
}

Los patrones GritQL también funcionan en archivos que no son TypeScript. Prefija tu patrón con language <name> para apuntar a otros lenguajes:

// Python: replace print statements with logging calls
await applyGritQL(
tree,
'src/handler.py',
'language python\n`print($msg)` => `logger.info($msg)`',
);

Los patrones GritQL usan fragmentos de código delimitados por backticks con $metavariables como comodines. Usa => para reescrituras y cláusulas where para condiciones.

import { addDependenciesToPackageJson } from '@nx/devkit';
// Add dependencies to package.json
addDependenciesToPackageJson(
tree,
{
'new-dependency': '^1.0.0',
},
{
'new-dev-dependency': '^2.0.0',
},
);
import { formatFilesInSubtree } from '@aws/nx-plugin/sdk/utils/format';
// Format all files that were modified
await formatFilesInSubtree(tree, 'optional/path/to/format');
import { readJson, updateJson } from '@nx/devkit';
// Read a JSON file
const packageJson = readJson(tree, 'package.json');
// Update a JSON file
updateJson(tree, 'tsconfig.json', (json) => {
json.compilerOptions = {
...json.compilerOptions,
strict: true,
};
return json;
});

Puedes importar generadores del Nx Plugin for AWS, y extenderlos o componerlos como desees, por ejemplo, podrías desear crear un generador que se base en un proyecto TypeScript:

import { tsProjectGenerator } from '@aws/nx-plugin/sdk/ts';
export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
const callback = await tsProjectGenerator(tree, { ... });
// Extend the TypeScript project generator here
// Return the callback to ensure dependencies are installed.
// You can wrap the callback if you wish to perform additional operations in the generator callback.
return callback;
};

Puedes usar y extender los generadores que usamos para clientes y hooks TypeScript de manera similar a lo anterior:

import { openApiTsClientGenerator } from '@aws/nx-plugin/sdk/open-api';
export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
await openApiTsClientGenerator(tree, { ... });
// Add additional files here
};

También exponemos un método que te permite construir una estructura de datos que puede usarse para iterar sobre operaciones en una especificación OpenAPI y por lo tanto instrumentar tu propia generación de código, por ejemplo:

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(import.meta.dirname, 'files'), // Template directory
'path/to/output', // Output directory
data,
);
};

Lo cual luego te permite escribir plantillas como:

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

Consulta el código base en GitHub para plantillas de ejemplo más complejas.

Puedes ejecutar tu generador de dos maneras:

Terminal window
pnpm nx g @my-project/nx-plugin:my-generator
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
Terminal window
pnpm nx g @my-project/nx-plugin:my-generator --dry-run

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

import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { yourGenerator } from './generator.js';
describe('your generator', () => {
let tree;
beforeEach(() => {
// Create an empty workspace tree
tree = createTreeWithEmptyWorkspace();
// Add any files that should already exist in the 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 () => {
// Run the generator
await yourGenerator(tree, {
name: 'test',
// Add other required options
});
// Check that files were created
expect(tree.exists('src/test/file.ts')).toBeTruthy();
// Check file content
const content = tree.read('src/test/file.ts', 'utf-8');
expect(content).toContain('export const test');
// You can also use snapshots
expect(tree.read('src/test/file.ts', 'utf-8')).toMatchSnapshot();
});
it('should update existing files', async () => {
// Run the generator
await yourGenerator(tree, {
name: 'test',
// Add other required options
});
// Check that existing files were updated
const content = tree.read('src/existing-file.ts', 'utf-8');
expect(content).toContain('import { test } from');
});
it('should handle errors', async () => {
// Expect the generator to throw an error in certain conditions
await expect(
yourGenerator(tree, {
name: 'invalid',
// Add options that should cause an error
}),
).rejects.toThrow('Expected error message');
});
});

Puntos clave para probar generadores:

  • Usa createTreeWithEmptyWorkspace() para crear un sistema de archivos virtual
  • Configura cualquier archivo prerequisito antes de ejecutar el generador
  • Prueba tanto la creación de nuevos archivos como las actualizaciones a archivos existentes
  • Usa snapshots para contenido de archivos complejo
  • Prueba condiciones de error para asegurar que tu generador falle de manera elegante

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

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

  • Directoriopackages/nx-plugin/src/<name>/
    • schema.json Schema para la entrada de tu generador
    • schema.d.ts Tipos TypeScript para tu schema
    • generator.ts Implementación del generador
    • generator.spec.ts Pruebas para tu generador
  • Directoriodocs/src/content/docs/guides/
    • <name>.mdx Página de documentación para tu generador
  • packages/nx-plugin/generators.json Actualizado para incluir tu generador
  • packages/nx-plugin/sdk/<prefix>.ts Actualizado para exponer tu generador desde el SDK (para generadores ts# y py#)

Luego puedes comenzar a implementar tu generador.