×
Community Blog How Does esModuleInterop Affect TSC?

How Does esModuleInterop Affect TSC?

This article explains how a specific configuration affects tsc compilation results based on the differences in specifications between CommonJS and ES Module.

By Cunyi (Hu Biao)

Background: Starting with an Unexpected Problem

Tsconfig has a configuration item esModuleInterop: boolean that affects how typescript handles module loading at compile time. After this configuration is altered (false -> true), error codes are reported during the image upload. The following is an excerpt of the error codes:

import * as XXX from '@xxx';

const obj = new XXX({
  accesstoken: config.accessToken,
});

The error codes reported are listed below:

TypeError: XXX is not a constructor

There are two common repair solutions:

  1. Change the value esModuleInterop back to false and modify the module import mode of the new code
  2. Adjust the existing codes and change them into the following codes:
- import * as XXX from '@xxx';
+ import XXX from '@xxx';

Then, the problem is quickly fixed. However, have we ever gone deeper into the details behind this feature? For example, are there any other repair solutions? This article explains how this configuration affects tsc compilation results based on the differences in specifications between CommonJS and ES Module.

An Introduction to CommonJS and ES Module

An Introduction to ES Module Syntax

Let��s say an ES Module foo.ts is defined like this:

export const foo = 'foo';
class Foo {};
export default Foo;

Example 1

  • Import Foo from './foo' indicates the default export of the modules imported into the foo module, which is Foo Class.
  • Import * as foo from './foo' indicates all the exports of the modules imported into the foo module, which is, { foo: 'foo', default: Foo }.

An Introduction to CommonJS Syntax

Similar to example 1, CommonJS is relatively simple. The conversion to CommonJS is listed below:

exports.foo = 'foo',
class Foo {};
exports.default = Foo;

Example 2

How Does tsc Convert ES Module to CommonJS

Typescript uses the ES Module specification. However, in the Node.js environment, tsc needs to compile the ES Module into the codes that conform to the CommonJS specification before they can be executed in Node.js. There are many differences between ES Module and CommonJS. Among them, one thing has more to do with esModuleInterop attributes. ES Module has a default import/export concept, while CommonJS does not.

So, there are two strategies for us when we import a module in ts code to remove such a difference:

  1. The first one is that no special processing is performed by default, which is esModuleInterop=false. Use the import * as XX from 'XX' syntax to import a CommonJS module.
  2. The second one is the compatibility mode, which is esModuleInterop=true. All exports of CommonJS are merged as a default export, and the CommonJS module is imported using the import XX from 'XX' syntax.

Note: Whether the esModuleInterop value is false or true has no effect on importing ES Module modules.

The Way to Compile When the Value of esModuleInterop Is False

Its default compilation behavior is listed below:

  • The import * as foo from 'abc' will be compiled as: const foo = require('abc').
  • The import foo, { bar } from 'abc' will be compiled as: const foo_1 = require('abc'), and the code that calls the module at the same time will be modified by tsc:
- foo.xxx();
- bar();
+ foo_1.default.xxx();
+ foo_1.bar();

In this case, when importing a CommonJS module, we need to pay attention to:

  1. Whether the CommonJS module is exported as a common object. If it is, we can also use import { XX } from 'abc' to import some attributes of the module.
  2. Whether the default attribute is exported from the CommonJS module. (Generally, there is no default attribute.) If there is no default attribute, the import XX from 'abc' syntax cannot be used.

The Way to Compile When the Value of esModuleInterop Is True

Interop: It can be understood that systems can work together without special configurations.

After the configuration is enabled, the tsc will patch CommonJS and convert it into a module that conforms to the ES Module specification. The tsc will introduce two assistant methods (__importDefault and__importStar) to smooth out the differences between the exported CommonJS and ES module modules.

Description of __importDefault

When using the default import syntax (import XX from 'abc'), it will be compiled as: const XX_1 = __importDefault(require('abc')). Generally, the CommonJS module does not export the default attribute, so the default import syntax cannot be used. The __importDefault is used to merge the content exported by CommonJS into a default export:

{ 
  "default": require('A CommonJS module')
}

Similar to the fs module, we can directly use the import fs from 'fs' to import the content exported by CommonJS:

import fs from 'fs'
fs.readFileSync();

// The pseudo code after compilation is shown as follows:

const fs = __importDefault(require('fs'));
// In this case, fs = { default: { readFileSync, writeFileSync} };
fs.default.readFileSync();

Example 3

Description of __importStar

When import * as XX from 'abc' is used to import all syntax, it will be compiled as: const XX=__importStar(require('abc').

ImportStar is an upgraded version of the importDefault. In addition to the default attribute set as the exported module, all enumerable attributes of the imported module (excluding prototype chain inheritance) will be proxied. Examples:

import * as fs from 'fs'
fs.readFileSync();

// The pseudo code after compilation is shown as follows:

const fs = __importStar(require('fs'));
// In this case, fs = { readFileSync, writeFileSync, default: { readFileSync, writeFileSync} };

fs.readFileSync();

Example 4

Back to the Question Mentioned at the Beginning

We can find the answer according to the implementation analysis of importStar and importDefault. Modules like fs (its export result is a common object) can be imported no matter what method we use. If we use the import * as fs syntax to import the fs module before the esModuleInterop is enabled, the rewrite method is still compatible after the esModuleInterop is enabled with no modification needed.

The code at the beginning of the article reports an error when it is running because what the XXX module exports are a class. The __importStar will export the enumerable attributes of the module, but the functions and classes cannot be exported in the module.exports = function /class scenario. However, we can use the default property to call the module:

import * as XXX from '@xxx';
- const obj = new XXX({
+ const xxx = new XXX.default({
   accesstoken: config.accessToken,
});

That is the third solution. This kind of writing method looks weird, and unfamiliar people may wonder �C where does the default attribute come from? Therefore, the best way to go when importing a CommonJS module in ts is still:

  • When esModuleInterop=false, use the import * as XX syntax
  • When esModuleInterop=true, use the import XX from 'XX' syntax
0 1 0
Share on

Alibaba F(x) Team

66 posts | 3 followers

You may also like

Comments