NodeJS - Entendendo de vez `require`,`exports` e `module.exports`
require, exports e module.exports no Node.js explicados com exemplos e erros comuns. Entenda CommonJS de vez, sem decorar.
Jonathan Juliani
Engenheiro de Software Sr - Full Stack · WithClutch Inc. | Juliani Tecnologia
Node.jsJavaScript
require vs exports vs module.exports: não é tudo igual
Se você é Dev/Analista de Sistemas e mexe com códigos diariamente provavelmente já procurou por aí como exportar uma function, constante, algo do tipo de um JS seu para utilizar em outro JS, e ficou na dúvida, ou deu aquele branco que acontece com quem mexe em diversos arquivos, linguagens, etc no dia a dia.
Isso acontece com todo mundo. Um bom dev não precisa decorar tudo — precisa saber onde buscar e como aplicar. Mas nem sempre a internet entrega uma resposta limpa, e a gente acaba adaptando no escuro.
Bora resolver isso de vez: vai ter código, vai ter erro proposital, e no final você vai entender exatamente o que acontece quando o Node lê um require.
O que você vai aprender:
Como require() funciona por baixo — o que o Node realmente faz quando encontra essa linha
A diferença entre exports, module.exports e quando cada um quebra
Por que module e exports já existem no seu arquivo sem você declarar — as variáveis ocultas do Node
O gotcha do = direto que causa bug silencioso em produção
As formas mais usadas de exportar funções e objetos — e qual a mais limpa
Pré-requisitos: familiaridade com JavaScript básico — funções, objetos e como executar um arquivo .js com o Node. Não vou ensinar a criar ou executar o projeto do zero.
require(): da pasta ao valor que chega no seu código
Inscreva-se no Ledger da Engenharia
Receba notas de arquitetura e performance na sua caixa de entrada. Mesma lista do convite—inscreva-se aqui quando quiser.
Quando você quer exportar algo no Node para usar em outro lugar, você sem querer está criando um módulo (module). O que é isso? Nada mais do que um lugar onde você tem um código que pode ser reutilizado em outros lugares, como se fosse um plugin de algum software, ou mesmo uma lib/package de qualquer linguagem de programação, onde importamos no nosso projeto e começamos a utilizar.
Um exemplo, faz de conta que estamos em um projeto e queremos usar o módulo/package uuid:
código
const uuid = require('uuid')
Se você ir na pasta node_modules/uuid/index.js verá que existe um module.exports lá:
código
// … resto do códigovar uuid = v4module.exports = uuid
Essa linha diz ao Node:
"Olha quando o fulano usar o require('uuid') você manda a var uuid pra ele"
Ou seja, sempre que usamos o require('alguma coisa') o node procura dentro do 'alguma coisa' o module.exports e retorna o valor atribuído a esse cara pra nós.
E no mesmo exemplo acima, podemos observar que dentro do módulo do uuid, que eles usam outros requires/exports:
código
var v4 = require('./v4')var uuid = v4module.exports = uuid
Ou seja, quando solicitamos o uuid, o Node vai lá e retorna pra gente o valor da var uuid, que por um acaso é o valor v4, mas esse valor v4 é uma variável que tem um "require"
Ou seja, no arquivo v4.js existe um module.exports e o valor disso é atribuído a var v4 e só depois é atribuído para a var uuid e finalmente quando fazemos o require('uuid') temos o resultado de tudo isso, que no fim das contas é o valor do module.exports do arquivo v4.js.
Ufa… quanta coisa pra pegar um negocinho de um arquivo… mas o Node faz isso tão rápido que nem percebemos, e mais pra frente vamos ver o porque de muitos lugares utilizarem dessa forma, fazendo vários exports de vários arquivos diferentes.
Ta Jon e o que tem no arquivo v4.js?
R:
código
//trecho de código e:module.exports = v4
Ou seja, quando fazemos o require('uuid') o valor que recebemos é a function v4()...
Tá entendi até agora, mas porque tudo isso pra pegar uma função? Não poderia estar no primeiro index.js da pasta do uuid?
Sim poderia, mas imagine que você tenha 50 funções (function) não ia ficar legal todas no index.js sendo exportadas, ia ser um código ruim de ler e de dar manutenção com grande probabilidade de erros acontecerem (tipo alguém mexer na função errada), assim a maioria dos lugares, pessoas, empresas, que tem libs/modules JS separam e desacoplam as funções em várias partes, cada uma com sua responsabilidade, e por fim disponibilizam no index, para que o usuário desse module decida qual vai utilizar.
Entendi Jon, então agora, como que faço um module? Como faço um export?
Variáveis ocultas do Node: o module e o exports que você nunca declarou
Sempre que você cria um arquivo .js pro Node, duas variáveis já existem no arquivo sem você declarar: module e exports. É por isso que module.exports funciona sem um var module = … no topo.
Se liga nesse código e, se possível, faz aí pra acompanhar o raciocínio:
Cria um arquivo chamado "oi.js" com esse conteúdo ( o conteúdo eu explico depois, prometo, então me acompanha e faz aí, e lembra de ter uma pasta vazia para nossos testes não darem ruim com outros arquivos teus * .js principalmente):
código
exports.digaOi = () => { console.log('Oi!')}
Dai agora cria um index.js com esse conteúdo ( na mesma pasta ):
código
const module = require('./oi')module.digaOi()
Se executarmos esse código, será que vai imprimir "Oi" no console? Nops. Dúvida? Tenta aí!
código
> node index.js(function (exports, require, module, __filename, __dirname) { const module = require('./module')SyntaxError: Identifier 'module' has already been declared
SyntaxError: Identifier 'module' has already been declared
Você declarou uma const chamada module, mas esse nome já existe no wrapper do Node. Vamos a outro teste — mude o index.js para:
código
module = require('./oi')module.digaOi()
E vamos rodar…
código
> node .\index.js> Oi!
Rapazzzz….. num é que funfo! O module realmente ta escondido, e quando atribuímos a função pra ele (sem uma dupla declaração) ele imprimiu bonito no console.
A mas Jon eu estudei javascript e sei que você não precisa declarar escrevendo o "const" (serve pra let, var) então ali você está criando uma const module do mesmo jeito!!!
Hum... sim, só que não. Primeiro porque quando declaramos explicitamente com o "var" na frente, deu duplicidade falando que a variável já existe, e segundo, vou te mostrar a real var module a seguir, e mais outra var escondida.
Pensou em alguma outra var que pode estar escondida já? Se pensou em "exports" quer dizer que você ta entendendo o que to falando, joinha pra ti! Vamos ver isso na prática? Pega o index.js e coloca o seguinte código:
código
console.log(module) // ou console.log({module})console.log(exports) // ou console.log({exports})
E agora, vamos executar o index.js:
código
> node index.js> Module { // algumas linhasexports: {}, // outras linhas}>
Nós imprimimos no console o objeto (var) "module" e o objeto (var) "exports" !
Uai, mas o que? Objeto exports? Da onde saiu?
Além do module, o Node injeta um objeto exports no seu arquivo. Mas não é nenhum objeto qualquer, ele aponta para o objeto "exports" que vemos ali dentro do objeto "module".
Então imagine que temos sempre o seguinte nos nossos arquivos JS:
código
var module = { … exports: {} //esse objetão aí em cima com o exports dentro…}var exports = module.exports = {}
[IMAGEM: diagrama mostrando exports e module.exports apontando para o mesmo objeto vazio, e uma seta separada quando exports = X quebra a referência | alt: "Diagrama do fluxo entre module, exports e module.exports no CommonJS do Node.js"]
Ou seja o "exports" recebe o valor do que está dentro de "module.exports" que no início de tudo é um objeto vazio, a não ser que a gente dê algum valor pra ele, aí agora dá pra explicar aquele código do "oi.js" que eu falei que ia explicar lembra?
O exports é um objeto JSON javascript (mas pode assumir a forma de uma string direto ou de um número, caso atribuirmos esse tipo de valor diretamente ao exports), sendo assim estamos dizendo ali que o exports vai ter um novo atributo chamado digaOi que é uma function, viu que simples?!
É por isso que em teoria podemos utilizar diretamente o "exports" em alguns casos, sem precisar utilizar a sintaxe "module.exports" para exportar algo, vou te mostrar tudo nesse outro teste, vamos mudar nosso "oi.js" e deixar assim:
oi = require('./oi')console.log(oi.digaOi())console.log(oi.oQueTemNoModule())console.log(oi.oQueTemNoExports())
E lá vamos nós novamente, executar o node:
código
node index.js
Depois de executar vamos ter os resultados dos 3 console.log, e o que aparece? O nosso Oi!
código
Oi!
Aparece o nosso objeto "module" com o objeto exports dentro dele com as functions que exportamos e mais algumas outras informações não relevantes agora:
Mas quando imprimir o exports direto no arquivo, aparece outra coisa:
código
Utilizando o Igual no Exports
[IMAGEM: screenshot do terminal mostrando module.exports com funções e exports local com string após exports = | alt: "Saída no Node após atribuir string direto em exports, quebrando a referência com module.exports"]
Uai, pq acontece isso Jon?
Pensa que no oi.js você tinha, no fundo, algo equivalente a:
Ou seja, você tirou o valor do "exports" que era "module.exports" e colocou o que você queria, no caso a string.
Isso, em nosso contexto de testes, pode não fazer diferença, mas você deve ter em mente que a função "require('')" retorna tudo o que está dentro de "module.exports" aí você pode se confundir e começar a ter um problema.
Se você atribuir com = só no exports, o require() em outro arquivo não vê essa função — ele lê module.exports. Teste no oi.js:
código
exports.digaOi = () => { console.log('Oi!')}module.exports.oQueTemNoModule = () => { console.log(module)}module.exports.oQueTemNoExports = () => { console.log(exports)}module.exports = 'Utilizando o Igual no Module Exports'
Agora, tenta executar o nosso "index.js" do jeito que ele está, que chama as funções "digaOi()", "oQueTemNoModule()" e "oQueTemNoExports()".
Se tentar, vai ver que teremos um erro, pois não existe essas funções no "module.exports" pois elas foram substituídas pelo valor da ultima linha que é uma string.
Ou seja, temos uma string no "module.exports", então se a gente alterar o nosso "index.js" para:
código
oi = require('./oi')// console.log(oi.digaOi())// console.log(oi.oQueTemNoModule())// console.log(oi.oQueTemNoExports())console.log(oi)
Aí veremos no console após a execução o seguinte:
código
> node ./index.js> Utilizando o Igual no Module Exports
Isso acontece porque nosso objeto "module.exports":
código
> Module {//linhasexports:{ digaOi: [Function],oQueTemNoModule: [Function],oQueTemNoExports: [Function] },// mais linhas}
Foi substituído por:
código
> Module {//linhasexports: "Utilizando o Igual no Module Exports",//mais linhas}
Deu pra ver: require() sempre devolve o que está em module.exports, não o que você achou que colocou em exports depois de um =.
Então Jon, como exporto minhas funções de um jeito que o require enxerga?
module.exports na prática: padrões que você vai ver em libs
Existem diversas formas, se quiser me acompanhe e comente ou apague tudo no seu "oi.js" e coloque isso:
Claro que se você tiver funções, ou qualquer outra coisa mais complexas e grandes, seria melhor ainda separar por arquivo (igual vimos no exemplo do uuid) e ir fazendo os devidos exports e requires, para utilizar seus módulos.
Assim você separa suas functions, e os seus exports e deixa cada coisa no seu lugar, lembrando que dessa forma deve-se utilizar o:
código
module.exports = { exemplo: suaFunction() }
e não apenas o:
código
exports = { exemplo: suaFunction() }
pois não estamos fazendo a sintaxe com "exports.NomeDaFunction" para cada uma das functions no código igual eu já expliquei mais pra cima.
E por fim cada um programa do jeito que quer (nem sempre é bom ou certo), e com o seu próprio padrão, e também da forma que acha mais fácil de se achar nos próprios códigos da vida, então é apenas a minha opinião, uma dica, que eu uso e que vejo muitos lugares, libs e projetos.
TL;DR — Referência rápida require / exports
Conceito
O que é
Pegadinha
require('algo')
Importa o module.exports do arquivo/módulo
Sempre retorna module.exports, não o exports diretamente
module.exports
O objeto que o require vai receber
Atribuir com = substitui tudo que veio antes
exports.X = ...
Atalho para module.exports.X = ...
Funciona enquanto não fizer exports = X (com = direto)
exports = X
⚠️ Quebra a referência com module.exports
Use sempre module.exports = X quando quiser substituir o objeto
module e exports
Variáveis ocultas injetadas pelo Node em todo .js
Não precisam ser declaradas — já estão lá
Próximos passos
Agora que você entende o que acontece sob o capô de um require:
Estruturas de Dados em Node.js: Ver esse modelo de módulo em ação em projetos maiores — a série usa exatamente essa separação por arquivo: Estruturas de Dados: um resumo das mais usadas
Antes de fechar
Faltou algo? Deu uma zika? Falei/errei em alguma coisa? Deixa nos comentários — a gente arruma junto.
Se esse artigo te salvou de um bug confuso, compartilha com alguém que tá no mesmo barco. É o jeito mais rápido de fixar qualquer coisa.