microsoft / microsoft/TypeScript

Tracking issue: Named imports from CJS module incorrectly allowed in nodenext

Aperta
#54,018 6 commenti 7 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Discussion
Lingua principale
Go
Stelle
111k
Fork
14.4k
Merge medio
1g 19h
PR unite (30g)
117

Descrizione

This issue has come up a few times, most recently at https://github.com/DefinitelyTyped/DefinitelyTyped/pull/65147#issuecomment-1515134407, and I don’t think we’ve had a canonical place to explain, discuss, or track it.

The symptom

Sometimes, when you’re writing an ES module in --module nodenext, you will want to import a CJS dependency, and TypeScript lets you used a named import:

import { createProgram } from "typescript";
createProgram(/* ... */);

but Node complains:

file:///project/out/main.mjs:1
import { createProgram } from "typescript";
         ^^^^^^^^^^^^^
SyntaxError: Named export 'createProgram' not found. The requested module 'typescript' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'typescript';
const { createProgram } = pkg;

Alternatively but equivalently, you might try to use a namespace import:

import * as ts from "typescript";
ts.createProgram(/* ... */);

and Node gives a much less helpful error:

file:///project/out/main.mjs:2
ts.createProgram();
   ^

TypeError: ts.createProgram is not a function

In either case, you find that you instead need to write a default import:

import ts from "typescript";
ts.createProgram(/* ... */);

Both TypeScript and Node are happy with this, but why did TypeScript let you write the forms that crash at runtime?

The problem

Node uses cjs-module-lexer to syntactically analyze CommonJS modules without executing them in order to turn module.exports properties into named exports that can be imported as named (or namespace) imports by ES modules. However, syntactic analysis has its limitations, and not all module.exports properties get detected on all modules. (In practice, it’s common for this to be all-or-nothing, as with the typescript package in its current state: it has no named exports available.)

Type definitions have no way of declaring which exports/properties of a CommonJS module will be detectable by Node’s named export analysis. In other words, typescript.d.ts is not misrepresenting the shape of the module; it would be impossible to “fix” it for Node ESM consumers without breaking it for CJS consumers or bundlers, which typically have more relaxed interop rules. TypeScript currently assumes that all declared exports/properties of a CJS module will be detectable by Node and available as named imports. This is unsound, leading to the error in the example above.

It should be noted that at least for the named import case, the crash occurs during Node’s module linking phase (as soon as the module graph is loaded—at startup, unless the affected part of the graph is isolated in a dynamic import), with a good runtime error message. While annoying to run into, it’s usually immediately diagnosable and fixable. The namespace import variation is a bit more insidious, since the import can be linked, but subsequent property accesses, which can occur later in execution, may fail.

Solutions and non-solutions

I’m putting this issue up for tracking/documentation purposes, not to advocate for a fix. But it’s worth mentioning a few ways of addressing the problem since people will ask or suggest them:

  • We could make a flag that makes potentially unsafe named/namespace imports always error. It would be very cumbersome and catch a lot of false positives, but some people may prefer the safety over the convenience.
  • We could make it possible to annotate modules or individual exports/properties in type declarations that cannot be correctly analyzed by Node. This sounds pretty fraught and fragile to me, given how often people postprocess both their types and JS by third-party build tools.
  • We cannot reasonably determine which exports/properties are detectable by Node by looking at the JS, e.g. by running cjs-module-lexer ourselves, because we never even resolve or read JS sources when types are found for them. Even without incurring the cost of running the (fast) lexer, the performance penalty for doing twice as much module resolution and file system hits would be unacceptable.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Si tratta di una issue di tracciamento e documentazione relativa agli import denominati e agli import di namespace non sicuri dai moduli CommonJS con --module nodenext; non sono stati identificati file di implementazione né test. Inizia esaminando le limitazioni descritte di cjs-module-lexer e la discussione collegata, quindi conferma che qualsiasi lavoro proposto abbia un ambito e criteri di accettazione definiti prima di tentare una modifica.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
javascript, nodejs, typescript
Ambito
compilers
Tipo di issue
Documentazione
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Ferma
Chiarezza
Da chiarire
Idoneità per principianti
25/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.