Guia para Monorepos para código front-end
Publicados: 2022-03-11Monorepos são um tema quente para uma discussão. Tem havido muitos artigos recentemente sobre por que você deve e não deve usar esse tipo de arquitetura para seu projeto, mas a maioria deles é tendenciosa de uma forma ou de outra. Esta série é uma tentativa de reunir e explicar o máximo de informações possível para entender como e quando usar monorepos.
Um Monorepositório é um conceito arquitetônico, que basicamente contém todo o significado em seu título. Em vez de gerenciar vários repositórios, você mantém todas as partes de código isoladas em um repositório. Lembre-se da palavra isolado — significa que monorepo não tem nada em comum com aplicativos monolíticos. Você pode manter vários tipos de aplicativos lógicos em um repositório; por exemplo, um site e seu aplicativo iOS.
Este conceito é relativamente antigo e surgiu há cerca de uma década. O Google foi uma das primeiras empresas a adotar essa abordagem para gerenciar suas bases de código. Você pode perguntar, se existe há uma década, então por que é um tema tão quente só agora? Principalmente, ao longo dos últimos 5-6 anos, muitas coisas sofreram mudanças dramáticas. ES6, pré-processadores SCSS, gerenciadores de tarefas, npm, etc.—hoje em dia, para manter um pequeno aplicativo baseado em React, você precisa lidar com empacotadores de projetos, suítes de teste, scripts CI/CD, configurações do Docker e sabe-se lá o que mais. E agora imagine que, em vez de um pequeno aplicativo, você precisa manter uma plataforma enorme composta por muitas áreas funcionais. Se você está pensando em arquitetura, você vai querer fazer duas coisas principais: Separar preocupações e evitar erros de código.
Para que isso aconteça, você provavelmente desejará isolar grandes recursos em alguns pacotes e usá-los por meio de um único ponto de entrada em seu aplicativo principal. Mas como você gerencia esses pacotes? Cada pacote terá que ter sua própria configuração de ambiente de fluxo de trabalho, e isso significa que toda vez que você quiser criar um novo pacote, terá que configurar um novo ambiente, copiar todos os arquivos de configuração e assim por diante. Ou, por exemplo, se você tiver que mudar algo em seu sistema de compilação, você terá que passar por cima de cada repositório, fazer um commit, criar um pull request e esperar por cada compilação, o que o deixa muito lento. Nesta etapa, estamos atendendo os monorepos.
Em vez de ter muitos repositórios com suas próprias configurações, teremos apenas uma fonte de verdade – o monorepo: um executor de suíte de testes, um arquivo de configuração do Docker e uma configuração para o Webpack. E você ainda tem escalabilidade, oportunidade de separar preocupações, compartilhamento de código com pacotes comuns e muitos outros profissionais. Parece bom, certo? Bem, é. Mas também existem alguns inconvenientes. Vamos dar uma olhada nos prós e contras exatos de usar o monorepo na natureza.
Vantagens do Monorepo:
- Um lugar para armazenar todas as configurações e testes. Como tudo está localizado em um repositório, você pode configurar seu CI/CD e bundler uma vez e, em seguida, reutilizar as configurações para compilar todos os pacotes antes de publicá-los no controle remoto. O mesmo vale para testes unitários, e2e e de integração — seu CI poderá iniciar todos os testes sem ter que lidar com configurações adicionais.
- Refatore facilmente recursos globais com confirmações atômicas. Em vez de fazer um pull request para cada repositório, descobrindo em qual ordem construir suas mudanças, você só precisa fazer um pull request atômico que conterá todos os commits relacionados ao recurso no qual você está trabalhando.
- Publicação de pacotes simplificada. Se você planeja implementar um novo recurso dentro de um pacote que depende de outro pacote com código compartilhado, pode fazê-lo com um único comando. É uma função que precisa de algumas configurações adicionais, que serão discutidas posteriormente em uma parte de revisão de ferramentas deste artigo. Atualmente, há uma rica seleção de ferramentas, incluindo Lerna, Yarn Workspaces e Bazel.
- Gerenciamento de dependência mais fácil. Apenas um pacote.json . Não há necessidade de reinstalar dependências em cada repositório sempre que desejar atualizar suas dependências.
- Reutilize o código com pacotes compartilhados, mantendo-os isolados. O Monorepo permite que você reutilize seus pacotes de outros pacotes, mantendo-os isolados uns dos outros. Você pode usar uma referência ao pacote remoto e consumi-los por meio de um único ponto de entrada. Para usar a versão local, você pode usar links simbólicos locais. Esse recurso pode ser implementado por meio de scripts bash ou pela introdução de algumas ferramentas adicionais, como Lerna ou Yarn.
Desvantagens do Monorepo:
- Não há como restringir o acesso apenas a algumas partes do aplicativo. Infelizmente, você não pode compartilhar apenas a parte do seu monorepo—você terá que dar acesso a toda a base de código, o que pode levar a alguns problemas de segurança.
Baixo desempenho do Git ao trabalhar em projetos de grande escala. Esse problema começa a aparecer apenas em aplicativos enormes com mais de um milhão de commits e centenas de desenvolvedores fazendo seu trabalho simultaneamente todos os dias no mesmo repositório. Isso se torna especialmente problemático, pois o Git usa um gráfico acíclico direcionado (DAG) para representar o histórico de um projeto. Com um grande número de commits, qualquer comando que percorra o gráfico pode se tornar lento à medida que o histórico se aprofunda. O desempenho também diminui devido ao número de refs (ou seja, ramificações ou tags, solucionáveis removendo refs que você não precisa mais) e a quantidade de arquivos rastreados (assim como seu peso, mesmo que o problema de arquivos pesados possa ser resolvido usando Git LFS).
Nota: Atualmente, o Facebook tenta resolver problemas de escalabilidade do VCS corrigindo o Mercurial e, provavelmente em breve, isso não será um problema tão grande.
- Maior tempo de construção. Como você terá muito código-fonte em um só lugar, levará muito mais tempo para o seu CI executar tudo para aprovar cada PR.
Revisão da ferramenta
O conjunto de ferramentas para gerenciar monorepos está em constante crescimento e, atualmente, é muito fácil se perder em toda a variedade de sistemas de construção para monorepos. Você sempre pode estar ciente das soluções populares usando este repositório. Mas, por enquanto, vamos dar uma olhada rápida nas ferramentas que são muito usadas hoje em dia com JavaScript:
- Bazel é o sistema de compilação orientado para monorepo do Google. Mais sobre Bazel: awesome-bazel
- O Yarn é uma ferramenta de gerenciamento de dependência JavaScript que oferece suporte a monorepos por meio de espaços de trabalho.
- Lerna é uma ferramenta para gerenciar projetos JavaScript com vários pacotes, construídos no Yarn.
A maioria das ferramentas usa uma abordagem muito semelhante, mas existem algumas nuances.

Vamos mergulhar mais fundo no fluxo de trabalho do Lerna, bem como nas outras ferramentas na Parte 2 deste artigo, pois é um tópico bastante amplo. Por enquanto, vamos apenas ter uma visão geral do que está dentro:
Lerna
Esta ferramenta realmente ajuda a lidar com versões semânticas, configurar fluxo de trabalho de construção, enviar seus pacotes, etc. A idéia principal por trás do Lerna é que seu projeto tenha uma pasta de pacotes, que contém todas as suas partes de código isoladas. E além dos pacotes, você tem um aplicativo principal, que por exemplo pode ficar na pasta src. Quase todas as operações no Lerna funcionam através de uma regra simples - você itera através de todos os seus pacotes e faz algumas ações sobre eles, por exemplo, aumentar a versão do pacote, atualizar a dependência de todos os pacotes, compilar todos os pacotes, etc.
Com Lerna, você tem duas opções de como usar seus pacotes:
- Sem empurrá-los para remoto (NPM)
- Empurrando seus pacotes para remoto
Ao usar a primeira abordagem, você pode usar referências locais para seus pacotes e basicamente não se importa com links simbólicos para resolvê-los.
Mas se você estiver usando a segunda abordagem, será forçado a importar seus pacotes remotamente. (por exemplo, import { something } from @yourcompanyname/packagename;
), o que significa que você sempre obterá a versão remota do seu pacote. Para desenvolvimento local, você terá que criar links simbólicos na raiz de sua pasta para fazer com que o empacotador resolva pacotes locais em vez de usar aqueles que estão dentro de seu node_modules/
. Por isso, antes de iniciar o Webpack ou seu bundler favorito, você deverá iniciar o lerna bootstrap
, que vinculará automaticamente todos os pacotes.
Fio
O Yarn inicialmente é um gerenciador de dependências para pacotes NPM, que não foi criado inicialmente para dar suporte a monorepos. Mas na versão 1.0, os desenvolvedores do Yarn lançaram um recurso chamado Workspaces . Na época do lançamento, não era tão estável, mas depois de um tempo, tornou-se utilizável para projetos de produção.
Workspace é basicamente um pacote, que tem seu próprio package.json e pode ter algumas regras de compilação específicas (por exemplo, um tsconfig.json separado se você usar TypeScript em seus projetos). Na verdade, você pode gerenciar sem o Yarn Workspaces usando o bash e ter exatamente a mesma configuração, mas essa ferramenta ajuda a facilitar o processo de instalação e atualização de dependências por pacote.
De relance, o Yarn com seus espaços de trabalho fornece os seguintes recursos úteis:
- Pasta
node_modules
única na raiz para todos os pacotes. Por exemplo, se você tiverpackages/package_a
epackages/package_b
—com seus própriospackage.json
—todas as dependências serão instaladas apenas na raiz. Essa é uma das diferenças entre como Yarn e Lerna funcionam. - Link simbólico de dependência para permitir o desenvolvimento de pacotes locais.
- Lockfile único para todas as dependências.
- Atualização de dependência focada caso você queira reinstalar dependências para apenas um pacote. Isso pode ser feito usando o sinalizador
-focus
. - Integração com Lerna. Você pode facilmente fazer o Yarn lidar com toda a instalação/links simbólicos e deixar o Lerna cuidar da publicação e do controle de versão. Esta é a configuração mais popular até agora, pois requer menos esforço e é fácil de trabalhar.
Links Úteis:
- Espaços de trabalho de fios
- Como construir o projeto mono-repo TypeScript
Bazel
O Bazel é uma ferramenta de compilação para aplicativos de grande escala, que pode lidar com dependências de vários idiomas e oferecer suporte a muitos idiomas modernos (Java, JS, Go, C++ etc.). Na maioria dos casos, usar o Bazel para aplicativos JS de pequeno e médio porte é um exagero, mas em grande escala pode trazer muitos benefícios devido ao seu desempenho.
Por sua natureza, o Bazel é semelhante ao Make, Gradle, Maven e outras ferramentas que permitem compilações de projetos com base no arquivo que contém uma descrição das regras de compilação e dependências do projeto. O mesmo arquivo no Bazel é chamado BUILD e está localizado dentro da área de trabalho do projeto Bazel. O arquivo BUILD usa seu Starlark, uma linguagem de construção de alto nível legível por humanos que se parece muito com o Python.
Normalmente, você não estará lidando muito com BUILD porque há muito clichê que pode ser facilmente encontrado na web e que já está configurado e pronto para desenvolvimento. Sempre que você deseja construir seu projeto, o Bazel basicamente faz o seguinte:
- Carrega os arquivos BUILD relevantes para o destino.
- Analisa as entradas e suas dependências, aplica as regras de construção especificadas e produz um gráfico de ação.
- Executa as ações de compilação nas entradas até que as saídas de compilação finais sejam produzidas.
Links Úteis:
- JavaScript e Bazel – Documentos para configurar um projeto Bazel para JS do zero.
- Regras JavaScript e TypeScript para Bazel – Boilerplate for JS.
Conclusão
Monorepos são apenas uma ferramenta. Existem muitos argumentos sobre se tem futuro ou não, mas a verdade é que, em alguns casos, essa ferramenta faz seu trabalho e lida com isso de maneira eficiente. Ao longo dos últimos anos, essa ferramenta evoluiu, ganhou muito mais flexibilidade, superou muitos problemas e removeu uma camada de complexidade em termos de configuração.
Ainda há muitos problemas a serem resolvidos, como baixo desempenho do Git, mas esperamos que isso seja resolvido em um futuro próximo.
Se você quiser aprender a criar um pipeline robusto de CI/CD para seu aplicativo, recomendo How to Build an Effective Initial Deployment Pipeline with GitLab CI .