Entendiendo esModuleInterop en el archivo tsconfig


142

Estaba revisando el .tsconfigarchivo de alguien y allí vi--esModuleInterop

Este es su .tsconfigarchivo

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

Aquí, mi pregunta principal es qué es "esModuleInterop": true,y "allowSyntheticDefaultImports": true,. Sé que dependen en cierto modo del "module": "commonjs",. ¿Alguien puede intentar explicarlo en el mejor lenguaje humano posible?

Los documentos oficiales de los allowSyntheticDefaultImportsestados

Permitir importaciones predeterminadas desde módulos sin exportación predeterminada. Esto no afecta a la emisión del código, solo verificando el tipo.

¿Eso significa? si no hay ningún valor predeterminado de exportación, creo que el único caso de uso del valor predeterminado de importación sería inicializar algo. como singleton?

La siguiente pregunta / respuesta tampoco tiene sentido. ¿Hay alguna forma de usar --esModuleInterop en tsconfig en lugar de ser una bandera?

Y --esModuleInteropdefinición en la página del compilador

Emite los ayudantes __importStar y __importDefault para la compatibilidad del ecosistema de babel en tiempo de ejecución y habilita --allowSyntheticDefaultImports para la compatibilidad del sistema de tipos.

También me pareció difícil de entender / comprender

Respuestas:


169

Planteamiento del problema

El problema ocurre cuando queremos importar el módulo CommonJS al código base del módulo ES6.

Antes de estas banderas, teníamos que importar módulos CommonJS con 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();

Podemos ver que de *alguna manera era equivalente a exportsvariable. Funcionó bien, pero no cumplía con las especificaciones de los módulos es6. En la especificación, el registro de espacio de nombres en la importación de estrellas ( momenten nuestro caso) puede ser solo un objeto simple, no invocable ( moment()no está permitido).

Solución

Con flag esModuleInteroppodemos importar módulos CommonJS de acuerdo con las es6especificaciones de los módulos. Ahora nuestro código de importación se ve así:

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

Funciona y es perfectamente válido con la especificación de módulos es6, porque momentno es un espacio de nombres de la importación de estrellas, es una importación predeterminada.

¿Pero cómo funciona? Como puede ver, debido a que hicimos una importación predeterminada, llamamos a la defaultpropiedad en el momentobjeto. Pero no declaramos ninguna defaultpropiedad en el exportsobjeto en la biblioteca de momentos. La llave está en __importDefaultfuncionamiento. Asigna module ( exports) a la defaultpropiedad de los módulos CommonJS:

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

Como puede ver, importamos los módulos es6 tal como están, pero los módulos CommonJS se envuelven en el objeto con la defaultclave. Esto hace posible importar valores predeterminados en módulos CommonJS.

__importStarhace un trabajo similar: devuelve esModules intactos, pero traduce los módulos CommonJS en módulos con la defaultpropiedad:

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

Importaciones sintéticas

¿Y para allowSyntheticDefaultImportsqué sirve? Ahora los documentos deberían ser claros:

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

En momentmecanografía, no hemos especificado la exportación predeterminada, y no deberíamos haberlo hecho, porque está disponible solo con la bandera activada esModuleInterop. Por allowSyntheticDefaultImportslo tanto , no informaremos de errores si queremos importar el módulo de terceros que no tiene exportación predeterminada.


Antes de la introducción de la --esModuleInteropbandera, noimport * as moment from 'moment' fue correcto. estaba. import moment = require('moment')
— Aluan Haddad

14

esModuleInteropgenera los ayudantes descritos en los documentos. Mirando el código generado, podemos ver exactamente lo que hacen:

//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 el módulo no es un esmódulo, lo que devuelve require se convierte en el predeterminado. Esto significa que si usa la importación predeterminada en un commonjsmódulo, todo el módulo es en realidad el predeterminado.

__importStarse describe mejor en este PR :

TypeScript trata una importación de espacio de nombres (es decir import * as foo from "foo") como equivalente a const foo = require("foo"). Las cosas son simples aquí, pero no funcionan si el objeto principal que se está importando es una primitiva o un valor con firmas de llamada / construcción. ECMAScript básicamente dice que un registro de espacio de nombres es un objeto simple.

Babel primero requiere en el módulo y busca una propiedad nombrada __esModule. Si __esModulese establece en true, entonces el comportamiento es el mismo que el de TypeScript, pero de lo contrario, sintetiza un registro de espacio de nombres donde:

  1. Todas las propiedades se extraen del módulo requerido y están disponibles como importaciones con nombre.
  2. El módulo originalmente requerido está disponible como importación predeterminada.

Entonces obtenemos esto:

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

allowSyntheticDefaultImportses el compañero de todo esto, establecer esto en falso no cambiará los ayudantes emitidos (ambos seguirán teniendo el mismo aspecto). Pero generará un error de mecanografiado si está utilizando la importación predeterminada para un módulo commonjs. Entonces esto import React from 'react'generará el error Module '".../node_modules/@types/react/index"' has no default export.si allowSyntheticDefaultImportses false.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.