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:

InndatakodeUtdata --module commonjsUtdata --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 type i 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 eksisterende commonjs og esnext modusene, 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.

Relaterte artikler