Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Conditional Exports no Node.js: ESM e CommonJS

Atualizado em: 10 de outubro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Conditional Exports no Node.js permite que um pacote entregue arquivos diferentes conforme o ambiente ou a forma de carregamento. A configuração fica no campo exports do package.json e pode distinguir ESM, CommonJS, Node.js, desenvolvimento e outras condições reconhecidas.

Esse recurso é útil para bibliotecas que precisam oferecer compatibilidade sem expor toda a estrutura interna. Ao mesmo tempo, configurações incorretas podem criar duas instâncias do pacote, tipos divergentes ou comportamento diferente entre testes e produção.

Neste guia, você aprenderá a montar condições, ordenar chaves, publicar ESM e CommonJS, evitar o dual package hazard, testar consumidores e versionar mudanças com segurança.

Estrutura básica

{
  "name": "minha-lib",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}

Um consumidor ESM recebe index.js, enquanto um consumidor CommonJS recebe index.cjs.

A ordem das condições importa

As condições são avaliadas na ordem em que aparecem. Coloque casos mais específicos antes de fallbacks amplos:

{
  "exports": {
    ".": {
      "node": {
        "import": "./dist/node.js",
        "require": "./dist/node.cjs"
      },
      "default": "./dist/browser.js"
    }
  }
}

Evite colocar default antes das condições específicas, pois ele pode capturar a resolução cedo demais.

Condição import

A condição import é usada quando a entrada é carregada por ESM ou import dinâmico:

import { criarCliente } from 'minha-lib';

O arquivo alvo deve ser compatível com o formato do pacote e com a extensão utilizada.

Condição require

A condição require atende consumidores CommonJS:

const { criarCliente } = require('minha-lib');

Se o pacote usa type: module, a entrada CommonJS normalmente recebe extensão .cjs.

Condição node

A condição node permite fornecer implementação específica do runtime:

{
  "exports": {
    ".": {
      "node": "./dist/node.js",
      "default": "./dist/universal.js"
    }
  }
}

Use quando a versão Node depende de módulos internos, streams ou sistema de arquivos.

Condição default

default funciona como fallback para ambientes que não correspondem às condições anteriores. Ela melhora compatibilidade com ferramentas que não anunciam uma condição específica.

Development e production

{
  "exports": {
    ".": {
      "development": "./dist/index.dev.js",
      "production": "./dist/index.prod.js",
      "default": "./dist/index.js"
    }
  }
}

Essas condições dependem de como o runtime ou ferramenta é iniciado. Não presuma que serão selecionadas automaticamente apenas pelo valor de NODE_ENV.

Condições personalizadas

Aplicações podem ativar condições por opção de execução. Isso é útil em ambientes controlados, mas bibliotecas públicas devem priorizar condições amplamente compreendidas.

Condições customizadas sem documentação tornam o pacote difícil de consumir e testar.

Subpaths condicionais

{
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    },
    "./errors": {
      "import": "./dist/errors.js",
      "require": "./dist/errors.cjs"
    }
  }
}

Cada subpath precisa manter paridade de API entre os formatos.

Tipos TypeScript

{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}

Os tipos devem descrever as duas entradas. Se ESM e CommonJS expõem estruturas diferentes, o problema está no design do pacote, não apenas nos arquivos de declaração.

Dual package hazard

Uma aplicação pode carregar a entrada ESM e a CommonJS ao mesmo tempo. Isso cria duas instâncias independentes:

// módulo A
import { cache } from 'minha-lib';

// módulo B
const { cache } = require('minha-lib');

Se a biblioteca mantém estado global, classes, símbolos ou registries, os consumidores podem observar inconsistências.

Como reduzir o risco

  • evite estado singleton no módulo;
  • mantenha uma implementação central compartilhada quando possível;
  • exporte fábricas em vez de instâncias globais;
  • teste carregamento misto;
  • documente o formato recomendado.

Wrapper CommonJS sobre implementação ESM

Dependendo do design, uma entrada pode adaptar a outra. Porém, como require() é síncrono e import ESM é assíncrono, nem toda API pode ser embrulhada sem alterar comportamento.

Evite soluções que bloqueiem ou retornem Promises inesperadas.

Implementações separadas

Gerar dois builds é comum, mas exige testes de paridade:

dist/
  index.js
  index.cjs
  index.d.ts

Execute a mesma suíte contra os dois arquivos para impedir divergência.

Encapsulamento com exports

Conditional exports também bloqueia caminhos não declarados. Veja Package Exports no Node.js para estruturar uma API pública pequena.

ESM como entrada principal

Projetos modernos podem oferecer ESM em import e manter CommonJS apenas como compatibilidade temporária. O guia ESM no Node.js explica o formato moderno.

Import dinâmico

A condição import também pode ser usada quando CommonJS chama import():

const modulo = await import('minha-lib');

Veja Dynamic Import no Node.js.

Testando com projeto consumidor

Não teste apenas arquivos dentro do repositório. Gere um tarball:

npm pack --dry-run
npm pack

Instale em projetos temporários ESM e CommonJS e execute imports reais.

Teste ESM

import test from 'node:test';
import assert from 'node:assert/strict';
import { somar } from 'minha-lib';

test('entrada ESM', () => {
  assert.equal(somar(2, 3), 5);
});

Teste CommonJS

const test = require('node:test');
const assert = require('node:assert/strict');
const { somar } = require('minha-lib');

test('entrada CommonJS', () => {
  assert.equal(somar(2, 3), 5);
});

Teste de identidade

Para bibliotecas com classes ou símbolos, valide comportamento quando ambos os formatos aparecem na mesma aplicação. O ideal é não depender de identidade global entre entradas.

Compatibilidade com ferramentas

Bundlers, test runners, TypeScript e gerenciadores de pacotes podem resolver condições de maneiras diferentes. Mantenha a configuração simples e teste a matriz realmente suportada.

Versionamento

Alterar a ordem de condições ou trocar o arquivo selecionado pode ser uma mudança incompatível, mesmo sem mudar a assinatura aparente. Avalie impacto em efeitos colaterais, formato, tipos e estado compartilhado.

Erros comuns

  • colocar default antes de condições específicas;
  • publicar arquivo que não está no tarball;
  • expor APIs diferentes em import e require;
  • manter singletons duplicados;
  • esquecer tipos de subpaths;
  • presumir que NODE_ENV seleciona condição;
  • testar apenas dentro do monorepo;
  • usar condições customizadas sem fallback.

Checklist para produção

  • mantenha poucas condições;
  • ordene do específico ao geral;
  • inclua default quando necessário;
  • teste ESM e CommonJS;
  • teste pacote empacotado;
  • garanta paridade de API;
  • evite estado global;
  • publique tipos corretos;
  • documente versões suportadas.

Conclusão

Conditional Exports no Node.js oferece compatibilidade controlada entre formatos e ambientes. Ele é poderoso quando a biblioteca possui uma API pública estável e builds equivalentes.

Use condições com parcimônia, teste consumidores reais e evite estado compartilhado implícito. A configuração deve facilitar o uso do pacote, não transformar a resolução em um quebra-cabeça.

Consulte a documentação oficial de conditional exports do Node.js e a referência de condições aninhadas.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita