Published on

NodeJS - Understanding `require`,`exports` and `module.exports`

Authors
  • avatar
    Name
    Jonathan Juliani
    Twitter

Quick note: work in progress on this translation

Não é Exatamente Tudo a Mesma Coisa

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.

mind blow image

Isso acontece com todo mundo, e ao meu ver um bom DEV não precisa lembrar de tudo sempre, ele precisa saber procurar e utilizar as informações corretas que achamos na internet nos momentos e ocasiões certas, mas nem sempre achamos claramente o que queremos e ainda temos que ir no escuro ou adaptar para nossos casos de uso. Por isso estou aqui escrevendo, assim eu lembro, e também tenho um lugar para consultar em caso de branco na memória, e consigo te ajudar se você está na mesma que eu.

Você precisa ter o básico de conhecimento em Javascript e NodeJS para acompanhar pois não vou ensinar aqui como criar/executar códigos.

Então vamos lá, quando você quer exportar algo no Node para usar em outro lugar você sem querer está criando um módulo (module), e 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 = 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:

var v4 = require('./v4')
var uuid = v4
module.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"

mind blow image

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 = 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?

Conte-me mais Sobre Modules

Existe algumas maneiras diferentes pra isso, vou mostrar elas, mas antes precisamos entender o "module". Sempre que você cria um arquivo JS para o Node, fica "escondido" uma variável no arquivo chamada como? Adivinha…?!

Chamada "module", e é por isso que conseguimos realizar o "module.exports" sem precisar declarar uma var module = X porque "module" já esta declarado.

Se liga nesse código e se possível, faz aí pra acompanhar o raciocínio por que agora eu:

Vou te provar que existem coisas escondidas no teu arquivo.JS que você não Vê

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? …. Éhhhhhhh…… nops…. dúvida? tenta aí!

> node index.js
(function (exports, require, module, __filename, __dirname) { const module = require('./module')
SyntaxError: Identifier 'module' has already been declared

Uai, como, onde, mas oque?

É isso mesmo jovem inocente, você declarou uma const chamada module, mas isso já existe! Vamos a um outro teste para te provar, 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?

É meu caro amigo jovem inocente, além do "module", "o Node esconde" outra "linha de código" criando um objeto chamado "exports" dentro dos nossos arquivos JS. 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 = {}

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.js

Depois de executar vamos ter os resultados dos 3 console.log, e o que aparece? 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)
}

Então, vamos a umas dicas de com o que você realmente deve se preocupar:

O "exports" aponta para o "module.exports" e esse cara se comporta como um objeto JSON javascript, então quando utilizamos a sintaxe:

exports.NomeDaFunction
exports.NomeDaVar
module.exports.NomeDaFunction
module.exports.NomeDaVar

Dessa forma acima estamos criando um novo atributo dentro do objeto exports, o que não interfere nos atributos anteriores, o que temos de tomar cuidado é se utilizarmos o "=", pois aí estamos fazendo um novo assign ou seja, estamos dando um novo valor por completo ao objeto "exports" substituindo tudo que existir lá dentro pelo que está depois do "=", vamos ao teste, 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"

E agora vamos rodar o index.js, quando o index.js imprimir o module, ele terá nossas funções:

> Module {
//linhas
exports:
{ digaOi: [Function],
oQueTemNoModule: [Function],
oQueTemNoExports: [Function] },
//mais linhas
}

Mas quando imprimir o "exports" teremos algo diferente:

> Utilizando o Igual no Exports

Uai, pq acontece isso Jon?

Pensa que você tem no teu "oi.js" as linhas:

var module = {
//bla bla bla
exports: {}
// bla bla bla
}
var exports = module.exports = {}

E depois você fez isso no final do arquivo:

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ê colocar uma função apenas no exports ( atribuindo com o "=" ) você não vai ter acesso a ela quando você fizer um require('') desse modulo em outro lugar do seu código, vamos a um novo teste, coloca isso no seu "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 Exports

Isso 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
}

Acredito que deu para explanar e entender como funciona o require () e o module.exports e exports.

Então Jon como vou fazer para exportar minhas funções e utilizar no require?

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.

Faltou algo? Deu uma zika aí? Falei/errei em alguma coisa? Deixa um comentário aí que a gente arruma e se ajuda, afinal não sou mestre nem expert em nada, estou tentando me ajudar e ajudar você, tentando explicar de um jeito fácil, simples, mas detalhado como funciona algumas coisas.