Módulos ES6: import, export e module bundling

1. Introdução aos Módulos ES6

Antes da chegada dos módulos ES6, a organização de código em JavaScript era um desafio. Desenvolvedores dependiam de scripts globais, onde cada arquivo adicionava variáveis ao escopo global, causando conflitos e poluição. Soluções como IIFEs (Immediately Invoked Function Expressions) e padrões de módulo improvisados tentavam contornar o problema, mas eram frágeis e difíceis de manter.

Os módulos ES6 (ECMAScript 2015) revolucionaram a forma como estruturamos código JavaScript. Eles introduziram um sistema oficial de módulos com escopo próprio, executado em strict mode automaticamente. Diferente de scripts tradicionais, módulos não poluem o escopo global — cada módulo tem seu próprio contexto de execução. Variáveis declaradas dentro de um módulo não são acessíveis externamente a menos que explicitamente exportadas.

2. Exportando com export

O sistema de módulos ES6 oferece duas formas principais de exportação: named exports e default exports.

Named exports permitem exportar múltiplos valores de um módulo:

// utils/matematica.js
export const PI = 3.14159;
export function somar(a, b) {
  return a + b;
}
export class Calculadora {
  constructor() {
    this.historico = [];
  }
  calcular(operacao, a, b) {
    this.historico.push({ operacao, a, b });
    return this[operacao](a, b);
  }
}

Default export é usado para exportar um valor principal por módulo:

// components/Botao.js
export default function Botao({ texto, onClick }) {
  return <button onClick={onClick}>{texto}</button>;
}

É possível combinar ambos no mesmo módulo e renomear exports com as:

// utils/helpers.js
function formatarData(data) { /* ... */ }
function formatarMoeda(valor) { /* ... */ }

export { formatarData as dataFormatada, formatarMoeda };
export default function log(mensagem) {
  console.log(`[LOG]: ${mensagem}`);
}

3. Importando com import

A sintaxe de importação é flexível e poderosa. Para named exports, usamos chaves com os nomes exatos:

import { PI, somar, Calculadora } from './utils/matematica.js';
console.log(somar(2, 3)); // 5

Default exports são importados sem chaves, podendo receber qualquer nome:

import Botao from './components/Botao.js';

Importação mista e renomeação com as:

import log, { dataFormatada as df, formatarMoeda } from './utils/helpers.js';

Para importar todo o módulo como um namespace:

import * as Matematica from './utils/matematica.js';
console.log(Matematica.PI); // 3.14159

4. Módulos ES6 no Node.js

O Node.js adotou oficialmente os módulos ES6 a partir da versão 12. Para habilitá-los, configure o package.json:

{
  "type": "module"
}

Alternativamente, use a extensão .mjs para arquivos ESModules. Com "type": "module", arquivos .js são tratados como ESModules. A importação dinâmica com import() funciona em ambos os sistemas:

// Exemplo de importação dinâmica
async function carregarModulo() {
  const modulo = await import('./utils/pesado.js');
  modulo.executar();
}

Diferenças importantes: require não está disponível em ESModules (use createRequire do módulo module se necessário), e o escopo de módulo é strict mode por padrão.

5. Módulos ES6 no React

No React, export default é padrão para componentes, enquanto export named é ideal para hooks customizados e utilitários:

// components/UsuarioCard.jsx
export default function UsuarioCard({ usuario }) {
  return (
    <div className="card">
      <h3>{usuario.nome}</h3>
      <p>{usuario.email}</p>
    </div>
  );
}

// hooks/useAuth.js
export function useAuth() {
  const [user, setUser] = useState(null);
  // lógica de autenticação
  return { user, login, logout };
}

// utils/validacao.js
export function validarEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

A organização de pastas típica em projetos React separa componentes, páginas, hooks e utilitários, facilitando imports limpos. Bibliotecas externas como React e ReactDOM são importadas diretamente:

import React from 'react';
import { useState, useEffect } from 'react';

Essa estrutura permite que bundlers realizem tree shaking, removendo código não utilizado.

6. Module Bundling: Conceitos e Ferramentas

Module bundling é o processo de combinar múltiplos módulos em um ou poucos arquivos otimizados para produção. Isso é necessário porque navegadores mais antigos não suportam ESModules nativamente, e mesmo os modernos se beneficiam de bundles menores para performance.

Webpack é o bundler mais tradicional para React. Uma configuração básica inclui:

// webpack.config.js
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: 'babel-loader',
      },
    ],
  },
  plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })],
};

Vite é um bundler moderno que usa ESBuild para desenvolvimento rápido e Rollup para produção. Sua configuração para React é minimalista:

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
});

Comparação rápida: Webpack é maduro e altamente configurável, Vite oferece HMR (Hot Module Replacement) instantâneo, Parcel é zero-config, e esbuild é extremamente rápido mas menos flexível.

7. Tree Shaking e Otimização de Bundles

Tree shaking é a remoção de código morto durante o bundle, possível graças à natureza estática dos ESModules. Para escrever código amigável ao tree shaking:

// ❌ Ruim para tree shaking
import { utils } from './utils';
utils.formatarData(data); // Carrega todo o módulo

// ✅ Bom para tree shaking
import { formatarData } from './utils/formatacao.js';
formatarData(data); // Apenas o necessário é incluído

Configure sideEffects no package.json para informar ao bundler quais arquivos têm efeitos colaterais:

{
  "sideEffects": [
    "./src/estilos/global.css",
    "*.scss"
  ]
}

Ferramentas como webpack-bundle-analyzer e vite inspect ajudam a visualizar o tamanho do bundle e identificar oportunidades de otimização.

8. Boas Práticas e Padrões com Módulos ES6

Evite dependências circulares: quando o módulo A importa B e B importa A, podem ocorrer erros de inicialização. Refatore extraindo dependências comuns para um terceiro módulo.

Use barrel files (index.js) para reexportação organizada:

// components/index.js
export { default as Botao } from './Botao';
export { default as Card } from './Card';
export { default as Modal } from './Modal';

Import dinâmico para code splitting com React.lazy e Suspense:

import React, { Suspense, lazy } from 'react';

const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {
  return (
    <Suspense fallback={<div>Carregando...</div>}>
      <Dashboard />
    </Suspense>
  );
}

Diferenças práticas entre CommonJS e ESModules: CommonJS usa require/module.exports, carregamento síncrono e cópia de valor. ESModules usam import/export, carregamento assíncrono e ligações ao vivo (live bindings). Em projetos Node.js + React, prefira ESModules sempre que possível para aproveitar tree shaking e melhor integração com ferramentas modernas.

Os módulos ES6 transformaram a forma como escrevemos JavaScript, trazendo clareza, reusabilidade e performance. Dominar import, export e module bundling é essencial para qualquer desenvolvedor que trabalhe com Node.js e React.

Referências