【问题标题】:Understanding esModuleInterop in tsconfig file了解 tsconfig 文件中的 esModuleInterop
【发布时间】:2019-10-07 20:27:03
【问题描述】:

我正在查看某人 .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": true,。我知道他们有点依赖 "module": "commonjs", 。有人可以尝试用最好的人类语言来解释吗?

allowSyntheticDefaultImports states 的官方文档

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

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

以下问题/答案也没有意义 Is there a way to use --esModuleInterop in tsconfig as opposed to it being a flag?

--esModuleInterop编译页面上的定义

为运行时 babel 发出 __importStar 和 __importDefault 助手 生态系统兼容性并启用 --allowSyntheticDefaultImports for 类型系统兼容性。

对我来说似乎也很难理解/理解

【问题讨论】:

  • 我会在 NodeJS v16 中给你一个实际的例子。如果您的 npm 包 A 导入了“集群”,并且您的项目依赖于您的 npm 包 A 并从中导入了一个类,那么在您的 npm 包 A 中没有将“esModuleInterop”设置为 true,则在 npm 包 A 中未定义集群,但是项目本身完全没问题。一直以来,一切都很好地编译,没有任何警告。

标签: typescript


【解决方案1】:

问题陈述

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

在这些标志之前,我们必须使用星号 (* as something) 导入 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();

我们可以看到* 在某种程度上等同于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 模块规范,因为 moment 不是来自星形导入的命名空间,它是默认导入。

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

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

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

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

合成进口

那么allowSyntheticDefaultImports 呢?它有什么用?现在文档应该很清楚了:

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

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

【讨论】:

  • 在引入--esModuleInterop 标志之前,import * as moment from 'moment'正确的。 import moment = require('moment') 是。
  • @AluanHaddad 似乎两种方法都有效:import foo = require('foo') 或 import * as foo from 'foo'
  • @tomwang1013 import * as m 形式的导入不允许 m()new m。一些模块加载器和捆绑器我会绕过它,但它违反了 ECMAScript 规范
  • @AluanHaddad 我明白你的意思。我的意思是打字稿会改变它们并平等对待它们
  • @tomwang1013 没有。这是不正确的。模块的功能不仅取决于编译器,在这种情况下是 TypeScript,还取决于作为运行时一部分的模块加载器。
【解决方案2】:

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:如果模块不是es 模块,则 require 返回的内容将成为默认值。这意味着如果您在 commonjs 模块上使用默认导入,则整个模块实际上是默认的。

__importStar 最好在this PR 中描述:

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

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

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

所以我们得到这个:

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

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

【讨论】:

    猜你喜欢
    • 2019-10-06
    • 2018-07-24
    • 1970-01-01
    • 2021-01-11
    • 2022-07-21
    • 2021-07-24
    • 2023-01-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多