Zrozumienie esModuleInterop w pliku tsconfig


142

Sprawdzałem czyjś .tsconfigplik i tam zauważyłem--esModuleInterop

To jego .tsconfigakta

{
  "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"]
}

Tutaj moje podstawowe pytanie brzmi: czym jest "esModuleInterop": true,i "allowSyntheticDefaultImports": true,. Wiem, że są zależne od "module": "commonjs",. Czy ktoś może spróbować wyjaśnić to w możliwie najlepszym ludzkim języku?

Oficjalne dokumenty dla allowSyntheticDefaultImportsstanów

Zezwalaj na domyślne importowanie z modułów bez domyślnego eksportu. Nie wpływa to na emisję kodu, tylko na sprawdzanie typu.

Czy to znaczy? jeśli nie ma żadnego domyślnego eksportu, to myślę, że jedynym przypadkiem użycia domyślnego importu byłoby zainicjowanie czegoś? jak singleton?

Następujące pytanie / odpowiedź również nie ma sensu. Czy istnieje sposób użycia opcji --esModuleInterop w tsconfig, zamiast bycia flagą?

I --esModuleInteropdefinicja na stronie kompilatora

Emituj pomocniki __importStar i __importDefault, aby zapewnić zgodność ekosystemu Babel w środowisku wykonawczym i włącz opcję --allowSyntheticDefaultImports, aby zapewnić zgodność systemu typów.

Wydawało mi się również trudne do zrozumienia / zrozumienia

Odpowiedzi:


174

Opis problemu

Problem pojawia się, gdy chcemy zaimportować moduł CommonJS do bazy kodu modułu ES6.

Przed tymi flagami musieliśmy importować moduły CommonJS z * as somethingimportem star ( ):

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

Widzimy, że *było to w jakiś sposób równoważne exportszmiennej. Działało dobrze, ale nie było zgodne ze specyfikacją modułów es6. W specyfikacji, rekord przestrzeni nazw w imporcie gwiazdy ( momentw naszym przypadku) może być tylko zwykłym obiektem, którego nie można wywołać ( moment()jest to niedozwolone).

Rozwiązanie

Z flagą esModuleInteropmożemy importować moduły CommonJS zgodnie ze es6specyfikacją modułów. Teraz nasz kod importu wygląda następująco:

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

Działa i jest całkowicie poprawny ze specyfikacją modułów es6, ponieważ momentnie jest przestrzenią nazw z importu gwiazdy, jest to domyślny import.

Ale jak to działa? Jak widać, ponieważ wykonaliśmy import domyślny, wywołujemy defaultwłaściwość momentobiektu. Ale nie zadeklarowaliśmy żadnej defaultwłaściwości exportsobiektu w bibliotece momentów. Kluczyk __importDefaultdziała. Przypisuje module ( exports) do defaultwłaściwości dla modułów CommonJS:

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

Jak widać, importujemy moduły es6 bez zmian, ale moduły CommonJS są opakowane w obiekt z defaultkluczem. Umożliwia to importowanie wartości domyślnych w modułach CommonJS.

__importStarwykonuje podobną pracę - zwraca nietknięte esModules, ale tłumaczy moduły CommonJS na moduły o defaultwłaściwości:

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

Import syntetyczny

A co z tym allowSyntheticDefaultImports, do czego to służy? Teraz dokumenty powinny być jasne:

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

We momentwpisach nie określiliśmy domyślnego eksportu i nie powinniśmy tego robić, ponieważ jest dostępny tylko z włączoną flagą esModuleInterop. Więc allowSyntheticDefaultImportspo prostu nie zgłosi błędu, jeśli chcemy zaimportować domyślne z modułu trzeciej strony, który nie ma domyślnego eksportu.


Przed wprowadzeniem --esModuleInteropflagi nieimport * as moment from 'moment' było poprawne. był. import moment = require('moment')
— Aluan Haddad

14

esModuleInteropgeneruje pomocników opisanych w dokumentach. Patrząc na wygenerowany kod, widzimy dokładnie, co robią:

//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: Jeśli moduł nie jest esmodułem, to, co jest zwracane przez require, staje się wartością domyślną. Oznacza to, że jeśli używasz domyślnego importu do commonjsmodułu, cały moduł jest w rzeczywistości domyślny.

__importStarnajlepiej opisuje ten PR :

TypeScript traktuje import przestrzeni nazw (tj. import * as foo from "foo") Jako odpowiednik const foo = require("foo"). Tutaj sprawy są proste, ale nie działają, jeśli importowany podstawowy obiekt jest prymitywem lub wartością z sygnaturami wywołania / konstruowania. ECMAScript zasadniczo mówi, że rekord przestrzeni nazw jest zwykłym obiektem.

Babel najpierw wymaga w module i sprawdza właściwość o nazwie __esModule. Jeśli __esModulejest ustawiona na true, to zachowanie jest takie samo jak w przypadku TypeScript, ale poza tym syntetyzuje rekord przestrzeni nazw, gdzie:

  1. Wszystkie właściwości są pobierane z wymaganego modułu i udostępniane jako nazwane importy.
  2. Pierwotnie wymagany moduł jest udostępniany jako domyślny import.

Więc otrzymujemy to:

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

allowSyntheticDefaultImportsjest towarzyszem tego wszystkiego, ustawienie tego na false nie zmieni wysyłanych helperów (oba nadal będą wyglądać tak samo). Ale spowoduje to błąd maszynopisu, jeśli używasz domyślnego importu dla modułu commonjs. Więc to import React from 'react'podniesie błąd Module '".../node_modules/@types/react/index"' has no default export., jeśli allowSyntheticDefaultImportsjest false.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.