node.js - Hvorfor trenger vi "nodenext" TypeScript-kompilatoralternativet når vi har "esnext"?
I TypeScript-kompilatorens alternativer for #module har vi en rekke valg inkludert nodenext og esnext, hvor nodenext er eksperimentelt (per nå).
Hvorfor trenger vi dette ekstra nodenext alternativet når esnext alternativet allerede ser ut til å fungere med node.js?
Eller sagt med andre ord, hva er forskjellen mellom nodenext og esnext?
kilde: https://github.com/microsoft/TypeScript/issues/46452#issuecomment-1066771513
Svar #1
modul og modulOppløsning
Det første som trenger avklaring er forskjellen mellom kompilatorinnstillingene module og moduleResolution. Den første er en utdata-innstilling: hvilken modul-relatert kode vil tsc være villig til å generere til JS? Den enkleste måten å se effekten av denne innstillingen er ved å veksle mellom innstillingene commonjs og esnext:
| Inndatakode | Utdata --module commonjs | Utdata --module esnext |
|---|---|---|
importer { createSourceFile } fra "typescript" | "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const typescript_1=require("typescript"); | importer { createSourceFile } fra "typescript" |
Mens denne innstillingen fundamentalt styrer emit, kan den pålegge begrensninger på hvilken modul-relatert inputkode som er tillatt. For eksempel kan du ikke skrive imports i stilen import fs=require("fs") under --module es2015 (eller høyere ES-mål) fordi det ikke finnes noe require i ES-modulsystemet. I tillegg er bruk av top-nivå await kun tillatt i --module es2022 (eller høyere) eller system fordi det krever tilsvarende støtte i modulinnlastingssystemet.
På den annen side handler --moduleResolution om algoritmen som brukes for å svare på spørsmålet «gitt et filsystem og en inngangsfil som inneholder en import fra "lodash", hvilke filer bør jeg lete etter for å finne den modulen?» Åpenbart er avgjørelsen om å lete i en mappe med et magisk navn node_modules relatert til Node (selv om en stor mengde ikke-Node-verktøy har kopiert dette for bekvemmelighet), og vil ikke være korrekt for alle mulige kjøretider.
Forskjeller i moduleResolution
Med denne konteksten er vi klare til å begynne å svare direkte på spørsmålet ditt. Den største og mest merkbare forskjellen mellom --module nodenext og --module esnext er at førstnevnte impliserer --moduleResolution nodenext, en ny oppløsningsmodus designet for Nodes spesifikke implementasjon av sameksisterende ESM og CJS, mens sistnevnte ikke impliserer en moduleResolution-innstilling fordi det ikke finnes en slik tilsvarende innstilling i TypeScript akkurat nå. Med andre ord, når du sier at du bruker --module esnext, har du lov til å skrive, og vi vil generere, de nyeste og beste ES-modul kodene, men vi vil ikke gjøre noe annerledes med hensyn til hvordan importene løses. Du vil sannsynligvis fortsette å bruke --moduleResolution node, som ble designet for Nodes implementasjon av CJS. Hva betyr dette for deg? Hvis du skriver ESM for Node, kan du sannsynligvis få noen ting til å fungere med --module esnext og --moduleResolution node, men nyere Node-spesifikke funksjoner som package.json-eksporter vil ikke fungere, og det vil være veldig lett å gjøre feil når du skriver import-baner. Banene vil bli evaluert av tsc under Nodes CJS-regler, men ved kjøring vil Node evaluere dem under sine ESM-regler siden du genererer ESM. Det er betydelige forskjeller mellom disse algoritmene—spesielt krever sistnevnte at relative imports bruker filendelser i stedet for å droppe .js, og index-filer har ingen spesiell betydning, så du kan ikke importere index-filen bare ved å navngi banen til katalogen.
Forskjeller i modul
Forskjellen som kan observeres i --module-innstillingen i seg selv er litt mer subtil. Som jeg nevnte tidligere, i esnext er det ikke tillatt å skrive import Foo=require("bar") fordi vi antar at det ikke finnes require. I nodenext vet vi at et gitt modul kan være et ES-modul eller det kan være et CJS-modul, basert på filtypen (.mts → .mjs antyder ESM og .cts → .cjs antyder CJS) og/eller type-feltet i den nærmeste package.json-filen. --module nodenext gjør det mulig å se på disse tingene for å avgjøre hva slags modul en gitt fil er, noe som styrer hvilken type modulutdata vi genererer. Hvis de nevnte forholdene resulterer i at et modul tolkes som CJS, er utdataene for den filen nesten identiske (kanskje identiske?) med det du ville fått fra --module commonjs. Hvis modulen tolkes som ESM, er utdataene veldig lik det du ville fått fra --module esnext, med ett unntak jeg kan huske uten å sjekke: du har fortsatt lov til å skrive import Foo=require("bar"), og det blir kompilert til:
import { createRequire as _createRequire } from "module";
const __require = _createRequire(import.meta.url);
const Foo = __require("bar");
Sammendrag
Jeg tror svaret på spørsmålet ditt kan oppsummeres slik:
- Node 12+ har støtte for CJS og ESM side om side, indikert av
typei package.json og spesielle filendelser, så vi trenger en modulutgivelsesmodus som forstår det. Den modusen kan omtrent tenkes på som en Node-basert velger mellom de eksisterendecommonjsogesnextmodusene, med noen få ekstra små forskjeller tilpasset Node. - Node 12+ bringer også store nye funksjoner for hvordan modulspesifikatorer i pakker kan løses, og håndhever en annen og mye strengere oppløsningsalgoritme spesielt for ESM-importer. Uten en tilsvarende TypeScript-oppløsningsmodus, mislykkes vi både i å løse disse nye funksjonene og lar deg skrive stier som ikke vil løses i ESM-oppløsning.
