npm e o ecossistema de pacotes
Esta aula explora o npm, o gerenciador de pacotes padrão do Node.js, cobrindo o arquivo package.json, a instalação de dependências, a criação de scripts e o versionamento semântico, com exemplos práticos e exercícios para fixação.
O npm (Node Package Manager) é o gerenciador de pacotes padrão do ecossistema JavaScript, especialmente para o Node.js. Ele permite instalar, compartilhar e gerenciar bibliotecas e ferramentas de código aberto, facilitando o reuso de código e a colaboração em projetos. Com o npm, você pode declarar as dependências do seu projeto no arquivo package.json, instalar pacotes com um simples comando e executar tarefas automatizadas através de scripts.
Nesta aula, vamos explorar os conceitos fundamentais do npm: o que é o package.json, como instalar dependências, como criar e usar scripts personalizados e como funciona o versionamento semântico (SemVer), que é vital para manter a compatibilidade entre versões de pacotes.
package.json
O package.json é o coração de qualquer projeto Node.js. Ele é um arquivo JSON que contém metadados sobre o projeto, como nome, versão, descrição, ponto de entrada, licença e, crucialmente, as dependências do projeto. Esse arquivo permite que outros desenvolvedores (ou você mesmo em outro ambiente) repliquem o ambiente de desenvolvimento com facilidade, pois todas as dependências estão listadas com suas versões.
O package.json pode ser criado manualmente ou através do comando npm init, que guia o usuário na criação de um arquivo básico. A seguir, um exemplo de um package.json típico:
{
"name": "meu-projeto",
"version": "1.0.0",
"description": "Um projeto de exemplo para aprender npm",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Erro: nenhum teste especificado\" && exit 1"
},
"keywords": ["npm", "exemplo"],
"author": "Seu Nome",
"license": "ISC",
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}Os campos mais importantes são dependencies e devDependencies. O primeiro lista os pacotes necessários para o funcionamento do projeto em produção; o segundo lista pacotes usados apenas durante o desenvolvimento, como ferramentas de teste ou transpiladores. O campo scripts define comandos que podem ser executados com npm run <script>, facilitando tarefas como iniciar o servidor ou rodar testes.
Além disso, o package.json é essencial para o controle de versão: quando você compartilha seu projeto, outras pessoas podem executar npm install para instalar todas as dependências listadas, garantindo que o projeto funcione de forma consistente.
Instalando dependências
Instalar dependências é uma das tarefas mais comuns no npm. O comando npm install <nome-do-pacote> baixa o pacote do registro npm e o adiciona ao package.json automaticamente. Existem variações: npm install --save (padrão) adiciona ao dependencies, enquanto npm install --save-dev adiciona ao devDependencies. Para instalar todas as dependências listadas no package.json (por exemplo, após clonar um repositório), basta executar npm install sem argumentos.
Vamos ver exemplos práticos:
# Instala uma dependência de produção
npm install express
# Instala uma dependência de desenvolvimento
npm install --save-dev nodemon
# Instala uma versão específica
npm install lodash@4.17.21
# Instala um pacote globalmente (para ferramentas de linha de comando)
npm install -g eslint
# Remove um pacote
npm uninstall express
# Lista os pacotes instalados
npm listQuando você instala um pacote, o npm cria uma pasta node_modules contendo o pacote e suas dependências transitivas. Essa pasta não deve ser versionada; em vez disso, usamos o package-lock.json (gerado automaticamente) para travar as versões exatas instaladas, garantindo reprodutibilidade. O package-lock.json deve ser commitado no controle de versão.
Além disso, o npm oferece o comando npm ci, que instala as dependências exatamente conforme o package-lock.json, sendo mais rápido e determinístico que npm install em ambientes de CI/CD.
Scripts
Os scripts no package.json são atalhos para comandos que você usa com frequência. Eles permitem automatizar tarefas como iniciar o servidor, rodar testes, buildar o projeto, etc. A seção scripts é um objeto onde cada chave é o nome do script e cada valor é o comando a ser executado no shell.
Para executar um script, use npm run <nome-do-script>. Existem alguns scripts especiais que podem ser executados sem o run, como npm start e npm test, pois são convenções populares.
Exemplo de scripts no package.json:
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"build": "webpack --mode production",
"test": "jest"
}
}Para executar esses scripts:
npm start # executa "node index.js"
npm run dev # executa "nodemon index.js"
npm run build # executa "webpack --mode production"
npm test # executa "jest"Os scripts também podem ser combinados usando operadores de shell como && (executa o próximo somente se o anterior for bem-sucedido) e ||. Por exemplo, "lint": "eslint . && prettier --check .".
Uma vantagem dos scripts é que eles são portáveis: você não precisa lembrar de comandos complexos, apenas npm run <script>. Além disso, o npm adiciona os binários locais (em node_modules/.bin) ao PATH durante a execução dos scripts, permitindo usar ferramentas como webpack ou jest sem instalá-las globalmente.
Versionamento semântico
O versionamento semântico (SemVer) é um padrão para atribuir versões a pacotes de software, visando comunicar o tipo de mudanças em cada lançamento. O formato é MAJOR.MINOR.PATCH, por exemplo, 1.4.2. Cada parte tem um significado específico:
- MAJOR: incrementado quando há mudanças incompatíveis com versões anteriores (breaking changes).
- MINOR: incrementado quando novas funcionalidades são adicionadas de forma compatível com versões anteriores.
- PATCH: incrementado quando correções de bugs são feitas sem adicionar novas funcionalidades.
Além disso, existem sufixos como -alpha, -beta, -rc para pré-lançamentos, e metadados de build como +build.
No npm, você pode especificar faixas de versão nos dependencies usando operadores. Os mais comuns são:
^1.4.2: aceita versões compatíveis com 1.x.x, ou seja, qualquer versão >= 1.4.2 e < 2.0.0 (mantém o MAJOR).~1.4.2: aceita versões >= 1.4.2 e < 1.5.0 (mantém o MINOR).1.4.2: exatamente a versão 1.4.2.>=1.4.2 <2.0.0: faixa explícita.*oulatest: qualquer versão (não recomendado para produção).
Exemplo de utilização:
{
"dependencies": {
"express": "^4.18.2",
"lodash": "~4.17.21",
"react": "18.2.0"
}
}O npm respeita essas faixas ao instalar pacotes, mas o package-lock.json trava as versões exatas que foram instaladas, garantindo que todos os ambientes usem as mesmas versões.
Entender SemVer é crucial para gerenciar dependências com segurança, pois mudanças MAJOR podem quebrar seu código, enquanto MINOR e PATCH geralmente são seguras. Ao publicar seus próprios pacotes, seguir SemVer ajuda a comunidade a confiar nas atualizações.
Boas práticas
Ao trabalhar com npm, algumas boas práticas incluem:
- Commitar o
package-lock.jsonpara garantir instalações reprodutíveis. - Usar
npm ciem pipelines de CI em vez denpm install. - Manter as dependências atualizadas, mas com cautela: use
npm updatepara atualizar dentro das faixas, enpm outdatedpara ver versões disponíveis. - Evitar instalar pacotes globalmente, preferindo usá-los como dependências de desenvolvimento.
- Documentar os scripts principais no README do projeto.
- Verificar a integridade dos pacotes com
npm auditpara vulnerabilidades de segurança.
Referências
- npm install - Documentação oficial
- npm scripts - Documentação oficial
- Semantic Versioning (semver.org)
- package.json - Documentação oficial
- Introdução ao npm - Node.js
- npm ci - Documentação oficial
Exercícios
- Exercício 1: Crie um novo projeto npm chamado
meu-appcom o comandonpm init -y. Depois, adicione a dependênciaexpress(versão 4.18.2) e a dependência de desenvolvimentonodemon. Ao final, exiba o conteúdo dopackage.json. - Exercício 2: No
package.jsondo projeto, adicione um script chamadodevque executenodemon index.js. Depois, executenpm run deve explique o que acontece. - Exercício 3: Explique a diferença entre
^4.17.0e~4.17.0em uma faixa de versão do npm. - Exercício 4: O que é o
package-lock.jsone por que é importante commitá-lo no controle de versão? - Exercício 5: Suponha que você precise instalar o pacote
lodashna versão 4.17.21, mas apenas para desenvolvimento (para usar em testes). Qual comando você usaria?
npm init -y
npm install express@4.18.2
npm install --save-dev nodemonO package.json terá as dependências adicionadas.package.json:"scripts": {
"dev": "nodemon index.js"
}Ao executar npm run dev, o npm executa o comando nodemon index.js, que inicia o servidor Node e o reinicia automaticamente a cada alteração no código.^4.17.0 permite qualquer versão >= 4.17.0 e < 5.0.0 (mantém o MAJOR). ~4.17.0 permite >= 4.17.0 e < 4.18.0 (mantém o MINOR). Ou seja, ^ é mais permissivo, aceitando novas funcionalidades, enquanto ~ só aceita correções de bugs.package-lock.json registra as versões exatas de cada pacote instalado, incluindo dependências transitivas. Commitá-lo garante que todos os desenvolvedores e ambientes de CI usem exatamente as mesmas versões, evitando inconsistências e bugs difíceis de reproduzir.npm install --save-dev lodash@4.17.21. Isso adiciona ao devDependencies a versão exata.