Dia 3 — Módulos: import e export

Informatica · Conteudo · publicado em 30/09/2026
Dia 3 de 16

Módulos: import e export

Aula 1

require e module.exports

require e module.exports

CommonJS é o sistema de módulos do Node desde o começo, e continua sendo o

padrão de qualquer projeto sem "type": "module" no package.json. A ideia é

simples: um arquivo exporta algo, outro arquivo importa. Importar é puxar o

valor de outro arquivo para o escopo atual; exportar é decidir o que desse

arquivo os outros podem ver.

// somar.js
module.exports = function somar(a, b) { return a + b; };

// app.js
const somar = require('./somar');
console.log(somar(2, 3));

Caminho: ./ é o arquivo ao lado

O caminho do módulo é o que vai entre aspas no require, e ele tem três

formas. ./ ou ../ é caminho relativo, resolvido a partir do arquivo que

está chamando. Um nome sem ./ é procurado dentro de node_modules — e é

assim que require('fs') acha um módulo nativo e require('mysql2') acha um

pacote instalado.

ChamadaOnde procura
require('./sala')sala.js; se não existir, sala/index.js
require('../config/db')um nível acima, config/db.js
require('node:fs')módulo nativo do runtime
require('mysql2')dentro de node_modules

Um módulo de pacote é o terceiro caso: mysql2 não é um arquivo seu, é o que o

npm install copiou para node_modules e o que vem com package.json e

versão própria. É a diferença entre o módulo nativo (node:fs, compilado

dentro do binário, sem arquivo em disco) e o pacote, que é uma pasta no seu

projeto.

O prefixo node: é opcional e serve para separar o que é nativo do runtime do

que é pacote publicado por alguém: require('node:fs') e require('fs') são o

mesmo objeto. Um detalhe que confunde: require.resolve('node:path') não

devolve um caminho, devolve node:path — módulo nativo não tem arquivo em disco,

ele é compilado dentro do binário. Só pacote instalado devolve caminho de verdade.

module.exports e exports são o mesmo objeto

exports é um apelido de module.exports criado antes do arquivo rodar. Isso

vale enquanto module.exports não for reatribuído:

exports.salvar = () => {};   // acrescenta uma propriedade
module.exports = { salvar }; // substitui o objeto inteiro

Com as duas linhas, quem importa recebe { salvar } — o exports.salvar foi

escrito no objeto antigo, que ninguém mais vai ler. Por isso a regra prática: ou

o arquivo exporta um valor e usa module.exports = valor (uma função, uma

classe, um objeto), ou exporta várias coisas e usa exports.nome = valor do

começo ao fim, sem trocar de estilo no meio.

O módulo roda uma vez só

require executa o arquivo uma vez e guarda o resultado em require.cache.

Pedir o mesmo caminho de novo devolve a mesma referência, sem reexecutar. É por

isso que um estado guardado no nível do módulo sobrevive entre todos que

importam — e por isso que guardar conexão de banco em variável de módulo

funciona num script e vira vazamento num programa que deveria poder reiniciar

estado.

createRequire e require.resolve

require.resolve('mysql2') diz o caminho que o require carregaria, sem

carregar. createRequire(arquivo) monta um require ancorado naquele arquivo,

que é como se resolve um pacote a partir de um projeto diferente do diretório

atual.

O que o Node cria sozinho

require, module, exports, __dirname, __filename e process não vêm de

import nenhum: são variáveis que o Node cria para cada arquivo que executa.

__dirname é o diretório deste arquivo e process.cwd() é onde o comando

foi executado — são diferentes, e quem organiza pastas precisa saber qual dos dois

está usando.

Exemplo

'use strict';

// Exemplo da aula 1 do dia 3: CommonJS, o sistema de modulos do Node.
//
// `require` carrega um arquivo e devolve o que ele exportou. Duas formas fazem o
// mesmo arquivo mudar de valor: `module.exports = valor` substitui tudo, e
// `exports.nome = valor` acrescenta uma propriedade ao objeto. Sao o mesmo
// objeto enquanto nada for reatribuido, e e por isso que trocar um pelo outro no
// meio do arquivo tem consequencia.

const fs = require('node:fs');
const path = require('node:path');
const { performance } = require('node:perf_hooks');
const { createRequire } = require('node:module');

// --- 1. caminho: relativo, absoluto e de pacote ---
//
// `./` e caminho RELATIVO ao arquivo que esta chamando, e `../` sobe um nivel.
// Sem `./`, o Node procura no `node_modules` — e e assim que `require('fs')`
// acha o modulo nativo e `require('mysql2')` acha um pacote do `npm install`.
console.log('--- caminho ---');
console.log('relativo ao arquivo: ./aqui.js  ->', path.join(__dirname, 'aqui.js'));
console.log('subindo um nivel:    ../sala.js ->', path.join(__dirname, '..', 'sala.js'));
console.log("require('./sala') procura: sala.js, e se nao existir, sala/index.js");

// O prefixo `node:` diz "isto e nativo do runtime, nao pacote de ninguem".
// `require('node:fs')` e `require('fs')` devolvem o mesmo objeto, o que se prova
// comparando os dois `require` e nao os caminhos.
console.log('');
console.log('--- nativo (node:) contra pacote (node_modules) ---');
console.log("require('node:fs') === require('fs'):", fs === require('fs'));
console.log("o prefixo node: e opcional e nao muda nada:",
  require('node:fs') === require('fs'));
// Um modulo nativo NAO tem arquivo: e compilado dentro do binario do Node. Por
// isso o `resolve` dele devolve o proprio nome, e nao um caminho no disco.
console.log("require.resolve('node:path') devolve:", require.resolve('node:path'));
console.log('(e um pacote instalado devolve um caminho de verdade:');
console.log('  ' + createRequire(__filename).resolve('mysql2').split('node-mysql')[1] + ')');

// --- 2. o cache: o modulo roda uma vez so ---
//
// `require` executa o arquivo uma vez e guarda o resultado no cache do processo.
// Pedir o mesmo caminho de novo devolve a MESMA referencia, sem reexecutar. E por
// isso que um estado guardado no nivel do modulo sobrevive entre todos que
// importam — e por isso que guardar conexao de banco em variavel de modulo
// funciona num script e vaza memoria num programa de verdade.
console.log('');
console.log('--- cache do require ---');
console.log('fs === require("node:fs"):', fs === require('node:fs'));
console.log('performance e o mesmo objeto:', performance === require('node:perf_hooks').performance);
console.log('modulos no cache depois de dois requires iguais:',
  Object.keys(require.cache).length);

// --- 3. o arquivo define e exporta ao mesmo tempo ---
//
// Este arquivo e um modulo comum, sem nada de especial. `module.exports` pode
// receber uma funcao direto — quem importa recebe a funcao, sem `.default` e sem
// `.chave`. E a forma mais comum de exportar uma utilitaria.
function somar(a, b) {
  return a + b;
}
module.exports = somar;

// Para exportar varias coisas, cada uma vira uma propriedade do objeto
// exportado. `exports` e um apelido de `module.exports`, valido enquanto o
// `module.exports` nao for reatribuido — como foi na linha de cima.
console.log('');
console.log('--- exportando deste arquivo ---');
console.log('typeof module.exports:', typeof module.exports);
console.log('quem importa recebe:', module.exports.name, '(a funcao)');
console.log('somar(2, 3) aqui dentro:', somar(2, 3));
console.log('exports === module.exports:', exports === module.exports);
console.log('  (false, e e o esperado: o `module.exports = somar` de cima trocou o');
console.log('   objeto, e o apelido `exports` continua apontando para o antigo)');

// --- 4. as variaveis que o Node cria sozinho ---
//
// `require`, `module`, `exports`, `__dirname`, `__filename` e `process` nao vem
// de nenhum import: sao variaveis que o proprio Node cria para cada arquivo que
// ele executa. Nenhuma delas existe no navegador.
//
// O detalhe que o `typeof` desta funcao revela: as cinco primeiras sao do
// ESCOPO DO MODULO, e nao globais. `globalThis.require` e `undefined` — o
// `require` existe dentro do arquivo, e nao em todo lugar. `process`, esse sim,
// e global, e por isso que qualquer pacote instalado consegue ler `process.env`.
function tiposDasVariaveis() {
  return {
    require: typeof require,
    module: typeof module,
    exports: typeof exports,
    __dirname: typeof __dirname,
    __filename: typeof __filename,
    'globalThis.require': typeof globalThis.require,
    'globalThis.__dirname': typeof globalThis.__dirname,
    process: typeof process,
    'globalThis.process': typeof globalThis.process,
  };
}
console.log('');
console.log('--- criadas pelo Node, sem import ---');
for (const [nome, tipo] of Object.entries(tiposDasVariaveis())) {
  console.log('  ' + nome.padEnd(20) + ' => ' + tipo);
}
console.log('  (as cinco primeiras so existem DENTRO do arquivo; process e global)');
console.log('');
console.log('__dirname e o diretorio DESTE arquivo:', __dirname);
console.log('process.cwd() e onde o comando foi executado:', process.cwd());
console.log('sao diferentes de proposito: quem chama o node escolhe o cwd');

Saída real

--- caminho ---
relativo ao arquivo: ./aqui.js  -> /root/materiais/node-mysql/codigo/t2/dia03/aqui.js
subindo um nivel:    ../sala.js -> /root/materiais/node-mysql/codigo/t2/sala.js
require('./sala') procura: sala.js, e se nao existir, sala/index.js

--- nativo (node:) contra pacote (node_modules) ---
require('node:fs') === require('fs'): true
o prefixo node: e opcional e nao muda nada: true
require.resolve('node:path') devolve: node:path
(e um pacote instalado devolve um caminho de verdade:
  /node_modules/mysql2/index.js)

--- cache do require ---
fs === require("node:fs"): true
performance e o mesmo objeto: true
modulos no cache depois de dois requires iguais: 1

--- exportando deste arquivo ---
typeof module.exports: function
quem importa recebe: somar (a funcao)
somar(2, 3) aqui dentro: 5
exports === module.exports: false
  (false, e e o esperado: o `module.exports = somar` de cima trocou o
   objeto, e o apelido `exports` continua apontando para o antigo)

--- criadas pelo Node, sem import ---
  require              => function
  module               => object
  exports              => object
  __dirname            => string
  __filename           => string
  globalThis.require   => undefined
  globalThis.__dirname => undefined
  process              => object
  globalThis.process   => object
  (as cinco primeiras so existem DENTRO do arquivo; process e global)

__dirname e o diretorio DESTE arquivo: /root/materiais/node-mysql/codigo/t2/dia03
process.cwd() e onde o comando foi executado: /root/materiais/node-mysql
sao diferentes de proposito: quem chama o node escolhe o cwd
Aula 2

import e export com ESM

import e export com ESM

ESM é o outro sistema de módulos do Node, e o padrão da linguagem.

O módulo ES é o arquivo que o Node trata com import/export, e ele só

funciona em arquivo que o Node não trata como CommonJS: um .mjs, ou um

.js num projeto com "type": "module" no package.json.

// util.mjs
export const PI = 3.14;
export function dobro(x) { return x * 2; }
export default class Caixa { /* ... */ }

// app.mjs
import Caixa, { PI, dobro as vezes2 } from './util.mjs';

O que está no exemplo é a forma nomeada: o import { } é o import nomeado, e

o que vem entre aspas depois do from é o caminho do módulo. O nome entre

colchetes é o que existe no módulo exportador. Com as vezes2 quem importa

está fazendo renomear no import, porque o nome local pode ser qualquer

identificador válido, enquanto o exportado é o do outro arquivo.

As quatro formas de exportar

FormaO que saiQuem importa
export function f() {}exportado com nomeimport { f }
export default algoo default, sem nomeimport algo from
export { a as b }renomeia o que saiimport { b }
export * from './x'repassa todos os nomesimport { ... }

O export { } é a lista do que sai, e ela existe separada do export colado

na declaração porque resolve um caso que o outro não alcança: um arquivo que

recebe o valor por require de outro e precisa repassá-lo sem mudar o nome

público. export { lerConfig } no fim do arquivo é o mesmo

export function lerConfig do começo, e o que muda é a ordem de escrita.

O export default é o único que não tem nome: quem importa decide como chamá-lo.

É a razão de existir o alias — import { default as Caixa } importa o default

com outro nome, porque default é palavra reservada e não serve como variável.

O namespace: o que o import entrega

O que o import dá é um objeto namespace: todos os exportados com nome, mais

o default na chave default.

import * as tudo from './util.mjs';
tudo.dobro(2);        // exportado com nome
tudo.default;         // o export default

import * as traz o mesmo objeto do import nomeado, e por isso é raro: quando

se sabe o que quer, import { f } é mais legível.

import() dinâmico e top-level await

O import estático tem duas limitações: precisa estar no começo do arquivo e

escreve o caminho no código. O import() dinâmico é uma expressão — pode

aparecer no meio, receber caminho montado em tempo de execução, e roda só quando

é alcançada. É ele que a documentação chama de import dinâmico, e é a ponte

entre os dois sistemas, porque funciona dentro de CommonJS.

const modulo = await import(caminhoMontadoEmTempoDeExecucao);

O top-level await é a outra metade: em ESM, await funciona no nível do

arquivo, sem envolver tudo numa async function. Em CommonJS isso é

SyntaxError. O exemplo desta aula é CommonJS e usa import() dentro de

main() por esse motivo — em ESM as duas linhas do começo ficariam no topo do

arquivo, sem função nenhuma.

import.meta

import.meta só existe em ESM. import.meta.url é o caminho do arquivo, e

equivale ao __filename do CommonJS, com a diferença de que é uma URL.

console.log(import.meta.url);   // file:///caminho/do/arquivo.mjs

Como é uma URL, new URL(import.meta.url).pathname devolve o caminho do

sistema quando o que se quer é passar para uma biblioteca de arquivos.

O que não se mistura

Um arquivo é CommonJS ou ESM, nunca os dois. require em arquivo ESM não

existe, e module.exports em arquivo ESM não exporta nada — sem erro, o que é

pior. Quem decide é o package.json, e a extensão .mjs ganha do package.json

qualquer que seja o type.

Exemplo

'use strict';

// Exemplo da aula 2 do dia 3: ESM, o outro sistema de modulos.
//
// ESM e `import`/`export`. Ele so funciona em arquivo que o Node NAO trata como
// CommonJS: um `.mjs`, ou um `.js` num projeto com `"type": "module"`.
//
// O `package.json` deste material diz `"type": "commonjs"`, entao ESTE arquivo e
// CommonJS e nao aceita `import` nem `await` no nivel do arquivo. E por isso que
// o exemplo usa `import()` dinamico: ele funciona de dentro de CommonJS, e e a
// ponte entre os dois sistemas. O modulo do outro lado (`modulo-esm.mjs`) e ESM
// de verdade, e e ele que a aula importa.

async function main() {
  // `import()` e uma EXPRESSAO: pode estar no meio do codigo, receber um
  // caminho montado em tempo de execucao, e roda so quando e alcancada. O
  // `import` estatico, ao contrario, tem de ser a primeira coisa do arquivo e
  // roda sempre.
  const modulo = await import('./modulo-esm.mjs');
  const { somaDois, versaoMaterial, agora, onde } = modulo;
  const Mensagem = modulo.default;

  console.log('--- import nomeado e default ---');
  console.log('nomeado  somaDois(2, 3)      =', somaDois(2, 3));
  console.log('default  new Mensagem("oi")  =', new Mensagem('oi').ver());
  console.log('const versaoMaterial =', versaoMaterial);

  // --- o namespace ---
  //
  // O que o `import` entrega e um OBJETO namespace: tudo que foi exportado com
  // nome, mais o `export default` na chave `default`. Ver a lista do que um
  // modulo exporta nao exige ler o arquivo dele.
  console.log('');
  console.log('--- o namespace do modulo ---');
  console.log('exportados com nome:', Object.keys(modulo).filter((k) => k !== 'default').join(', '));
  console.log('export default:      default ->', modulo.default.name);
  console.log('o namespace nao traz variavel local do modulo, nem nada de outro modulo');

  // --- alias nos dois lados ---
  //
  // `export { x as y }` renomeia o que sai; `import { y as x }` renomeia o que
  // entra. Sao o mesmo mecanismo apontado em direcoes opostas.
  console.log('');
  console.log('--- alias ---');
  console.log('no modulo:  const soma = ... ; export { soma as somaDois }');
  console.log('aqui:       const { somaDois } = await import(...)');
  console.log('o nome interno `soma` nao sai do modulo:', modulo.soma === undefined);
  console.log('o nome exportado `somaDois` sai:', typeof modulo.somaDois);
  console.log('e a mesma funcao que a aula importou:', modulo.somaDois === somaDois);

  // `import * as` traz tudo que tem nome; o default fica em `.default`.
  const { default: Default, ...resto } = modulo;
  console.log('import * as traz', Object.keys(resto).length, 'exportados com nome e o default a parte');
  console.log('  "default" continua acessivel:', new Default('mesma classe').ver());

  // --- `import()` com caminho montado ---
  //
  // Nada impede que o caminho seja uma variavel. Com `import` estatico isso nao
  // existe: o caminho e escrito no codigo, e o Node o resolve antes de rodar.
  const caminho = 'node:' + 'os';
  const moduloOs = await import(caminho);
  console.log('');
  console.log('--- import() com caminho construido ---');
  console.log('import("' + caminho + '") -> modulo nativo do Node');
  console.log('type():', moduloOs.type(), '| platform():', moduloOs.platform());

  // --- import.meta: so existe em ESM ---
  //
  // O modulo exporta o que ele proprio ve: `import.meta.url` e o caminho do
  // arquivo de ONDE a funcao foi definida, nao de onde ela foi chamada. No
  // CommonJS o equivalente e `__filename`.
  console.log('');
  console.log('--- import.meta, do lado de dentro do modulo ---');
  console.log('import.meta.url:', onde());
  console.log('como caminho:    ', new URL(onde()).pathname);
  console.log('o `await import("./modulo-esm.mjs")` deste arquivo so funciona');
  console.log('porque `import()` e valido dentro de uma funcao `async` em CommonJS;');
  console.log('em ESM o mesmo codigo nem precisaria dela.');

  // --- o que nao se mistura ---
  console.log('');
  console.log('--- a regra que decide tudo ---');
  console.log('package.json "type": "commonjs" -> require / module.exports, sem top-level await');
  console.log('package.json "type": "module"   -> import / export, com top-level await');
  console.log('arquivo com extensao .mjs        -> ESM, qualquer que seja o type');
  console.log('os dois no mesmo arquivo         -> nao funciona, e a falha nao avisa');
  console.log('');
  console.log('este arquivo e CommonJS, e rodou mesmo assim.');
}

main().catch((erro) => {
  console.error('falhou:', erro.code || erro.name, '-', erro.message);
  process.exit(1);
});

Saída real

[modulo-esm.mjs] este arquivo rodou no momento do import, uma vez so
--- import nomeado e default ---
nomeado  somaDois(2, 3)      = 5
default  new Mensagem("oi")  = Mensagem: oi
const versaoMaterial = t2/dia03

--- o namespace do modulo ---
exportados com nome: PI, agora, onde, somaDois, versaoMaterial
export default:      default -> Mensagem
o namespace nao traz variavel local do modulo, nem nada de outro modulo

--- alias ---
no modulo:  const soma = ... ; export { soma as somaDois }
aqui:       const { somaDois } = await import(...)
o nome interno `soma` nao sai do modulo: true
o nome exportado `somaDois` sai: function
e a mesma funcao que a aula importou: true
import * as traz 5 exportados com nome e o default a parte
  "default" continua acessivel: Mensagem: mesma classe

--- import() com caminho construido ---
import("node:os") -> modulo nativo do Node
type(): Linux | platform(): linux

--- import.meta, do lado de dentro do modulo ---
import.meta.url: file:///root/materiais/node-mysql/codigo/t2/dia03/modulo-esm.mjs
como caminho:     /root/materiais/node-mysql/codigo/t2/dia03/modulo-esm.mjs
o `await import("./modulo-esm.mjs")` deste arquivo so funciona
porque `import()` e valido dentro de uma funcao `async` em CommonJS;
em ESM o mesmo codigo nem precisaria dela.

--- a regra que decide tudo ---
package.json "type": "commonjs" -> require / module.exports, sem top-level await
package.json "type": "module"   -> import / export, com top-level await
arquivo com extensao .mjs        -> ESM, qualquer que seja o type
os dois no mesmo arquivo         -> nao funciona, e a falha nao avisa

este arquivo e CommonJS, e rodou mesmo assim.