Skip to content

fix(shared): replace CJS require() with static ESM import for semver#1

Merged
Minnzen merged 1 commit into
Minnzen:mainfrom
EduardF1:fix/semver-esm-dynamic-require
May 8, 2026
Merged

fix(shared): replace CJS require() with static ESM import for semver#1
Minnzen merged 1 commit into
Minnzen:mainfrom
EduardF1:fix/semver-esm-dynamic-require

Conversation

@EduardF1

@EduardF1 EduardF1 commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Problem

When running any example with tsx (or any Node.js ESM loader), startup crashes immediately:

Error: Dynamic require of "semver" is not supported
    at getNpmSemver (packages/shared/dist/chunk-Y6FXYEAI.mjs:5:9)
    at satisfies (packages/shared/dist/index.mjs:132:10)

Root cause: packages/shared/src/semver.ts uses a lazy-init pattern with require("semver") (CJS). When tsup bundles this into .mjs output for the ESM format, the dynamic require() call is preserved in the bundle but require is not available in ESM context — Node.js throws at runtime.

Fix

Replace the lazy require() with a static top-level import:

// Before
function getNpmSemver() {
  _npmSemver ??= require("semver")
  return _npmSemver
}

// After
import semver from "semver"

The lazy-loading was purely a startup-cost optimisation; with a static import tsup can tree-shake unused exports and the module resolves correctly in both CJS and ESM bundles.

Testing

pnpm build
npx tsx examples/agent-cli/index.tsx  # now starts correctly ✅

Tested on Node.js v24.15.0, pnpm v10.32.1, Windows.

Dynamic require('semver') inside an ESM bundle causes a runtime error:
  'Dynamic require of semver is not supported'

This affects all examples when run with tsx (Node ESM mode) because
tsup bundles the lazy-init require() into the .mjs output where CJS
require() is unavailable.

Fix: replace the lazy require() pattern with a static top-level import.
The lazy-loading was only an optimisation (defer cost until first call);
with a static import tsup can tree-shake unused exports and the bundle
resolves the module correctly in both CJS and ESM contexts.

Tested: pnpm build + tsx examples/agent-cli/index.tsx now starts correctly.
@EduardF1 EduardF1 force-pushed the fix/semver-esm-dynamic-require branch from a50e48e to 47cb291 Compare April 30, 2026 18:02
@EduardF1

EduardF1 commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

Hi @Minnzen, friendly nudge on this one. The change replaces the CJS require('semver') call with a static ESM import to fix the resolution error in the linked issue. Single-file change, runtime behavior identical when semver loads.

@Minnzen

Minnzen commented May 8, 2026

Copy link
Copy Markdown
Owner

Thanks for the catch and the clean fix, @EduardF1! 🙏 Verified locally, merging now

@Minnzen Minnzen merged commit 4e34ef6 into Minnzen:main May 8, 2026
1 check failed
Minnzen added a commit that referenced this pull request May 12, 2026
Same root cause as the 0.3.1 semver fix (#1): when a dependency is ESM-only
("type": "module" with no `require` export), our dual CJS+ESM build emits
`require(...)` in the .js output that crashes on Node >= 20 with
ERR_REQUIRE_ESM / ERR_PACKAGE_PATH_NOT_EXPORTED.

The semver fix worked because semver ships dual CJS+ESM. ansi-tokenize and
marked v17 are ESM-only, so a static import is not enough — they must be
inlined into the CJS bundle via tsup's `noExternal`.

Affected packages:
- shared:       @alcalzone/ansi-tokenize
- ink-renderer: @alcalzone/ansi-tokenize
- ui:           marked

Discovered by the new cross-env import smoke harness (next commit). All
three now load cleanly under ESM, CJS, and tsx loaders.
Minnzen added a commit that referenced this pull request May 12, 2026
Adds tests/smoke/ — a tiny workspace package that imports each
@claude-code-kit/* package via three loaders (ESM, CJS, tsx) and asserts
named exports exist. Catches the regression class of:
- 0.3.1 #1: `Dynamic require of "semver" is not supported` under tsx
- the ansi-tokenize / marked CJS-require crash fixed in the previous
  commit (which this harness discovered)

CI now runs a 3 x 3 matrix (Node 18 / 20 / 22 x esm / cjs / tsx) after
the standard build job. `pnpm smoke` runs all three loaders locally;
release:check now includes it.

Layout: tests/smoke/
    package.json   # workspace deps + react + zod + tsx
    esm.mjs
    cjs.cjs
    tsx.ts
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