Pular para o conteúdo

Gerador de Gerador do Nx

Adiciona um Nx Generator a um projeto TypeScript para ajudar a automatizar tarefas repetitivas como scaffolding de componentes ou imposição de estruturas de projeto específicas.

Utilização

Gerar um Generator

Você pode gerar um generator de duas formas:

  1. Instale o Nx Console VSCode Plugin se ainda não o fez
  2. Abra o console Nx no VSCode
  3. Clique em Generate (UI) na seção "Common Nx Commands"
  4. Procure por @aws/nx-plugin - ts#nx-generator
  5. Preencha os parâmetros obrigatórios
    • Clique em Generate

    Opções

    Parâmetro Tipo Padrão Descrição
    pluginProject Obrigatório string - TypeScript project to add the generator to. We recommend creating a ts#project in a top-level 'tools' directory.
    name Obrigatório 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>)

    Saída do Generator

    O generator criará os seguintes arquivos no projeto pluginProject especificado:

    • Directorysrc/<name>/
      • schema.json Esquema para entrada do generator
      • schema.d.ts Tipos TypeScript para o esquema
      • generator.ts Implementação inicial do generator
      • generator.spec.ts Testes para o generator
    • generators.json Configuração Nx para definir seus generators
    • package.json Criado ou atualizado com entrada “generators”
    • tsconfig.json Atualizado para usar CommonJS

    Este generator atualizará o pluginProject selecionado para usar CommonJS, pois os Nx Generators atualmente só suportam CommonJS (consulte esta issue do GitHub sobre suporte a ESM).

    Generators Locais

    Selecione seu projeto local nx-plugin ao executar o generator ts#nx-generator, e especifique um nome, diretório opcional e descrição.

    Definindo o Esquema

    O arquivo schema.json define as opções aceitas pelo seu generator. Ele segue o formato JSON Schema com extensões específicas do Nx.

    Estrutura Básica

    Um arquivo schema.json possui a seguinte estrutura 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"]
    }

    Exemplo Simples

    Aqui está um exemplo simples com algumas opções 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 Interativos (CLI)

    Você pode personalizar os prompts exibidos ao executar seu generator via CLI adicionando a propriedade x-prompt:

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

    Para opções booleanas, use um prompt sim/não:

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

    Seleções Dropdown

    Para opções com um conjunto fixo de escolhas, use enum para permitir seleção entre as opções:

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

    Um padrão comum é permitir seleção entre projetos existentes no 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"
    }

    A propriedade x-dropdown: "projects" instrui o Nx a preencher o dropdown com todos os projetos do workspace.

    Argumentos Posicionais

    Você pode configurar opções para serem passadas como argumentos posicionais ao executar o generator via linha de comando:

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

    Isso permite executar o generator como nx g your-generator my-component ao invés de nx g your-generator --name=my-component.

    Definindo Prioridades

    Use a propriedade x-priority para indicar opções mais importantes:

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

    Opções podem ter prioridades "important" ou "internal". Isso ajuda o Nx a ordenar propriedades na extensão VSCode e CLI do Nx.

    Valores Padrão

    Você pode fornecer valores padrão para opções:

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

    Mais Informações

    Para mais detalhes sobre esquemas, consulte a documentação de Opções do Nx Generator.

    Tipos TypeScript com schema.d.ts

    Junto com schema.json, o generator cria um arquivo schema.d.ts que fornece tipos TypeScript para as opções:

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

    Esta interface é usada na implementação do generator para fornecer segurança de tipos e autocompletar:

    import { YourGeneratorSchema } from './schema';
    export default async function (tree: Tree, options: YourGeneratorSchema) {
    // TypeScript conhece os tipos de todas as opções
    const { name, directory = 'src/components', withTests = true } = options;
    // ...
    }

    Implementando um Generator

    Após criar o generator como acima, você pode escrever sua implementação em generator.ts.

    Um generator é uma função que modula um sistema de arquivos virtual (Tree), lendo e escrevendo arquivos para fazer as alterações desejadas. As mudanças no Tree só são escritas em disco quando o generator termina, exceto em modo “dry-run”.

    Aqui estão operações comuns que você pode realizar no generator:

    Lendo e Escrevendo Arquivos

    // Ler um arquivo
    const content = tree.read('path/to/file.ts', 'utf-8');
    // Escrever um arquivo
    tree.write('path/to/new-file.ts', 'export const hello = "world";');
    // Verificar se um arquivo existe
    if (tree.exists('path/to/file.ts')) {
    // Fazer algo
    }

    Gerando Arquivos de Templates

    Você pode gerar arquivos com o utilitário generateFiles do @nx/devkit. Isso permite definir templates em sintaxe EJS e substituir variáveis.

    import { generateFiles, joinPathFragments } from '@nx/devkit';
    // Gerar arquivos de templates
    generateFiles(
    tree,
    joinPathFragments(__dirname, 'files'), // Diretório do template
    'path/to/output', // Diretório de saída
    {
    // Variáveis para substituir nos templates
    name: options.name,
    nameCamelCase: camelCase(options.name),
    nameKebabCase: kebabCase(options.name),
    // Adicione mais variáveis conforme necessário
    },
    );

    Manipulação de AST (Abstract Syntax Tree) TypeScript

    Você pode usar o método tsAstReplace exposto pelo Nx Plugin for AWS para substituir partes de uma AST TypeScript.

    import { tsAstReplace } from '@aws/nx-plugin/sdk/utils/ast';
    import * as ts from 'typescript';
    // Exemplo: Incrementar número de versão em um arquivo
    tsAstReplace(
    tree,
    'path/to/version.ts',
    'VariableDeclaration:has(Identifier[name="VERSION"]) NumericLiteral',
    (node: ts.NumericLiteral) =>
    ts.factory.createNumericLiteral(Number(node.text) + 1));

    Adicionando Dependências

    import { addDependenciesToPackageJson } from '@nx/devkit';
    // Adicionar dependências ao package.json
    addDependenciesToPackageJson(
    tree,
    {
    'new-dependency': '^1.0.0',
    },
    {
    'new-dev-dependency': '^2.0.0',
    },
    );

    Formatando Arquivos Gerados

    import { formatFilesInSubtree } from '@aws/nx-plugin/sdk/utils/format';
    // Formatar todos os arquivos modificados
    await formatFilesInSubtree(tree, 'optional/path/to/format');

    Lendo e Atualizando Arquivos JSON

    import { readJson, updateJson } from '@nx/devkit';
    // Ler um arquivo JSON
    const packageJson = readJson(tree, 'package.json');
    // Atualizar um arquivo JSON
    updateJson(tree, 'tsconfig.json', (json) => {
    json.compilerOptions = {
    ...json.compilerOptions,
    strict: true,
    };
    return json;
    });

    Estendendo um Generator do Nx Plugin for AWS

    Você pode importar generators do Nx Plugin for AWS e estendê-los ou compô-los conforme desejar. Por exemplo, criar um generator que estende um projeto TypeScript:

    import { tsProjectGenerator } from '@aws/nx-plugin/sdk/ts';
    export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
    const callback = await tsProjectGenerator(tree, { ... });
    // Estenda o generator de projeto TypeScript aqui
    // Retorne o callback para garantir instalação de dependências
    // Você pode encapsular o callback para operações adicionais
    return callback;
    };

    Generators OpenAPI

    Você pode usar e estender os generators que usamos para clients e hooks TypeScript de forma similar:

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

    Também expomos um método para construir estruturas de dados que permitem iterar sobre operações em especificações OpenAPI e instrumentar sua própria geração de código:

    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'), // Diretório de templates
    'path/to/output', // Diretório de saída
    data,
    );
    };

    Isso permite escrever templates como:

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

    Consulte o repositório no GitHub para exemplos mais complexos.

    Executando Seu Generator

    Você pode executar seu generator de duas formas:

    1. Instale o Nx Console VSCode Plugin se ainda não o fez
    2. Abra o console Nx no VSCode
    3. Clique em Generate (UI) na seção "Common Nx Commands"
    4. Procure por @my-project/nx-plugin - my-generator
    5. Preencha os parâmetros obrigatórios
      • Clique em Generate

      Testando Seu Generator

      Testes unitários para generators são simples de implementar. Aqui está um padrão típico:

      import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
      import { yourGenerator } from './generator';
      describe('your generator', () => {
      let tree;
      beforeEach(() => {
      // Criar workspace vazio
      tree = createTreeWithEmptyWorkspace();
      // Adicionar arquivos pré-existentes
      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 () => {
      // Executar o generator
      await yourGenerator(tree, {
      name: 'test',
      // Adicionar outras opções necessárias
      });
      // Verificar criação de arquivos
      expect(tree.exists('src/test/file.ts')).toBeTruthy();
      // Verificar conteúdo
      const content = tree.read('src/test/file.ts', 'utf-8');
      expect(content).toContain('export const test');
      // Snapshots também podem ser usados
      expect(tree.read('src/test/file.ts', 'utf-8')).toMatchSnapshot();
      });
      it('should update existing files', async () => {
      await yourGenerator(tree, {
      name: 'test',
      // Adicionar outras opções necessárias
      });
      const content = tree.read('src/existing-file.ts', 'utf-8');
      expect(content).toContain('import { test } from');
      });
      it('should handle errors', async () => {
      await expect(
      yourGenerator(tree, {
      name: 'invalid',
      // Opções que devem causar erro
      }),
      ).rejects.toThrow('Mensagem de erro esperada');
      });
      });

      Pontos-chave para testar generators:

      • Use createTreeWithEmptyWorkspace() para criar sistema de arquivos virtual
      • Configure arquivos pré-requisitos antes de executar o generator
      • Teste criação de novos arquivos e atualizações de existentes
      • Use snapshots para conteúdo complexo
      • Teste condições de erro para garantir falha controlada

      Contribuindo com Generators para @aws/nx-plugin

      Você também pode usar ts#nx-generator para estruturar um generator dentro de @aws/nx-plugin.

      Quando executado em nosso repositório, este generator criará os seguintes arquivos:

      • Directorypackages/nx-plugin/src/<name>/
        • schema.json Esquema para entrada do generator
        • schema.d.ts Tipos TypeScript para o esquema
        • generator.ts Implementação do generator
        • generator.spec.ts Testes para o generator
      • Directorydocs/src/content/docs/guides/
        • <name>.mdx Página de documentação do generator
      • packages/nx-plugin/generators.json Atualizado para incluir seu generator

      Você pode então começar a implementar seu generator.