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.
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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
