Fios: Um novo gerenciador de pacotes para JavaScript – Facebook Engineering

Na comunidade JavaScript, os engenheiros compartilham centenas de milhares de peças de código para que possamos evitar a reescrita de componentes básicos, bibliotecas ou estruturas próprias. Cada peça de código pode, por sua vez, depender de outras peças de código, e essas dependências são gerenciadas por gerenciadores de pacotes. O mais popular gerenciador de pacotes JavaScript é o cliente npm, que fornece acesso a mais de 300.000 pacotes no registro npm. Mais de 5 milhões de engenheiros usam o registro npm, que vê até 5 bilhões de downloads por mês.

Usamos o cliente npm com sucesso no Facebook durante anos, mas conforme o tamanho da nossa base de código e o número de engenheiros cresceu, tivemos problemas de consistência, segurança e desempenho. Depois de tentar resolver cada problema à medida que ele surgia, nos propusemos a construir uma nova solução para nos ajudar a gerenciar nossas dependências de forma mais confiável. O produto desse trabalho chama-se Fios – uma alternativa rápida, confiável e segura para o cliente npm.

Temos o prazer de anunciar o lançamento do Yarn de código aberto, uma colaboração com a Exponent, Google e Tilde. Com o Yarn, os engenheiros ainda têm acesso ao registro npm, mas podem instalar pacotes mais rapidamente e gerenciar dependências de forma consistente em máquinas ou em ambientes offline seguros. O Yarn permite que os engenheiros se movam mais rapidamente e com confiança ao usar código compartilhado para que possam se concentrar no que importa – construir novos produtos e recursos.

A evolução do gerenciamento de pacotes JavaScript no Facebook

Nos dias antes dos gerentes de pacotes, era comum os engenheiros JavaScript confiarem em um pequeno número de dependências armazenadas diretamente em seus projetos ou servidas por um CDN. O primeiro grande gerenciador de pacotes JavaScript, npm, foi construído logo após a introdução do Node.js, e rapidamente se tornou um dos mais populares gerenciadores de pacotes do mundo. Milhares de novos projetos de código aberto foram criados e engenheiros compartilharam mais código do que nunca.

Muitos de nossos projetos no Facebook, como o React, dependem do código no registro do npm. No entanto, como escalamos internamente, enfrentamos problemas de consistência ao instalar dependências em diferentes máquinas e usuários, o tempo necessário para puxar dependências e tivemos algumas preocupações de segurança com a forma como o cliente npm executa o código de algumas dessas dependências automaticamente. Tentamos construir soluções em torno desses problemas, mas eles próprios muitas vezes levantaram novos problemas.

Attempts at scaling the npm client

Initially, seguindo as melhores práticas prescritas, apenas verificamos em package.json e pedimos aos engenheiros para executar manualmente npm install. Isto funcionou suficientemente bem para os engenheiros, mas avariou nos nossos ambientes de integração contínua, que precisam de ser lixados e cortados da Internet por razões de segurança e fiabilidade.

A solução seguinte que implementámos foi verificar todos os node_modules no repositório. Enquanto isso funcionava, isso tornou algumas operações simples bastante difíceis. Por exemplo, atualizar uma versão menor do babel gerou um commit de 800.000 linhas que foi difícil de aterrissar e acionou regras de cotão para seqüências de bytes utf8 inválidas, terminações de linha do windows, imagens não png-crushed, e mais. A fusão de mudanças para node_modules levava frequentemente engenheiros um dia inteiro. Nossa equipe de controle de fontes também apontou que nossa pasta node_modules era responsável por uma tremenda quantidade de metadados. O React Native package.json lista atualmente apenas 68 dependências, mas depois de executar npm install o diretório node_modules contém 121.358 arquivos.

Fizemos uma última tentativa de escalar o cliente npm para trabalhar com o número de engenheiros no Facebook e a quantidade de código que precisamos instalar. Decidimos zipar toda a pasta node_modules e carregá-la em um CDN interno para que tanto os engenheiros quanto nossos sistemas de integração contínua pudessem baixar e extrair os arquivos de forma consistente. Isso nos permitiu remover centenas de milhares de arquivos do controle de código fonte, mas fez com que os engenheiros precisassem de acesso à Internet não apenas para puxar o novo código, mas também para compilá-lo.

Tivemos também que trabalhar em torno de problemas com o recurso de shrinkwrap do npm, que usamos para bloquear as versões de dependência. Os arquivos shrinkwrap não são gerados por padrão e cairão fora de sincronia se os engenheiros se esquecerem de gerá-los, então escrevemos uma ferramenta para verificar se o conteúdo do arquivo shrinkwrap corresponde ao que está em node_modules. Estes ficheiros são enormes blobs JSON com chaves não ordenadas, no entanto, as alterações a eles gerariam commits massivos e difíceis de rever. Para mitigar isso, nós precisávamos adicionar um script adicional para classificar todas as entradas.

Finalmente, atualizar uma única dependência com npm também atualiza muitas não relacionadas baseadas em regras de versionamento semântico. Isto torna cada mudança muito maior do que o previsto, e tendo que fazer coisas como submeter node_modules ou fazer upload para um CDN tornou o processo menos que ideal para engenheiros.

Construindo um novo cliente

Conteúdo do que continuar construindo infra-estrutura em torno do cliente npm, nós decidimos tentar olhar para o problema de forma mais holística. E se, em vez disso, tentássemos construir um novo cliente que abordasse os problemas centrais que estávamos a experimentar? Sebastian McKenzie em nosso escritório de Londres começou a hackear essa idéia e rapidamente ficamos entusiasmados com seu potencial.

Como trabalhamos nisso, começamos a falar com engenheiros de todo o setor e descobrimos que eles enfrentavam um conjunto similar de problemas e haviam tentado muitas das mesmas soluções, muitas vezes focadas na resolução de um único problema de cada vez. Tornou-se óbvio que, colaborando com todo o conjunto de problemas que a comunidade estava enfrentando, poderíamos desenvolver uma solução que funcionasse para todos. Com a ajuda dos engenheiros da Exponent, Google e Tilde, construímos o cliente Yarn e testamos e validamos o seu desempenho em cada grande estrutura JS e para casos de uso adicional fora do Facebook. Hoje, estamos animados para compartilhá-lo com a comunidade.

Introducing Yarn

Yarn é um novo gerenciador de pacotes que substitui o fluxo de trabalho existente para o cliente npm ou outros gerenciadores de pacotes, mantendo-se compatível com o registro npm. Ele tem o mesmo conjunto de recursos dos fluxos de trabalho existentes enquanto opera mais rápido, mais seguro e mais confiável.

A função principal de qualquer gerenciador de pacotes é instalar algum pacote – um pedaço de código que serve a um propósito particular – de um registro global para o ambiente local de um engenheiro. Cada pacote pode ou não depender de outros pacotes. Um projeto típico pode ter dezenas, centenas, ou até milhares de pacotes dentro de sua árvore de dependências.

Estas dependências são versionadas e instaladas com base no versionamento semântico (semver). Semver define um esquema de versionamento que reflete os tipos de mudanças em cada nova versão, se uma mudança quebra uma API, adiciona uma nova funcionalidade, ou corrige um bug. Entretanto, semver depende de desenvolvedores de pacotes não cometerem erros – mudanças de quebra ou novos bugs podem encontrar seu caminho para dependências instaladas se as dependências não estiverem bloqueadas.

Arquitetura

No ecossistema do Nó, dependências são colocadas dentro de um diretório node_modules em seu projeto. Entretanto, essa estrutura de arquivo pode diferir da árvore de dependências real, já que as dependências duplicadas são fundidas entre si. O cliente npm instala dependências dentro do diretório node_modules não-deterministicamente. Isso significa que com base nas dependências de ordem são instaladas, a estrutura de um diretório node_modules poderia ser diferente de uma pessoa para outra. Essas diferenças podem causar bugs “funciona na minha máquina” que levam muito tempo para caçar.

Yarn resolve esses problemas em torno de versionamento e não-determinismo usando lockfiles e um algoritmo de instalação que é determinístico e confiável. Esses arquivos bloqueiam as dependências instaladas para uma versão específica, e garantem que cada instalação resulte exatamente na mesma estrutura de arquivos em node_modules em todas as máquinas. O arquivo de bloqueio escrito usa um formato conciso com chaves ordenadas para garantir que as alterações sejam mínimas e a revisão seja simples.

O processo de instalação é dividido em três passos:

  1. Resolução: Yarn começa a resolver dependências fazendo pedidos ao registro e procurando recursivamente cada dependência.
  2. Fetching: A seguir, Yarn procura em um diretório cache global para ver se o pacote necessário já foi baixado. Se não o fez, Yarn vai buscar o tarball para o pacote e coloca-o no cache global para que possa funcionar offline e não precisará de descarregar as dependências mais do que uma vez. As dependências também podem ser colocadas no controle do código fonte como tarballs para instalações totalmente offline.
  3. Linking: Finalmente, o Yarn liga tudo junto copiando todos os arquivos necessários do cache global para o local node_modules directory.

Ao quebrar estas etapas de forma limpa e com resultados determinísticos, o Yarn é capaz de paralelizar operações, o que maximiza a utilização de recursos e torna o processo de instalação mais rápido. Em alguns projetos do Facebook, o Yarn reduziu o processo de instalação em uma ordem de grandeza, de vários minutos para apenas alguns segundos. O Yarn também usa um mutex para garantir que múltiplas instâncias CLI em execução não colidam e poluam umas às outras.

Através de todo esse processo, o Yarn impõe garantias rigorosas em torno da instalação do pacote. Você tem controle sobre quais scripts de ciclo de vida são executados para quais pacotes. Os checksums dos pacotes também são armazenados no arquivo de bloqueio para garantir que você obtenha o mesmo pacote todas as vezes.

Features

Além de tornar as instalações muito mais rápidas e confiáveis, Yarn tem recursos adicionais para simplificar ainda mais o fluxo de trabalho de gerenciamento de dependência.

  • Compatibilidade com os fluxos de trabalho npm e bower e suporta registros de mistura.
  • Capacidade de restringir licenças de módulos instalados e um meio de saída de informações de licença.
  • Expõe uma API JS pública estável com logging abstraído para consumo via ferramentas de compilação.
  • Saída CLI legível, mínima e bonita.

Fio em produção

No Facebook nós já estamos usando o Fio em produção, e ele tem funcionado muito bem para nós. Ele alimenta a dependência e o gerenciamento de pacotes para muitos de nossos projetos JavaScript. Com cada migração nós permitimos que os engenheiros construíssem offline e ajudámos a acelerar o seu fluxo de trabalho. You can see how install times for Yarn and npm compare on React Native under different conditions, which you can find here.

Getting started

The easiest way to get started is to run:

npm install -g yarn

yarn

The yarn CLI replaces npm in your development workflow, either with a matching command or a new, similar command:

  • npm installyarn

    With no arguments, the yarn command will read your package.json, fetch packages from the npm registry, and populate your node_modules folder. It is equivalent to running npm install.

  • npm install --save <name>yarn add <name>

    We removed the “invisible dependency” behavior of npm install <name> and split the command. Running yarn add <name> is equivalent to running npm install --save <name>.

Future

Many of us came together to build Yarn to solve common problems, and we knew that we wanted Yarn to be a true community project that everyone can use. Yarn is now available on GitHub and we’re ready for the Node community to do what it does best: Use Yarn, share ideas, write documentation, support each other, and help build a great community to care for it. We believe that Yarn is already off to a great start, and it can be even better with your help.