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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions