Why allowSyntheticDefaultImports Alone Won’t Import React – Use esModuleInterop

This article explains how the TypeScript compiler options allowSyntheticDefaultImports and esModuleInterop affect importing React, showing the differences in emitted JavaScript and why the latter is needed for default imports to work correctly.

Node Underground
Node Underground
Node Underground
Why allowSyntheticDefaultImports Alone Won’t Import React – Use esModuleInterop

According to the official documentation, when allowSyntheticDefaultImports is set to true and a module does not explicitly export a default, TypeScript lets you write an import like import react from 'react' instead of the namespace form.

However, allowSyntheticDefaultImports only affects type checking; it does not change the emitted JavaScript. The compiled code for the default‑import style accesses react_1.default, which is undefined because the original module has no default export.

Using the namespace import import * as React from "react" compiles to a CommonJS require that returns the module object, so console.log('react', react) prints the expected React object.

To make the default‑import style work, you must enable the esModuleInterop compiler option. This option adds a helper __importDefault that wraps a non‑default export in an object with a default property, allowing the compiled code to access react_1.default correctly.

When esModuleInterop is on, the emitted code for import react from 'react' looks like:

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = __importDefault(require("react"));
console.log('react', react_1.default);

For the namespace import with esModuleInterop, the compiler generates helpers __createBinding, __setModuleDefault, and __importStar to copy all exports onto a new object and set its default property to itself:

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? function (o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } });
} : function (o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
});
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? function (o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
} : function (o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const react = __importStar(require("react"));
console.log('react', react);

In short, allowSyntheticDefaultImports only helps the TypeScript type system, while esModuleInterop changes the emitted JavaScript so that default imports work even when the original module lacks a default export.

TypeScriptReActesModuleInteropmodule importsallowSyntheticDefaultImports
Node Underground
Written by

Node Underground

No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.