By Cunyi (Hu Biao)
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:
esModuleInterop
back to false
and modify the module import mode of the new code- 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.
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 }
.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
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:
esModuleInterop=false
. Use the import * as XX from 'XX'
syntax to import a CommonJS module.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.
Its default compilation behavior is listed below:
import * as foo from 'abc'
will be compiled as: const foo = require('abc')
.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:
import { XX } from 'abc'
to import some attributes of the module.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.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
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:
esModuleInterop=false
, use the import * as XX
syntaxesModuleInterop=true
, use the import XX from 'XX'
syntax66 posts | 3 followers
FollowLouis Liu - August 27, 2019
digoal - May 19, 2021
Louis Liu - August 26, 2019
digoal - October 18, 2022
Alibaba Cloud Native - July 20, 2023
HaydenLiu - December 5, 2022
66 posts | 3 followers
FollowA low-code development platform to make work easier
Learn MoreHelp enterprises build high-quality, stable mobile apps
Learn MoreAlibaba Cloud (in partnership with Whale Cloud) helps telcos build an all-in-one telecommunication and digital lifestyle platform based on DingTalk.
Learn MoreMore Posts by Alibaba F(x) Team