Skip to content

Node16 module resolution does not exactly match node for dual ESM/CJS modules #56529

Closed as not planned
@djcsdy

Description

@djcsdy

🔎 Search Terms

"ESM", "CJS", "ES Module", "CommonJS", "dual", "module", "Node16", "module resolution"

🕗 Version & Regression Information

  • This is the behavior in every version I tried (4.8.4, 4.9.5, 5.0.4, 5.1.6, 5.2.2, 5.3.2, git main), and I reviewed the FAQ for entries about moduleResolution, Node16

⏯ Playground Link

https://github.com/djcsdy/node-resolution

(This bug can't be reproduced on Playground because it requires a package.json, so I've provided a minimal git repository instead).

💻 Code

Consider the npm package "unknown" (https://www.npmjs.com/package/unknown).

This package exports a dual ESM/CJS module with bundled type definitions.

unknown's package.json includes:

{
    "type": "module",
    "main": "./dist/cjs/unknown.js",
    "module": "./dist/unknown.js",
    "exports": {
        ".": {
            "require": "./dist/cjs/unknown.js",
            "default": "./dist/unknown.js"
        }
    },
    "types": "./dist/unknown.d.ts"
}

This bug manifests when importing a project like unknown from a CommonJS project with

tsconfig.json:

{
    "compilerOptions": {
        "module": "node16",
        "strict": true
    }
}

index.ts:

import {hasProperty} from "unknown";
hasProperty({}, "foo");

🙁 Actual behavior

On import {hasProperty} from "unknown";, TypeScript reports:

index.ts:1:27 - error TS1479: The current file is a CommonJS module whose
imports will produce 'require' calls; however, the referenced file is an
ECMAScript module and cannot be imported with 'require'. Consider writing a
dynamic 'import("unknown")' call instead.

🙂 Expected behavior

TypeScript should recognise that the package "unknown" can be imported as a CommonJS module from a CommonJS context, or as an ES Module from an ESM context, and treat it appropriately in each case.

Additional information about the issue

It is possible to work around the problem by editing the "unknown" module's package.json to remove "type": "module".

This seems to indicate that TypeScript is treating the module as ESM-only because of that line.

This does not match node's behaviour. My understanding is that for node, "exports": { ".": { "require": "./dist/cjs/unknown.js", ... } } } takes precedence, and that node treats the module as CJS when imported using require, even though "type": "module" is set and the imported file has a plain ".js" extension.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ExternalRelates to another program, environment, or user action which we cannot control.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions