typescript - Înțelegerea esModuleInterop în fișierul tsconfig
Verificam fișierul .tsconfig al cuiva și acolo am observat --esModuleInterop
Acesta este fișierul său .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_modules"]
}
Aici, întrebarea mea principală este ce este "esModuleInterop": true, și "allowSyntheticDefaultImports": true,. Știu că ele depind oarecum de "module": "commonjs",. Poate cineva să încerce să explice acest lucru în cel mai clar limbaj uman posibil?
Documentația oficială pentru allowSyntheticDefaultImports afirmă
Permite importurile implicite din modulele fără export implicit. Acest lucru nu afectează generarea codului, doar verificarea tipurilor.
Ce înseamnă asta? Dacă nu există niciun export implicit, atunci cred că singurul caz de utilizare al importului implicit ar fi să inițializezi ceva? Ca un singleton?
Întrebarea/răspunsul următor nu are sens de asemenea. Există o modalitate de a folosi --esModuleInterop în tsconfig în loc să fie un flag?
Și definiția --esModuleInterop pe pagina compilatorului
Generați helper-ele __importStar și __importDefault pentru compatibilitatea runtime a ecosistemului babel și activați --allowSyntheticDefaultImports pentru compatibilitatea cu sistemul de tipuri.
De asemenea, mi s-a părut dificil de înțeles/comprehend
Răspuns #1
Formularea problemei
Problema apare atunci când vrem să importăm un modul CommonJS într-o bază de coduri modul ES6.
Înainte de aceste steaguri, a trebuit să importăm module CommonJS cu import de tip star (* 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();
Putem vedea că * a fost cumva echivalent cu variabila exports. A funcționat bine, dar nu era conform specificației modulelor es6. În specificație, înregistrarea spațiului de nume în importul cu stea (moment în cazul nostru) poate fi doar un obiect simplu, nu apelabil (moment() nu este permis).
Soluție
Cu flag-ul esModuleInterop putem importa module CommonJS în conformitate cu specificația modulelor es6. Acum codul nostru de import arată astfel:
// 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();
Funcționează și este perfect valid conform specificației modulelor ES6, deoarece moment nu este un namespace din importul cu star, este un import implicit.
Dar cum funcționează? După cum poți vedea, pentru că am făcut o importare implicită, am apelat proprietatea default pe un obiect moment. Dar nu am declarat o proprietate default pe obiectul exports în biblioteca moment. Cheia este funcția __importDefault. Aceasta atribuie modulului (exports) proprietatea default pentru modulele CommonJS:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
După cum puteți vedea, importăm module ES6 așa cum sunt, dar modulele CommonJS sunt învelite într-un obiect cu cheia default. Acest lucru face posibil să importăm valorile implicite din modulele CommonJS.
__importStar face o treabă similară - returnează esModulele nemodificate, dar traduce modulele CommonJS în module cu o proprietate 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;
};
Importuri sintetice
Și ce zici de allowSyntheticDefaultImports - la ce servește? Acum documentația ar trebui să fie clară:
Allow default imports from modules with no default export. This does not affect code emit, just typechecking.În tipările moment nu avem specificat exportul implicit și nu ar trebui să avem, pentru că este disponibil doar cu flag-ul esModuleInterop activat. Așadar, allowSyntheticDefaultImports nu va raporta o eroare dacă vrem să importăm implicit dintr-un modul terț care nu are un export implicit.
Răspunsul #2
esModuleInterop generează ajutoarele descrise în documentație. Privind codul generat, putem vedea exact ce fac acestea:
//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: Dacă modulul nu este un modul es, atunci ceea ce returnează require devine implicit. Aceasta înseamnă că dacă folosești importul implicit pe un modul commonjs, întregul modul este de fapt implicit.
__importStar este cel mai bine descris în acest PR:
TypeScript tratează o importare de namespace (adică
import * as foo from "foo") ca echivalent cuconst foo=require("foo"). Lucrurile sunt simple aici, dar nu funcționează dacă obiectul principal importat este un tip primitiv sau o valoare cu semnături de apel/construcție. ECMAScript spune practic că un record de namespace este un obiect simplu.Babel cere mai întâi modulul și verifică dacă există o proprietate numită
__esModule. Dacă__esModuleeste setată latrue, atunci comportamentul este același ca cel al TypeScript, dar în caz contrar, sintetizează un registru de namespace unde:
- Toate proprietățile sunt preluate din modulul require și făcute disponibile ca importuri denumite.
- Modulul inițial necesar este disponibil ca o importare implicită.
Deci obținem asta:
// 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 este companionul pentru toate acestea, setarea acestei opțiuni pe false nu va schimba helperii emiși (ambii vor arăta la fel). Dar va genera o eroare TypeScript dacă folosești importul default pentru un modul commonjs. Așadar, acest import React from 'react' va genera eroarea Module '".../node_modules/@types/react/index"' has no default export. dacă allowSyntheticDefaultImports este false.
