require vs import em TypeScript: tutorial passo a passo para quem está confuso

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

require(x) vs import x: por que isso é uma eterna dor de cabeça para desenvolvedores TypeScript

\n\n

Se você já escreveu em TypeScript e viu um erro como Cannot use import statement outside a module ou require is not defined — você não está sozinho. No Stack Overflow, a pergunta "The difference between 'require(x)' and 'import x'" acumulou dezenas de milhares de visualizações. E isso não é por acaso: a confusão entre os dois sistemas de módulos é uma das armadilhas mais comuns em que até desenvolvedores experientes caem.

\n\n

Neste tutorial, não vamos apenas listar as diferenças. Vamos percorrer o caminho da total incompreensão até a escolha confiante da sintaxe correta. Você aprenderá como o TypeScript compila seu código, por que o require ainda existe e quando usar import.

\n\n

1. Contexto histórico: de onde vieram os dois sistemas

\n\n

Para entender a diferença, precisamos olhar para a história do JavaScript. Inicialmente, o Node.js não tinha um sistema de módulos embutido. Os desenvolvedores criaram o CommonJS — uma forma síncrona de conectar módulos via require(). Essa abordagem funciona no lado do servidor, mas não era adequada para navegadores — que precisam de carregamento assíncrono.

\n\n

Mais tarde, surgiu o padrão ES Modules (ESM) — a sintaxe oficial de módulos do ECMAScript. O TypeScript, como uma extensão do JS, suporta ambas as opções. Mas é aqui que começa a confusão: você escreve import, e o compilador o transforma em require — ou vice-versa.

\n\n

2. Sintaxe: principais diferenças

\n\n

Vamos detalhar como cada abordagem se parece.

\n\n

CommonJS (require)

\n\n
// Importação de módulo inteiro\nconst fs = require('fs');\n\n// Importação de parte do módulo (desestruturação)\nconst { readFile, writeFile } = require('fs');\n\n// Exportação\nmodule.exports = { myFunction, myVariable };\nexports.myFunction = () => { ... };
\n\n

ES Modules (import)

\n\n
// Importação de módulo inteiro\nimport fs from 'fs';\n\n// Importação de exportações nomeadas\nimport { readFile, writeFile } from 'fs';\n\n// Importação de tudo como objeto\nimport * as fs from 'fs';\n\n// Exportação\nexport const myVariable = 42;\nexport function myFunction() { ... }\nexport default class MyClass { }
\n\n

À primeira vista, a diferença é apenas na sintaxe. Mas vamos mais fundo.

\n\n

3. Importação estática vs dinâmica

\n\n

Esta é a diferença chave que afeta como seu código funciona.

\n\n
    \n
  • require — dinâmico. Você pode chamá-lo em qualquer lugar, dentro de uma condição ou função. O módulo é carregado no momento da chamada.
  • \n
  • import — estático. As importações são resolvidas em tempo de compilação, antes da execução do código. Elas devem sempre estar no nível superior do arquivo.
  • \n
\n\n

Exemplo de require dinâmico:

\n\n
let myModule;\nif (process.env.NODE_ENV === 'production') {\n  myModule = require('./production-config');\n} else {\n  myModule = require('./dev-config');\n}
\n\n

Esse truque não funciona com import — resultará em um erro de sintaxe. Mas para isso existe o import() dinâmico (retorna uma Promise):

\n\n
if (condition) {\n  const module = await import('./some-module');\n  module.doSomething();\n}
\n\n

4. Como o TypeScript compila módulos

\n\n

Tudo depende da configuração module no tsconfig.json. Aqui estão as principais opções:

\n\n
    \n
  • "module": "commonjs" — seu import será transformado em require. O código funcionará no Node.js sem configurações adicionais.
  • \n
  • "module": "esnext" — o import permanecerá como está. Adequado para navegadores modernos ou bundlers (Webpack, Vite).
  • \n
  • "module": "node16" ou "nodenext" — o TypeScript determina automaticamente

Blogs

Book Recommendations