Skip to content

fix(build): make messenger, ui and mdx dist resolvable by Node's ESM loader - #11

Merged
laruss merged 2 commits into
mainfrom
fix/esm-resolvable-dist
Aug 5, 2026
Merged

fix(build): make messenger, ui and mdx dist resolvable by Node's ESM loader#11
laruss merged 2 commits into
mainfrom
fix/esm-resolvable-dist

Conversation

@laruss

@laruss laruss commented Aug 5, 2026

Copy link
Copy Markdown
Owner

What was broken

messenger, ui and mdx declare "type": "module" with only an exports.import condition, so Node reads their published output as ESM — where relative specifiers need an explicit file extension and a directory never resolves to its index.js. Their builds were missing tsc-alias's resolveFullPaths, which core and devtools already set, so dist shipped the shortened forms that only bundlers and CJS accept:

import "./i18n";                    // ERR_UNSUPPORTED_DIR_IMPORT
import { logger } from "./logger";  // ERR_MODULE_NOT_FOUND

Reproducible in two lines against the previously published tarball:

cd node_modules/@react-text-game/messenger/dist
node -e "import('./index.js').catch(e=>console.log(e.code))"   # ERR_UNSUPPORTED_DIR_IMPORT
node -e "import('./chat.js').catch(e=>console.log(e.code))"    # ERR_MODULE_NOT_FOUND

mdx had the same missing setting and is fixed alongside.

The second, quieter bug in ui

resolveFullPaths does not fix @react-text-game/ui/i18n: it imported its English strings from JSON, which Node refuses without a with { type: "json" } import attribute (ERR_IMPORT_ATTRIBUTE_MISSING).

This one failed silently rather than loudly. core reaches that entry through a dynamic import inside Game.init, wrapped in a try/catch that treats any failure as "the UI package isn't installed" (packages/core/src/i18n/utils.ts). So under Node and SSR, UI strings degraded to raw translation keys with nothing logged.

The locale is now a TypeScript module, matching the existing messenger/src/i18n/en.ts precedent. The exported uiTranslations declaration is byte-identical to before.

Verification

  • Imported every emitted module of all five packages under plain Node: 209 modules, 0 resolver errors.
  • Declaration files carry full specifiers too, so consumers on node16/nodenext typecheck.
  • lint, typecheck, test:coverage, build and publint all green; the new ui.ts is at 100% coverage and present in LCOV.
  • Re-ran the repro above against the freshly published tarballs from the registry: both silent, and core's internal loadUITranslations() now returns "Main Menu" instead of {}.

Note on ordering

These versions are already on npm (messenger@0.1.1, ui@0.6.1, mdx@0.3.1), published from this branch. Please merge without squashing so the commit SHA referenced in the CHANGELOG entries (332db0b) stays valid.

🤖 Generated with Claude Code

konstantin-mf and others added 2 commits August 5, 2026 01:53
…loader

These three packages declare "type": "module" with only an exports.import
condition, so Node reads their output as ESM, where relative specifiers need an
explicit extension and a directory never resolves to its index.js. Their builds
were missing tsc-alias's resolveFullPaths, which core and devtools already set,
so dist shipped the shortened forms that only bundlers and CJS accept:

  import "./i18n";                    // ERR_UNSUPPORTED_DIR_IMPORT
  import { logger } from "./logger";  // ERR_MODULE_NOT_FOUND

ui/i18n had a second, independent blocker that resolveFullPaths does not fix: it
imported its English strings from JSON, which Node refuses without a
with { type: "json" } import attribute. That one failed silently rather than
loudly - core reaches the entry through a dynamic import in Game.init wrapped in
a try/catch that treats any failure as "the UI package isn't installed" - so UI
strings degraded to raw translation keys under Node and SSR. The locale is now a
TypeScript module, matching messenger/src/i18n/en.ts; the exported uiTranslations
type is byte-identical to before.

Verified by importing every emitted module of all five packages under plain Node:
209 modules, no resolver errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@laruss
laruss merged commit 423d8d0 into main Aug 5, 2026
1 check passed
@laruss
laruss deleted the fix/esm-resolvable-dist branch August 5, 2026 00:05
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.

2 participants