Criando Monorepo com Lerna e Yarn Workspaces

Image for post
Image for post

Conforme os aplicativos crescem, você inevitavelmente chegará a um ponto em que deseja escrever componentes reutilizáveis ​​compartilhados que podem ser usados ​​em qualquer lugar. Historicamente, temos repositórios separados para cada pacote. No entanto, isso se torna um problema por alguns motivos:

  • Não se ajusta bem. Antes que você perceba, você tem dezenas de repositórios de pacotes diferentes repetindo o mesmo processo de construção, teste e lançamento.
  • Promove o agrupamento de componentes desnecessários. Precisamos criar um novo repo para este botão? Vamos colocá-lo junto com este outro pacote. Agora aumentamos o tamanho do pacote para algo que 95% dos consumidores não usarão.
  • Isso torna a atualização difícil. Se você atualizar um componente básico, agora terá que atualizar seus consumidores, os consumidores de seu consumidor, etc. Esse problema fica pior conforme você escala.

Para tornar nossos aplicativos o mais eficientes, precisamos ter pacotes pequenos. Isso significa que devemos incluir apenas o código que estamos usando em nosso pacote.

Junto com isso, ao desenvolver bibliotecas de componentes compartilhados, queremos ter semver sobre pedaços individuais ao invés do pacote inteiro. Isso evita cenários onde:

  1. O consumidor A só precisa do pacote para um componente e está ligado a v1.
  2. O consumidor B usa o pacote para todos os componentes. Eles ajudam a criar e modificar outros componentes no pacote e ele cresceu muito. Agora está ligado a v8.
  3. O consumidor A agora precisa de uma correção de bug para o componente que usa. Eles precisam atualizar para v8.

Lerna

Lerna e Yarn Workspaces nos dão a capacidade de construir bibliotecas e aplicativos em um único repo (também conhecido como Monorepo ) sem nos forçar a publicar no NPM até que estejamos prontos. Isso torna mais rápido a iteração local ao construir componentes que dependem uns dos outros.

Lerna também fornece comandos de alto nível para otimizar o gerenciamento de vários pacotes. Por exemplo, com um comando Lerna, você pode iterar por todos os pacotes, executando uma série de operações (como linting, teste e build) em cada pacote.

Vários grandes projetos de JavaScript usam monorepos, incluindo: Babel , React , Jest , Vue , Angular e mais.

Monorepo

Neste guia, iremos utilizar:

  • 🐉 Lerna — gerente do Monorepo
  • 📦 Yarn Workspaces — gerenciamento lógico de vários pacotes
  • 🚀 React — biblioteca JavaScript para interfaces de usuário
  • 💅 styled-components — CSS em JS com elegância
  • 🛠 Babel — Compila JavaScript de última geração
  • 📖 Storybook — Ambiente de componentes de UI
  • 🃏 Jest — Teste de Unidade / Snapshot

Você pode acompanhar ou visualizar o repositório final aqui .

Ok, vamos começar! Primeiro, vamos criar um novo projeto e configurar o Lerna.

Isso cria um arquivo package.json para seu projeto.

Você notará que um arquivo lerna.json também foi criado, bem como uma pasta /packages que conterá nossas bibliotecas. Vamos agora modificar nosso arquivo lerna.json para usar o Yarn Workspaces. Usaremos controle de versão independente para que possamos aplicar semver corretamente para cada pacote.

Também precisaremos modificar nosso package.json para definir onde Yarn Workspaces estão localizados.

Babel

A seguir, vamos adicionar todas as dependências de que precisaremos para o Babel 7.

Usando -W instrui Yarn a instalar as dependências fornecidas para todo o espaço de trabalho. Essas dependências geralmente são compartilhadas entre todos os pacotes.

Após yarn ser executado, você terá uma pasta /node_modules. Não queremos adicionar ao Git nenhum desses pacotes, então vamos adicionar um .gitignore.

Ok, de volta ao Babel. Para definir a configuração global do Babel, precisaremos de um arquivo babel.config.js na raiz do repositório.

Este arquivo diz ao Babel como compilar nossos pacotes. Agora, vamos criar um script para executar o Babel. Vamos adicionar isso ao nosso package.json.

Vamos dissecar este comando. lerna exec irá pegar qualquer comando e executá-lo em todos os pacotes diferentes. Este comando instrui o Babel a executar em paralelo sobre cada pacote, puxando da pasta /src e compilando na pasta /lib. Não queremos incluir quaisquer testes ou histórias (que veremos mais tarde) no resultado compilado.

Usar --root-mode upward é o molho especial para usar com Yarn Workspaces. Isso diz ao Babel que node_modulesestão localizados na raiz ao invés de aninhados dentro de cada um dos pacotes individuais. Isso evita que cada pacote tenha o mesmo node_modules e os extrai até a raiz. Mais a frente, estaremos utilizando uma abordagem semelhante para testes.

React

Concluímos a infraestrutura para um Monorepo. Vamos criar alguns pacotes para ele consumir. Usaremos React e styled-components para desenvolver nossos componentes de UI, então vamos instalá-los primeiro.

Então, dentro de /packages vamos criar uma pasta chamada /button e configurar nosso primeiro pacote.

Este arquivo informa aos consumidores que module ficará dentro da pasta /src e que o resultado executado pelo Babel ( main) ficará dentro de /lib. Este será o principal ponto de entrada do pacote. Listar o peerDependencies ajuda a garantir que os consumidores estejam incluindo os pacotes corretos.

Também queremos vincular nossas dependências raiz ao nosso pacote recém-criado. Vamos criar um script para fazer isso dentro do nosso package.json.

Agora podemos simplesmente executar yarn bootstrap para instalar e vincular todas as dependências.

Ok, agora vamos criar o nosso primeiro componente: <Button />.

Vamos testar se o Babel está configurado corretamente. Devemos ser capazes de executar yarn build e ver uma pasta /lib criada para nosso novo pacote.

Storybook

O Storybook nos fornece um playground de UI interativo para nossos componentes. Isso torna o desenvolvimento de components um charme. Vamos configurar o Storybook para visualizar nosso componente Button recém-criado .

Também queremos configurar o Storybook para que ele saiba onde encontrar nossas histórias.

Então, podemos criar nossa primeira história para o botão recém-criado dentro /packages/button/src.

Finalmente, vamos adicionar um script para iniciar o Storybook.

Então, podemos usar yarn devpara ver o nosso botão 🎉

Testes

Antes de prosseguirmos, vamos configurar nosso ambiente de testes e criar um teste simples para nosso botão. Usaremos Jest para testes de unidade. Ele selecionará automaticamente todos os arquivos que terminam com .spec.js.

A seguir, vamos definir nossa configuração do Jest no diretório raiz.

Você pode modificar isso como quiser. Também queremos adicionar alguns scripts ao nosso package.json.

Finalmente, vamos criar nosso primeiro teste junto com nosso componente de botão. Utilizaremos o teste de snapshot, pois este é um componente puramente de apresentação.

Agora podemos executar nosso teste via yarn unit.

Múltiplos Pacotes

O principal motivo da estrutura de Monorepo é oferecer suporte a vários pacotes. Isso nos permite ter um único processo de lint, build, teste e lançamento para todos os pacotes. Vamos criar um pacote de entrada e adicionar um novo componente.

E:

Ok, agora temos um componente Input. Vamos correr yarn bootstrap novamente para ligar nossos pacotes e criar uma nova história.

Nossa instância do Storybook ainda deve estar em execução via yarn dev, mas se não, execute novamente o comando. Podemos observar que nosso componente foi renderizado corretamente.

Finalmente, vamos garantir que o Babel funcione conforme o esperado para vários pacotes executando yarn build.

Ambos os pacotes foram compilados com sucesso 🎉

E os testes? Vamos criar outro teste para o componente Input.

Então podemos executar novamente yarn unit.

Lançamento (Release)

Nota : Você precisa fazer o commit e push para o seu repositório antes de lançar uma nova versão. Se você ainda não fez isso, faça agora.

Vamos lançar a primeira versão de nossos pacotes. Podemos usar npx lerna changed para ver quais pacotes foram alterados. Você também pode usar npx lerna diff para ver especificamente quais linhas foram alteradas.

Nota: lerna pode ser instalado globalmente para remover a necessidade de uso npx. Você também pode adicionar scripts em seu, package.json pois lerna é uma devDependency.

Podemos ver que ele reconhece o Button e o Input. Agora, vamos simular a liberação deles com npx lerna version.

Parabéns! 🎉 As tags foram enviadas ao GitHub. Para lançar nossos pacotes para o npm, você pode usar o npx lerna publish.

Linting & Formatting

Deve fácil para os outros contribuírem. É por isso que toda a formatação e linting são tratados com:

Graças aos ganchos pré-commit do git com husky , temos a garantia de que todo o código está formatado corretamente antes de ser enviado ao GitHub. O lint e a formatação adequadas economizaram tempo e dores de cabeça ao revisar as pull-requests. Recomendamos que você desenvolva e chegue a um acordo sobre regras de linting e formatação para reduzir a quantidade de comentários de revisão de código desse tipo. Você pode ver nossas regras aqui .

Conclusão

Parabéns, agora você tem um monorepo completo! Se você quer ir ainda mais longe, aqui estão algumas outras idéias:

Créditos

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store