Понимание esModuleInterop в файле tsconfig


142

Я просматривал чей-то .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штатов

Разрешить импорт по умолчанию из модулей без экспорта по умолчанию. Это не влияет на выдачу кода, только на проверку типов.

Это значит? если нет экспорта по умолчанию, чем я думаю, единственный вариант использования импорта по умолчанию - это что-то инициализировать? как синглтон?

Следующий вопрос / ответ также не имеет смысла. Есть ли способ использовать --esModuleInterop в tsconfig вместо того, чтобы он был флагом?

И --esModuleInteropопределение на странице компилятора

Выпустите помощники __importStar и __importDefault для совместимости с экосистемой babel во время выполнения и включите --allowSyntheticDefaultImports для совместимости с системами типов.

Мне тоже показалось трудным понять / понять

Ответы:


175

Постановка задачи

Проблема возникает, когда мы хотим импортировать модуль CommonJS в кодовую базу модуля ES6.

До этих флагов нам приходилось импортировать модули CommonJS с помощью star ( * as something) 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мы можем импортировать модули CommonJS в соответствии со es6спецификацией модулей. Теперь наш код импорта выглядит так:

// 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это не пространство имен из звездного импорта, это импорт по умолчанию.

Но как это работает? Как видите, поскольку мы выполняли импорт по умолчанию, мы вызываем defaultсвойство momentобъекта. Но мы не объявляли никаких defaultсвойств exportsобъекта в библиотеке моментов. Ключ в __importDefaultфункции. Он назначает module ( exports) defaultсвойству для модулей CommonJS:

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')был.
— Алуан Хаддад,

15

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модулем, то то, что возвращается требованием, становится значением по умолчанию. Это означает, что если вы используете импорт по умолчанию для commonjsмодуля, по умолчанию будет фактически весь модуль.

__importStarлучше всего описывается в этом PR :

TypeScript рассматривает импорт пространства имен (т.е. import * as foo from "foo") как эквивалент const foo = require("foo"). Здесь все просто, но они не работают, если первичный импортируемый объект является примитивом или значением с сигнатурами вызова / конструкции. ECMAScript в основном говорит, что запись пространства имен - это простой объект.

Babel сначала требует в модуле и проверяет свойство с именем __esModule. Если __esModuleустановлено true, то поведение такое же, как у TypeScript, но в остальном он синтезирует запись пространства имен, где:

  1. Все свойства взяты из модуля require'd и доступны как именованный импорт.
  2. Первоначально модуль require'd доступен в качестве импорта по умолчанию.

Получаем вот что:

// 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. Так что это import React from 'react'вызовет ошибку, Module '".../node_modules/@types/react/index"' has no default export.если allowSyntheticDefaultImportsесть false.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.