Para de usar `npm install` pra instalar dependências.

Tenho duas perguntas pra ti:

  • Tu inclui o package-lock.json no teu controle de versão?
  • Tu usa npm install pra instalar dependências de um projeto quando só quer rodar ele?

Vou procurar responder aqui o que acho que é correto, sempre considerando a possibilidade de estar errado e aberto à crítica de vocês.

Sim, tu deveria colocar o package-lock.json no controle de versão, e atualizar ele sempre que é alterado. Não, tu não deveria usar npm install pra meramente instalar as dependências de um repositório que clonou ou deu pull.

A ideia toda do package-lock.json é manter as dependências da tua aplicação reprodutíveis. Clonou um repositório e só quer rodá-lo? O package-lock.json garante que, em qualquer lugar que ele seja clonado, as dependências que vão ser baixadas sejam iguais.

Mas, pra fins de manter essa reprodutibilidade, tu não pode chegar lá e alterar o arquivo bem na hora que vai baixar as dependências. O package.json sabe quais as dependências imediatas que tu especificou, mas só o package-lock.json sabe que subdependências estas dependências estão usando.

Pois bem: são exatamente alterações inadvertidas que o npm install acaba fazendo. Ele potencialmente atualiza as dependências de dependências dentro do package-lock.json, o que é uma boa, mas deveria ser feito conscientemente e não por acidente -- e, se feito, deveria ser atualizado no controle de versão também. A questão é que, se tu muda teu package-lock.json toda vez que instala dependências... bem, acho que é o mesmo que não ter o arquivo.

Deste modo, pra só instalar as dependências de um repositório antes de rodá-lo, o correto (até onde eu tenha sido capaz de confirmar) seria usar o comando npm ci. Algumas pessoas veem este comando usado em Dockerfiles e similares, e parecem acreditar que o comando tenha diretamente a ver com "continuous integration". Mas, na verdade, o ci neste caso significa clean install. O que ele faz? Ele baixa as dependências sem alterar o package.json ou o package-lock.json. Se ambos os arquivos estão fora de sincronia, caso que pode ocorrer quando alterações ao package-lock.json não são atualizadas no controle de versão, o npm ci falha, e daí, sim, pode-se usar o npm install para arrumar a coisa toda. Mas ocorrências como estas deveriam ser raras, e rodar o npm install deveria ser usado somente para instalar novas dependências (e talvez pra instalar atualizações destas e suas próprias subdependências? Sinceramente não tenho certeza).

Takeaways

  • Usa o npm install pra adicionar dependências
  • Usa o npm ci para recriar o ambiente de dependências de um repositório pra rodar ele
  • Outra coisa: usa o npm uninstall pra remover uma dependência, isto deve alterar o package-lock.json; não é só ir lá e manualmente apagar a linha que lista a dependência

Novamente, fico aberto a observações com possíveis melhorias, ou que demonstrem que estou equivocadamente propondo algo que não é melhor prática. Agradeço pela atenção de vocês.

Discordo. Se as dependencias no package.json estiver bem escrito, o npm install não irá fazer alterações (significativas).

Costumo deixar minhas dependências apenas com tolerância aos patches, assim não há quebra de funcionalidade.

Porém há sim o risco de algum pacote na cadeia inserir uma breaking change em um patch, o que não é correto mas é possível.

Concordo com vc. Quantos projetos por aí o pessoal não se preocupa em setar atualizações apenas nas versões hotfix e minor. O projema são alguns projetos que sismam em quebrar/remover certas coisas mesmo em versões minor, as vezes quebrando o projeto. Nesse segundo caso, ia quebrar tanto pra um quanto pra outro.

Só acrescentaria que o npm ci deve ser utilizado primariamente em processos de automações que incluem: plataformas de testes, integrações contínuas e implantação em ambiente produtivo. Você utiliza nesses ambientes porque você quer evitar problemas na automação. Em ambiente de desenvolvimento ainda é recomendado utilizar o npm install.

No npm ci você quer replicar exatamente um ambiente que já está validado com todas as suas dependências e subdependências. Você não quer correr o risco de atualizar alguma dependência na árvore do ESLint e não conseguir gerar a build. Também é por isso que você só deve utilizar o comando npm ci em ambientes automáticos ou de implantação, você precisa sempre validar as vulnerabilidades e bugs do seu código em ambiente de desenvolvimento.

Faz bastante sentido, costumo ver bastante nos `Dockerfile` de vários projetos que tem esse arquivo de exemplo para deploy.
Sim, é isso mesmo, o `npm ci` seria pra esses ambientes onde você não quer experimentar uma possivel atualização de subdependencia que acaba quebrando alguma feature, e na propria documentação do npm eles comentar sobre usar nos ambientes de testes automatizados, builds e etc. https://docs.npmjs.com/cli/v8/commands/npm-ci

Concordo, em um dos projetos que trabalho com certa frequência sempre utilizo o npm ci, isso pq o projeto é legado (7 anos), e utilizamos o node 14 nele, ao rodar npm install acaba modificando o package-lock.json e a chance de algo dar errado é enorme. Então para projetos legados pelo menos, acredito que seja a melhor opção.

Que massa esse tab! Eu não conhecia o comando npm ci, pensei como muitos que seria algo relacionado ao Continuous Integrator.

Sim, sobre package-lock.json, eu sempre coloco ele no controle de versão. E recentemente tenho usado o pnpm, que tem um arquivo de manifesto das dependências das dependências diferente do npm, e a experiência tem sido muito legal!!


E outra coisa, eu acho que nesse trecho do seu tab:

"O package-lock.json sabe quais as dependências imediatas que tu especificou, mas só o package-lock.json sabe que dependências estas dependências estão usando."

Seria primeiro o package.json e depois o package-lock.json, certo? Se não for, desconsidera.

Ele arrumou! ^^

Obrigado pelo comentário! Escrevi os nomes tantas vezes que me confundi. Vou editar ali.

Sensacional mano. É aquela coisa, sempre aprendendo algo novo. Obrigado.

É até interessante seu ponto de vista. Mas pra ser sincero, nunca tive problemas utilizando apenas o npm install. Vou pesquisar mais a fundo sobre o comando npm ci pra entender melhor o funcionamento.

Obrigado por trazer esse tema em discussão.

Interessante. Geralmente eu uso yarn, mais por costume mesmo, nem sei se faz alguma diferença de fato. Teria algo equivalente pro yarn também?

Me parece algo interessante para projetos legados, como um colega que comentou aqui falou. Mas no geral, para projetos com versões mais atuais, acho que seria meio redundante as duas perguntas, não? O npm install acaba chegando no mesmo fim do npm ci e o package-lock.json poderia ser setado com opcional quando se fala de controle de versão. Não sei se estou correto no meu pensamento, aberto a comentários. :)

Entendo e reconheço a validade do seu ponto de vista, concordando em grande medida com ele. No entanto, é crucial estarmos cientes dos potenciais ataques que o "package lock" pode sofrer ao adotar essa estratégia. Recomendo que use a ferramenta "npm audit".

O "npm audit" é uma ferramenta que analisa as dependências do projeto em busca de vulnerabilidades conhecidas e fornece detalhes, juntamente com recomendações de correção, para garantir maior segurança.

Além disso, uma alternativa ao uso do "package lock" é especificar versões estáticas das dependências no "package.json", o que nos permite ter maior controle sobre as versões exatas dos pacotes que estamos utilizando, minimizando riscos associados a atualizações inesperadas ou incompatibilidades.

Boa. Noa ultimos meses comecei a usar o pnpm. Ele é muito rápido e tem umas saidas legais no terminal quando você atualiza ou instala alguma coisa. Gostei bastante.

Ja me ocorreu de uma falha em comunicação a um banco Firebird com nodejs e a biblioteca node-firebird devido a uma dependência incorreta. Bem surpreendente considerando que a diferença das versões era bem pequena.

Bom post no meu ponto de vista, não sabia deste comando. Obrigado