Розуміння 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 для сумісності типів.

Також мені здавалося важким для розуміння / розуміння

Відповіді:


169

Постановка проблеми

Проблема виникає, коли ми хочемо імпортувати модуль CommonJS в кодову базу модулів ES6.

Перед цими прапорами нам довелося імпортувати модулі CommonJS із * as somethingімпортом зірки ( ):

// 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функціонує. Він призначає модуль ( 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')
— Алуан Хаддад

14

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"). Тут речі прості, але вони не спрацьовують, якщо основний об'єкт, який імпортується, є примітивом або значенням з підписами call / construct. В основному ECMAScript говорить, що запис простору імен є простим об'єктом.

Спочатку Babel вимагає в модулі і перевіряє наявність властивості __esModule. Якщо__esModule встановлено значення true, то поведінка така ж, як у TypeScript, але в іншому випадку вона синтезує запис простору імен де:

  1. Усі властивості зриваються з модуля Requ'd та стають доступними як імпорт з ім’ям.
  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. Так це 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.