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.
1 2 3 |
module.exports.math = (val1, val2) => { return val1 + val2; } |
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.
1 2 3 4 5 6 7 8 9 10 11 |
{ "name": "commons", "version": "1.0.0", "private": true, "description": "", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } |
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.
1 |
npm link ../commons/ |
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.
1 2 3 4 |
const math = require("commons/math"); console.log("API A"); const resultado = math(1,2); console.log(resultado); |
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).
1 |
NODE_PATH=../commons node index |
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).
1 |
node -r dotenv/config ./index |
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.
1 |
npm i ts-node tsconfig-paths |
Depois, vá no seu tsconfig.json e ajuste quatro configurações:
1 2 3 4 5 6 |
"outDir": "./dist/", "baseUrl": ".", "paths": { "commons/*": ["../commons/*"] }, "rootDirs": ["./", "../commons"], |
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:
1 2 3 4 |
"scripts": { "start": "tsc && ts-node -r tsconfig-paths/register ./dist/index", "dev": "ts-node -r tsconfig-paths/register ./index" }, |
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!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.