Desmistificando monorepos com Turborepo

Desmistificando monorepos com Turborepo

Nos últimos anos, o termo “monorepo” tem ganhado certa relevância e talvez você, assim como eu há algum tempo atrás, já ouviu falar sobre monorepo mas não entende o que isso quer dizer exatamente.

À medida que a complexidade dos projetos de desenvolvimento de software aumenta, encontrar maneiras eficientes de gerenciar o código e colaborar se torna essencial. Nesse cenário, os monorepos emergem como uma solução poderosa, um conceito que envolve a gestão de vários projetos em um único repositório.

Neste artigo, além de explorar o conceito de monorepo, vamos introduzir o Turborepo, uma ferramenta que simplifica ainda mais a gestão e construção de projetos em monorepos.

O que é um Monorepo?

Um monorepo (abreviação de "monolithic repository") é um repositório de código que contém múltiplos projetos, módulos ou pacotes sob um único guarda-chuva. Isso contrasta com a abordagem tradicional de "multirepo", em que cada projeto tem seu próprio repositório. No contexto de um monorepo, várias partes do software são desenvolvidas, versionadas e gerenciadas juntas em um único repositório, pois esses projetos geralmente estão relacionados e compartilham dependências comuns, tornando mais fácil a manutenção deles em um só lugar.

Benefícios e desafios dos Monorepos

Pra que você tenha uma visão mais abrangente sobre o assunto, vamos listar alguns pontos positivos na utilização de monorepos:

  1. Compartilhamento de Código e Dependências: Os monorepos facilitam o compartilhamento de código e dependências entre projetos relacionados. Isso reduz a duplicação de código, mantendo uma base de código coesa e consistentemente atualizada.
  2. Refatoração e Mudanças em Escala: Fazer alterações que afetam múltiplos projetos é mais fácil em um monorepo, pois as mudanças podem ser aplicadas de maneira consistente em toda a base de código. Isso é particularmente benéfico ao lidar com refatorações ou atualizações de bibliotecas.
  3. Integração Contínua Simplificada: Manter um fluxo de integração contínua e entrega contínua é mais eficiente em um monorepo, pois as dependências compartilhadas são atualizadas simultaneamente, reduzindo conflitos de versões.
  4. Comunicação Aprimorada: Equipes que trabalham em projetos relacionados podem se comunicar de maneira mais eficaz, já que todo o código está em um só lugar. Isso também pode levar a uma resolução mais rápida de problemas e à troca de melhores práticas.
  5. Gerenciamento de Versões Simplificado: Compartilhar dependências entre projetos significa que todas as partes do sistema podem usar as mesmas versões de bibliotecas, evitando conflitos de versões e melhorando a consistência.

Mas claro, nem tudo são flores. Existem também certos pontos a se ter em mente quando se utiliza um monorepo:

  1. Complexidade Inicial: Configurar um monorepo requer um planejamento cuidadoso e a escolha de ferramentas adequadas. A complexidade da configuração pode ser um obstáculo inicial.
  2. Gerenciamento de Tamanho: À medida que o monorepo cresce, pode se tornar volumoso e desafiador de navegar. Estratégias de organização, modularização e indexação se tornam essenciais.
  3. Conflitos de Mesclagem (Merge): Conflitos de mesclagem podem se tornar mais complexos em um monorepo, especialmente quando várias equipes contribuem para diferentes partes do código.
  4. Impacto de Alterações: Uma única alteração pode afetar múltiplos projetos, exigindo testes abrangentes e validações para garantir a estabilidade.

Por isso é importante a utilização de ferramentas que auxiliem no gerenciamento desse tipo de repositório, e é aqui que o Turborepo entra. Embora existam várias ferramentas disponíveis, atualmente o Turborepo tem ganhado bastante destaque.

Criando um Monorepo com Turborepo

Segundo a própria documentação do Turborepo, ele é um sistema de compilação inteligente otimizado para projetos Javascript e Typescript.

Uma das principais características do Turborepo é sua capacidade de armazenar em cache as saídas de compilação. Isso significa que se você fizer uma alteração em um projeto, o Turborepo reconstruirá apenas o projeto afetado e quaisquer projetos que dependam dele. Isso pode acelerar significativamente os tempos de build, especialmente em monorepos grandes.

O Turborepo também oferece suporte a compilações paralelas, permitindo aproveitar totalmente o poder de processamento do seu computador. Isso pode acelerar ainda mais os tempos de construção e facilitar o trabalho com monorepos extensos.

Para começar a usar o Turborepo, você pode executar o pacote create-turbo, que vai criar e configurar um projeto em uma pasta que você definir, e usando o gerenciador de pacotes de sua escolha:

npx create-turbo@latest

Feito isso, você terá uma estrutura semelhante a esta:

Estrutura de pastas geradas pelo Turborepo

Você pode notar as pastas apps e packages, e as subpastas dentro delas. Cada uma dessas subpastas (docs, web, eslint-config-custom, tsconfig e ui) são projetos (chamados de workspaces) separados, com seus respectivos package.json e alguns com tsconfig.json e eslintrc.js.

A diferença entre eles é que os projetos da pasta packages, são pacotes globais usados pelos projetos da pasta apps. O Turborepo já cria alguns de exemplo, como os pacotes de configuração do ESLint e do Typescript, além do ui, que seria um pacote de componentes globais. Então, na pasta apps, ficam as aplicações propriamente ditas, como a docs e a web, que por padrão são aplicações Next.js.

Compartilhamento de Código entre Projetos

Aqui já dá pra perceber que fica fácil criar vários apps diferentes com as mesmas configurações e componentes, e essa capacidade é um dos maiores benefícios de um monorepo. Isso promove a reutilização de código e a manutenção consistente, uma vez que as alterações podem ser aplicadas em um único local e refletidas em todos os projetos dependentes. Vamos explorar como isso é alcançado usando o exemplo de um componente de botão do pacote ui.

E já temos um exemplo disso nos arquivos gerados pelo Turborepo. Dentro do pacote ui, temos alguns componentes, como o Button e o Header, e que são usados nos apps docs e web.

// packages/ui/Button.tsx
"use client";

import * as React from "react";

export const Button = () => {
  return <button onClick={() => alert("boop")}>Boop</button>;
};

// packages/ui/Header.tsx
import * as React from "react";

export const Header = ({ text }: { text: string }) => {
  return <h1>{text}</h1>;
};

Aqui está a magia: ao fazer alterações no componente de botão dentro do pacote ui, essas mudanças serão refletidas em todos os projetos que utilizam esse componente. Isso reduz a complexidade de manutenção e garante a consistência da interface do usuário em todo o monorepo.

Ao compartilhar código entre projetos usando pacotes, também é importante levar em consideração as dependências. O Turborepo facilita essa tarefa, permitindo que você atualize as dependências compartilhadas de forma coordenada.

Executando os projetos

Com a estrutura já criada, podemos executar os workspaces usando os comandos do Turborepo. De início, temos 4 comandos já configurados no package.json na raiz do monorepo, que são: build, dev e lint, além do format usando o Prettier.

Agora vejamos o arquivo turbo.json na raiz do projeto:

{
  "$schema": "<https://turbo.build/schema.json>",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**"]
    },
    "lint": {},
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Esse arquivo define os comandos build, lint e dev. Ao executar qualquer um desses comandos, seja com o npm ou usando o comando turbo global, o comportamento padrão é executar em todos os workspaces que têm aquele comando configurado. Ou seja, se você executar npm run build na raiz do monorepo, isso irá compilar os workspaces docs e web, pois são os únicos que em seu package.json têm um comando build configurado.

Aproveitando o Cache para Maior Eficiência

Um ponto a se notar é que ao executar novamente o comando npm run build, você pode observar mensagens indicando o uso do cache:

Tasks:    2 successful, 2 total
Cached:    2 cached, 2 total
  Time:    162ms >>> FULL TURBO

Essa mensagem indica que o Turborepo identificou que não houve alterações desde a última compilação e, portanto, reutilizou informações em cache para acelerar o processo. Essa otimização é particularmente valiosa em projetos complexos, onde a redução do tempo de compilação e execução pode ter um impacto significativo na produtividade geral.

Flexibilidade de Execução com Filtros

Embora a execução padrão do Turborepo seja aplicar comandos a todos os workspaces relevantes, é comum que se deseje executar um comando apenas em um subconjunto específico de workspaces. Para atender a essa necessidade, o Turborepo oferece a flag --filter. Essa funcionalidade permite selecionar quais workspaces serão alvo da execução do comando, oferecendo maior flexibilidade e controle sobre o fluxo de trabalho.

A flag --filter é especialmente útil quando você deseja aplicar um comando apenas a um conjunto específico de workspaces que compartilham características comuns. Por exemplo, você pode filtrar workspaces com base em determinadas tags, nomes ou outros critérios definidos.

Explorando a Documentação

O Turborepo oferece uma documentação abrangente que detalha todos os recursos, opções e melhores práticas. A documentação oficial do Turborepo é uma fonte valiosa de informações para aprofundar seus conhecimentos e descobrir maneiras avançadas de otimizar seu fluxo de trabalho.

Conclusão

Em um cenário de desenvolvimento dinâmico e em constante evolução, ferramentas como o Turborepo se destacam como soluções vitais para otimizar a gestão de monorepos. Sua abordagem inovadora, que inclui o aproveitamento de cache e a capacidade de filtrar workspaces, oferece uma vantagem competitiva ao acelerar significativamente o ciclo de desenvolvimento.

Ao adotar o Turborepo, as equipes de desenvolvimento podem se concentrar em criar código de alta qualidade e funcionalidades, confiantes de que a ferramenta cuida dos aspectos complexos de compilação, testes e execução. Em suma, o Turborepo emerge como uma ferramenta poderosa e promissora para equipes que trabalham com monorepos, oferecendo eficiência e velocidade para enfrentar os desafios crescentes no desenvolvimento de software.

💡
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.