Olá pessoal!
Tenho certeza que você já viu projetos Node.js usando diferentes gerenciadores de pacotes, ou seja:
Eu mesmo vi isso e trabalhei com todos os itens acima, mas sempre tive uma pergunta em mente - o que leva as pessoas/equipes a usarem yarn ou pnpm em vez de npm ? Quais são as vantagens? Há algum contra?
Bem… vamos descobrir!
Decidi comparar npm , yarn e pnpm em termos de “velocidade”…
Você verá três medidas abaixo:
Gere um arquivo de bloqueio sem cache.
Instale dependências de arquivos de bloqueio existentes sem nenhum cache.
Instale dependências de arquivos de bloqueio existentes com cache global.
Existem dois tipos de cache:
Global.
Geralmente armazenado no diretório inicial do usuário (fe, ~/.yarn/berry/cache
).
Local.
Armazenado no diretório do projeto (fe, <project-dir>/.yarn
).
Embora os números 2 e 3 sejam os casos de uso mais comuns em minha experiência, também peguei o número 1 por precaução (embora seja um caso muito raro).
Usei um projeto de amostra do create-react-app como exemplo de benchmarks.
É um gerenciador de pacotes padrão para o ecossistema Node.js – o que mais dizer? Ele vem com o pacote de instalação, portanto está basicamente pronto para uso quando você instala o Node.js em sua máquina (ou em qualquer provedor de CI se você configurar o Node.js lá).
Na minha opinião, isso é um grande “profissional” - você não precisa instalá-lo separadamente!
Nada excepcional aí - simplesmente… funciona! E não vi nenhum bug importante ao longo dos anos - parece bastante estável e dá conta do recado.
Os recursos do npm que usei até agora:
O npm armazena dependências na pasta node_modules
da raiz do seu projeto. Bem direto.
ℹ️ package-lock.json
armazena informações sobre registros para os pacotes listados - é MUITO útil se você tiver pacotes de um único escopo, ou seja, @example-company
em registros diferentes (por exemplo - pacotes npm e GitHub ):
Agora, vamos ver como ele funciona em termos de velocidade de instalação…
Levoupackage-lock.json
e instalar dependências sem nenhum cache.
Comando usado:
npm i
Levoupackage-lock.json
sem nenhum cache.
Comando usado:
npm ci
Levoupackage-lock.json
com cache global.
Comando usado:
npm ci
Consegui criar um espaço de trabalho e gerenciar dependências para todo o espaço de trabalho de uma só vez e para projetos específicos separadamente.
Em outras palavras - ele faz o trabalho sem bugs/problemas e a documentação oficial é bastante direta.
Recursos do espaço de trabalho que usei até agora:
Honestamente, não experimentei muito alguns dos recursos do fio . Quer dizer, usei muito em termos de “instalação de dependências” enquanto trabalhava em alguns projetos, e é isso.
O yarn não vem com um instalador Node.js , então você terá que instalá-lo separadamente. Isso significa que haveria uma etapa adicional em seus pipelines de CI - você teria que configurar o fio antes de instalar as dependências do projeto.
O fio tem duas abordagens para instalar dependências:
“ Zero instalações ” (padrão) - cria a pasta .yarn
e lista os pacotes nos arquivos yarn.lock
e .pnp.cjs
.
Um regular - semelhante ao npm , armazena dependências em node_modules
e as lista no arquivo yarn.lock
.
ℹ️ Os arquivos yarn lock armazenam informações sobre registros de todos os pacotes listados SOMENTE se você usar a abordagem de instalação antiga (regular).
⚠️ Tenha em mente que “ Zero Installs ” parece armazenar pacotes no cache local e fornecer links para seus arquivos de bloqueio:
Pode ser importante para você se você tiver um pipeline Dockerfile ou CI onde você instala dependências em um ambiente limpo e depois deseja movê-lo para outro (você terá que copiar a pasta .yarn
e o cache local).
Como a abordagem padrão para o Yarn agora é “ Zero Instalações ” e tem melhor desempenho do que a abordagem antiga, registraremos benchmarks apenas com essa abordagem.
Levouyarn.lock
e instalar dependências sem cache.
Comando usado:
yarn install
Levou
Comando usado:
yarn install --frozen-lockfile
Levou
Comando usado:
yarn install --frozen-lockfile
Consegui criar um espaço de trabalho e gerenciar dependências para todos os projetos de uma vez e para projetos específicos separadamente.
Recursos do espaço de trabalho que usei até agora:
A documentação está boa, mas os nomes e sinalizadores dos comandos são um tanto confusos.
Por exemplo, devo executar isto para executar o script test
na raiz ( . ) e no projeto b2b
aninhado:
yarn workspaces foreach -A --include '{.,b2b}' run test
Em comparação com npm :
npm run test --workspace=b2b --include-workspace-root
O pnpm está atualmente em alta - muitas empresas e projetos de código aberto o utilizam .
Assim como o yarn - pnpm não vem com um instalador Node.js , então você terá que instalá-lo separadamente. Isso significa que haverá uma etapa adicional em seus pipelines de CI – você terá que configurar o pnpm antes de instalar as dependências do seu projeto.
pnpm é considerado um “ gerenciador de pacotes rápido e eficiente em espaço em disco ” …
Na verdade, concordo com a afirmação “eficiente de espaço em disco” em termos de gerenciamento local de dependências.
Por padrão, o pnpm elimina a duplicação de dependências compartilhadas. pnpm cria links simbólicos para os pacotes que são usados em múltiplas dependências. ou seja, se os pacotes a
e b
usarem o pacote c
como uma dependência - o pnpm armazenará o pacote c
como uma única cópia e criará links simbólicos para os pacotes a
e b
. Dessa forma, o gerenciador de pacotes não cria cópias impressas e economiza memória no seu SSD/HDD.
ℹ️ pnpm-lock.yaml
não armazena informações sobre registros dos pacotes listados.
⚠️ Tenha em mente que o pnpm às vezes armazena dependências no cache global, em vez de mantê-lo como um projeto.
Levoupnpm-lock.yaml
e instale dependências sem nenhum cache.
Comando usado:
pnpm install
Levoupnpm-lock.yaml
sem cache.
Comando usado:
pnpm i --frozen-lockfile
Levoupnpm-lock.yaml
com cache global.
Comando usado:
pnpm i --frozen-lockfile
Agora, é aí que as coisas se tornam realmente interessantes…
O pnpm tem muitas opções de configuração, mas algumas funcionalidades básicas simplesmente não funcionam!
Vamos revisar alguns bugs que enfrentei:
É importante poder instalar dependências apenas para projetos específicos – é bastante útil para monorepos quando você cria pipelines relacionados a projetos específicos dentro do espaço de trabalho.
ou seja, imagine que você tem em seu espaço de trabalho:
Todos esses são projetos NPM separados, mas fazem parte do mesmo repositório ☝️
Agora, você deseja que um pipeline execute apenas testes de ponta a ponta. Então, você precisa apenas de dependências de teste ponta a ponta, certo?
Bem, você não conseguirá fazer isso - o pnpm está forçando você a instalar dependências para todo o espaço de trabalho!
pnpm install --filter <project-name>
deveria instalar dependências apenas para projetos selecionados, mas não funciona.
Há um bug com um ano de idade e foi recentemente fechado com uma correção que não funcionava.
O pnpm por padrão instala dependências para todo o espaço de trabalho (todos os projetos) quando você executa pnpm install
Você pode alternar esse comportamento se definir recursive-install=false
em .npmrc
na raiz do seu espaço de trabalho.
MAS introduz outro bug que já tem quase 2 anos .
Por padrão, pnpm armazena a lista de dependências em um único arquivo de bloqueio (igual a npm e yarn ).
Você também pode alternar esse comportamento se definir shared-workspace-lockfile=false
em .npmrc
na raiz do seu espaço de trabalho.
Isso nos permitiria manter o recurso de espaço de trabalho e usar o sinalizador --ignore-workspace
para instalar dependências para um projeto específico.
De qualquer forma, esta configuração apresenta mais alguns problemas:
eslint
e tsc --noEmit
geram um erro “JavaScript Heap Out of Memory” em meus pipelines de ações do GitHub .
Algumas das dependências são armazenadas no cache global e vinculadas simbolicamente em node_modules/.pnpm
.
# | npm | fio | pnpm |
---|---|---|---|
Gere um arquivo de bloqueio | 60 segundos | 16,5 segundos | 31 segundos |
Instale dependências sem nenhum cache | 18 segundos | 11 segundos | 8 segundos |
Instale dependências com cache global | 8 segundos | 8 segundos | 5 segundos |
De acordo com o benchmark acima, npm é o gerenciador de pacotes mais lento ☝️
De qualquer forma, vamos interpretar esses resultados…
É um caso raro. Normalmente, um arquivo de bloqueio é criado na inicialização do projeto e então se expande quando você instala/atualiza pacotes.
Com isso em mente - não parece ser algo muito importante em que se confiar ao escolher um gerenciador de pacotes.
Na maioria dos casos, seus projetos mantêm uma lista específica de dependências e você raramente adiciona/remove algo.
Provavelmente, você atualizará versões de seus pacotes de tempos em tempos - essas alterações são pequenas e você reutilizará o restante dos pacotes do cache.
Em outras palavras, o caso de uso comum é -- buscar novos pacotes do registro de pacotes e pegar o restante do cache.
pnpm (5-8 seg) é quase duas vezes mais rápido que npm (8-18 seg) com fio (8-11 seg) no meio.
Acho que o pnpm faz o melhor trabalho se o seu requisito para o gerenciador de pacotes for tão simples quanto “instalar apenas dependências”.
Embora o pnpm não venha com um instalador Node.js pronto para uso, é fácil configurá-lo em pipelines de CI com corepack ou action existente .
Eu prefiro npm , porque:
package-lock.json
para que você possa instalar dependências com um único escopo de registros diferentes.
Essas vantagens superam os segundos de velocidade e espaço em disco que eu economizaria com yarn ou pnpm .
Quais são os seus critérios para escolher um gerenciador de pacotes? Não seja tímido e deixe-me saber sua opinião na seção de comentários abaixo! 👇😊