Emit real ESM output and drop dead pnpm build config#55
Merged
Conversation
Two follow-ups flagged but deliberately left out of the dependency bump.
builder-bob 0.41 started warning that the esm option is disabled while
exports['.'].import and exports['.'].require are both set, and that
exports['.'].types should not be set alongside them. The warnings were
pointing at a real defect, not a style preference: with esm disabled, bob
emits ESM syntax into lib/module/*.js but writes no `type` marker, so
Node resolves the import condition and then parses those files as
CommonJS. Node 24 papers over it with module syntax detection; with
detection off — which is Node 18/20 behaviour — importing the package
fails outright with ERR_REQUIRE_CYCLE_MODULE.
Enabling esm on the commonjs and module targets makes bob write the
`type` markers, rewrite relative imports to carry .js extensions, and
split declarations into lib/typescript/{commonjs,module}. The typescript
target picks esm up from the other targets, so it needs no option of its
own. The exports map now nests types under each condition, as bob asked.
Verified against a packed tarball resolved from a scratch consumer:
require() lands on lib/commonjs, import() on lib/module, and with syntax
detection disabled the old layout throws while this one resolves. tsc
picks up the commonjs declarations under moduleResolution node16 and the
module ones under bundler.
Separately, onlyBuiltDependencies has zero references in pnpm 11.1.1 —
it, ignoredBuiltDependencies and neverBuiltDependencies were all replaced
by the allowBuilds map. The empty array was dead config with a comment
that overstated it. Removed, and the comment now describes what pnpm 11
actually enforces. Every other setting in the file is still live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
yosriady
approved these changes
Jul 21, 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 free
to 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.
Summary
The two follow-ups I flagged in #54 and deliberately kept out of it.
The builder-bob warnings turned out to be pointing at a real defect, not a style preference. With
esmdisabled, bob emits ESM syntax intolib/module/*.jsbut writes notypemarker. Node resolves theimportcondition to those files and then parses them as CommonJS. Node 24 papers over this with module syntax detection, which is why nothing looked broken — but with detection disabled, which is Node 18/20 behaviour, importing the package fails outright:Key changes
Real ESM output
esm: trueon thecommonjsandmoduletargets. It is a per-target option, not top-level — bob's schema rejects it at the root, and thetypescripttarget derives it by checking whether any other target sets it, so it needs no option of its own.{"type":"commonjs"}/{"type":"module"}markers into each output dir, rewrites relative imports to carry.jsextensions, and splits declarations intolib/typescript/{commonjs,module}.exports['.']neststypesunder each condition instead of alongside them, and the top-leveltypesfollows the declarations tolib/typescript/commonjs/index.d.ts.Dead pnpm config
onlyBuiltDependencieshas zero references in pnpm 11.1.1 — it,ignoredBuiltDependenciesandneverBuiltDependencieswere all replaced by theallowBuildsmap. The empty array was doing nothing, under a comment that overstated it. Removed; the comment now describes what pnpm 11 actually enforces.blockExoticSubdeps,minimumReleaseAge,trustPolicy,trustPolicyExcludeandoverridesare all still live settings.Verification
Against a packed tarball extracted into a scratch consumer, so this exercises the published artifact rather than the working tree:
require.resolvelib/commonjs/index.jsimport.meta.resolvelib/module/index.jsERR_REQUIRE_CYCLE_MODULE)tscmoduleResolution: node16lib/typescript/commonjs/index.d.tstscmoduleResolution: bundlerlib/typescript/module/index.d.tspnpm typecheck/lint/testpnpm buildpnpm install --frozen-lockfileCI's
test -d lib/{commonjs,module,typescript}assertions still hold.Note for reviewers
This changes the published package layout, so it is the one part of these changes that consumers can observe. Metro is unaffected — React Native resolves through the
react-nativefield tosrc. The declaration files move fromlib/typescript/index.d.tstolib/typescript/{commonjs,module}/index.d.ts; anything deep-importing that path would break, though it is not a documented entry point. Worth landing before the next release rather than after.🤖 Generated with Claude Code
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.