voltar aos artigosadvanced topics
$cd~/artigos/es-modules-javascript-modulos-esm
Advanced Topics
série 33 Conceitos JS·parte 34 de 38

ES Modules: O Sistema de Entrega de Código JavaScript

23 minVanderlei José Alamino

"Antes dos módulos, JavaScript era como um galpão sem prateleiras. ES Modules trouxeram as prateleiras padronizadas."

ES Modules (ESM) são o sistema de módulos nativo desde ES2015 — import/export declarativos, suporte em browsers e Node.js, e base para tree-shaking.

snippetJavaScript
export const PI = 3.14159;export function square(x) { return x * x; } import { PI, square } from './math.js';

Neste guia da série 33 Conceitos JS — projeto open source criado por Leonardo Maldonado — você entende ESM vs CommonJS, live bindings, imports dinâmicos e deploy em browser/Node.

Pré-requisitos: IIFE, Modules & Namespaces (história dos módulos) e Async/Await (dynamic import e top-level await).

es-modules-javascript-modulos-esm.meta.jsonjson

// metadados do artigo

{
  "tag": "advanced topics",
  "readTime": "23 min",
  "author": "Vanderlei José Alamino",
  "source": "33jsconcepts.com",
  "series": "33 Conceitos JS",
  "part": {
    "current": 34,
    "total": 38
  }
}
abrir fonte original
es-modules-javascript-modulos-esm.mdmarkdown

Fonte de referência

Conteúdo adaptado a partir do guia original em inglês.

33jsconcepts.com
01

seção

Analogia: Contêineres de Transporte

snippettext
CommonJS (require)     →  runtime, dinâmico, difícil otimizarES Modules (import)    →  build time, estático, tree-shakeable

Imports ESM são declarações no top level — bundlers veem o grafo inteiro antes de executar código.

02

seção

ESM vs CommonJS

AspectoES ModulesCommonJS
Sintaxeimport / exportrequire / module.exports
CarregamentoAssíncronoSíncrono (Node)
AnáliseEstáticaDinâmica
ExportsLive bindingsCópias de valor
Strict modeSempreOpcional
Tree-shakingSimNão
snippetJavaScript
// CommonJSmodule.exports = { PI, square };const { PI } = require('./math'); // ESMexport const PI = 3.14159;import { PI, square } from './math.js';

CommonJS permite require(condition ? 'a' : 'b') — flexível, mas impede otimização estática.

03

seção

Live Bindings

ESM exporta referências live; CommonJS copia valores no require():

snippetJavaScript
// ESM — count atualiza no importerexport let count = 0;export function increment() { count++; } import { count, increment } from './counter.js';increment();console.log(count);  // 1
snippetJavaScript
// CommonJS — count importado fica congeladolet count = 0;module.exports = { count, increment: () => count++ }; const { count, increment } = require('./counter');increment();console.log(count);  // 0 (cópia)

Imports são read-only no consumer: count = 10 → TypeError.

04

seção

Export Syntax

Named exports:

snippetJavaScript
export const PI = 3.14159;export function add(a, b) { return a + b; } const helper = () => {};export { helper as utilHelper };

Default export — um por módulo:

snippetJavaScript
export default class Logger { log(msg) { console.log(msg); } }

Barrel files (re-export):

snippetJavaScript
export { formatDate } from './date.js';export { default as Logger } from './logger.js';// export * from './math.js' — cuidado com tree-shaking
05

seção

Import Syntax

snippetJavaScript
import { PI, add } from './math.js';import Logger from './logger.js';           // defaultimport * as math from './math.js';          // namespaceimport React, { useState } from 'react';    // mixedimport './polyfills.js';                    // side-effect only

Regra das chaves: default → sem {}; named → com {}.

snippetJavaScript
export default function Logger() {}export function format() {} import Logger from './logger.js';      // ✓ defaultimport { format } from './logger.js';  // ✓ namedimport { Logger } from './logger.js';  // ✗ Error

Bare specifiers ('react', 'lodash') — Node resolve em node_modules; browser precisa de bundler ou import map.

06

seção

Características dos Módulos

  • Strict mode automático — sem "use strict"
  • Escopo de módulo — variáveis não vazam para window/global
  • Singleton — top-level roda uma vez por import
  • this no top level = undefined
  • Hoisting — imports são elevados (mas escreva imports no topo)
  • Browser: