Vilka TypeScript-modulinställningar för ett bibliotek som används i webbläsare och Node.js
Det finns många resurser om detta, men det är lätt förvirrande och jag kan inte hitta några nyare resurser om detta:
Idag år 2024 vill jag skapa ett bibliotek, skrivet i Typescript. Det kommer att användas i React-projekt som använder en webpack-bundlare. Och appen kommer att renderas på servern (node.js) och i webbläsaren.
Hur konfigurerar jag tsconfig.json? Vilken inställning ska jag välja för module?
Kontext:
Biblioteket finns redan i CJS, men i en nuvarande stor omskrivning funderar jag på att uppgradera till ESM, eftersom vi har problem i våra appar som använder detta bibliotek (som också är CJS för tillfället) med att importera endast ESM-moduler. Och också, med CJS kan vi inte utnyttja automatiskt uppdelade paket när vi använder dynamiska importer i webpack.
Dokumentationen
Typescript-referensen för module anger att es2015,es2020,es2022,esnext inte bör användas för node.js
Och det står att
node16 och nodenext är de enda korrekta modulalternativen för alla appar och bibliotek som är avsedda att köras i Node.js v12 eller senare
Men nämner inte om nodenext eller node16 kan användas för webbläsare/bundlers som webpack också.
Vad är det? Vilken ska jag välja?
Jag undrar om jag borde gå för en delad väg med att publicera en /lib/cjs och /lib/esm version, med require och module fält i export-inställningarna i package.json?
Det lämnar mig fortfarande med frågan vilket module-utgångsinställning jag ska använda i tsconfig så att både webbläsare (genom webpack) och node.js kan använda ESM. Eller borde jag välja en av dessa miljöer för att fortsätta använda CJS?
Svar #1
På grund av historiska skäl finns det helt enkelt för många kombinationer, men de flesta krävs inte längre. Sammanfattningsvis är följande fyra regler mer än tillräckliga om du vill publicera ett ESM-endast paket som kan användas både av Node.js och webbläsare:
- Ditt paket bör använda
"type": "module'. Om du skapar ett bibliotek för Node.js endast, då:
"module": "NodeNext"
"moduleResolution": "NodeNext"
Om du skapar ett bibliotek för Frontend endast via bundler, då:
"module": "ESNext"
"moduleResolution": "Bundler"Slutligen, om du skriver ett bibliotek för både Node.js och frontend då:
"module": "NodeNext"
"moduleResolution": "NodeNext"Mer förklaring
Kombinationen av NodeNext + NodeNext är en delmängd av ESNext + Bundler. Ett alternativt sätt att säga detta är att NodeNext + NodeNext är ESM med begränsningar enligt Node.js modulupplösningsalgoritm. Det vanligaste exemplet är det obligatoriska kravet på att ha filändelse i importuttalanden, t.ex. import {} from 'other.js' i NodeNext + NodeNext. En sådan begränsning gäller inte i ESNext + Bundler-läge.
Så, om du publicerar för både Node.js (förväntar dig att använda utan bundling) och för frontend (förväntar dig att använda import-maps eller bundler), då bör du använda module: NodeNext + moduleResolution: NodeNext.
Detta säkerställer att TypeScript (och andra moderna bundlers) respekterar main, exports-deklarationer som specificeras av package.json-specifikationen.
Tänk också på att "type": "module" är viktigt. Om det saknas, även med module: NodeNext, kommer TypeScript-kompilatorn att kompilera *.ts-filer till *.js med CJS require-satser och *.mts-filer till *.mjs med ESM-importsatser.
Slutligen finns det också ett alternativt sätt att tänka på det! Om du använder TypeScript som en kompilator för att kompilera ditt bibliotek, bör du använda NodeNext + NodeNext. Och om du använder en bundler, har du valet mellan båda kombinationerna (även om jag fortfarande rekommenderar att använda NodeNext + NodeNext på grund av den strikta semantik det introducerar).
För en mer detaljerad och djupgående förklaring av module och moduleResolution, är detta en bra läsning.