nodejs / nodejs/import-in-the-middle

`export const x = expr as Generic<A, B>` produces a phantom export named after `B`, colliding when 2+ such casts share a last type arg

Open
#296 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
170
Forks
58
Avg merge
3d 19h
Merged PRs (30d)
7

Description

import-in-the-middle's ESM export scanner (lib/get-esm-exports.mjses-module-lexer) misparses a specific TypeScript cast shape: export const x = expr as GenericType<Arg1, Arg2>;. When the cast target has 2+ comma-separated type arguments, the scanner emits a phantom second export named after the literal text of the last type argument — not tied to any real binding.

Not specific to the word unknown — swapping it for number produces a phantom number export instead. But unknown is exactly what codegen tools emit as the trailing type arg for typed documents with no variables (e.g. graphql-code-generator's typed-document-node plugin: export const MyQueryDocument = {...} as unknown as DocumentNode<MyQuery, unknown>;). Any generated file with 2+ such exports gets two phantom "unknown" exports, which collide in the generated wrapper module — invalid ESM, crashes the module linker before user code runs:

SyntaxError: Duplicate export of 'unknown'

Repro

Full runnable repro: https://gist.github.com/tmair/a10f56297ca85729faabedc897d47918

git clone https://gist.github.com/a10f56297ca85729faabedc897d47918.git phantom-export

Run:

npm install
node --experimental-loader=import-in-the-middle/hook.mjs --import ./register.mjs app.mjs

Expected: 1

Actual:

file:///.../types.ts?iitm=true:16
export { $0 as "foo", $1 as "unknown", $2 as "bar", $3 as "unknown" }
                                                    ^^^^^^^^^^^^^^^
SyntaxError: Duplicate export of 'unknown'

Environment

  • import-in-the-middle: reproduces on 3.5.0 and 3.5.1 (latest)
  • Node.js: v26.3.0 locally; also seen on v24.21.0 in production
  • Triggered via Node's native TypeScript execution (running .ts files directly, no separate transpile step) combined with --experimental-loader=import-in-the-middle/hook.mjs

Boundary conditions checked

  • Single-arg generic cast (1 as Array<unknown>) — no phantom export.
  • Type annotation instead of cast (const foo: Generic<A, B> = ...) — no phantom export.
  • Only a cast expression as Generic<Arg1, Arg2> with 2+ type args triggers it; phantom export name = last type argument's text.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with lib/get-esm-exports.mjs and reproduce the issue using the linked gist and its npm install/node command, then inspect how es-module-lexer handles the multi-argument TypeScript cast. Done means the scanner no longer emits phantom exports for this shape, so the repro prints 1 without a duplicate-export SyntaxError.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, nodejs, typescript
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
75/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.