Uh oh!
There was an error while loading. Please reload this page.
fix(react): make the published entry loadable by Node's own ESM resolver, and gate it - #5218
Merged
Merged
Conversation
… load the built entry `packages/react/dist/index.js` re-exported through extensionless relative specifiers (`./SchemaRenderer`, `./hooks`, ...). Node's ESM resolver does not extension-search relative specifiers, so importing the published entry under plain Node failed with ERR_MODULE_NOT_FOUND. `tsc` never rewrites import specifiers, so the defect is in the source, not in a build setting: the root tsconfig's `moduleResolution: "bundler"` permits the extensionless form and bare `tsc` copies it straight into dist. - packages/react: 92 specifiers extensioned; tsconfig pinned to module/moduleResolution `nodenext` so a missing extension is a compile error (TS2835) rather than a review item. - packages/types, core, i18n: 163 specifiers extensioned. These are react's dependency closure — react's own entry stayed unloadable until they were fixed too, because evaluation crosses into them.
Two legs, because neither is honest alone (objectui#4538): - `pnpm check:esm-specifiers` reads SOURCES and needs no build, so it runs per pull request in ci.yml's Type Check job. For a package whose build preserves specifiers (a bare emitting `tsc`), the emitted specifier IS the source specifier, so the defect is judgeable without building anything. - `pnpm check:node-esm-load` builds every published package and actually `import()`s each entry in a child node. Wired to a nightly workflow, not to pull requests, for the reason published-dist-gate.yml records: this repo has no per-PR full-repo build. The load leg EVALUATES rather than resolves, which is the card's measured point: plugin-charts' own entry resolves fine and the tree was still broken. A missing module is attributed to the package that OWNS it, so the four plugins failing on packages/mobile/dist/useBreakpoint produce one finding against @object-ui/mobile rather than four against innocent importers. Seven packages still carry the defect and are ledgered with a reason each; two of them were held by other sessions. The ledger is a ratchet — an entry whose package is now clean is itself a failure.
Each entry now names the card that clears it, and the three that block other packages say how many — mobile blocks 6, permissions 4, providers 2. A ledger entry whose reason is just "follow-up" tells the next reader nothing about whether it is worth picking up.
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This was referenced Aug 18, 2026
os-support-ai
marked this pull request as ready for review
August 18, 2026 15:22
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#4538
Premise check first, as triage asked
The card is from 2026-08-13. Confirmed still true before anything was changed — built
dist/index.jscarried 10 extensionless re-exports, and the entry did not load:Triage scoped this as "a build/emit setting". It is not
packages/react's build is a baretsc, and TypeScript never rewrites import specifiers — there is no emit flag that appends.js. What the source writes is whatdistships. The extensionless form was permitted only because the roottsconfig.jsonsets"moduleResolution": "bundler". So this is a source change, not a build setting.Enforcement is pinned in
packages/react/tsconfig.jsononly —module/moduleResolution: nodenext, which makes a missing extension a compile error rather than a review item. The roottsconfig.jsonis untouched; 38 packages extend it and flipping it there is a different change.Reverse-verified. Restoring the old
src/index.tsreds both enforcement legs, and restores byte-identically afterwards:That ablation needed no rebuild and does not claim one: the specifier leg reads sources, so there is no
dist/for a stale artifact to hide in. The load-leg numbers below are the ones that came from a real build.Why three more packages are in the diff
Fixing
packages/reactalone did not make its entry loadable. Rebuilt clean, it still failed one package over:@object-ui/reactevaluates intotypes,coreandi18n, each a bare-tscbuild with the identical defect. Those three are specifier text only — no build-config change, no API change. Declared as a claim-surface amendment on the issue before this PR opened. After them:And the card's original repro —
plugin-charts, whose own entry was always clean, failing only once evaluation crossed into@object-ui/react:Specifiers changed: react 92, types 95, core 12, i18n 56. Applied by a codemod that resolves every specifier against the filesystem, so a commented-out import to a deleted module is left alone rather than rewritten.
The check imports and evaluates — it does not stop at resolution
scripts/check-node-esm-load.mjs, two legs, because neither is honest alone.Specifier leg (
pnpm check:esm-specifiers) reads sources, needs no build, runs per PR inci.yml's Type Check job. For a specifier-preserving build the emitted specifier is the source specifier, so the defect is judgeable without building anything.Load leg (
pnpm check:node-esm-load) builds every published package andimport()s each entry in a childnode, through its ownexportsmap, no bundler and no loader hooks. Nightly workflow, not per-PR — the same tradepublished-dist-gate.ymlrecords, since this repo has no per-PR full-repo build.Measured on a full build of all 39 published ESM packages: 17 entries imported and evaluated. The gate asserts a floor on that count, so "imported nothing, found nothing" cannot be a green verdict.
The load leg attributes a missing module to the package that owns it. Four plugins died on
packages/mobile/dist/useBreakpoint— not their file. Without attribution that is a dozen findings for one cause; with it, one finding against@object-ui/mobile.Two false-criterion traps the measurements caught, both now pinned by tests:
@object-ui/console, whosetscstep carries"noEmit": trueand emits none of them. The gate reads the tsconfig, followingextends.react/src/index.tsup by six lines — the license header — so the gate pointed at line 3 for whattscreported at line 9.Other packages with the same emission: ledgered, and stated
Seven carry it and are not fixed here —
app-shell(1259),fields(295),auth(54),mobile(27),collaboration(15),permissions(14),providers(9). Filed as #5214.app-shellandfieldsare held by other sessions; the rest are outside this card's surface and not needed for its acceptance bar.The ledger is a ratchet, not a mute button: an entry whose package has become clean is itself a failure, so it cannot outlive the debt. Clearing those seven would take the repo from 17 to 35 of 39 loadable entries — 11 more packages fail only because of them. Separately ledgered as a different question:
plugin-dashboardandplugin-mapfail on a third-party.cssimport, which no extension fixes.Verification, all at
98b8d31efvitest run packages/{react,types,core,i18n}/ scripts/type-check(react, types, core, i18n)lint(same 4)check:esm-specifiers,changeset:check, changeset-presence,check:control-bytes,lint:coverage,type-check:coverage,type-check:scripts,docs:check-linksturbo run build --filter=!@object-ui/siteThe full build is the downstream evidence: every consumer of
@object-ui/reactis itself atscbuild reading its regenerated.d.ts, and all 43 build tasks pass.content/docs/guide/ci-cd-pipeline.mdgained the workflow section and job-table entry its pin test requires.Generated by Claude Code