Skip to content

4.0.5: "type": "module" makes the shipped .d.ts files unresolvable under NodeNext (TS2834) #238

Description

@laazyj

Summary

4.0.5 added "type": "module" to package.json (#237), but dist/types/** still emits relative imports without file extensions. Under moduleResolution: node16/nodenext those declaration files are now ESM, where extensionless relative specifiers are not resolvable, so every export of the package fails to type-resolve.

4.0.3 is unaffected — without the "type" field the declarations were interpreted as CJS, where extension inference applies.

Reproduction

// package.json
{ "name": "repro", "private": true, "type": "module" }
// tsconfig.json
{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "skipLibCheck": false,
    "noEmit": true
  },
  "include": ["src"]
}
// src/index.ts
import { Graph, alg } from "@dagrejs/graphlib";

const g = new Graph();
g.setEdge("a", "b");
console.log(alg.isAcyclic(g));

With @dagrejs/graphlib@4.0.5:

node_modules/@dagrejs/graphlib/dist/types/index.d.ts(30,23): error TS2834: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Consider adding an extension to the import path.
node_modules/@dagrejs/graphlib/dist/types/index.d.ts(31,25): error TS2834: ...
node_modules/@dagrejs/graphlib/dist/types/index.d.ts(32,23): error TS2834: ...
node_modules/@dagrejs/graphlib/dist/types/index.d.ts(33,22): error TS2834: ...

With @dagrejs/graphlib@4.0.3: clean.

Cause

dist/types/index.d.ts in 4.0.5:

export { Graph } from './lib/graph';
export { version } from './lib/version';
export * as json from './lib/json';
export * as alg from './lib/alg/index';
export type { GraphOptions, Edge, Path, WeightFunction, EdgeFunction, Label } from './lib/types.js';

Only the last line carries .js — because the corresponding source import was written with the extension. The other four are emitted verbatim from extensionless source imports. 5 of the 24 .d.ts files under dist/types/ are affected (index.d.ts, lib/json.d.ts, lib/alg/index.d.ts, and others re-exporting siblings).

The runtime bundles are fine; this is limited to the declaration output.

Why this may not have shown up in testing

skipLibCheck: true — the default in most project templates — suppresses TS2834 entirely. The imports still fail to resolve, so Graph, json and alg silently become any rather than erroring. Consumers get no compile error, just erased types. It surfaces loudly only under skipLibCheck: false, or through type-aware lint rules: in our case @typescript-eslint/no-unsafe-call, no-unsafe-member-access and no-unsafe-assignment fired on every graphlib call site while tsc stayed green.

That combination means the blast radius is likely wider than the issue tracker suggests — most affected consumers would see degraded types rather than a failure.

Suggested fix

Either:

  1. Add explicit .js extensions to the relative imports in src/, so tsc emits resolvable specifiers into dist/types/ (matches what lib/types.js already does); or
  2. Emit a CommonJS-flavoured declaration build alongside the ESM one and point exports["."].require.types at it.

Option 1 is the smaller change and fixes both the ESM and CJS resolution paths at once.

Versions

  • @dagrejs/graphlib 4.0.5 (broken), 4.0.3 (fine)
  • TypeScript 7.0.2; also reproduces on 6.x
  • moduleResolution: NodeNext

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