Criar um monorepositório com áreas de trabalho lerna e fios

aprender-e-fios

Nos últimos anos, o conceito de mono-repositórios se estabeleceu com sucesso, pois pode simplificar significativamente o processo de desenvolvimento de projetos de software modulares, como infraestruturas baseadas em microsserviços. As principais vantagens dessa abordagem arquitetônica são óbvias na prática; portanto, proponho criar seu próprio repositório de teste a partir do zero, entendendo simultaneamente as nuances de trabalhar com áreas de trabalho de fios e lerna . Bem, vamos começar!

Considere a estrutura do nosso projeto, que será três bibliotecas localizadas na pasta packages / , bem como package.json no diretório raiz.

├── package.json └── packages ├── app │  ├── index.js │  └── package.json ├── first │  ├── index.js │  └── package.json └── second ├── index.js └── package.json 

Entende-se que temos duas bibliotecas independentes primeiro e segundo , além de uma biblioteca de aplicativos que importará funções das duas primeiras. Por conveniência, todos os três pacotes são colocados no diretório packages . Você pode deixá-los na pasta raiz ou colocá-los em um diretório com qualquer outro nome, mas, para seguir as convenções geralmente aceitas, as colocaremos dessa maneira.

As bibliotecas primeiro e segundo por simplicidade do experimento conterão apenas uma função no index.js , cada uma das quais retornará uma sequência de hello em nome do módulo. Para o primeiro exemplo, ficará assim:

 // packages/first/index.js const first = () => 'Hi from the first module'; module.exports = first; 

No módulo do aplicativo , exibiremos a mensagem Oi do aplicativo no console, além de cumprimentos de dois outros pacotes:

 // packages/app/index.js const first = require('@monorepo/first'); const second = require('@monorepo/second'); const app = () => 'Hi from the app'; const main = () => { console.log(app()); console.log(first()); console.log(second()); }; main(); module.exports = { app, main }; 

Para que o primeiro e o segundo estejam disponíveis no aplicativo , os denotamos como dependências nas dependências .

Além disso, para cada biblioteca, adicionamos o prefixo @ monorepo / no nome do valor na frente do nome principal do pacote ao package.json local.

 // packages/app/package.json { "name": "@monorepo/app", "version": "1.0.0", "main": "index.js", "license": "MIT", "dependencies": { "@monorepo/first": "^1.0.0", "@monorepo/second": "^1.0.0" } } 

Por que preciso de um prefixo com um ícone de cachorro na frente do nome do pacote npm (@ monorepo /)?
A adição de um prefixo é opcional, mas esse é exatamente o tipo de convenção de nomenclatura de pacotes a que muitos monorepositórios aderem: babel ,
interface do usuário material , angular e outros. O fato é que cada usuário ou organização tem seu próprio escopo no site npm , portanto, há uma garantia de que todos os módulos com o @ somescope / postfix são criados pela equipe do somescope e não pelos atacantes. Além disso, torna-se possível chamar nomes de módulos que já foram utilizados. Por exemplo, você não pode simplesmente criar e criar seu próprio módulo de utilitários , porque essa biblioteca já existe . No entanto, adicionando o postfix @ myscopename / , podemos obter nossos utils ( @ myscopename / utils ) com blackjack e jovens senhoras.

Um análogo da vida real para o nosso projeto de teste pode ser várias bibliotecas para trabalhar com dados, ferramentas de validação, análise ou apenas um conjunto de componentes da interface do usuário. Se assumirmos que vamos desenvolver um aplicativo Web e móvel (por exemplo, usando React e React Native, respectivamente), e tivermos parte da lógica reutilizada, pode valer a pena colocá-lo em componentes separados, para uso posterior em outros projetos. Adicione a isso o servidor no Node.js e você terá um caso muito real da vida.

Áreas de trabalho de fios


O toque final antes de criar um repositório mono completo será o design do package.json na raiz do nosso repositório. Preste atenção à propriedade de áreas de trabalho - especificamos o valor de packages / * , que significa "todas as subchaves na pasta packages ". No nosso caso, este é um aplicativo , primeiro , segundo .

 // package.json { "name": "monorepo", "version": "1.0.0", "main": "packages/app/index.js", "license": "MIT", "private": true, "workspaces": [ "packages/*" ] } 

Além disso, “private”: true deve ser especificado em package.json , pois os espaços de trabalho estão disponíveis apenas em projetos privados.

Para que tudo decole, execute o comando yarn (análogo ao yarn install ou npm install ) no diretório raiz. Como as dependências existentes no módulo de aplicativo são definidas como áreas de trabalho no pacote raiz.json , na verdade, não faremos download de nada do npm-registry , mas simplesmente vincularemos ("vincularemos") nossos pacotes.

 yarn 

imagem

Agora podemos executar o comando do nó. no diretório raiz que executará o script no arquivo packages / app / index.js .

 node . 

imagem

Vamos ver como isso funciona. Ao chamar yarn , criamos links simbólicos em node_modules para nossos diretórios na pasta packages .

imagem

Devido a esse relacionamento nas dependências, tivemos uma grande vantagem - agora, ao mudar no primeiro e no segundo módulos, nosso aplicativo receberá a versão atual desses pacotes sem reconstruir. Na prática, é muito conveniente, porque podemos conduzir o desenvolvimento local de pacotes, ainda os definindo como dependências de terceiros (que acabam se tornando).

A próxima vantagem importante que você pode obter ao trabalhar com áreas de trabalho de fios é a organização do armazenamento de dependências de terceiros.

Saiba mais sobre o armazenamento de dependências no nível superior.
Suponha que desejássemos usar a biblioteca lodash no primeiro e no segundo . Ao executar o comando yarn add lodash a partir dos diretórios apropriados, receberemos uma atualização para o package.json local - a versão atual do pacote aparecerá em dependências .
 "dependencies": { "lodash": "^4.17.11" } 

Quanto ao próprio pacote lodash - fisicamente, a biblioteca será instalada no node_modules no nível raiz uma vez.
Se a versão exigida do pacote externo (no nosso caso, lodash ) for diferente para o primeiro e o segundo (por exemplo, primeiro você precisará do lodash v3.0.0 e no segundo v4.0.0 ), um pacote com uma versão inferior ( 3.0.0 ) chegará ao nó node_modules raiz , e a versão lodash para o segundo módulo será armazenada em pacotes locais / second / node_modules .
Além das vantagens, essa abordagem pode ter pequenas desvantagens, que o fio permite ignorar com a ajuda de sinalizadores adicionais. Você pode ler mais sobre essas nuances na documentação oficial .

Adicionar Lerna


O primeiro passo para trabalhar com o lerna é instalar o pacote. Geralmente eles executam uma instalação global ( yarn global add lerna ou npm i -g lerna ), mas se você não tiver certeza se deseja usar esta biblioteca, poderá usar a chamada usando npx .

Inicializaremos o lerna a partir do diretório raiz:

 lerna init 

imagem

De fato, realizamos várias ações ao mesmo tempo com a ajuda de um comando: criamos um repositório git (se não tivesse sido inicializado antes disso), criamos um arquivo lerna.json e atualizamos nosso pacote raiz.json .

Agora, no recém-criado arquivo lerna.json, adicione duas linhas - “npmClient”: “yarn” e “useWorkspaces”: true . A última linha diz que já usamos áreas de trabalho de fios e não há necessidade de criar a pasta app / node_modules com links simbólicos para o primeiro e o segundo .

 // lerna.json { "npmClient": "yarn", "packages": [ "packages/*" ], "version": "1.0.0", "useWorkspaces": true } 

Testes com Lerna


Para mostrar a conveniência de trabalhar com o lerna, adicione testes para nossas bibliotecas.
No diretório raiz, instalamos o pacote para testing - jest . Execute o comando:

 yarn add -DW jest 

Por que preciso do sinalizador -DW?
O sinalizador -D (- dev) é necessário para que o pacote jest seja instalado como uma dependência do desenvolvedor, e o sinalizador -W (- ignore-workspace-root-check) permite a instalação no nível raiz (o que precisamos).

O próximo passo é adicionar um arquivo de teste ao nosso pacote. Para a conveniência do nosso exemplo, faremos todos os testes semelhantes. Para o primeiro exemplo, o arquivo de teste terá a seguinte aparência:

 // packages/first/test.js const first = require('.'); describe('first', () => { it('should return correct message', () => { const result = first(); expect(result).toBe('Hi from the first module'); }); }); 

Também precisamos adicionar um script para executar testes no package.json de cada uma de nossas bibliotecas:

 // packages/*/package.json ... "scripts": { "test": "../../node_modules/.bin/jest --colors" }, ... 

O toque final será atualizar o pacote root.json . Adicione um script de teste que chamará lerna run test --stream . O parâmetro após lerna run define o comando que será chamado em cada um de nossos pacotes a partir da pasta packages / , e o sinalizador --stream nos permitirá ver a saída dos resultados no terminal.

Como resultado, o package.json do diretório raiz ficará assim:

 // package.json { "name": "monorepo", "version": "1.0.0", "main": "packages/app/index.js", "license": "MIT", "private": true, "workspaces": [ "packages/*" ], "scripts": { "test": "lerna run test --stream" }, "devDependencies": { "jest": "^24.7.1", "lerna": "^3.13.2" } } 

Agora, para executar os testes, precisamos apenas executar o comando a partir da raiz do nosso projeto:

 yarn test 

imagem

Atualização de versão com Lerna


A próxima tarefa popular, com a qual o lerna pode lidar com a qualidade, será atualizar as versões dos pacotes. Imagine que, após a implementação dos testes, decidimos atualizar nossas bibliotecas da 1.0.0 para a 2.0.0. Para fazer isso, basta adicionar a linha “update: version”: “lerna version --no-push” ao campo de scripts do root package.json e, em seguida, execute yarn update: version no diretório raiz. O sinalizador --no-push é adicionado para que, após a atualização da versão, as alterações não sejam enviadas para o repositório remoto, o que o lerna faz por padrão (sem esse sinalizador).

Como resultado, nosso root package.json ficará assim:

 // package.json { "name": "monorepo", "version": "1.0.0", "main": "packages/app/index.js", "license": "MIT", "private": true, "workspaces": [ "packages/*" ], "scripts": { "test": "lerna run test --stream", "update:version": "lerna version --no-push" }, "devDependencies": { "jest": "^24.7.1", "lerna": "^3.13.2" } } 

Execute o script de atualização da versão:

 yarn update:version 

Em seguida, seremos solicitados a selecionar a versão para a qual queremos mudar:

imagem

Ao clicar em Enter , obtemos uma lista de pacotes nos quais a versão é atualizada.

imagem

Confirmamos a atualização digitando y e recebemos uma mensagem sobre a atualização bem-sucedida.

imagem

Se tentarmos executar o comando git status , não receberemos a mensagem para confirmar, trabalhando em uma árvore limpa , porque A versão lerna não apenas atualiza as versões dos pacotes, mas também cria um commit e uma tag git indicando a nova versão (v2.0.0 no nosso caso).

Recursos de trabalho com a equipe da versão lerna
Se você adicionar a linha "version": "lerna version --no-push" em vez de "update: version": "lerna version --no-push" no campo de scripts do root package.json , é possível que encontre comportamentos inesperados e console vermelho. O fato é que o npm-scripts chama o comando version (script reservado) imediatamente após atualizar a versão do pacote, o que leva a uma chamada recursiva para a versão lerna . Para evitar essa situação, basta atribuir um nome diferente ao script, por exemplo, update: version , como foi feito em nosso exemplo.

Conclusão


Esses exemplos mostram um centésimo de todas as possibilidades que a lerna possui em conjunto com os espaços de trabalho de fios . Infelizmente, até agora não encontrei instruções detalhadas para trabalhar com monorepositórios em russo, para que possamos assumir que o começo foi feito!

Link para o repositório do projeto de teste.

Source: https://habr.com/ru/post/pt448766/


All Articles