Módulos ESM com NodeJS

Módulos ESM com NodeJS

Até antes da versão 12.0.0 do NodeJS, nós usávamos o comando require e module.exports para exportar/importar um determinado módulo dentro de um outro arquivo.

E foi o que fizemos durante toda a nossa jornada:

const db = require('../db/connection.js');

....

module.exports = app;

Até os dias de hoje, o NodeJS ainda utiliza (por padrão) o CommonJS (CJS), que por sua vez, nos obriga a importar/exportar nosso módulos por meio do module.export e também do comando require.

No entanto, a partir da versão 12.0.0, o NodeJS passou a dar suporte aos módulos ESM, o que permitiu o uso dos comandos export default, exportimport 😉

Habilitando o uso do import/export

Quando temos uma aplicação setada com NodeJS, existem duas formas diferentes para fazermos o uso dos comandos import e export dos módulos ESM.

A primeira delas, envolve alterar a extenção dos seus arquivos, que estão como .js para .mjs, o que faz com que o NodeJS entenda que você está lidando com os módulos ECMAScript, o que permite o uso dos comandos import e export.

Caso você queria manter a extenção .js nos seus arquivos, você vai precisar adicionar uma nova configuração dentro do package.json, indicando ao NodeJS que estamos usando módulos:

{
  "type": "module"
}

Com a configuração acima, o NodeJS será instruído a tratar todos os arquivos .js como módulos ECMAScript, permitindo o uso dos comandos import e export.

Vejamos um exemplo de um arquivo package.json completo, que faz a implementação do módulo ECMAScript:

{
  "name": "22)-testes",
  "version": "1.0.0",
  "description": "",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "mocha"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.7.7",
    "body-parser": "^1.20.3",
    "chai": "^4.4.0",
    "chai-http": "^4.4.0",
    "dotenv": "^16.4.5",
    "express": "^4.21.1",
    "express-handlebars": "^8.0.1",
    "mocha": "^10.7.3",
    "mysql2": "^3.11.3",
    "sequelize": "^6.37.4"
  }
}

Note que a chave "type" foi adicionada logo após a "description", mas ela poderia estar declarada em qualquer lugar, desde que não tinha sido aninhada dentro de qualquer outra chave.

Usando os comandos Import e Export

Agora que já habilitamos o uso do ESM no NodeJS, vejamos como usar os módulos ESM!

O uso desses novos comandos são bem simples, basta que você troque seus antigos require para import, da seguinte forma:

const express = require('express');//Usa o CommomJS

...

import express from 'express';//Usa o ESM

Caso a sua biblioteca ou módulo faça a exportação de mais de um módulo por vez, você pode utilizar:

import { db, alter, status } from '../db/connection.js';//Usa o ESM

Já para fazer exportações, basta trocar os comandos module.exports para export default da seguinte forma:

module.exports = app;//Usa o CommomJS

...

export default app;

Caso você queria exportar mais coisas, basta usar o comando export sem o default:

// Exportando múltiplos objetos, funções ou variáveis
export const PI = 3.14159;
export const square = (x) => x * x;
export function add(x, y) {
  return x + y;
}

Já para fazer a importação, você pode usar:

// Importando objetos exportados de forma nomeada
import { PI, square, add } from './math.js';

console.log(PI); // 3.14159
console.log(square(2)); // 4
console.log(add(2, 3)); // 5

Observação: o uso do export default também pode ser usado em conjunto com o export, desde que exista apenas um único export default por módulo.

Realizando exportações nomeadas

Com o ESM, você também consegue realizar exportações nomeadas, isto é, exportar de forma direta uma função ou uma determinada classe:

exports.myFunction = () => {};//Usando o CommomJS

...

export const myFunction = () => {};//Usando o ESM

Importando módulos dinâmicos

Durante a sua jornada como desenvolvedor NodeJS, por vezes, será necessário a importação de módulos dinâmicos, que nada mais são do que módulos que carregam uma função assíncrona:

const module = await import('module-name');

É importante ressaltar que no ambiente ESM, __dirname e __filename não estão disponíveis para acesso direito, o que nos obriga a importá-los dessa maneira:

import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

Export Default VS Export

No caso do export default, ele é usado exclusivamente para exportar um valor único por módulo. A partir do momento que você tem um export default dentro de um arquivo Javascript, o compilador do NodeJS começa a apresentar problemas.

É importante destacar que ao realizar a importação, você não precisa usar o nome exato do valor que foi exportado em outro arquivo, por exemplo:

export default function add(x, y) {
  return x + y;
}

...

import addFunction from './math.js';
console.log(addFunction(2, 3)); // 5

Já o export é usado para exportar múltiplos valores (variáveis, funções, objetos...) de um módulo. Como por exemplo:

export const PI = 3.14159;

export function add(x, y) {
  return x + y;
}

...

import { PI, add } from './math.js';
console.log(PI); // 3.14159
console.log(add(2, 3)); // 5

Posso ter um export default dentro de um módulo em conjunto com varios export?

Sim, mas desde que exista apenas um único export default dentro do seu arquivo, vamos ver um exemplo:

// Exportação default (apenas um por módulo)
const circleArea = (radius) => Math.PI * radius * radius;
export default circleArea;

// Várias exportações nomeadas
export const rectangleArea = (width, height) => width * height;
export const triangleArea = (base, height) => 0.5 * base * height;

A importação de todos esses elementos, aconteceria da seguinte forma:

// Importando a exportação default e exportações nomeadas
import circleArea, { rectangleArea, triangleArea } from './shapes.js';

console.log(circleArea(3)); // Área do círculo com raio 3
console.log(rectangleArea(4, 5)); // Área do retângulo
console.log(triangleArea(4, 5)); // Área do triângulo

Portanto, o export default te permite exportar um único valor como padrão do módulo, ja o export sozinho, te permite exportar múltiplos valores nomeados do mesmo módulo.

Conclusão

Nesta lição, nós aprendemos a utilizar os comandos import, export e export default do ECMAScript 😉

Até a próxima!

Criadores de Conteúdo

Foto do William Lima
William Lima
Fundador da Micilini

Inventor nato, escreve conteudos de programação para o portal da micilini.

Torne-se um MIC 🤖

Mais de 100 mic's já estão conectados na plataforma.