Às vezes me pego pensando o quanto o package.json é subestimado no meio do desenvolvimento Node/Web.
O senso comum trata ele quase como uma lista de compras: “nomes de pacotes com uma numeração na frente”, e quando essa lista está pronta a gente cruza os dedos e roda um npm i (na maioria das vezes npm i -f, aquele jeitinho pra não ter que lidar com peerDependencies 😀) e ficamos esperando o download das nossas dependências.
E quando dá um problema inexplicável na sua aplicação? Sempre aparece alguém com o mantra clássico:
“Apaga a node_modules, deleta o package-lock.json e roda um install de novo.”
É praticamente o “desliga o modem, espera 5 segundos e liga de novo” das nossas aplicações.
Mas será que o package.json é só isso? Ou será que ele é, de fato, o core da sua aplicação e deveria receber mais atenção? 🔍.
Package.json como contrato
A documentação do NPM tem uma longa e extensa definição sobre o que o package.json representa e logo nos primeiros momentos de leitura já percebemos de que não se trata apenas de um arquivo ou um "simples objeto JavaScript". Podemos listar algumas das responsabilidades que esse arquivo tem segundo a documentação que acabamos de ver:
É o Manifesto do seu projeto: define a identidade da sua aplicação: nome, versão e outros metadados. É essencial para qualquer aplicação conseguir consumir demais pacotes npm (e no caso de libs a importância se estende para conseguirmos publicar e disponibilizar essa lib para demais desenvolvedores)
Campos obrigatórios e complementares:
name
eversion
são obrigatórios apenas se você pretende publicar o pacote no npm (version deve seguir o padrão do semanticVersion: major.minor.patch). Para projetos internos (apps que não vão pro registry), tecnicamente você consegue rodar sem version, mas a boa prática é sempre manter. Como campos complementares podemos ter: description, author, license, repository, keywords, homepage e entre outros (úteis para documentação, publicação e identificação).Dependencies: é onde definimos os pacotes essenciais para que a nossa aplicação funcione (exemplo: no caso de uma aplicação Angular, aqui vamos encontrar os pacotes vitais
@angular
). Dependências vão para o bundle final(prod) da aplicação ao contrario de dependências de desenvolvimento.DevDependencies: pacotes que são usados apenas no ambiente de desenvolvimento: jest, eslint, cypress, bundlers, TypeScript e entre outros.
Um ponto importante sobre Dependencies e DevDependencies: elas seguem um padrão de versionamento baseado no semVer: (^, ~, 1.0.0), que é importante para controle de compatibilidade e atualizações previsíveis dos nossos pacotesScripts automatizados: esse campo do arquivo é responsável por adicionar scripts personalizados para executarmos alguma rotina que precisamos através do comando
npm run <nome_do_comando>
. Aqui também temos acesso a hooks que podem ser disparados de maneirapre<nome_comando>
oupost<nome_do_comando>
{
"scripts": {
"build": "echo '🔨 Construindo app...'",
"prebuild": "echo '📦 Limpando dist antes do build...'",
"postbuild": "echo '✅ Build finalizado com sucesso!'"
}
}
Na ordem de execução ficaria => prebuild, build, postbuild.
Existem alguns outros campos bem legais do package.json como:
- Engine: que define versões mínimas e máximas requeridas para que você possa executar aplicação
- OS: em que sistema operacional sua aplicação pode ser executada (e se for necessário em qual OS não pode ser executado)
- CPU: o tipo de arquitetura de CPU que sua aplicação pode ser executada (semelhante ao OS)
Com isso, o package.json deixa de ser apenas “uma lista de compras” e passa a ser visto como o contrato central que garante que seu projeto vai rodar, compilar e ser instalado do jeito planejado.
Rodando o install e entendendo como ele funciona
Então o seu package.json
já está configurado e com as dependências que você precisa listadas com as versões alinhadas, agora seu próximo passo é rodar o npm install
e ver toda aquela mágica de toneladas e toneladas de pacotes sendo baixadas. Mas como funciona isso por debaixo dos panos? é um processo simples? quando executamos o install o npm simplesmente tira os pacotes nas versões que precisamos de algum canto misterioso e auto-mágico
, assim como um mágico tira um coelho da cartola?
e a resposta é .... NÃO!
Na verdade o processo de download e instalação de pacotes npm é muito mais burocrático do que parece.
Assim que executamos um npm install
nas nossas aplicações a primeira pergunta que o npm se faz é: "cadê o package-lock.json dessa aplicação ???"
package-lock.json: A caixa-preta do projeto
Quando algum avião tem qualquer tipo de problema, o primeiro lugar que todos vão checar é a famigerada "caixa preta", a caixa preta não abre margem para chutes e achismos, o que está nela são os registros absolutos do avião...assim como o package-lock.json
é a caixa preta dos seus pacotes. O lock não é apenas um arquivo gigantesco que serve para complicar a sua vida com changes no momento de um git rebase ou um git pull, ele é justamente o registro fiel de como cada dependência sua foi instalada, um raio-x perfeito da arvore de dependências da aplicação
Vamos olhar com um pouco mais de proximidade para o caso do @angular/core
e tomar como exemplo para os demais:
- no package.json a dependência está listada como:
"@angular/core": "^16.2.0",
Quando usamos o semVer range syntax (~, ^) não sabemos exatamente a versão especifica do pacote que será baixada para o nosso projeto, temos como ter um controle e uma certa visibilidade, mas se usamos o range syntax (o mais comum) ficamos com algumas incertezas sobre a versão. Essas incertezas se tornam certezas quando olhamos no detalhe o registro da dependência no package-lock.json
Aqui nós temos o registro exato da dependência:
- A dependência foi instalada na versão
16.2.9
. - Ela foi encontrada no registry do npm (não foi configurado o uso de nenhum "articatory" ou qualquer outro tipo de Registry privado (como Nexus, Verdaccio, etc.). Curiosidade: o arquivo do pacote vem no formato .tgz. Experimente rodar npm pack no build de uma lib Node e veja o pacote gerado:
nome_da_sua_lib
.tgz. - O pacote está integro e tem a garantia disso através de uma chave SHA512, mas o que isso quer dizer ? quer dizer que o pacote que você baixou está em conformidade com o que o autor publicou no npm, evitando que você baixe pacotes com conteúdos malicioso/prejudiciais.
- Todos os pacotes adicionais que a dependência precisa serão baixados para funcionar corretamente graças ao campo
requires
.
E por aqui já podemos ver o quanto é importante termos apenas as dependências que precisamos nos nossos projetos, afinal as dependências também tem dependências o que gera mais download de pacotes....logo node_modules tende ao infinito 😁.
Para finalizarmos o assunto package-lock.json: se sua aplicação já estiver com um install ou seu projeto clonado já vier com um package-lock.json (é uma boa práticas sempre que necessário comitarmos o package-lock.json, inclusive é uma questão de segurança), o npm vai usar esse arquivo como referencia e ir fazendo o download de dependência por dependência seguindo com fidelidade a questão do registry(presente no campo resolved) e das versões, garantindo a reprodutibilidade
do seu projeto.
Continuando o fluxo do npm install
Dada a necessária explicação sobre o package-lock.json, vamos voltar a falar agora sobre como o npm te entrega esses pacotes, do registry até a sua máquina...e para isso nada melhor que um fluxograma certo? (fluxogramas salvam vidas, pode confiar)
Neste fluxo ainda não estamos considerando peerDependencies no processo de instalação, uma breve explicação sobre peerDependecies:
- São dependências que os pacotes precisam que o consumidor também instale, porque ela não vai instalar sozinha.
- A partir do npm v7, o npm tenta instalar automaticamente as peerDependencies quando possível. Se houver conflito de versões que ele não consegue resolver, ele falha e pede instalação manual.
E após todo esse processo de: checagem, download, extração e disponibilização, os seus pacotes vão estar prontos para consumo em runTime e sua aplicação (esperamos todos nós) vai estar funcional.
Estamos certos em cruzar os dedos toda vez que rodamos um npm i
, afinal de contas olha quantos processos são executados por "de baixo do capô", e quase um jogo de azar no qual sempre torcemos pelo nosso gerenciador de pacotes nos salvar de mais algum bug que vai nos custar horas.
E você? já tomou algum erro de peerDependecies ou de ETARGET?
Top comments (0)