Skip to content

feat!: ship dual ESM/CJS builds and require Node 20.9+ - #273

Closed
Walkeezy wants to merge 1 commit into
mainfrom
feat/dual-esm-cjs-exports
Closed

feat!: ship dual ESM/CJS builds and require Node 20.9+#273
Walkeezy wants to merge 1 commit into
mainfrom
feat/dual-esm-cjs-exports

Conversation

@Walkeezy

@Walkeezy Walkeezy commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Ships dual ESM/CJS builds for every public entry point (., /http, /datocms, /next) and raises the Node floor.

BREAKING: engines.node is now >=20.9.0. Node 18 has been EOL since April 2025, and 20.9 matches Next 16's own floor — which also removes the need to test against two different next majors per Node version.

The /next subpath keeps bare next/* specifiers

An earlier revision of this branch rewrote next/headersnext/headers.js, so moduleResolution: nodenext could resolve them without a paths hack. That breaks next build under Turbopack, the default bundler in Next 16:

Error: Could not parse module '.../app-route/vendored/contexts/app-router-context.js', file not found
Error: Failed to collect page data for /api/draft   [MODULE_UNPARSABLE]

Turbopack does not apply its react-server aliases to fully-specified next/*.js requests, so server-layer code pulls the client navigation module. webpack does alias the .js form (create-compiler-aliases.js), which is why this looked fine locally.

specifier raw Node ESM webpack Turbopack
'next/headers.js' ❌ build fails
'next/headers' ❌ unresolvable

Neither form is universally correct, but /next is bundler-only by nature — cookies()/draftMode() only work inside a Next request context — so bare specifiers win. The paths type-check mapping is restored in tsconfig.json with a comment explaining why not to "fix" it again.

Verification

  • scripts/verify-packed-package.mjs — packs the tarball, then asserts ESM / CJS / createRequire resolution and type-checks consumers under nodenext, nodenext+CJS, and node10. New --layout-only flag runs the offline subset.
  • scripts/verify-next-build.mjs (new) — builds a real Next app (Server Component + three route handlers, covering next/headers, next/navigation, next/server in the server-only layer) against the packed tarball on both bundlers, type-checked under moduleResolution: bundler like real apps use. Verified it actually fails when the .js form is reintroduced.

CI gains package-smoke (Node 20.9.0, 22) and next-build (next 15 + 16, each on Turbopack and webpack), plus a stable CI gate job — the matrix job names embed matrix values, so requiring them directly would break branch protection on every version bump.

release.yml now runs verify:release, which omits the registry-dependent smokes so a registry blip cannot fail a publish. It still builds and asserts the published layout offline.

Before merging

Require the CI status check on main. It currently requires none, and verify:release is justified by PR CI having gated the same commit:

gh api --method PATCH repos/smartive/utils/branches/main/protection/required_status_checks \
  --input - <<'EOF'
{"strict": true, "checks": [{"context": "CI"}]}
EOF

Expect a 2.0.0 release from the BREAKING CHANGE footer.

@Walkeezy
Walkeezy force-pushed the feat/dual-esm-cjs-exports branch from babe285 to 301ab6f Compare August 5, 2026 09:21
@Walkeezy Walkeezy changed the title feat!: ship dual ESM/CJS builds and require Node 18.18+ feat!: ship dual ESM/CJS builds and require Node 20.9+ Aug 5, 2026
Ship dual ESM and CommonJS builds for every public entry point (`.`,
`/http`, `/datocms`, `/next`), verified against a packed tarball and by
real Next builds on both bundlers.

The `/next` subpath keeps bare `next/*` specifiers. The fully-specified
`next/headers.js` form resolves under Node and webpack but makes
Turbopack miss its react-server aliases, so `next build` fails with
MODULE_UNPARSABLE on app-router-context.

BREAKING CHANGE: engines.node is now >=20.9.0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Walkeezy
Walkeezy force-pushed the feat/dual-esm-cjs-exports branch from 301ab6f to 816cbc0 Compare August 5, 2026 09:35
@Walkeezy Walkeezy closed this Aug 5, 2026
@Walkeezy
Walkeezy deleted the feat/dual-esm-cjs-exports branch August 5, 2026 09:42
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.

1 participant