Comprendre esModuleInterop dans le fichier tsconfig


142

J'étais en train de vérifier le .tsconfigfichier de quelqu'un et là j'ai repéré--esModuleInterop

Ceci est son .tsconfigdossier

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

Ici, ma principale question est de savoir ce qu'est "esModuleInterop": true,et "allowSyntheticDefaultImports": true,. Je sais qu'ils dépendent en quelque sorte du "module": "commonjs",. Quelqu'un peut-il essayer de l'expliquer dans le meilleur langage humain possible?

La documentation officielle des allowSyntheticDefaultImportsÉtats

Autoriser les importations par défaut à partir de modules sans exportation par défaut. Cela n'affecte pas l'émission de code, juste la vérification de type.

Cela signifie t-il? s'il n'y a pas de valeur par défaut d'exportation, je pense que le seul cas d'utilisation de la valeur par défaut d'importation serait d'initialiser quelque chose? comme singleton?

La question / réponse suivante n'a pas non plus de sens. Existe-t-il un moyen d'utiliser --esModuleInterop dans tsconfig plutôt que d'être un indicateur?

Et --esModuleInteropdéfinition sur la page du compilateur

Émettez les helpers __importStar et __importDefault pour la compatibilité de l'écosystème babel à l'exécution et activez --allowSyntheticDefaultImports pour la compatibilité des systèmes de types.

Il m'a également semblé difficile de comprendre / comprendre

Réponses:


172

Énoncé du problème

Un problème se produit lorsque nous voulons importer le module CommonJS dans la base de code du module ES6.

Avant ces drapeaux, nous devions importer des modules CommonJS avec l' * as somethingimportation 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();

Nous pouvons voir que *c'était en quelque sorte équivalent à exportsvariable. Cela fonctionnait bien, mais ce n'était pas conforme aux spécifications des modules es6. Dans les spécifications, l'enregistrement d'espace de noms dans l'importation en étoile ( momentdans notre cas) ne peut être qu'un objet simple, non appelable ( moment()n'est pas autorisé).

Solution

Avec flag, esModuleInteropnous pouvons importer des modules CommonJS conformément aux es6spécifications des modules. Maintenant, notre code d'importation ressemble à ceci:

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

Cela fonctionne et c'est parfaitement valide avec les spécifications des modules es6, car ce momentn'est pas un espace de noms de l'importation d'étoiles, c'est l'importation par défaut.

Mais comment ça marche? Comme vous pouvez le voir, comme nous avons importé par défaut, nous appelons defaultpropriété sur momentobjet. Mais nous n'avons déclaré aucune defaultpropriété sur l' exportsobjet dans la bibliothèque moment. La clé est en __importDefaultfonction. Il attribue module ( exports) à la defaultpropriété des modules CommonJS:

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

Comme vous pouvez le voir, nous importons les modules es6 tels quels, mais les modules CommonJS sont enveloppés dans un objet avec une defaultclé. Cela permet d'importer les valeurs par défaut sur les modules CommonJS.

__importStarfait le même travail - il renvoie des esModules intacts, mais traduit les modules CommonJS en modules avec la defaultpropriété:

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

Importations synthétiques

Et qu'en est-il allowSyntheticDefaultImports, à quoi ça sert? Maintenant, les documents devraient être clairs:

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

Dans les momenttypages, nous n'avons pas spécifié d'export par défaut, et nous n'aurions pas dû, car il est disponible uniquement avec l'indicateur activé esModuleInterop. Donc, allowSyntheticDefaultImportsnous ne signalerons pas d'erreur si nous voulons importer la valeur par défaut du module tiers qui n'a pas d'exportation par défaut.


Avant l'introduction du --esModuleInteropdrapeau, ce import * as moment from 'moment'n'était pas correct. import moment = require('moment')était.
— Aluan Haddad

14

esModuleInteropgénère les helpers décrits dans la documentation. En regardant le code généré, nous pouvons voir exactement ce qu'ils font:

//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: Si le module n'est pas un esmodule, ce qui est renvoyé par require devient la valeur par défaut. Cela signifie que si vous utilisez l'importation par défaut sur un commonjsmodule, l'ensemble du module est en fait le module par défaut.

__importStarest mieux décrit dans ce PR :

TypeScript traite une importation d'espace de noms (c'est-à-dire import * as foo from "foo") comme équivalente à const foo = require("foo"). Les choses sont simples ici, mais elles ne fonctionnent pas si l'objet principal importé est une primitive ou une valeur avec des signatures d'appel / de construction. ECMAScript dit essentiellement qu'un enregistrement d'espace de noms est un objet simple.

Babel requiert d'abord dans le module, et recherche une propriété nommée __esModule. Si __esModuleest défini sur true, alors le comportement est le même que celui de TypeScript, mais sinon, il synthétise un enregistrement d'espace de noms où:

  1. Toutes les propriétés sont extraites du module requis et rendues disponibles sous forme d'importations nommées.
  2. Le module initialement requis est rendu disponible comme importation par défaut.

Nous obtenons donc ceci:

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

allowSyntheticDefaultImportsest le compagnon de tout cela, définir ceci sur false ne changera pas les helpers émis (les deux auront toujours la même apparence). Mais cela provoquera une erreur de typographie si vous utilisez l'importation par défaut pour un module commonjs. Donc, cela import React from 'react'augmentera l'erreur Module '".../node_modules/@types/react/index"' has no default export.si allowSyntheticDefaultImportsc'est le cas false.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.