esModuleInterop în TypeScript

Face importul implicit (import x from "pachet") al modulelor CommonJS să funcționeze corect, la verificare și la rulare.

Sintaxă
"compilerOptions": {
  "esModuleInterop": true
}

tsc --esModuleInterop

Ce face esModuleInterop

Multe pachete npm vechi sunt CommonJS: exportă o singură valoare cu module.exports = .... În modulele ES, ai vrea să le imporți cu import salut from "salut". Fără ajutor, codul generat pentru CommonJS caută .default, care nu există, și programul se oprește cu TypeError.

esModuleInterop rezolvă ambele părți: tsc permite importul implicit (pornește automat allowSyntheticDefaultImports) și adaugă în JavaScript funcții ajutătoare (__importDefault, __importStar) care fac importul să funcționeze la rulare. allowSyntheticDefaultImports singur permite doar scrierea, la verificare, fără să schimbe JavaScript-ul generat; e util când altă unealtă (bundler) face conversia.

Implicit, esModuleInterop este false, dar devine true pentru module node16/node18/node20/nodenext/preserve. allowSyntheticDefaultImports este true când esModuleInterop este pornit sau module este system. Pentru cod nou, lasă-l pornit.

Parametri

Parametru Descriere
esModuleInterop: true Permite importul implicit și generează cod ajutător pentru CommonJS.
allowSyntheticDefaultImports: true Doar permite import x from pentru module fără export default; nu schimbă JavaScript-ul generat.

Exemple

Exemplul 1: Pachet CommonJS, fără și cu esModuleInterop

Pachetul salut exportă o funcție cu module.exports, iar declarația lui folosește export =. Fără opțiune, tsc refuză importul implicit. Cu opțiunea, codul generat folosește __importDefault și programul rulează.

// node_modules/salut/index.js
module.exports = function (nume) {
  return "Salut, " + nume + "!";
};

// node_modules/salut/index.d.ts
declare function salut(nume: string): string;
export = salut;

// tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2022",
    "esModuleInterop": true
  }
}

// index.ts
import salut from "salut";

console.log(salut("Ana"));

Rezultat (tsc 5.9)

$ npx tsc --esModuleInterop false
index.ts(1,8): error TS1259: Module '"/proiect/node_modules/salut/index"' can only be default-imported using the 'esModuleInterop' flag

$ npx tsc && cat index.js
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const salut_1 = __importDefault(require("salut"));
console.log((0, salut_1.default)("Ana"));

$ node index.js
Salut, Ana!

Exemplul 2: Doar allowSyntheticDefaultImports

Cu "esModuleInterop": false și "allowSyntheticDefaultImports": true, tsc acceptă importul, dar nu adaugă __importDefault. Fără un bundler care să facă conversia, programul se oprește.

// tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2022",
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true
  }
}

Rezultat (tsc 5.9)

$ npx tsc && cat index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const salut_1 = require("salut");
console.log((0, salut_1.default)("Ana"));

$ node index.js 2>&1 | grep -m1 Error
TypeError: (0 , salut_1.default) is not a function

Exemplul 3: Import implicit dintr-un modul Node.js

Modulele încorporate din Node.js sunt CommonJS. Cu module: nodenext (care pornește esModuleInterop), importul implicit funcționează.

import path from "node:path";
import util from "node:util";

console.log(path.join("documente", "facturi", "ianuarie.pdf"));
console.log(path.basename("/home/ana/poze/iasi.jpg", ".jpg"));
console.log(util.format("%s are %d ani", "Ana", 25));

Rezultat

documente/facturi/ianuarie.pdf
iasi
Ana are 25 ani

Greșeli frecvente

  • Să scrii import * as salut from "salut" și să apelezi salut(). Cu esModuleInterop, tsc dă eroarea TS2497: obiectul de namespace nu poate fi apelat; folosește importul implicit.
  • Să pornești doar allowSyntheticDefaultImports când tsc generează și JavaScript-ul. Verificarea trece, rularea nu.

Comenzi înrudite

  • module: Alege formatul de module din JavaScript-ul generat (ES modules, CommonJS) și regulile de import pe care le verifică tsc.
  • verbatimModuleSyntax: Importurile rămân exact cum sunt scrise; se șterg doar cele marcate cu type, deci tipurile trebuie importate cu import type.
  • importHelpers: Ia funcțiile ajutătoare (__awaiter, __decorate…) din pachetul tslib, în loc să le copieze în fiecare fișier.
  • allowUmdGlobalAccess: Permite folosirea unei biblioteci UMD ca variabilă globală dintr-un fișier care este modul (are import/export).