我正在检查某人的.tsconfig文件,在那里我发现了esModuleInterop

这是他的.tsconfig文件

{
  "compilerOptions": {
    "moduleResolution": "node",
    "target": "es6",
    "module": "commonjs",
    "lib": ["esnext"],
    "strict": true,
    "sourceMap": true,
    "declaration": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "declarationDir": "./dist",
    "outDir": "./dist",
    "typeRoots": ["node_modules/@types"]
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modues"]
}

在这里,我的主要问题是什么是“esModuleInterop”:true,和 “allowSyntheticDefaultImports”:真的,。我知道它们有点依赖于"module": "commonjs",。有人能试着用最好的人类语言解释一下吗?

allowsyntheticdefaulultimports的官方文档声明

允许从没有默认导出的模块进行默认导入。这并 不影响代码发射,只是类型检查。

这是什么意思?如果没有任何导出默认,那么我认为导入默认的唯一用例是初始化一些东西?就像一个单身汉?

下面的问题/答案也没有意义 是否有一种方法可以在tsconfig中使用——esModuleInterop,而不是将其作为标志?

以及编译器页面上的——esModuleInterop定义

为运行时babel释放__importStar和__importDefault帮助 生态系统兼容性和启用——allowsyntheticdefaulultimports for 类型系统兼容性。

我也很难理解


当前回答

问题陈述

当我们想要将CommonJS模块导入到ES6模块代码库时,出现了问题。

在这些标志之前,我们必须导入CommonJS模块,并使用*作为import:

// node_modules/moment/index.js
exports = moment
// index.ts file in our app
import * as moment from 'moment'
moment(); // not compliant with es6 module spec

// transpiled js (simplified):
const moment = require("moment");
moment();

我们可以看到,*在某种程度上等同于exports变量。它工作得很好,但它不符合es6模块规范。在规范中,星型导入中的命名空间记录(在我们的例子中是moment)只能是一个普通对象,不可调用(moment()是不允许的)。

解决方案

通过esModuleInterop标志,我们可以按照es6模块规范导入CommonJS模块。现在我们的导入代码看起来像这样:

// index.ts file in our app
import moment from 'moment'
moment(); // compliant with es6 module spec

// transpiled js with esModuleInterop (simplified):
const moment = __importDefault(require('moment'));
moment.default();

它可以工作,在es6 modules规范中完全有效,因为moment不是命名空间,它是默认导入。

但它是如何工作的呢?如你所见,因为我们做了默认导入,所以我们在moment对象上调用了default属性。但是我们没有在moment库中的exports对象上声明默认属性。键是__importDefault函数。它将module (exports)分配给CommonJS模块的默认属性:

var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};

如你所见,我们按原样导入es6模块,但CommonJS模块被包装成一个带有默认键的对象。这使得在CommonJS模块中导入默认值成为可能。

__importStar做了类似的工作——它返回的是原封不动的esModules,但将CommonJS模块转换为具有默认属性的模块:

// index.ts file in our app
import * as moment from 'moment'

// transpiled js with esModuleInterop (simplified):
const moment = __importStar(require("moment"));
// note that "moment" is now uncallable - ts will report error!
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};

合成进口

那么allowsyntheticdefaulultimports呢?它是干什么用的?现在文档应该清楚了:

允许从没有默认导出的模块进行默认导入。这不会影响代码发出,只会影响类型检查。

在moment类型中,我们没有指定默认导出,也不应该指定,因为只有打开esModuleInterop标志时它才可用。因此,如果我们想从没有默认导出的第三方模块导入default, allowSyntheticDefaultImports将不会报告错误。

其他回答

esModuleInterop生成文档中列出的helper。查看生成的代码,我们可以清楚地看到这些代码的作用:

//ts 
import React from 'react'
//js 
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = __importDefault(require("react"));

__importDefault:如果该模块不是es模块,则require返回的内容将成为默认值。这意味着如果你在commonjs模块上使用默认导入,整个模块实际上都是默认的。

__importStar最好的描述在这个PR中:

TypeScript treats a namespace import (i.e. import * as foo from "foo") as equivalent to const foo = require("foo"). Things are simple here, but they don't work out if the primary object being imported is a primitive or a value with call/construct signatures. ECMAScript basically says a namespace record is a plain object. Babel first requires in the module, and checks for a property named __esModule. If __esModule is set to true, then the behavior is the same as that of TypeScript, but otherwise, it synthesizes a namespace record where: All properties are plucked off of the require'd module and made available as named imports. The originally require'd module is made available as a default import.

所以我们得到这个:

// ts
import * as React from 'react'

// emitted js
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
var React = __importStar(require("react"));

allowsyntheticdefaulultimports是所有这些的同伴,将其设置为false不会改变所发出的帮助程序(它们看起来仍然相同)。但是如果你对commonjs模块使用默认的导入,它会引发一个typescript错误。因此,从' React '导入React将引发错误Module '”…/node_modules/@types/react/index"'没有默认导出。如果allowsyntheticdefaulultimports为false。

问题陈述

当我们想要将CommonJS模块导入到ES6模块代码库时,出现了问题。

在这些标志之前,我们必须导入CommonJS模块,并使用*作为import:

// node_modules/moment/index.js
exports = moment
// index.ts file in our app
import * as moment from 'moment'
moment(); // not compliant with es6 module spec

// transpiled js (simplified):
const moment = require("moment");
moment();

我们可以看到,*在某种程度上等同于exports变量。它工作得很好,但它不符合es6模块规范。在规范中,星型导入中的命名空间记录(在我们的例子中是moment)只能是一个普通对象,不可调用(moment()是不允许的)。

解决方案

通过esModuleInterop标志,我们可以按照es6模块规范导入CommonJS模块。现在我们的导入代码看起来像这样:

// index.ts file in our app
import moment from 'moment'
moment(); // compliant with es6 module spec

// transpiled js with esModuleInterop (simplified):
const moment = __importDefault(require('moment'));
moment.default();

它可以工作,在es6 modules规范中完全有效,因为moment不是命名空间,它是默认导入。

但它是如何工作的呢?如你所见,因为我们做了默认导入,所以我们在moment对象上调用了default属性。但是我们没有在moment库中的exports对象上声明默认属性。键是__importDefault函数。它将module (exports)分配给CommonJS模块的默认属性:

var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};

如你所见,我们按原样导入es6模块,但CommonJS模块被包装成一个带有默认键的对象。这使得在CommonJS模块中导入默认值成为可能。

__importStar做了类似的工作——它返回的是原封不动的esModules,但将CommonJS模块转换为具有默认属性的模块:

// index.ts file in our app
import * as moment from 'moment'

// transpiled js with esModuleInterop (simplified):
const moment = __importStar(require("moment"));
// note that "moment" is now uncallable - ts will report error!
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};

合成进口

那么allowsyntheticdefaulultimports呢?它是干什么用的?现在文档应该清楚了:

允许从没有默认导出的模块进行默认导入。这不会影响代码发出,只会影响类型检查。

在moment类型中,我们没有指定默认导出,也不应该指定,因为只有打开esModuleInterop标志时它才可用。因此,如果我们想从没有默认导出的第三方模块导入default, allowSyntheticDefaultImports将不会报告错误。

附注:这个答案是我从阅读@Krzysztof Grzybek的回答和我提供的文档链接中得到的理解

// node_modules/moment/index.js
1. exports = moment
// index.ts file in our app
2. import * as moment from 'moment'
3. moment(); // not compliant with es6 module spec

// transpiled js (simplified):
4. const moment = require("moment");
5. moment();

假设allowSyntheticDefaultImports设置为true,它告诉typescript可以从实际上没有默认导出的模块中编写默认导入。(此处Moment没有默认导出) 如果我们将esModuleInterop设置为true,那么第3行将给出以下错误

This expression is not callable.
  Type 'typeof moment' has no call signatures.

因为,AMD/UMD模块不被视为es6模块,并且使用两个单独的函数来导入,即__importStar和__importDefault https://www.typescriptlang.org/tsconfig#esModuleInterop,因此第3行试图导入对象,因此它是不可调用的。然而,我所见过的大多数项目都试图保持esModuleInterop:false,因此他们不想将UMD模块与es6模块区别对待