Skip to content

Missing "type": "module" causes import * as to yield only { default } in ESM+CJS hybrid runtimes (e.g. tsx) #233

Description

@roygal-pentera

Bug

@dagrejs/graphlib@4.0.1 ships dist/graphlib.esm.js as the exports.import target, but the package has no "type": "module" field. This means Node.js (and tools like tsx) must determine the file format from the nearest package.json, which defaults to CommonJS for plain .js files.

The result depends on the runtime:

Runtime Behavior Outcome
Plain Node.js v22 Auto-detects ESM syntax via --experimental-detect-module import * as gives all named exports ✓
tsx v4.x Treats .js as CJS (no "type": "module") import * as gives only { default }

Reproduction

From inside an ESM package (with "type": "module" in its own package.json), run via tsx:

import * as graphlib from "@dagrejs/graphlib";
const { Graph, alg } = graphlib;

console.log(Object.keys(graphlib)); // [ 'default' ]  ← expected: [ 'Graph', 'alg', 'json', 'version' ]
console.log(typeof Graph);          // 'undefined'
console.log(typeof alg);            // 'undefined'

new Graph({ directed: true });      // TypeError: Graph is not a constructor
$ npx tsx test.ts
graphlib keys: [ 'default' ]
Graph type: undefined
TypeError: Graph is not a constructor

Vs. the same file run with plain node:

$ node test.mjs
Plain node graphlib keys: [ 'Graph', 'alg', 'json', 'version' ]
Graph type: function

Root cause

package.json declares:

{
  "exports": {
    ".": {
      "import": "./dist/graphlib.esm.js",
      "require": "./dist/graphlib.cjs.js"
    }
  }
}

But there is no "type": "module". For any runtime that doesn't auto-detect ESM syntax (Node.js without --experimental-detect-module, tsx, bundlers in strict mode), graphlib.esm.js is treated as CommonJS. The file's export { p as Graph, ... } syntax is either a parse error or silently wrapped as default.

Fix

Either of these would resolve it:

Option A — add "type": "module" to package.json:

{
  "type": "module"
}

Option B — rename the ESM output file to .mjs so its format is unambiguous regardless of package type:

"exports": {
  ".": {
    "import": "./dist/graphlib.esm.mjs",
    "require": "./dist/graphlib.cjs.js"
  }
}

Workaround

Until this is fixed, consumers can use createRequire to explicitly load the CJS file:

import { createRequire } from "node:module";
import type * as graphlibTypes from "@dagrejs/graphlib";

const { Graph, alg } = createRequire(import.meta.url)("@dagrejs/graphlib") as typeof graphlibTypes;

Environment

  • @dagrejs/graphlib: 4.0.1
  • tsx: 4.21.0
  • Node.js: 22.22.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions