typescript 了解 tsconfig 文件中的 esModuleInterop

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/56238356/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 08:03:04  来源:igfitidea点击:

Understanding esModuleInterop in tsconfig file

typescript

提问by anny123

I was checking out someone .tsconfigfile and there I spotted --esModuleInterop

我正在检查某人的.tsconfig文件,然后我发现--esModuleInterop

This is his .tsconfigfile

这是他的.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"]
}

Here, My primary question is what is "esModuleInterop": true,and "allowSyntheticDefaultImports": true,. I know they are sort of dependent on the "module": "commonjs",. Can someone try to explain it in the best human language possible?

在这里,我的主要问题是什么是 "esModuleInterop": true,"allowSyntheticDefaultImports": true,。我知道他们有点依赖 "module": "commonjs",. 有人可以尝试用最好的人类语言来解释它吗?

The official docs for allowSyntheticDefaultImportsstates

allowSyntheticDefaultImports各州的官方文档

Allow default imports from modules with no default export. This does not affect code emit, just typechecking.

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

Does that mean? if there isn't any export default than I think the only use case of the import default would be to initialise something? like singleton?

那是什么意思?如果没有任何导出默认值,我认为导入默认值的唯一用例是初始化某些东西?喜欢单身?

The following question/answer does not make sense as well Is there a way to use --esModuleInterop in tsconfig as opposed to it being a flag?

以下问题/答案也没有意义 有没有办法在 tsconfig 中使用 --esModuleInterop 而不是标志?

And --esModuleInteropdefinition on the compiler page

--esModuleInterop编译器页面上的定义

Emit __importStar and __importDefault helpers for runtime babel ecosystem compatibility and enable --allowSyntheticDefaultImports for typesystem compatibility.

发出 __importStar 和 __importDefault 帮助程序以实现运行时 babel 生态系统兼容性,并启用 --allowSyntheticDefaultImports 以实现类型系统兼容性。

Also seemed difficult for me to understand/comprehend

我似乎也很难理解/理解

回答by Krzysztof Grzybek

Problem statement

问题陈述

Problem occurs when we want to import CommonJS module into ES6 module codebase.

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

Before these flags we had to import CommonJS modules with star (* as something) import:

在这些标志之前,我们必须使用 star ( * as something) import导入 CommonJS 模块:

// 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();

We can see that *was somehow equivalent to exportsvariable. It worked fine, but it wasn't compliant with es6 modules spec. In spec, namespace record in star import (momentin our case) can be only a plain object, not callable (moment()is not allowed).

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

Solution

解决方案

With flag esModuleInteropwe can import CommonJS modules in compliance with es6modules spec. Now our import code looks like this:

使用 flagesModuleInterop我们可以导入符合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();

It works and it's perfectly valid with es6 modules spec, because momentis not namespace from star import, it's default import.

它可以工作并且完全适用于 es6 模块规范,因为moment它不是来自星型导入的命名空间,而是默认导入。

But how it works? As You can see, because we did default import, we call defaultproperty on momentobject. But we didn't declare any defaultproperty on exportsobject in moment library. The key is in __importDefaultfunction. It assigns module (exports) to defaultproperty for CommonJS modules:

但它是如何工作的?如您所见,因为我们进行了默认导入,所以我们defaultmoment对象上调用属性。但是我们没有在 moment 库中的 objectdefault上声明任何属性exports。关键在__importDefault功能上。它将模块 ( exports)分配给defaultCommonJS 模块的属性:

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

As You can see, we import es6 modules as it is, but CommonJS modules are wrapped into object with defaultkey. This makes possible to import defaults on CommonJS modules.

如您所见,我们按原样导入了 es6 模块,但 CommonJS 模块使用defaultkey包装到对象中。这使得在 CommonJS 模块上导入默认值成为可能。

__importStardoes the similar job - it returns untouched esModules, but translate CommonJS modules into modules with defaultproperty:

__importStar做类似的工作 - 它返回未触及的 esModules,但将 CommonJS 模块转换为具有default属性的模块:

// 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;
};

Synthetic imports

合成进口

And what about allowSyntheticDefaultImports, what it is for? Now the docs should be clear:

那么allowSyntheticDefaultImports,它的用途是什么?现在文档应该很清楚了:

Allow default imports from modules with no default export. This does not affect code emit, just typechecking.

Allow default imports from modules with no default export. This does not affect code emit, just typechecking.

In momenttypings we don't have specified default export, and we shouldn't have, beacuse it's available only with flag esModuleInteropon. So allowSyntheticDefaultImportsjust won't report error if we want to import default from thrid party module which don't have default export.

moment打字中,我们没有指定默认导出,我们也不应该有,因为它只有在esModuleInterop打开标志时才可用。所以allowSyntheticDefaultImports如果我们想从没有默认导出的第三方模块导入默认值,就不会报错。

回答by Titian Cernicova-Dragomir

esModuleInteropgenerates the helpers outlined in the docs. Looking at the generated code, we can see exactly what these do:

esModuleInterop生成文档中概述的助手。查看生成的代码,我们可以确切地看到它们的作用:

//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: If the module is not an esmodule then what is returned by require becomes the default. This means that if you use default import on a commonjsmodule, the whole module is actually the default.

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

__importStaris best described in this PR:

__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 __esModuleis set to true, then the behavior is the same as that of TypeScript, but otherwise, it synthesizes a namespace record where:

  1. All properties are plucked off of the require'd module and made available as named imports.
  2. The originally require'd module is made available as a default import.

TypeScript 将命名空间导入(即import * as foo from "foo")视为等同于const foo = require("foo"). 事情在这里很简单,但是如果要导入的主要对象是原始对象或具有调用/构造签名的值,则它们不起作用。ECMAScript 基本上说命名空间记录是一个普通对象。

Babel 首先需要在模块中,并检查名为__esModule. 如果__esModule设置为true,则行为与 TypeScript 相同,但除此之外,它会合成一个命名空间记录,其中:

  1. 所有属性都从 require'd 模块中提取出来,并作为命名导入提供。
  2. 最初需要的模块作为默认导入提供。

So we get this:

所以我们得到这个:

// 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"));

allowSyntheticDefaultImportsis the companion to all of this, setting this to false will not change the emitted helpers (both of them will still look the same). But it will raise a typescript error if you are using default import for a commonjs module. So this import React from 'react'will raise the error Module '".../node_modules/@types/react/index"' has no default export.if allowSyntheticDefaultImportsis false.

allowSyntheticDefaultImports是所有这些的伴随,将其设置为 false 不会改变发出的助手(它们看起来仍然相同)。但是,如果您对 commonjs 模块使用默认导入,则会引发打字稿错误。所以如果是,这import React from 'react'将引发错误。Module '".../node_modules/@types/react/index"' has no default export.allowSyntheticDefaultImportsfalse