jonathanjuliani
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.
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.exportse quando cada um quebra - Por que
moduleeexportsjá 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 node_modules ao valor que chega no seu código
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:
const uuid = require('uuid')Se você ir na pasta node_modules/uuid/index.js verá que existe um module.exports lá:
// … resto do código
var uuid = v4
module.exports = uuidEssa 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:
var v4 = require('./v4')
var uuid = v4
module.exports = uuidOu 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:
//trecho de código e:
module.exports = v4Ou 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):
exports.digaOi = () => {
console.log('Oi!')
}Dai agora cria um index.js com esse conteúdo ( na mesma pasta ):
const module = require('./oi')
module.digaOi()Se executarmos esse código, será que vai imprimir "Oi" no console? Nops. Dúvida? Tenta aí!
> node index.js
(function (exports, require, module, __filename, __dirname) { const module = require('./module')
SyntaxError: Identifier 'module' has already been declaredSyntaxError: 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:
module = require('./oi')
module.digaOi()E vamos rodar…
> 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:
console.log(module) // ou console.log({module})
console.log(exports) // ou console.log({exports})E agora, vamos executar o index.js:
> node index.js
> Module {
// algumas linhas
exports: {},
// 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:
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:
exports.digaOi = () => {
console.log('Oi!')
}
exports.oQueTemNoModule = () => {
console.log(module)
}
exports.oQueTemNoExports = () => {
console.log(exports)
}E no nosso "index.js" vamos fazer o seguinte:
oi = require('./oi')
console.log(oi.digaOi())
console.log(oi.oQueTemNoModule())
console.log(oi.oQueTemNoExports())E lá vamos nós novamente, executar o node:
node index.jsDepois de executar vamos ter os resultados dos 3 console.log, e o que aparece? O nosso Oi!
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:
> Module {
//algumas linhas
exports:
{ digaOi: [Function],
oQueTemNoModule: [Function],
oQueTemNoExports: [Function] },
// mais linhas
}E tamos também o nosso "objeto exports" ( que é o mesmo do module nesse caso) e que também está com as functions que exportamos:
> { digaOi: [Function],
oQueTemNoModule: [Function],
oQueTemNoExports: [Function] }Então Jon porque não usamos "module.exports" em tudo?
Você pode fazer isso também, se substituir o "oi.js" com essas linhas de código abaixo, você terá o mesmo resultando rodando o "index.js":
module.exports.digaOi = () => {
console.log('Oi!')
}
module.exports.oQueTemNoModule = () => {
console.log(module)
}
module.exports.oQueTemNoExports = () => {
console.log(exports)
}Tá mas Jon e se eu fizer um de cada? Tipo "module.exports.X" e "exports.Y"?
Cara, larga de ser chato, mas vai funcionar do mesmo jeito, substitui o "oi.js" pelo código abaixo e roda mais uma vez o "index.js":
exports.digaOi = () => {
console.log('Oi!')
}
module.exports.oQueTemNoModule = () => {
console.log(module)
}exports = X: o bug silencioso que a doc raramente explica
O exports aponta para o module.exports. Enquanto você adiciona propriedades com ., tudo certo:
exports.NomeDaFunction
exports.NomeDaVar
module.exports.NomeDaFunction
module.exports.NomeDaVarCom = você substitui o objeto inteiro — e aí a referência com module.exports quebra. Coloca isso no oi.js:
exports.digaOi = () => {
console.log('Oi!')
}
module.exports.oQueTemNoModule = () => {
console.log(module)
}
module.exports.oQueTemNoExports = () => {
console.log(exports)
}
exports = 'Utilizando o Igual no Exports'Roda o index.js. Quando imprimir o module, ele ainda terá as funções:
Module {
exports: {
digaOi: [Function],
oQueTemNoModule: [Function],
oQueTemNoExports: [Function],
},
}Mas quando imprimir o exports direto no arquivo, aparece outra coisa:
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:
var module = { exports: {} }
var exports = module.exports = {}E no final do arquivo fez:
exports = 'Utilizando o Igual no Exports'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:
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:
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:
> node ./index.js
> Utilizando o Igual no Module ExportsIsso acontece porque nosso objeto "module.exports":
> Module {
//linhas
exports:
{ digaOi: [Function],
oQueTemNoModule: [Function],
oQueTemNoExports: [Function] },
// mais linhas
}Foi substituído por:
> Module {
//linhas
exports: "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
requireenxerga?
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:
exports.digaOi = () => {
console.log('Oi!')
}
exports.digaTudoBem = () => {
console.log('Tudo bem')
}Esse acima nós já vimos hoje, e agora abaixo utilizando o a sintaxe com "module" na frente, que também funciona:
module.exports.digaOi = () => {
console.log('Oi!')
}
module.exports.digaTudoBem = () => {
console.log('Tudo bem')
}E nada te impede de fazer assim que também vai funcionar:
module.exports = {
digaOi: () => {
console.log('Oi')
},
digaTudoBem: () => {
console.log('Tudo bem')
},
}Mas o que você mais vai encontrar, e seria mais interessante de fazer é dessa forma:
const digaOi = () => {
console.log('Oi!')
}
const digaTudoBem = () => {
console.log('Tudo bem')
}
module.exports = {
digaOi,
digaTudoBem,
}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:
module.exports = { exemplo: suaFunction() }e não apenas o:
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:
- ECMAScript moderno: O
import/exportdo ES6 convive com orequireno Node atual — veja as diferenças na prática: ECMAScript ES7 ao ES10: as features que você usa sem saber - 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.
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.
Artigos relacionados
Promises em JavaScript: do zero ao async/await
Promises e async/await em JavaScript do zero: estados, encadeamento, tratamento de erros e os gotchas que a documentação oficial passa batido.
Ler publicaçãoSpread e Rest em JavaScript: arrays, objetos e os gotchas de cópia rasa
Spread operator e rest em JavaScript: como funcionam em arrays, objetos e funções, e os gotchas de cópia rasa que todo dev encontra.
Ler publicação