Experts in Angular

Angular CLIAngular CLI Builders: Aprofundando a Arquitetura da Nave-Mãe
Os builders do Angular CLI são os verdadeiros motores da sua nave espacial de desenvolvimento. Eles permitem que você personalize e automatize tarefas complexas, transformando seu código em uma aplicação Angular completa e funcional.

Angular CLI Builders: Aprofundando a Arquitetura da Nave-Mãe

No universo do Angular CLI, os builders desempenham um papel fundamental como engenheiros especializados, responsáveis por executar tarefas complexas no seu código, como construir, testar e servir sua aplicação. Cada comando do Angular CLI, como ng build, ng test e ng serve, invoca um builder específico para realizar a tarefa desejada.

Imagine os builders como os diferentes módulos da sua nave espacial Angular, cada um com uma função específica e essencial para o funcionamento do todo. Eles são os responsáveis por transformar seu código-fonte em uma aplicação funcional, executando tarefas como compilação, otimização, empacotamento, testes e muito mais.

Arquitetura dos Builders: O Mapa da Nave-Mãe

Os comandos do Angular CLI utilizam uma ferramenta interna chamada Architect para executar os builders. O Architect delega o trabalho para funções de manipulador chamadas builders. Cada função de manipulador recebe dois argumentos:

  1. options: Um objeto JSON que contém as opções e configurações definidas pelo usuário na linha de comando.
  2. context: Um objeto BuilderContext que fornece informações contextuais sobre a execução do builder, como o caminho do projeto, a configuração do workspace e acesso a um agendador de tarefas.

Essa separação de responsabilidades é semelhante à dos schematics, que são utilizados para outros comandos do CLI que modificam seu código (como ng generate).

Retorno do Builder: O Relatório da Missão

A função de manipulador do builder pode ser síncrona (retornando um valor), assíncrona (retornando uma Promise) ou assíncrona com múltiplos valores (retornando um Observable). O valor de retorno deve sempre ser do tipo BuilderOutput, um objeto que contém um campo booleano success e um campo opcional error que pode conter uma mensagem de erro.

Builders do Angular: Os Tripulantes da Nave

O Angular fornece alguns builders nativos que são utilizados pelo CLI para comandos como ng build e ng test. As configurações padrão para esses e outros builders podem ser encontradas e personalizadas na seção "architect" do arquivo de configuração do workspace, angular.json.

Builders Personalizados: Expandindo as Fronteiras da Nave-Mãe

Além dos builders nativos, você também pode criar seus próprios builders personalizados para estender e personalizar o Angular CLI. Esses builders podem ser executados diretamente usando o comando ng run, permitindo que você realize tarefas específicas do seu projeto, como executar scripts personalizados, gerar documentação ou integrar com ferramentas externas.

Estrutura do Projeto do Builder: O Manual de Construção

Um builder reside em uma pasta de “projeto” com uma estrutura semelhante à de um workspace Angular, com arquivos de configuração globais no nível superior e configurações mais específicas em uma pasta src com os arquivos de código que definem o comportamento do builder. Por exemplo, sua pasta myBuilder pode conter os seguintes arquivos:

ArquivosPropósito
src/my-builder.tsArquivo principal com a definição do builder.
src/my-builder.spec.tsArquivo para testes.
src/schema.jsonDefinição das opções de entrada do builder.
builders.jsonDefinição dos builders.
package.jsonDependências.
tsconfig.jsonConfiguração do TypeScript.

Exportar para as Planilhas

Os builders podem ser publicados no npm, permitindo que você os compartilhe com a comunidade Angular e os utilize em diferentes projetos.

Criando Builders Personalizados: Expandindo as Capacidades da sua Nave-Mãe

No mundo da engenharia espacial, a capacidade de construir e personalizar módulos é essencial para adaptar a nave a diferentes missões. Da mesma forma, no universo do Angular CLI, a criação de builders personalizados permite que você expanda as funcionalidades da sua “nave-mãe” de desenvolvimento, adicionando novas tarefas e ferramentas que atendam às suas necessidades específicas.

Anatomia de um Builder: O Esqueleto da Criação

Um builder é, em essência, uma função que recebe opções e um contexto, e retorna uma promessa (Promise) de um objeto BuilderOutput. Essa promessa indica se a operação do builder foi bem-sucedida e, caso contrário, inclui uma mensagem de erro.

Vamos começar com um esqueleto de builder que copia um arquivo para um novo local:

import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { promises as fs } from 'fs';

interface Options extends JsonObject {
  source: string; // Caminho do arquivo de origem
  destination: string; // Caminho do destino
}

export default createBuilder(copyFileBuilder); // Exporta o builder

async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> {
  context.reportStatus(`Copiando ${options.source} para ${options.destination}.`); // Informa o status

  try {
    await fs.copyFile(options.source, options.destination); // Copia o arquivo
  } catch (err) {
    context.logger.error('Falha ao copiar o arquivo.'); // Registra o erro no console
    return {
      success: false,
      error: (err as Error).message, // Retorna um objeto de saída com erro
    };
  }

  context.reportStatus('Concluído.'); // Informa o status de conclusão
  return { success: true }; // Retorna um objeto de saída com sucesso
}

Adicionando Lógica ao Builder: Dando Vida à Criação

Agora, vamos adicionar a lógica para copiar o arquivo. O código abaixo obtém os caminhos dos arquivos de origem e destino das opções do usuário e copia o arquivo usando a versão Promise da função copyFile do Node.js. Se a operação de cópia falhar, retorna um erro com uma mensagem sobre o problema.

import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { promises as fs } from 'fs';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> {
  context.reportStatus(`Copying ${options.source} to ${options.destination}.`);

  try {
    await fs.copyFile(options.source, options.destination);
  } catch (err) {
    context.logger.error('Failed to copy file.');
    return {
      success: false,
      error: (err as Error).message,
    };
  }

  context.reportStatus('Done.');
  return { success: true };
}

Com este exemplo, você aprendeu a criar um builder personalizado simples que copia um arquivo. A partir daqui, você pode explorar as diversas possibilidades do Angular CLI e criar builders que realizem tarefas específicas do seu projeto, como otimizar imagens, gerar documentação ou executar scripts personalizados.

Lembre-se: Ao criar seus próprios builders, você estará expandindo as capacidades do Angular CLI e tornando sua “nave-mãe” de desenvolvimento ainda mais poderosa e versátil.

Tratando a Saída do Builder

Assim como uma nave espacial envia relatórios de status para a base durante uma missão, um builder Angular CLI também precisa comunicar seu progresso e possíveis erros. Isso é essencial para que você possa acompanhar a execução do builder e identificar problemas rapidamente.

O Silêncio da copyFile(): Um Desafio para a Depuração

Por padrão, a função copyFile() do Node.js não imprime nenhuma informação no console. Isso pode dificultar a compreensão do que o builder está fazendo e a identificação da causa de um erro, caso ocorra.

A Solução: Utilizando a API de Logger

Para adicionar contexto e facilitar a depuração, você pode utilizar a API de Logger do Angular CLI para registrar informações adicionais no console. Isso também permite que o builder seja executado em um processo separado, mesmo que a saída padrão e o erro sejam desativados.

Obtendo uma Instância de Logger

Você pode obter uma instância de Logger a partir do objeto de contexto (context) passado para a função do builder.

Exemplo: Adicionando Logs ao Builder copyFileBuilder
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { promises as fs } from 'fs';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> {
  context.reportStatus(`Copying ${options.source} to ${options.destination}.`);

  try {
    await fs.copyFile(options.source, options.destination);
    context.logger.info(`Successfully copied ${options.source} to ${options.destination}.`);
  } catch (err) {
    context.logger.error('Failed to copy file.');
    context.logger.error(`Error details: ${(err as Error).message}`);
    return {
      success: false,
      error: (err as Error).message,
    };
  }

  context.reportStatus('Done.');
  return { success: true };
}

Neste exemplo, adicionamos uma chamada a context.logger.error para registrar o erro no console, caso a cópia do arquivo falhe. O Logger também pode ser usado para registrar mensagens de log (context.logger.log) e avisos (context.logger.warn).

Benefícios do Logger:

  • Depuração: Facilita a identificação e correção de erros durante o desenvolvimento.
  • Monitoramento: Permite acompanhar o progresso da execução do builder em tempo real.
  • Execução em Processos Separados: O builder pode ser executado em um processo separado, mesmo que a saída padrão e de erro estejam desativadas.

Comunicação Eficaz: A Chave para o Sucesso

Ao utilizar o Logger para registrar informações relevantes sobre a execução do seu builder, você garante uma comunicação clara e objetiva com o usuário, facilitando a depuração, o monitoramento e a resolução de problemas.

Relatórios de Progresso e Status: Mantendo a Tripulação Informada

Assim como uma nave espacial transmite informações sobre sua posição e status para a base em Terra, os builders do Angular CLI também precisam relatar seu progresso e status durante a execução. Isso permite que você monitore o andamento das tarefas e identifique possíveis problemas em tempo real.

API de Relatório de Progresso e Status: O Canal de Comunicação

A API do Angular CLI Builder inclui ferramentas para reportar progresso e status. Para relatar o progresso de uma tarefa, você pode utilizar o método context.reportProgress(), que recebe três argumentos:

  1. Valor Atual: O valor atual do progresso (por exemplo, o número de arquivos processados).
  2. Total (opcional): O valor total do progresso (por exemplo, o número total de arquivos a serem processados).
  3. String de Status (opcional): Uma mensagem de status personalizada.

No nosso exemplo de cópia de arquivo, a operação termina rapidamente, então não precisamos de um relatório de progresso detalhado. No entanto, podemos usar o método context.reportStatus() para informar o status da operação.

import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { promises as fs } from 'fs';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> {
  context.reportStatus(`Copying ${options.source} to ${options.destination}.`);

  try {
    await fs.copyFile(options.source, options.destination);
    context.logger.info(`Successfully copied ${options.source} to ${options.destination}.`);
    context.reportProgress(100, 100, 'Copy complete');
  } catch (err) {
    context.logger.error('Failed to copy file.');
    context.logger.error(`Error details: ${(err as Error).message}`);
    return {
      success: false,
      error: (err as Error).message,
    };
  }

  context.reportStatus('Done.');
  return { success: true };
}

Neste exemplo, utilizamos context.reportStatus() para informar que o arquivo está sendo copiado e, em seguida, que a operação foi concluída.

Boas Práticas:

  • Mantenha as mensagens de status curtas e informativas: Evite mensagens muito longas, pois elas podem ser truncadas pela interface do usuário.
  • Utilize o context.reportProgress() para tarefas demoradas: Se seu builder realizar uma tarefa que pode levar algum tempo, utilize o context.reportProgress() para informar o progresso ao usuário.
  • Limpe o status ao final da execução: Ao finalizar a execução do builder, chame context.reportStatus('') para limpar a mensagem de status.

Com as ferramentas de relatório de progresso e status do Angular CLI, você garante que sua “nave-mãe” de desenvolvimento esteja sempre transmitindo informações claras e precisas para sua tripulação, facilitando o monitoramento e a tomada de decisões durante a execução das tarefas.

Entrada do Builder: Abastecendo a Nave com Dados

Assim como uma nave espacial precisa de combustível e suprimentos para realizar sua missão, os builders do Angular CLI também precisam de dados de entrada para executar suas tarefas. Esses dados podem ser fornecidos de diferentes formas, oferecendo flexibilidade e controle para o usuário.

Invocando o Builder: Dando a Partida

Você pode invocar um builder indiretamente através de um comando do Angular CLI, como ng build, ou diretamente com o comando ng run. Em ambos os casos, você deve fornecer os dados de entrada obrigatórios, mas pode permitir que outras entradas assumam valores padrão pré-configurados para um alvo específico, especificados por uma configuração ou definidos na linha de comando.

Origem dos Dados de Entrada: O Suprimento da Nave

Os dados de entrada do builder podem vir de três fontes principais:

  1. Opções Padrão: Cada builder possui um conjunto de opções padrão definidas no arquivo angular.json. Essas opções são utilizadas quando o usuário não especifica valores na linha de comando ou em uma configuração específica.
  2. Configurações: Você pode criar diferentes configurações para um builder, cada uma com seus próprios valores padrão para as opções. Isso permite que você personalize o comportamento do builder para diferentes cenários, como desenvolvimento, teste e produção.
  3. Linha de Comando: Ao executar um comando do Angular CLI, você pode sobrescrever as opções padrão ou as configurações definidas no arquivo angular.json, passando valores diretamente na linha de comando.
Exemplo: Configurando Opções de Construção

Imagine que você criou um builder personalizado chamado meu-builder com as seguintes opções:

interface Options extends JsonObject {
  inputPath: string; // Caminho do diretório de entrada
  outputPath: string; // Caminho do diretório de saída
  optimizationLevel: number; // Nível de otimização (0 a 9)
}

No arquivo angular.json, você pode definir as opções padrão para esse builder:

{
  "projects": {
    "meu-app": {
      "architect": {
        "meu-builder": {
          "builder": "meu-pacote:meu-builder",
          "options": {
            "inputPath": "src",
            "outputPath": "dist",
            "optimizationLevel": 5
          }
        }
      }
    }
  }
}

Ao executar o comando ng run meu-app:meu-builder, o builder utilizará as opções padrão definidas acima. No entanto, você pode sobrescrevê-las na linha de comando:

ng run meu-app:meu-builder --optimizationLevel 9 --outputPath build

Fornecer entradas para um builder no Angular CLI pode ser feito de várias maneiras, seja diretamente através da linha de comando, indiretamente através de comandos CLI ou configurando alvos específicos no arquivo angular.json. Essa flexibilidade permite que você adapte facilmente o comportamento do builder para diferentes ambientes e necessidades de projeto.

Validação de Entrada do Builder: Garantindo a Integridade da Missão

Assim como uma nave espacial precisa de um sistema de segurança para garantir que os dados de entrada sejam válidos e confiáveis, os builders do Angular CLI também precisam de um mecanismo de validação para garantir que as opções fornecidas pelo usuário estejam corretas e completas.

Você define entradas do builder em um esquema JSON associado a esse builder. Similar aos schematics, a ferramenta Architect coleta os valores de entrada resolvidos em um objeto options e valida seus tipos contra o esquema antes de passá-los para a função do builder.

Para nosso exemplo de builder, options deve ser um JsonObject com duas chaves: source e destination, cada uma das quais é uma string.

Você pode fornecer o seguinte esquema para validação de tipo desses valores.

Esquema de Validação

src/schema.json:

{
  "$schema": "http://json-schema.org/schema",
  "type": "object",
  "properties": {
    "source": {
      "type": "string"
    },
    "destination": {
      "type": "string"
    }
  },
  "required": ["source", "destination"]
}
DICA: Este é um exemplo mínimo, mas o uso de um esquema para validação pode ser muito poderoso. Para mais informações, consulte o site JSON schemas.

Para vincular a implementação do builder ao seu esquema e nome, você precisa criar um arquivo de definição do builder, que pode ser apontado no package.json.

Arquivo de Definição do Builder

Crie um arquivo chamado builders.json que se pareça com isto:

builders.json:

{
  "builders": {
    "copy": {
      "implementation": "./dist/my-builder.js",
      "schema": "./src/schema.json",
      "description": "Copies a file."
    }
  }
}

No arquivo package.json, adicione uma chave builders que informa à ferramenta Architect onde encontrar nosso arquivo de definição do builder.

package.json:

{
  "name": "@example/copy-file",
  "version": "1.0.0",
  "description": "Builder for copying files",
  "builders": "builders.json",
  "dependencies": {
    "@angular-devkit/architect": "~0.1200.0",
    "@angular-devkit/core": "^12.0.0"
  }
}

O nome oficial do nosso builder agora é @example/copy-file:copy. A primeira parte é o nome do pacote e a segunda parte é o nome do builder conforme especificado no arquivo builders.json.

Implementação do Builder com Validação

Esses valores são acessados em options.source e options.destination.

Código Atualizado do Builder

src/my-builder.ts:

import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { promises as fs } from 'fs';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> {
  context.reportStatus(`Copying ${options.source} to ${options.destination}.`);

  try {
    await fs.copyFile(options.source, options.destination);
    context.logger.info(`Successfully copied ${options.source} to ${options.destination}.`);
    context.reportProgress(100, 100, 'Copy complete');
  } catch (err) {
    context.logger.error('Failed to copy file.');
    context.logger.error(`Error details: ${(err as Error).message}`);
    return {
      success: false,
      error: (err as Error).message,
    };
  }

  context.reportStatus('Done.');
  return { success: true };
}

Validar as entradas usando um esquema JSON ajuda a garantir que seu builder receba dados corretos e estruturados conforme esperado. Isso não apenas ajuda a evitar erros durante a execução, mas também fornece feedback claro e específico quando os valores de entrada não atendem aos critérios definidos.

transformando seu código em uma aplicação Angular completa e funcional

Os builders do Angular CLI são os verdadeiros motores da sua nave espacial de desenvolvimento. Eles permitem que você personalize e automatize tarefas complexas, transformando seu código em uma aplicação Angular completa e funcional.

Ao longo deste artigo, exploramos a arquitetura dos builders, desde sua estrutura de projeto até a forma como eles interagem com o Angular CLI. Aprendemos a criar builders personalizados, definindo suas opções de entrada, validando os dados e reportando o progresso da execução.

Com o conhecimento adquirido, você está pronto para expandir as capacidades do Angular CLI e criar builders que atendam às necessidades específicas do seu projeto. Seja para otimizar imagens, gerar documentação ou executar scripts personalizados, os builders são seus aliados na jornada de desenvolvimento Angular.

Lembre-se, o Angular CLI é uma ferramenta poderosa e flexível, e os builders são a chave para desbloquear todo o seu potencial. Ao dominar essa ferramenta, você estará construindo aplicações Angular mais eficientes, personalizadas e prontas para conquistar o universo da web.