Yarn: Espaços de trabalho: arquivo de bloqueio por espaço de trabalho

Criado em 28 fev. 2018  ·  54Comentários  ·  Fonte: yarnpkg/yarn

Você quer solicitar um recurso ou relatar um bug ?
recurso

Qual é o comportamento atual?
O uso de espaços de trabalho yarn para um monorepo que inclui um módulo de nó de nível superior cria apenas um único yarn.lock na raiz do monorepo, sem yarn.lock que seja específico para o módulo de nó de nível superior.

Qual é o comportamento esperado?
Quero usar espaços de trabalho yarn para gerenciar um monorepo que inclui aplicativos (módulos de nó de nível superior) e bibliotecas. Mas ter apenas um único arquivo yarn.lock na raiz do monorepo me impede de empacotar meu aplicativo em uma imagem docker, por exemplo. Eu adoraria uma maneira de obter um arquivo yarn.lock para determinados espaços de trabalho que precisam ter um seu próprio, porque esse espaço de trabalho pode ser usado posteriormente fora do monorepo.

Um exemplo:
Se eu tiver um monorepo com 2 espaços de trabalho: workspace-a e workspace-b . workspace-a usa alguns dos módulos exportados de workspace-b . Se eu quiser empacotar workspace-a em uma imagem docker (ou qualquer outra forma de empacotar esse espaço de trabalho sozinho, sem todo o monorepo), não tenho um yarn.lock para ele. Isso significa que quando movo os arquivos de workspace-a para um ambiente diferente do monorepo, perderei um arquivo yarn.lock e, ao instalar dependências, perderei todos os vantagens de um arquivo de bloqueio (sabendo que estou instalando as mesmas dependências usadas no desenvolvimento).

Estou bastante surpreso por não ter conseguido encontrar um problema sobre isso. Eu sou o único que quer trabalhar com monorepos dessa forma? Talvez eu esteja faltando alguma coisa? Minha solução alternativa atual é usar lerna sem içar, então terei um arquivo de bloqueio por pacote.
O recurso nohoist lançado recentemente também não parece ajudar (embora eu esperasse), pois não cria um yarn.lock por área de trabalho.
Este problema está de alguma forma relacionado a este comentário sobre outro assunto. Pensei que pode merecer um problema próprio.

Mencione seu node.js, yarn e versão do sistema operacional.
nó 8.9.3, fio 1.5.1, OSX 10.13.3

cat-feature triaged

Comentários muito úteis

Portanto, para nós, não queremos empacotar todo o monorepo no contêiner docker resultante. Estamos usando o docker na produção e essas imagens devem ser o mais claras possível. Nosso repo mono é bastante grande e contém vários microsserviços que compartilham código entre eles usando pacotes de biblioteca (e algumas das bibliotecas são relevantes para alguns dos microsserviços, mas não todos). Portanto, quando empacotamos um microsserviço, queremos que a imagem contenha os arquivos desse microsserviço e quaisquer outras dependências como dependências adequadas - baixado de nosso registro privado e criado para o arco da imagem docker.

Portanto, acho que a principal consideração aqui é manter as imagens do docker o mais leves possível, e empacotar todo o monorepo não atende às nossas necessidades. Além disso, quando executamos "yarn" dentro da imagem do microsserviço, não queremos ter links simbólicos lá, apenas uma dependência normal.

A solução aqui não precisa ser criar um arquivo yarn.lock por espaço de trabalho, também pode ser um comando yarn, que ajuda no processo de empacotamento de um determinado espaço de trabalho, gerando um arquivo yarn.lock para um espaço de trabalho sob demanda, etc. ..

Espero que tenha ajudado a esclarecer o caso de uso. 🍻

Todos 54 comentários

baseado no yarn blog :

Quando você publica um pacote que contém um yarn.lock, nenhum usuário dessa biblioteca será afetado por ele. Quando você instala dependências em seu aplicativo ou biblioteca, apenas seu próprio arquivo yarn.lock é respeitado. Arquivos de bloqueio em suas dependências serão ignorados.

não parece necessário empacotar yarn.lock ao publicar o pacote individual ... é mais um artefato de desenvolvimento para todo o repo, portanto, não há necessidade de colocá-lo em cada pacote.

@connectdotz pode não ser necessário para uma biblioteca ou pacote publicado, mas para construir um contêiner do docker, você vai querer implantar em algum lugar onde ele definitivamente esteja.

claro ... mas o contêiner do docker de desenvolvimento não teria apenas o repo inteiro e, portanto, o yarn.lock de qualquer maneira? Posso ver que usamos contêineres docker para testar nosso projeto monorepo para diferentes sistemas operacionais ou plataformas, caso em que apenas implantamos o repo inteiro e seu yarn.lock. Você pode me dar um exemplo de caso de uso de que você precisa para implantar pacotes individuais do projeto monorepo em contêineres docker durante o ciclo de desenvolvimento, para que eu possa obter um entendimento mais concreto ...

Portanto, para nós, não queremos empacotar todo o monorepo no contêiner docker resultante. Estamos usando o docker na produção e essas imagens devem ser o mais claras possível. Nosso repo mono é bastante grande e contém vários microsserviços que compartilham código entre eles usando pacotes de biblioteca (e algumas das bibliotecas são relevantes para alguns dos microsserviços, mas não todos). Portanto, quando empacotamos um microsserviço, queremos que a imagem contenha os arquivos desse microsserviço e quaisquer outras dependências como dependências adequadas - baixado de nosso registro privado e criado para o arco da imagem docker.

Portanto, acho que a principal consideração aqui é manter as imagens do docker o mais leves possível, e empacotar todo o monorepo não atende às nossas necessidades. Além disso, quando executamos "yarn" dentro da imagem do microsserviço, não queremos ter links simbólicos lá, apenas uma dependência normal.

A solução aqui não precisa ser criar um arquivo yarn.lock por espaço de trabalho, também pode ser um comando yarn, que ajuda no processo de empacotamento de um determinado espaço de trabalho, gerando um arquivo yarn.lock para um espaço de trabalho sob demanda, etc. ..

Espero que tenha ajudado a esclarecer o caso de uso. 🍻

@netanelgilad obrigado pelos detalhes, ajuda a esclarecer que seu caso de uso é mais sobre a publicação de pacotes individuais, para produção ou desenvolvimento, em contêineres docker. Junte-se à discussão em # 4521 para que possamos começar a consolidá-los.

Embora eu possa ver o uso de arquivos de bloqueio individuais, eles não são necessários. Se você executar o docker a partir da raiz do repo com o sinalizador -f apontando para os arquivos individuais, você terá todo o repo como contexto e poderá copiar no package.json e no yarn.lock da raiz.

Você só precisa do package.json para os pacotes que criará na imagem e o yarn só instalará os pacotes para os arquivos package.json que você copiou, mesmo que o yarn.lock inclua muito mais.

EDIT: Com isso dito. faz com que o docker cache não seja usado para mudanças de pacote em qualquer pacote mesmo que não esteja incluído na compilação

4206 está relacionado / duplicado e o caso de uso descrito ali é exatamente o problema que estamos enfrentando:

Digamos que temos dez pacotes diferentes. Queremos que todos eles vivam em seu próprio repositório para que as pessoas possam trabalhar neles de forma independente, se quiserem, mas também queremos ser capazes de vinculá-los se necessário. Para fazer isso, temos um mega repositório com um submódulo para cada pacote e um package.json que faz referência a cada um desses submódulos como um espaço de trabalho.

Tenho problemas semelhantes com espaços de trabalho. Meu projeto é um web-app que depende de muitos pacotes locais:

web-app/
|--node_modules/
|--packages/
|  |--controls/
|  |  |--src/
|  |  |--package.json
|  |--utils/
|     |--src/
|     |--package.json
|--src/
|--package.json
|--yarn.lock

Os pacotes de espaço de trabalho controls e utils não são publicados e são usados ​​por caminhos. O problema é que preciso liberar o pacote controls ( yarn pack ) e não quero construí-lo / testá-lo sozinho. Isso significa que quero fazer yarn install dentro de web-app/packages/constols/ . Com os espaços de trabalho, ele usará o arquivo de nível superior web-app/yarn.lock junto com o nível superior web-app/node-modules/ . Portanto, ele instala todos os pacotes em vez de um subconjunto, especificado em web-app/packages/controls/package.json . Mas preciso verificar se meu pacote tem todas as dependências necessárias em seu próprio package.json e não funciona com a sorte de preencher as dependências ausentes de outros espaços de trabalho.

Existem 2 soluções possíveis:

  1. Se não for root, use yarn.lock do root, mas instale apenas os pacotes especificados no local package.json .
  2. Não procure configurações de nível superior, mas .yarnrc/.npmrc .

Também lutando com isso. Temos um projeto Angular CLI junto com nossa API, então eles estão no mesmo repositório e estão tentando enviar o front-end para o Heroku.

Estamos usando um buildpack que diz ao Heroku para saltar para o repositório de front-end primeiro: https://github.com/lstoll/heroku-buildpack-monorepo

O problema é que não há yarn.lock dentro desse pacote nohoist , então o Heroku apenas instala com o npm e acabamos com todos os novos pacotes em vez dos bloqueados

Você pode apenas usar o arquivo yarn.lock global com os pacotes individuais. Recentemente, abordei meu Dockerfile assim:

WORKDIR /app
ENV NODE_ENV=production

ADD yarn.lock /app/
ADD package.json /app/

# Only copy the packages that I need
ADD packages/my-first-package /app/packages/my-first-package
ADD packages/my-second-package /app/packages/my-second-package

RUN cd /app && yarn install --frozen-lockfile

Isso instalará apenas dependências que estão realmente em uso pelos dois pacotes que copiei e não por mais ninguém.

Eu tenho um processo de construção em que primeiro gostaria de criar um artefato de lançamento de um pacote e, em seguida, não ter nenhuma de suas dependências instaladas. Isso é bastante fácil com a compilação de vários estágios do Docker

  1. Adicione apenas yarn.lock, package.json e o pacote de IU ao docker
  2. execute yarn install --frozen-lockfile
  3. execute o processo de construção
  4. inicie um novo estágio e adicione yarn.lock, package.json e os pacotes de tempo de execução / pastas de espaço de trabalho necessários
  5. Faça um COPY --from=<stage> para o artefato construído
  6. execute yarn install --frozen-lockfile e exponha um comando RUN .

E você acabará com um pequeno contêiner que contém apenas as dependências especificadas em seu arquivo yarn.lock e necessárias na produção.

@ johannes-scharlach
Acabei usando um método quase idêntico ao que você está descrevendo. compilar vários estágios é uma boa dica :).

@connectdotz Acho que, do meu lado, esse problema pode ser

Passei os últimos dias tentando converter nosso monorepo primeiro para os espaços de trabalho Lerna e depois para Yarn. O Yarn funcionou geralmente de forma mais confiável e está muito perto do que precisamos, especialmente com a recente introdução de yarn workspaces run <script> e outras coisas legais como wsrun .

No entanto, o único yarn.lock é um ponto problemático:

  • Não tenho certeza de como migrar corretamente nossos arquivos de bloqueio existentes para um único, consulte https://github.com/yarnpkg/yarn/issues/6563. Temos dezenas de milhares de linhas lá e adicionar pacotes existentes como espaços de trabalho introduziu muitos problemas sutis de versão.
  • Instalar dependências apenas em pacotes específicos ("de-hoisting" / "vendoring") A compilação dockerizada não é bem suportada, veja acima (https://github.com/yarnpkg/yarn/issues/5428#issuecomment-403722271) ou https: //github.com/yarnpkg/yarn/issues/4521.

O que você pensaria sobre os espaços de trabalho do Yarn serem apenas um pequeno núcleo - uma declaração de pacotes sem qualquer funcionalidade específica. Por exemplo:

  • Se você quisesse executar algum script em seus espaços de trabalho, faria yarn workspaces run <script> .
  • Se você quisesse um único arquivo de bloqueio e içamento (os dois estão necessariamente ligados?), Esta seria sua raiz package.json :
    json "workspaces": { "packages": ["packages/*"], "hoistDependencies": true }
  • Se você quiser migrar seus arquivos de bloqueio atuais para uma estrutura suspensa, você executará yarn workspaces hoist-dependencies .

Etc. Estes são apenas exemplos e, na prática, alguns recursos provavelmente seriam opt-out em vez de opt-in (por exemplo, as pessoas esperam um único yarn.lock e içamento até agora), mas a ideia geral é que os espaços de trabalho seriam ser uma base leve para tarefas abrangentes de repositório.

O que você acha?

Acredito que o problema que esta solicitação de recurso está resolvendo é o mesmo que em # 4521. Um comando para fazer essencialmente o que @ johannes-scharlach descreve certamente seria mais viável do que um arquivo de bloqueio por área de trabalho.

Também há um RFC aberto agora para espaços de trabalho aninhados , que parece semelhante a esta solicitação de recurso, embora eu acredite que esteja resolvendo um problema diferente.

O que você acha dos espaços de trabalho do Yarn serem apenas um pequeno núcleo - uma declaração de pacotes sem nenhuma funcionalidade específica

Os espaços de trabalho não mudarão drasticamente, acho que estamos satisfeitos com sua interface atual.

Se você quisesse executar algum script em seus espaços de trabalho, faria yarn workspaces run <script>

Isso já é possível (v1.10, # 6244).

Se você quiser migrar seus arquivos de bloqueio atuais para uma estrutura suspensa, execute yarn workspaces hoist-dependencies .

Já que não mudaremos a interface do espaço de trabalho, seria o oposto ( dehoistDependencies ).

O que não gosto nisso é que toma um comportamento técnico (içamento) e tenta transformá-lo em um comportamento semântico. Você deve se concentrar na história do usuário e, em seguida, descobrir a implementação, e não o contrário.

Nesse caso, acho que seu caso de uso ("Instalando dependências em pacotes específicos apenas") seria melhor resolvido estendendo yarn --focus .

Acho que a questão central é se o içamento e um único arquivo yarn.lock são estritamente necessários para os espaços de trabalho. Quero dizer, é o que realmente os define ou é "apenas" a primeira característica que eles obtiveram historicamente?

Por exemplo, em nosso caso de uso, o melhor comportamento hipotético dos espaços de trabalho seria:

  • Levante node_modules no momento do desenvolvimento para obter eficiência.
  • Mantenha os arquivos yarn.lock para construção (nós construímos pacotes específicos no Docker, algo que outras pessoas mencionaram neste tópico também) e também para que os pacotes possam bloquear suas versões específicas. Veja também # 6563.
  • Execute scripts via yarn workspaces run <script> mesmo se você não precisar (ou evitar) içamento.

O içamento pode ser desabilitado com nohoist , run pode ser "desabilitado" apenas por não usar o comando, mas não é possível "desabilitar" o único arquivo yarn.lock , e eu ' Não tenho certeza se é um recurso tão importante que não pode ser desativado ou se ainda não foi solicitado o suficiente :)

Acho que a melhor maneira de resolver isso seria yarn install --app-mode package@version

Dessa forma, você pode simplesmente copiar os arquivos de bloqueio do espaço de trabalho ao publicar seu aplicativo em uma determinada versão e instalar em app-mode respeitará o arquivo de bloqueio incluído.

O Yarn não precisa instalar o lockfile inteiro; ele deve ser capaz de extrair facilmente apenas a parte do gráfico de dependência relevante para aquele pacote.

Na verdade, isso pode ser bastante fácil de fazer manualmente mesmo agora:

  • baixe o zip do pacote diretamente do registro (yarn não tem equivalente, npm tem: npm pack package@version )
  • extraia o gzip em node_modules / package
  • cd em node_modules / package
  • execute yarn install --production a partir daí (respeitará o arquivo de bloqueio incluído)

editar: infelizmente, está tudo errado, pois o arquivo de bloqueio do espaço de trabalho não inclui as versões dos pacotes dentro do espaço de trabalho, que podem ser dependências do pacote do aplicativo. Seria necessário algo mais envolvido do que copiar ao criar arquivos de bloqueio de aplicativos a partir de arquivos de bloqueio do espaço de trabalho.

Não tenho certeza se lockfiles separados é a resposta, mas tenho um problema semelhante. Eu tenho um monorepo configurado com uma CLI e um back-end. A CLI requer alguns pacotes que são específicos da plataforma e funcionam apenas em máquinas desktop com uma configuração particular. Por outro lado, preciso ser capaz de construir minha api em uma imagem docker, o que é fundamentalmente impossível na implementação atual de espaços de trabalho.

Caso de uso muito semelhante ao @samuela aqui! Este seria extremamente útil!

Meu caso de uso pode parecer ridículo em comparação com os outros "reais". Mas eu tenho um monorepo para alguns utilitários - neste caso, os ganchos de reação - dentro de packages/* .

Eu tenho um segundo espaço de trabalho próximo a packages/* , e esse é local/* . Na verdade, isso está no gitignore, e a ideia é que os desenvolvedores da empresa possam fazer o que quiserem lá, por exemplo, colocar create-react-app apps lá e testar os ganchos durante o desenvolvimento.

Agora, embora os pacotes local/* estejam no gitignore, a raiz yarn.lock está simplesmente inchada e poluída - e registrada no git - por causa dos espaços de trabalho locais.

O que eu desejaria é uma maneira de especificar que alguns espaços de trabalho devem usar alguns arquivos de bloqueio específicos, por exemplo, algum mapeamento como este:

  "workspaces": {
    "packages": [
      "packages/*",
      "local/*"
    ],
    "lockfiles": {
      "local/*": "./local.yarn.lock"
    }
  }

Ou até mesmo uma maneira de especificar "não coloque nada _este_ espaço de trabalho no arquivo de bloqueio".

Mas sim, o meu não é um caso de uso sério em primeiro lugar :)

Não tenho certeza se lockfiles separados é a resposta, mas tenho um problema semelhante. Eu tenho um monorepo configurado com uma CLI e um back-end. A CLI requer alguns pacotes que são específicos da plataforma e funcionam apenas em máquinas desktop com uma configuração particular. Por outro lado, preciso ser capaz de construir minha api em uma imagem docker, o que é fundamentalmente impossível na implementação atual de espaços de trabalho.

Você acertou em cheio - a meu ver, um dos principais benefícios do arquivo yarn.lock é a criação de compilações de produção congeladas! Os criadores do Yarn esqueceram disso?

Outro argumento para resolver o problema do arquivo de bloqueio único é a propriedade do código. Se você tem um monorepo que usa algo como o recurso CODEOWNERS do GitHub, não é possível conceder a propriedade completa de um pacote a um grupo de desenvolvedores. Isso porque, se eles instalarem algo em seu próprio espaço de trabalho, eles invariavelmente alterarão o arquivo de bloqueio de nível raiz. Essa alteração precisará ser aprovada pelos proprietários do código do lockfile, que, dado um monorepo de escala suficiente, será diferente dos proprietários do espaço de trabalho original.

Mais uma razão para ter a opção de gerar arquivos de bloqueio por espaço de trabalho: o Google App Engine se recusa a iniciar um serviço Node sem um arquivo de bloqueio (NPM / Yarn). Isso é excelente devops da parte deles, mas uma dor para nós. Até agora, as opções que temos são:

  • Implante tudo com env vars indicando qual serviço queremos dizer e modifique nosso yarn start (o único ponto de entrada com suporte) para ramificar com base em env vars
  • Faça com que o script de construção copie o arquivo de bloqueio principal para cada espaço de trabalho e implante apenas o serviço no qual estamos interessados. (Obrigado @ johannes-scharlach)

Em última análise, acho que um comando yarn install --workspace-lockfile gerado por arquivos de bloqueio do espaço de trabalho seria a melhor solução.

Ter uma opção para arquivos de bloqueio de nível de pacote nos ajudaria também. Nosso caso de uso é um pouco diferente, estamos testando uma nova maneira de gerenciar dependências locais.

Portanto, já temos alguns repositórios mono e alguns que contêm apenas um único pacote. Todos eles são publicados e, portanto, podem ser usados ​​juntos, no entanto, muitas vezes é muito útil tê-los localmente e com links simbólicos.

Mas alguns desenvolvedores têm dificuldade em gerenciar links simbólicos, etc., então estamos testando um repositório mono de espaço de trabalho de fios vazio padrão que todos nós clonamos para nossas máquinas e, em seguida, clonamos repositórios de pacote nesse repositório mono local. Alguns de nós pode ter apenas um pacotes clones, alguns podem ter 5. Este é super conveniente e torna local, repo cruz, desenvolvimento de dependência cruzada uma brisa absoluta.

Mas encontramos um problema que não podemos resolver: editar dependências não atualiza o arquivo de bloqueio de yarn local, sempre atualiza o root para o repositório mono vazio no qual não atualizamos as dependências de atualização (ele tem tudo sob /packages gitignored.

Ter a opção de não bloquear as gravações de arquivos na raiz do repo mono seria ótimo e fazer com que elas gravem no nível do pacote.

Como observação, também encontrei problemas de implantação e construção em torno do Docker que outros mencionaram e isso também resolveria isso!

Esse recurso seria muito valioso para mim também. No meu caso tenho um monorepo com alguns pacotes implantados em diferentes plataformas. Um é um aplicativo Next.js implantado com Now.sh e o outro é um monte de funções de nuvem implantadas no Firebase.

Em ambas as implantações, o código-fonte é empacotado e carregado para instalação e construção na nuvem. Não ter um arquivo yarn.lock para acompanhar o código-fonte significa que as dependências são instaladas usando as versões em package.json e nenhuma versão está bloqueada.

Eu também adoraria habilitar os arquivos yarn.lock em cada área de trabalho.

Sei que os espaços de trabalho do yarn são principalmente destinados a monorepos, mas nosso caso de uso é bastante semelhante ao de @LeeCheneler .

Basicamente, criamos uma biblioteca de componentes React que usamos como uma dependência em diferentes projetos (em que todos têm seus próprios repositórios). Ao usar os espaços de trabalho do yarn, podemos facilmente fazer referência à versão local da biblioteca de componentes e fazer com que as alterações sejam propagadas para a versão local de nossos outros projetos rapidamente. Também não precisamos modificar o package.json ao enviar para a produção porque a dependência library: "*" funciona sem nenhuma alteração. Nosso único problema é que, sem os arquivos de bloqueio de fios, as versões de produção de cada projeto podem acabar usando diferentes versões de pacote.

Tenho que imaginar que esse problema seria comum entre os desenvolvedores de pacotes que usam espaços de trabalho do yarn.

Outro problema crítico com um arquivo de bloqueio de nível superior é que ele quebra o cache da camada do Docker. Normalmente, seria possível otimizar o armazenamento em cache do Docker copiando primeiro o package.json e o yarn.lock. Se o Docker não perceber mudanças nesses arquivos, ele usará uma camada anterior. Se esse arquivo de bloqueio for único para todo o monorepo, entretanto, qualquer mudança em qualquer pacote invalidará o cache. Para nós, isso resulta em pipelines CI / CD ridiculamente lentos, em que cada pacote é criado sem o cache. Existem outras ferramentas, como o Lerna, que verificam se há alterações no pacote para executar determinados scripts. Isso também é interrompido, pois uma mudança de dependência no arquivo de bloqueio pode não ser detectada por estar no nível superior.

Desculpe levantar este problema (um pouco antigo), mas também tenho um caso de uso em que isso seria útil. Tenho cerca de 10 microsserviços hospedados e desenvolvidos independentemente, mas seria bom ter um repositório de espaço de trabalho central onde você pudesse digitar yarn install para instalar dependências em todas as pastas e, em seguida, executar yarn start para executar um script que iniciará todos os microsserviços. Isso é algo que poderia ser feito com um script relativamente simples, mas também parecia viável com espaços de trabalho de yarn, mas não consegui fazê-lo funcionar respeitando yarn.locks em cada micorservice

@nahtnam Acho que a ideia do Yarn 1.x monorepo é um pouco diferente. Não se trata de projetos independentes sob o mesmo teto, é mais sobre um grande projeto singular com alguns de seus componentes expostos (chamados de espaços de trabalho). Esses componentes não são totalmente independentes, mas podem ser usados ​​da seguinte forma: o compilador Babel como uma entidade maior e preset-env como submódulo. Além disso, eles são homogêneos no sentido de que suas dependências são unificadas: se alguns pacotes dependem de core-js , deve ser a mesma versão core-js em cada um deles, porque você não pode bloqueie versões diferentes com um único arquivo de bloqueio de raiz, nem faz sentido que as partes do projeto dependam de versões diferentes. E por ser um projeto, todos os seus componentes são automaticamente vinculados à raiz node_modules , o que é estranho para projetos totalmente independentes.

Então, se você está desenvolvendo um pacote de microsserviços (que são independentes, e alguns deles não seriam tocados em anos enquanto outros seriam criados / atualizados, talvez desenvolvidos por equipes diferentes), então eles deveriam ter arquivos de bloqueio pessoais sem root lock (o problema do Docker também aparece aqui). A única questão é qual ferramenta ajudará na execução de scripts. Lerna pode ser uma resposta, pois não está ligada ao Yarn.

A única questão é qual ferramenta ajudará na execução de scripts. Lerna pode ser uma resposta, pois não está ligada ao Yarn.

@ the-spyke Não só isso. yarn workspaces também resolve, ligando módulos para desenvolvimento da mesma forma que npm link faz, que é a principal razão de estarmos usando. npm link não está funcionando bem em alguns casos.

@ the-spyke Obrigado! Por algum motivo, pensei que estava invertido (espaços de trabalho lerna vs yarn). Eu olhei em lerna e parece que resolve meu problema

Acabei usando espaços de trabalho para cada projeto em que estou trabalhando, por causa de como é fácil ter um pacote utilities separado que posso atualizar quando necessário (mesmo que seja publicado), e o fato de que Eu não tenho que reinstalar dependências se eu quiser fazer um fork de algum pacote (essencialmente me permitindo trabalhar em dois branches ao mesmo tempo, o que para mim era inédito), e sempre que eu quiser usar um pacote do meu utilities (que inclui a maioria das coisas que eu costumo usar), posso usá-lo imediatamente sem adicioná-lo a package.json do segundo pacote (mas isso é obviamente uma boa ideia no caso de instalação separada e é necessário para importações automáticas de IDE); tudo simplesmente funciona. @ the-spyke tem um bom argumento, talvez independent projects under one roof não seja o propósito dos espaços de trabalho e, no entanto, é basicamente isso que pareço estar fazendo aqui: tenho um único repositório monorepo-base , que exclui a pasta packages , enquanto cada pasta em packages é um repositório git independente e separado.
image
É claro que isso me leva ao tópico deste tópico; uma vez que não comprometo todos os pacotes como um repo, o nível de raiz yarn.lock não faz sentido. Tenho usado --no-lockfile para instalar tudo e recentemente tive um problema com versões conflitantes de class-validator . Por enquanto, vou bloquear todas as dependências para versões específicas (honestamente, esse nível de controle faz mais sentido para mim) e ver como funciona. Vou ler toda a trilha novamente, talvez haja algumas dicas que eu possa usar para o meu caso de uso.

PS.
yarn why não funciona sem lockfile para um, e percebi que algumas pessoas mencionaram problemas com o App Engine. Suponho que, no caso de cada pacote ser um repositório separado, o arquivo de bloqueio pode ser gerado toda vez na instalação (sem adicioná-lo ao VCS). Não tenho certeza sobre esse caso específico.

Infelizmente, a solução sugerida por @ johannes-scharlach fica muito confusa se sua imagem construída requer módulos de nó de tempo de execução que não estão incluídos em alguma pasta de construção, porque você terá que descobrir exatamente quais módulos são necessários para executar e copiá-los meticulosamente para o estágio final de construção.

(um pouco fora do tópico) @GrayStrider você também pode utilizar o campo "resoluções" em package.json - é a única maneira de forçar uma versão em uma dependência aninhada, por exemplo, se você quiser que todos os lodashes sejam a versão exata, não importa quão profundamente aninhado. No entanto, isso pode introduzir bugs muito sutis que serão difíceis de detectar.

Aqui está uma solução que encontramos - com impacto mínimo no fluxo de trabalho existente do Docker.

  1. ln project/yarn.lock packages/package1/yarn.lock - crie um link simbólico físico a partir da raiz yarn.lock em cada pacote.
  2. Adicione COPY yarn.lock . a cada packages/package1/Dockerfile
  3. yarn install dentro do Docker

Vantagens :

  • Não precisa copiar todo o seu monorepo em uma camada de imagem
  • Não é necessário mesclar seus Dockerfiles de nível de pacote em um único Dockerfile na raiz
  • Satisfaz basicamente o requisito de arquivos de bloqueio do espaço de trabalho

Desvantagens :

  • --frozen-lockfile não funciona. Como os pacotes dos espaços de trabalho não estão incluídos no yarn.lock , portanto, o yarn vê um pacote que você "adicionou" ao package.json que não existe no yarn.lock

Esta é uma pequena desvantagem, pois você pode contorná-la executando yarn --frozen-lockfile como a primeira etapa em seu pipeline de CI / CD

Editar:
Como um aparte, eu realmente acho que os documentos do yarn sobre a instalação poderiam ser um pouco mais claros sobre como o arquivo de bloqueio é usado pelo processo de resolução de pacote.

Editar:
Acontece que o git não oferece suporte a hard-links, ele apenas oferece suporte a soft links simbólicos, então essa estratégia não funcionará.

Outra alternativa é simplesmente usar um githook pré-comprometido para copiar yarn.lock em cada um de seus espaços de trabalho ... não é o ideal, pois ainda permite problemas ao implementar a partir de sua máquina local.

@dan-cooke muito obrigado por seus insights, muito apreciado!

@ dan-cooke, isso quebra o cache da camada do Docker, pois uma nova dependência em qualquer espaço de trabalho invalidaria a camada de instalação para todos os Dockerfiles.

@migueloller Se a camada de instalação não deve mudar, então você não deve usar um arquivo de bloqueio para todos os pacotes. Achatar e elevar dependências em uma única lista gigante é todo o propósito dos espaços de trabalho. Não é como se "quebrasse" o cache do Docker, o cache é invalidado porque as dependências reais não são especificadas em package.json , então o código final de seu pacote depende do conteúdo de yarn.lock . Como resultado, você precisa reconstruir todos os pacotes em cada alteração em yarn.lock e o Docker faz tudo certo. E não é como where every package is built without the cache porque todas as compilações podem reutilizar a camada com yarn install (provavelmente você precisará configurar isso).

@migueloller
Correto. Felizmente, não adicionamos novas dependências com frequência, então isso só será um problema uma vez por sprint, no máximo.

E mesmo assim, é um pequeno preço a pagar (para nós) por dependências reproduzíveis no Docker

@ the-spyke, de fato. É por isso que esse problema é sobre ter um arquivo de bloqueio individual por pacote. Dessa forma, a camada em cache só é invalidada quando as dependências do pacote mudam e são independentes de outros pacotes.

Talvez também valha a pena mover esta discussão para o próprio npm, que também oferece suporte a espaços de trabalho a partir da v7.0.

Estive pesquisando tópicos relacionados e gostaria de acrescentar alguns esclarecimentos ao meu comentário acima (principalmente voltado para desenvolvedores menos experientes, suponho, uma vez que os problemas que estava enfrentando eram, em certa medida, devido ao meu fracasso em compreender a importância de lockfile ).

"bloquear todas as dependências para uma versão específica" é chamado de fixação ; infelizmente, não vai impedir que as coisas potencialmente quebrar se as atualizações sub -dependency (parágrafos finais do artigo aqui ), que eu não ter considerado. Isso é exatamente o que lockfile evita que aconteça.

Já perdi horas mais do que o suficiente quebrando atualizações no passado em várias ocasiões; Vou experimentar usar lockfile no monorepo, já que prefiro lidar com conflitos de fusão e dor de cabeça organizacional do que bugs invisíveis causados ​​por pequenas atualizações.

Acima dito, estou muito ansioso para qualquer progresso neste assunto

@migueloller Arquivos de bloqueio individuais significam Monorepos de Fios individuais. Você não pode ter um arquivo de bloqueio para um espaço de trabalho porque ele quebra a uniformidade das dependências no Monorepo. Se você quiser fazer isso, está se afastando da ideia original do Yarn Monorepo: trata-se de unificar, elevar e reduzir dependências em uma lista plana na raiz, em vez de ter diferentes versões (e até subárvores inteiras) em diferentes espaços de trabalho .

@ the-spyke, mas o problema original é exatamente o oposto. Um arquivo de bloqueio por área de trabalho.

Não consigo entender como você não pode ter um arquivo de bloqueio por espaço de trabalho.

Quebra a uniformidade de profundos no Monorepo? Claro para desenvolvimento. Mas todo o propósito das dependências compartilhadas sai pela janela quando você deve implantar microsserviços leves de cada um de seus espaços de trabalho

Depósitos compartilhados e içados só fazem sentido no desenvolvimento.

Para que um espaço de trabalho fique no Docker, é necessário um arquivo de bloqueio.

@dan-cooke Como você pode ver , também tive esse problema em 2018, mas agora tenho uma opinião diferente.

Você está dizendo Docker e microsserviços. Mas e se eu desenvolver um pacote normal de npm ? Não tenho nenhuma subárvore de dependências de production para fixar, porque elas serão fornecidas pelo usuário final de acordo com minha especificação dependencies . Então, o que eu quero é maximizar minha experiência de desenvolvimento, e isso é o que Monorepos e Yarn Workspaces fazem perfeitamente.

Ao mesmo tempo, se você estiver desenvolvendo microsserviços (MSs), existem 2 situações possíveis:

  1. Projetos independentes. Alguns MSs estão em desenvolvimento, outros não foram tocados há anos. Nesse caso, eles são totalmente independentes. É possível ter UserService usando [email protected] e MessagesService usando [email protected] . Esse não é um mundo fácil onde você apenas vincula as pastas dos espaços de trabalho à raiz node_modules . Então, não adianta ter arquivo de bloqueio de root. Crie arquivos separados (raízes) e gerencie-os de forma independente. Isso é chamado de Multirepo nos documentos do Yarn. Mas agora o que você está dizendo é "Desejo executar tarefas em pastas diferentes da pasta raiz por conveniência". E esse é um tópico completamente diferente.

  2. Projetos com dependências unificadas como Jest / Babel / etc. É para isso que os espaços de trabalho foram feitos, mas nos MSs existem requisitos adicionais. Durante os estágios de CI, como linting e teste, tudo funciona bem porque funciona da mesma forma que em uma máquina de desenvolvedor: deps instalado pelo Yarn em node_modules raiz e nivelado. Além disso, você provavelmente armazena em cache a fase yarn install para acelerar compilações simultâneas.

    Na produção é completamente diferente: começando com isso, você só precisa de dependências para um espaço de trabalho e terminando com como instalar aquele pacote utils ? Deve ser vinculado ou baixado como tarball? Então, o que você realmente precisa é não ter arquivos de bloqueio por espaço de trabalho, mas ter um comando como yarn install --prod <workspace> que você pode executar especificando um espaço de trabalho e ele instalará apenas departamentos de produção e ao mesmo tempo ignorar outros espaços de trabalho não referenciados. Por exemplo, se meu data WS depende de utils WS, mas não de logging WS, então logging si e seus departamentos não devem aparecer em node_modules . Um resultado semelhante, mas uma abordagem completamente diferente para um "arquivo de bloqueio por área de trabalho".

    Se você estiver publicando pacotes de construção em um repositório (npm, Arifactory, GutHub), você pode obter um comportamento semelhante apenas copiando o arquivo de bloqueio em um espaço de trabalho e fazendo yarn install --prod aqui. Ele deve avisar sobre arquivos desatualizados, mas em vez de recriá-lo do zero com versões novas, ele deve apenas remover o excesso de dependências dele (apenas tentei e parece legítimo). Deve ser ainda melhor e robusto com o uso de Espelho Offline.

    E no final você tem Focused Workspaces implementado exatamente para Multirepos.

Então, o que eu estava dizendo é que talvez o problema não pareça o que é.

@ the-spyke
Eu vejo o que você está dizendo. Acho que definitivamente depende de como esse resultado é alcançado. Talvez um arquivo de bloqueio por área de trabalho não seja realmente a melhor maneira de obter o resultado desejado. Você fez alguns pontos positivos.

Definitivamente, não é uma solução "tamanho único" de qualquer maneira

@ the-spyke, você trouxe alguns pontos positivos. Talvez seja necessário pensar mais sobre quais problemas os espaços de trabalho do Yarn foram projetados para resolver e se seu uso para gerenciar grandes monorepos está alinhado com esse design.

Estou curioso para saber como você resolveria um cenário como este:

.
└── packages
    ├── app1
    ├── app2
    ├── lib1
    ├── lib2
    └── lib3

lib3 é uma biblioteca compartilhada e depende de app1 e app2 . lib1 é usado apenas por app1 e lib2 é usado apenas por app2 . Com base em suas sugestões, lib1 e app1 devem estar em seu próprio espaço de trabalho com seu próprio arquivo de bloqueio e o mesmo com lib2 e app2 . Agora, a questão é o que fazer com lib3 se _both_ app1 e app2 dependerem disso? Talvez alguém pudesse fazer com que ambas as áreas de trabalho ( app1 e app2 ) adicionassem lib3 à sua área de trabalho e executassem yarn install em cada aplicativo? O Yarn permite isso? Haveria algum conflito se alguém quisesse executar app1 e app2 em desenvolvimento local (talvez app1 seja um aplicativo React e app2 uma API GraphQL) ? Parece que pode funcionar.

A próxima pergunta a ser feita seria "Como alguém obtém os benefícios de içar com este método?" Por exemplo, se app1 e app2 compartilham muitas dependências comuns, seria bom içá-las. Eu posso ver como isso pode estar fora do escopo, porém, e é um problema a ser resolvido pelo Yarn PnP (ele não copia arquivos para node_modules e em vez disso tem um cache compartilhado).

Vou tentar isso e vou relatar de volta. Se isso acabar funcionando, talvez estejamos apenas usando os espaços de trabalho do Yarn de forma errada o tempo todo ...

EDIT: Eu experimentei e funciona.

Mudei minha postura agora e percebi que, embora ter um arquivo de bloqueio individual por área de trabalho possa ser a primeira coisa que vem à mente ao gerenciar um monorepo inteiro com áreas de trabalho do Yarn, pode não ser a pergunta certa. Uma pergunta melhor poderia ser "Os espaços de trabalho do Yarn foram projetados para gerenciar um monorepo?". A resposta, como sempre, é "depende".

Se você é Babel e tem uma única equipe trabalhando no monorepo e tudo deve mudar em sincronia, então sim, é para isso que os espaços de trabalho do Yarn foram projetados. Mas se você é uma organização com várias equipes e está usando um monorepo, provavelmente não deseja gerenciar todo o monorepo com uma única raiz de espaço de trabalho do Yarn. Você provavelmente deseja apenas usar o comportamento padrão do Yarn ou várias raízes do espaço de trabalho do Yarn dentro do monorepo. Isso será determinado por quais aplicativos você está construindo, quantas equipes existem, etc.

Para nós, ficou claro que para cada entidade implementável (em nosso caso há um Dockerfile), queremos ter um yarn install separado feito para cada uma (seja uma raiz de espaço de trabalho ou não). Isso fornece clareza sobre a propriedade do código, permite implantações isoladas que acontecem em cadências diferentes, resolve problemas de cache com lockfiles e Docker, etc. Existem algumas desvantagens para isso:

  • E quanto aos pacotes node_modules duplicados? Esta é uma classe comum de problemas com monorepostos e, embora os espaços de trabalho do Yarn ajudem na elevação, não é uma solução monorepo geral. Existem outras soluções, no entanto. Por exemplo, Yarn PnP cuida disso. Você também pode usar Lerna sem fio e usar a opção --hoist .
  • E sobre a utilidade de executar comandos em áreas de trabalho durante o desenvolvimento? Novamente, os espaços de trabalho do Yarn permitem que você faça isso, mas isso não significa que se deva transformar todo o monorepo em uma raiz do espaço de trabalho do Yarn. A construção das ferramentas e scripts necessários será diferente para cada equipe e depende de seu monorepo. Os espaços de trabalho do Yarn provavelmente não foram projetados para executar tarefas monorepo. Pode-se tentar dobrar um pouco os espaços de trabalho do Yarn para fazer este trabalho (ou seja, executar scripts NPM no monorepo usando yarn workspace ... ), mas é importante ter em mente que uma única raiz do espaço de trabalho para todo o monorepo provavelmente não dar a você o que você precisa, a menos que você seja como Babel, Jest, React, etc.

Há uma série de outros problemas associados à execução de um monorepo. Por exemplo, que tal rastrear dependências e apenas reconstruir coisas que mudaram para economizar tempo em CI? Os espaços de trabalho do Yarn podem ajudar, permitindo que você consulte o gráfico de dependência. O Lerna faz isso, por exemplo, para permitir a classificação topológica dos comandos executados. Na verdade, o Yarn v2 também permite que você consulte o gráfico de dependência. O gerenciador de pacotes PNPM também faz isso. Mas eu diria que, dependendo da complexidade do monorepo, pode-se tentar ferramentas construídas para isso (não gerenciadores de pacotes) como Bazel , Pants , Buck , etc.

@migueloller De acordo com seus requisitos, vejo que você não precisa de pacotes estritamente independentes ou outras coisas exóticas, você também deseja instalações de desenvolvedor mais enxutas. Nesse caso, você deve começar com o Yarn Monorepo regular: raiz única e todos os pacotes como espaços de trabalho. Você terá tempos de instalação mais rápidos, menor uso de disco e app1 usará lib1 e lib3 vinculados locais. A única desvantagem será a invalidação do cache de CI com mais frequência, porque a adição de um devDep a lib1 atualizará o yarn.lock compartilhado. Mas normalmente você não atualiza dependências que costumam se preocupar muito com essa compensação.

lib1 pode depender de lodash@^4.5.0" and lib2 may depend on lodash@^4.10.0 ". No caso do Monorepo você quer uma única versão de lodash sendo usada, então O Yarn irá instalar algo compatível com ambos os especificadores como ` [email protected] " içado para root node_modules. E no caso de uma atualização, você está atualizando aquela única versão unificada, de modo que todos os espaços de trabalho estão sempre na mesma página. Este é o comportamento desejado.

Existem também situações em que equipes independentes desenvolvem projetos independentes. Neste caso, proj1 pode querer ficar em [email protected] com proj2 tendo [email protected] e atualizar isso com suas próprias cadências. Claro, você pode conseguir isso usando especificadores mais estritos como lodash@~4.5.0 , mas isso ainda pode atualizar a dependência de segundo nível muito cedo. Portanto, em geral, esses projetos podem não estar relacionados, apenas por acaso estarem dentro de um repositório git. Nesse caso, não há razão para vinculá-los como um Yarn Monorepo e compensar a independência por um arquivo de bloqueio compartilhado. Apenas trate-os como o que são: projetos separados com sua vida independente. E isso é chamado de Multirepo. No Unix, todos os seus diretórios estão abaixo de / , mas isso não significa que todos os projetos JS possíveis em seu PC devem ser um Monorepo :-)

Construir uma imagem Docker mínima possível para uso em produção não está totalmente relacionado ao Yarn, mas você pode forçar o Yarn a reutilizar um artefato de desenvolvimento chamado yarn.lock e ajudá-lo nessa tarefa também.

@ the-spyke, neste exemplo eu usei apenas 3 áreas de trabalho, mas em nosso repositório real, temos mais de 20 áreas de trabalho com uma combinação de bibliotecas e cargas de trabalho implantadas para front-end e back-end. A maneira como vejo agora é que temos um monorepo (ou o que você chama de multirepo) onde provavelmente fará sentido ter várias raízes de espaço de trabalho Yarn com arquivos de bloqueio independentes. Estamos pensando em usar uma unidade implantável como unidade de separação, ela se alinha bem com os lockfiles.

Acho que, para mim, o que torna esse trabalho muito bom é o fato de que os espaços de trabalho do Yarn suportam caminhos fora da raiz do espaço de trabalho, embora a postagem inicial do blog diga o contrário. Por exemplo, você pode ter este:

{
  "workspaces": [
    "../lib1",
    "../lib3"
  ]
}

Temos o mesmo caso de uso que @migueloller e uma ideia possível é que o Yarn suporte vários conjuntos de espaços de trabalho, como este:

{
  "workspaces": {
    "frontend-app": ["frontend", "common"],
    "backend-app": ["backend", "common"]
  }
}

O Yarn manteria dois arquivos de bloqueio adicionais (imagino que o yarn.lock ainda existiria):

.
└── monorepo/
    ├── yarn.frontend-app.lock
    ├── yarn.backend-app.lock
    └── packages/
        ├── frontend
        ├── backend
        └── common

Ao construir uma imagem Docker, por exemplo, para front-end, criamos um contexto (por exemplo, via tar ) que inclui:

.
└── <Docker build context>/
    ├── yarn.frontend-app.lock
    └── packages/
        ├── frontend
        └── common

O que eu não pensei profundamente é se é possível instalar (link em node_modules ) as versões corretas das dependências se o front-end e o back-end bloquearem versões diferentes. Mas, puramente do ponto de vista de alto nível, os espaços de trabalho bidimensionais do Yarn é provavelmente o que procuramos.

(Algo semelhante também foi postado aqui .)

Parece que você não precisa de um arquivo de bloqueio por espaço de trabalho, mas em vez disso, você precisa de node_modules por espaço de trabalho para implantação

@gfortaine , se você ler a discussão, vai perceber que não é esse o caso. A razão para ter um arquivo de bloqueio separado não tem nada a ver com a instalação, mas em vez disso, ter um arquivo de bloqueio que só muda quando um pacote específico muda. Um lockfile de nível superior mudará com _cada_ mudança de dependência do espaço de trabalho, mas um lockfile com escopo para um único pacote só mudará quando as dependências _that_ do pacote mudarem.

Vale ressaltar que isso pode ser feito em user-land. Usando o pacote @yarnpkg/lockfile , pode-se analisar o arquivo de bloqueio de nível superior e, em seguida, usar yarn workspaces info pode-se determinar as dependências do espaço de trabalho. Usando essas informações, junto com o package.json de cada área de trabalho, pode-se gerar um arquivo de bloqueio por área de trabalho. Então, pode-se configurar isso como um script postinstall no repo para manter esses arquivos de bloqueio individuais em sincronia com o de nível superior.

Eu poderia tentar construir isso e relatar minhas descobertas.

Parece que acabei de encontrar esta implementação, embora para pnpm: @ pnpm / make-dedicated-lockfile

Espero que isso ajude 👍

Minha necessidade para este comportamento (controle de versão por espaço de trabalho, mas ainda tenho arquivos de bloqueio em cada pacote) é que eu tenho um monorepo aninhado, onde uma subárvore é exportada para outro repo inteiramente, então deve permanecer independente. No momento, estou preso ao lerna / npm e alguma lógica personalizada para tentar equilibrar as versões. Seria bom se o yarn (eu acho que v2, já que é onde está o suporte aninhado?) Pudesse gerenciar todos eles de uma vez, mas deixar o subconjunto correto da fixação global em cada um.

Um script de pós-instalação pode tentar gerenciar os arquivos de bloqueio diretamente, suponho. Parece complicado, mas seria bom de qualquer maneira.

Esta página foi útil?
0 / 5 - 0 avaliações