Repository navigation
[BUG]: v4.0.0 publishes uncompiled .ts sources that are required extensionless — package fails to load under Node's CJS resolver #7995
Description
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.tssrc/lib/regex.tssrc/lib/clean_number.tssrc/lib/sort_object_keys.tssrc/lib/custom_country_codes.tssrc/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.jsSame 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:
- Compile the
.tssources to.jsduringprepublishOnlyso the publishedsrc/is all JavaScript, as in v3. - Keep the
.tssources but add explicit extensions at the require sites (require('./mod.ts')won't help Node either), or emit sibling.jsfiles next to them. - If shipping raw
.tsis intentional, anexportsmap pointing Node consumers atdist/plus a note in the v4 migration docs would at least make the new contract explicit.
- Compile the
- Happy to test a patch or open a PR for option 1 if that's the direction you'd like.
Activity
camdecoster commentedon Aug 28, 2026
Thanks for reporting this issue! You're right that this breaks resolving in Node. Let me mull this over a bit and I'll follow up with you.
camdecoster commentedon Aug 31, 2026
I opted to follow something similar to what you called option
iabove. It's not perfect, but it should unblock you.slaweet commentedon Sep 1, 2026
Thanks @camdecoster — tested #8000 (
cam/7995/convert-ts-js-publish@ f945b67,npm pack, installed the tarball) and it fixes both repros.The tarball ships compiled
.jssiblings for all the problem modules, no raw.ts, with the.d.tsfiles still included.Module resolution now succeeds; bare Node fails only on missing DOM, same as 3.7.0.
Vitest scenario — fixed: the suite that died with
Cannot find module './mod'now passes (58 tests, exit 0), and strictvue-tsctypecheck passes against the shipped declarations.Looking forward to the patch release, thanks for the quick turnaround!