Skip to content

fix: add .js to relative imports so the ESM build loads in node - #4

Merged
murrayju merged 1 commit into
mainfrom
murrayju/esm-relative-imports
Aug 4, 2026
Merged

fix: add .js to relative imports so the ESM build loads in node#4
murrayju merged 1 commit into
mainfrom
murrayju/esm-relative-imports

Conversation

@murrayju

@murrayju murrayju commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

Found while verifying the published 0.4.0-pre.3 against a pristine monaco 0.56: importing this package as native ESM throws.

Error [ERR_MODULE_NOT_FOUND]: Cannot find module
.../lib/esm/pgsql/pgsql imported from .../lib/esm/timescale/timescale.js

src/timescale/timescale.ts imported ../pgsql/pgsql without an extension. TypeScript emits relative specifiers verbatim, so the ESM output carries an extensionless import that Node can't resolve — and our exports map points the import condition straight at that output.

This predates the 0.56 work — reproduced identically on 0.3.2. Every ESM consumer without a bundler to paper over it has been broken; the bundler-based consumers we happen to have (webpack, rolldown) are why nobody noticed.

index.ts already used .js throughout, so this just makes the rest of the package consistent. The ../types imports in the contribution files get the same treatment — those are import type and erase at compile time, so they were never part of the bug, but leaving them inconsistent invites the next one.

A dead end worth recording

I tried enforcing this with the compiler rather than by hand. module: node16 on the ESM build does flag extensionless relative imports — but only when the package is "type": "module", which this isn't. Instead it silently emitted CommonJS into lib/esm, which would have been a much worse bug than the one I was fixing. Caught it by checking the emitted output; reverted.

A lint rule (import/extensions) is the right home for this, but it's currently 'off' in .eslintrc.js and turning it on is its own change. Noting it as a follow-up rather than smuggling it in here.

Verification

Against a real registry install of 0.4.0-pre.3 with the rebuilt lib dropped in:

before after
ESM (import) ERR_MODULE_NOT_FOUND ✅ pgsql 657 builtins, timescale 825
CJS (require) ✅ all 6 languages, unchanged
consumer tsc (bundler)

Also confirmed lib/cjs still emits real CommonJS and lib/esm still emits real ESM, since that was the failure mode of the approach I backed out of.

Lint, 228 tests, and both build targets pass on Node 24.

Please re-cut the prerelease

0.4.0-pre.3 has this bug. Worth a 0.4.0-pre.4 before the final 0.4.0 — happy to re-verify against it.

Verifying the published 0.4.0-pre.3 against a pristine monaco 0.56 turned up a
bug that predates this branch. Importing the package as native ESM throws:

  Error [ERR_MODULE_NOT_FOUND]: Cannot find module
  .../lib/esm/pgsql/pgsql imported from .../lib/esm/timescale/timescale.js

`src/timescale/timescale.ts` imported `../pgsql/pgsql` without an extension.
TypeScript emits relative specifiers verbatim, so the ESM output carried an
extensionless import, which Node cannot resolve -- and our exports map points the
`import` condition straight at that output. Reproduced identically on 0.3.2, so
every ESM consumer without a bundler to paper over it has been broken; the
bundler-based consumers we happen to have are why nobody noticed.

`index.ts` already used `.js` throughout, so this makes the rest of the package
consistent with it. The `../types` imports in the contribution files get the same
treatment: those are `import type` and erase at compile time, so they were never
part of the bug, but leaving them inconsistent invites the next one.

Tried enforcing this with the compiler instead of by hand. `module: node16` on the
ESM build does flag extensionless relative imports, but only when the package is
`"type": "module"` -- which this is not, so it silently emitted CommonJS into
lib/esm instead. Reverted; the linter is a better home for this rule than a
tsconfig that changes the output format as a side effect.

Verified against a real registry install of the prerelease with the rebuilt lib
dropped in: ESM now loads (pgsql 657 builtins, timescale 825, correctly layering),
CJS is unchanged and still emits CommonJS, and a consumer typechecks under
`moduleResolution: bundler`.
@murrayju
murrayju merged commit 5129b9d into main Aug 4, 2026
2 checks passed
@murrayju
murrayju deleted the murrayju/esm-relative-imports branch August 4, 2026 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant