Como gerenciar dependências entre projetos Node.js

Quando estamos trabalhando em diversos projetos com Node.js, seja qual for a natureza deles, é muito comum haver a necessidade de compartilhamento de módulos para evitar duplicação de código e facilitar a manutenção futura.

Não vou falar aqui de publicação no NPM e nem no GitHub, que seriam duas alternativas bem viáveis, muito profissionais (principalmente NPM por permitir versionamento de módulos) e  também e as mais fáceis de fazer, mas que no entanto exigem que você tenha os seus módulos compartilhados como públicos (free) ou como privados, mas pagando para o NPM.

Entendo aqui que você está usando uma abordagem monorepo para o versionamento do seu projeto, ou seja, guarda todos eles em um mesmo repositório.

E embora você possa fazer esse referenciamento de dependências simplesmente usando o require e passando o caminho relativo “indo buscar” o módulo em questão (o que costuma gerar caminhos gigantescos), existem outras alternativas, sendo que o intuito do post de hoje é justamente mostrá-las.

Ah, também não vou entrar em alternativas mais complexas de fazer gestão de monorepo usando Lerna ou Rush. Vou tentar abordagens mais light e que podem resolver o seu problema, como resolveram o meu.

NPM LINK

o NPM oferece uma solução usando NPM LINK, um utilitário e linha de comando que cria links simbólicos, os famosos atalhos, de um módulo para outro.

Para fazer um teste de NPM LINK, vamos criar primeiro a estrutura em questão, para você ver na prática.

Primeiro, crie um projeto commons e dentro dele um arquivo math.js, como abaixo.

Agora, rode um npm init nesse commons e altere o package.json criado para incluir uma instrução de privacidade em relação à publicação no NPM.

Pronto. Acabamos de criar nosso módulo que vai ser compartilhado entre diferentes projetos.

O próximo passo, é criamos outro projeto (ou vários, se quiser), um api-a, colocar um index.js dentro dele e rodar um npm init dentro. Só para deixar tudo inicializado.

Agora, vi linha de comando, entre no projeto api-a e execute o seguinte comando.

Isso irá criar um link simbólico do commons no node_modules do seu projeto api-a, ou seja, se você tiver um index.js como abaixo, ele vai funcionar como se o commons fosse um módulo instalado via NPM.

O problema do NPM LINK é produção. Como geralmente a gente publica os arquivos do projeto sem node_modules, você teria de incluir um script de recriação do link para que ele fosse recriado toda vez que publicar o projeto no servidor.

NODE_PATH

Outra forma de resolver este problema de compartilhamento de módulos entre diferentes projetos é setando a variável de ambiente NODE_PATH. Esta variável define caminho(s) que o Node.js irá procurar por dependências além da node_modules.

Ou seja, se você está usando um módulo que não está na node_modules e sim em um outro projeto compartilhado, basta setar via .env ou na execução da aplicação a variável NODE_PATH apontando para esta pasta de módulos compartilhados.

Abaixo, exemplo considerando que a commons está um nível acima do projeto api-a e que estamos na pasta do mesmo (sem arquivo .env).

Isso vai fazer com que possamos chamar os módulos da commons pelo nome, sem usar caminho relativo e mesmo assim o Node.js vai achar eles.

Com arquivo .env fica ainda mais fácil né, porque é só configurar o NODE_PATH nele e carregá-lo na inicialização do projeto, sendo que recomendo fazer isso via linha de comando, como abaixo (considerando .env na raiz do projeto).

Curso Node.js e MongoDB
Curso Node.js e MongoDB

TypeScript

Agora, se você está usando TypeScript, nenhuma das alternativas anteriores irá funcionar. Pelo menos não facilmente. Isso porque o TypeScript verifica já em tempo de desenvolvimento os módulos e caminhos utilizados, o que gera erros de compilação que te impedem de executar a aplicação.

Para resolver isso no TypeScript exigirá uma série de passos adicionais.

O primeiro deles é instalar dois pacotes no seu projeto.

Depois, vá no seu tsconfig.json e ajuste quatro configurações:

A primeira configuração é apenas a definição da pasta onde ficarão os .JS compilados a partir dos .TS, provavelmente você já tem esta configuração na sua aplicação TypeScript.

A segunda configuração diz a URL base para resolução de caminhos de arquivos e é pré-requisito para a segunda configuração, só por isso defini ela (usei . que quer dizer “pasta atual”).

A terceira configuração define aliases (apelidos) para os caminhos relativos que queremos mapear os nossos módulos compartilhados. Aqui estou dizendo que todos módulos que começarem com commons/ na verdade devem ser resolvidos para ../commons/ (ou seja, estão um nível acima em relação a URL base).

E por último, para que não dê erro em tempo de desenvolvimento, a configuração rootDirs diz pro TypeScript onde estão todos os arquivos .TS necessários para a correta interpretação (em tempo de dev) e compilação do projeto como um todo. Assim, além de olhar na pasta raiz do projeto, o TS vai olhar na pasta commons também.

Feito essas configurações, agora é hora de ajustar nossos scripts de inicialização no package.json, para que:

  • o ts-node suba a aplicação ao invés do node normal;
  • o tsconfig-paths resolva os caminhos dos módulos baseados nos aliases;

O seu package.json deve ficar semelhante a esse:

Assim, quando você rodar npm run dev, vai executar o seu projeto a partir do index.ts e quando rodar npm start, vai compilar tudo e rodar o seu projeto a partir do arquivo index.js (compilado).

Use o npm run dev em tempo de desenvolvimento e o npm start em produção.

Espero que isso seja tão útil para você quanto foi pra mim, especialmente este último.

Um abraço e sucesso.

Quer fazer um curso de full-stack JS comigo? Clique no banner abaixo!

Curso FullStack
Curso FullStack