Skip to content

[BUG]: v4.0.0 publishes uncompiled .ts sources that are required extensionless — package fails to load under Node's CJS resolver #7995

Description

@slaweet

Description

v4.0.0 ships six uncompiled TypeScript sources inside the published tarball's src/, but the surrounding CommonJS files still require() them without an extension. Node's CJS resolver does not try .ts, so any consumer that resolves plotly.js through Node's own resolution — rather than through a bundler configured to resolve .ts — fails to load the package at all.

The main entry is affected, so this is not limited to subpath imports:

Error: Cannot find module './mod'
Require stack:
- .../plotly.js/src/lib/index.js
- .../plotly.js/build/plotcss.js
- .../plotly.js/src/core.js
- .../plotly.js/lib/core.js
- .../plotly.js/lib/index.js

The .ts files published under src/ in 4.0.0:

  • src/lib/mod.ts
  • src/lib/regex.ts
  • src/lib/clean_number.ts
  • src/lib/sort_object_keys.ts
  • src/lib/custom_country_codes.ts
  • src/plots/map/get_map_fit_bounds.ts
  • (also src/components/modebar/attributes.ts, src/traces/image/attributes.ts)

They are required extensionless from ~20 .js files, e.g.:

Requiring file Line
src/lib/index.js:78 var modModule = require('./mod');
src/lib/index.js:116 lib.sortObjectKeys = require('./sort_object_keys').default;
src/lib/index.js:181 var regexModule = require('./regex');
src/lib/index.js:217 lib.cleanNumber = require('./clean_number').default;
src/lib/coerce.js:13-14 require('./regex').counter / require('./mod').modHalf
src/lib/angles.js:3, src/lib/dates.js:7, src/lib/geometry2d.js:3 require('./mod')
src/lib/geo_location_utils.js:16 require('./custom_country_codes')
src/plots/map/layout_defaults.js:8 require('./get_map_fit_bounds')
src/plots/cartesian/constants.js:3, src/components/grid/index.js:4, src/components/colorscale/attributes.js:4-5, src/plots/geo/layout_attributes.js:8, src/plots/map/constants.js:3, src/traces/scatter3d/attributes.js:14, src/traces/scattergl/attributes.js:10 require('../../lib/regex') / require('../../lib/sort_object_keys')

This appears to be a side effect of #7680 ("Enable TypeScript compatibility within the library"). Nothing in the v4.0.0 release notes indicates that Node/CJS consumers are expected to switch to dist/, so I assume it is unintended rather than a documented breaking change.

Bundlers that resolve .ts by default (Vite/Rollup/rolldown, webpack with .ts in resolve.extensions) are unaffected — production builds succeed. What breaks is anything using Node's resolution: plain require(), and — the way I hit it — test runners that externalize node_modules to Node's CJS loader (Vitest server.deps default, Jest without a .ts-aware moduleFileExtensions). Neither server.deps.inline: [/plotly\.js/] nor deps.optimizer.web.include: ['plotly.js'] works around it on the Vitest side.

Steps to reproduce

$ mkdir plotly-repro && cd plotly-repro && npm init -y && npm i plotly.js@4.0.0

$ node -e "require.resolve('plotly.js/src/lib/mod')"
Error: Cannot find module 'plotly.js/src/lib/mod'

(require.resolve('plotly.js/src/lib/mod') is exactly the resolution src/lib/index.js:78 performs internally. node_modules/plotly.js/src/lib/mod.ts exists on disk.)

Loading the package the normal way fails the same way — this needs a DOM-ish global only to get as far as the failing require:

$ node -e "global.self = global; global.window = global; require('plotly.js')"
Error: Cannot find module './mod'
Require stack:
- .../plotly.js/src/lib/index.js
- .../plotly.js/build/plotcss.js
- .../plotly.js/src/core.js
- .../plotly.js/lib/core.js
- .../plotly.js/lib/index.js

Same for require('plotly.js/lib/core').

For contrast, on plotly.js@3.7.0 the identical command resolves the whole module graph and only fails later, at runtime, on a genuinely missing DOM API (document is not defined) — i.e. module resolution succeeded.

Notes

  • Environment: plotly.js 4.0.0, Node 24.13.1, Linux, pnpm 10, Vite 8.2.1. Also reproduced through Vitest 4.1.10 (Unhandled Rejection: Error: Cannot find module './mod', which makes the test run exit non-zero even when every test passes).
  • Possible fixes, in rough order of preference:
    1. Compile the .ts sources to .js during prepublishOnly so the published src/ is all JavaScript, as in v3.
    2. Keep the .ts sources but add explicit extensions at the require sites (require('./mod.ts') won't help Node either), or emit sibling .js files next to them.
    3. If shipping raw .ts is intentional, an exports map pointing Node consumers at dist/ plus a note in the v4 migration docs would at least make the new contract explicit.
  • Happy to test a patch or open a PR for option 1 if that's the direction you'd like.

Metadata

Metadata

Assignees

Labels

P2considered for next cyclebugsomething brokensize: 3

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions