Skip to content

Seven published packages still emit extensionless relative specifiers, so plain Node ESM cannot load them #5214

Description

@os-support-ai

Split out of the work on #4538, which fixed @object-ui/react plus the dependency closure its entry evaluates through (types, core, i18n). The seven packages below carry the identical defect and were left out of that PR: two of them were held by other sessions at the time, and the rest were outside its declared file surface. They are recorded in the new gate's SPECIFIER_DEBT ledger so they cannot get worse; this card is to clear them.

#4538 remains the source of the analysis and is not addressed further here.

The defect

Node's ESM resolver does not extension-search relative specifiers. A bare tsc never rewrites import specifiers, so an extensionless ./Foo in the source is an extensionless ./Foo in the published dist, and importing the entry under plain Node fails with ERR_MODULE_NOT_FOUND. Bundled consumers are unaffected, which is why everything is green today.

Measured, 2026-08-18, on a full build of every published package

Specifier leg (pnpm check:esm-specifiers) — packages whose build preserves specifiers:

packageextensionless specifiers
@object-ui/app-shell1259
@object-ui/fields295
@object-ui/auth54
@object-ui/mobile27
@object-ui/collaboration15
@object-ui/permissions14
@object-ui/providers9

Load leg (pnpm check:node-esm-load) — 17 of 39 published ESM entries import and evaluate. The remaining 22 break down as:

  • the 7 above, each failing on its own file;
  • 11 more that fail on somebody else's fileplugin-calendar, plugin-designer, plugin-grid, plugin-list, plugin-timeline and plugin-view all die on packages/mobile/dist/useBreakpoint; plugin-detail, plugin-form, plugin-gantt and plugin-kanban on packages/permissions/dist/PermissionProvider; plugin-report on packages/providers/dist/DataSourceProvider;
  • 2 that fail on a .css import from a third-party dependency (plugin-dashboard, plugin-map) — a different question, ledgered separately as LOAD_DEBT;
  • 2 that are not importable by design (create-plugin is a bin-only CLI, runner is a built web app).

So clearing these seven takes the repo from 17 to 35 of 39 loadable entries. @object-ui/mobile at 27 specifiers and @object-ui/permissions at 14 are the cheapest wins by a wide margin.

The fix shape, already proven on four packages

  1. Append the explicit extension to every relative specifier in the files the build emits (./x becomes ./x.js; a directory import becomes ./x/index.js). Resolve each one against the filesystem rather than pattern-matching — a specifier inside a comment must be left alone.
  2. Optionally pin module/moduleResolution to nodenext in that package's own tsconfig.json, which makes a missing extension a compile error (TS2835/TS2834) instead of a review item. Done for packages/react in finding(react): the built dist emits extensionless relative imports, so plain Node ESM cannot load the published entry #4538.
  3. Delete the package's entry from SPECIFIER_DEBT in scripts/check-node-esm-load.mjs in the same commit — the ledger is a ratchet and fails on a stale entry.

⛔ Do not change moduleResolution in the roottsconfig.json. 38 packages extend it; a repo-wide flip is a much larger change and is not what this card is.

@object-ui/app-shell at 1259 specifiers is large enough that it probably deserves its own card rather than riding along with the small ones.


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions