Skip to content

build: ESM only — remove CJS artifacts, require branches and types-cjs - #3318

Merged
ryansolid merged 1 commit into
nextfrom
remove-cjs
Sep 9, 2026
Merged

build: ESM only — remove CJS artifacts, require branches and types-cjs#3318
ryansolid merged 1 commit into
nextfrom
remove-cjs

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Summary

All runtime packages publish ESM only and declare engines.node >= 22.12. Every .cjs artifact, every require branch in the exports maps, the types-cjs/ declaration mirrors and the sync-dual-types.mjs pipeline are gone.

Why now. Node 20 is EOL (2026-04); every supported Node require()s ESM natively (unflagged in 22.12). Every other runtime Solid targets (Bun, Deno, workers, Tauri, SolidTV's bundled output, OpenTUI) is ESM-native and never read the CJS files. The dual build existed for Jest, which Solid 2 neither documents nor ships tooling for. Keeping it cost a build matrix a third larger and — as the observe-tier PR showed — a second set of per-tier artifacts (node.cjs / node.dev.cjs / node.observe.cjs) that had to be kept honest by hand.

Resolution is unchanged. Each { import: X, require: Y } pair collapses to X's entries at import's position, so condition order is preserved: browser still precedes node, development still precedes observe, and a node16/nodenext TS consumer still finds the server types under the node key before the top-level client ones. A CJS host resolves the same files through the same conditions.

What replaces the CJS coverage

  • packages/signals/tests/dist-artifacts.test.ts (renamed from dist-cjs-artifacts): for each tier, spawns a real Node that require()s the core and engine entries and checks they are the same instance import() yields (vitest's module runner would hand import its own copy, so the probe cannot run in-process). Also scans every shipped module for a top-level await — the one thing that makes require(esm) throw.
  • packages/web/test/server/exports-server-conditions.spec.tsx: the require walk @solidjs/web → solid-js → @solidjs/signals lands on the same ESM file per tier (prod / development / observe), and the chain actually loads through a CJS require with DEV defined in dev and undefined in prod.
  • packages/test-integration/test-imports.mjs: every public specifier (now including /attribution, /serialization, /frames*, /rich-args) is loaded through both import() and require(); ERR_REQUIRE_ASYNC_MODULE fails the run.
  • packages/test-integration/fixtures/packaged-types/nodenext-cjs (was node16-cjs): the packed-types check for a CommonJS TS project moves to module: NodeNext, the setting under which TS type-checks require() of ESM. Passes against the packed tarballs.

Consumer impact

  • ESM apps, Vite, Vitest, Bun, Deno, workers: none.
  • CommonJS Node apps: Node 22.12+; require("solid-js") keeps working.
  • TypeScript CommonJS projects: module: "NodeNext" (TS 5.8+); Node16 reports TS1479.
  • Jest: needs Node 22.12+; presets that map to .cjs paths have nothing to map to.

Out of scope

@solidjs/babel-plugin and @solidjs/compiler's napi loader are build-time tooling loaded by Babel/Node tooling, not runtime artifacts; unchanged.

Size budget unchanged (ESM artifacts are byte-identical). Full pnpm test, test:integration, and scripts/size pass.

…-cjs mirror

Node >= 22.12 loads ESM through require() natively, and Node 20 is EOL, so
the second module graph bought nothing but a build matrix a third larger,
a dual-types pipeline (sync-dual-types.mjs), and three flat CJS builds in
signals whose tiers had to be kept in step with the ESM ones by hand.

Runtime packages (signals, solid-js, web + subpaths, universal, h, html)
now declare engines.node >= 22.12 and publish ESM only. Exports maps lose
their import/require split — the import branch's entries are spliced in
at its position, so condition order (and therefore which types a node16
TS consumer sees) is unchanged. `main` points at the ESM server entry.

Guards replacing the CJS coverage:
- signals dist test require()s each tier's core + engine in a child Node
  and checks it is the same instance import yields (vitest's module
  runner would give import its own copy, so the probe spawns real Node),
  and scans every shipped module for a top-level await — the one thing
  that breaks require(esm).
- web's export-conditions test walks the require hops web → solid-js →
  signals per tier onto the ESM files, and loads the chain through a
  real CJS require.
- test-integration's import smoke test now also require()s every public
  specifier (fails on ERR_REQUIRE_ASYNC_MODULE); the packaged-types
  fixture moves from module: Node16 to NodeNext, the setting under which
  TS type-checks require() of ESM.

Build-time tooling (@solidjs/babel-plugin, @solidjs/compiler's napi
loader) is unchanged.

Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Sep 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a39415c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@solidjs/signals Patch
solid-js Patch
@solidjs/web Patch
@solidjs/universal Patch
@solidjs/h Patch
@solidjs/html Patch
test-integration Patch
@solidjs/element Patch
@solidjs/babel-plugin Patch
@solidjs/compiler Patch
@solidjs/diagnostics Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 34330546237

Coverage remained the same at 71.851%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1006
Covered Lines: 771
Line Coverage: 76.64%
Relevant Branches: 788
Covered Branches: 518
Branch Coverage: 65.74%
Branches in Coverage %: Yes
Coverage Strength: 15.0 hits per line

💛 - Coveralls

@ryansolid

Copy link
Copy Markdown
Member Author

Verified before merge:

  • No stale references to .cjs / types-cjs / sync-dual-types / d.cts anywhere in tooling, CI workflows or smoke-release.mjs; .nvmrc is 24 so every workflow can require(esm).
  • Export maps are a faithful collapse: at every branch types sits exactly where the old { import: { types, default } } did, development still precedes observe, browser precedes node; no import/require keys remain, no .cjs paths remain.
  • Clean worktree install → forced build + types: no CJS artifacts in any dist/. All suites green (signals 1603, solid 594, web 712/777/165, universal 43, diagnostics 31, element 10, html 192, h 61, babel 255), type tests, test-imports.mjs (import + require(esm) walk of every public specifier), packaged-types NodeNext-CJS fixture, compiler TSRX runtime harness. Size harness byte-identical to next on all ten scenarios.

Claude via Cursor

@ryansolid
ryansolid merged commit cc84862 into next Sep 9, 2026
6 checks passed
@ryansolid
ryansolid deleted the remove-cjs branch September 9, 2026 08:53
@codspeed-hq

codspeed-hq Bot commented Sep 9, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 142 untouched benchmarks


Comparing remove-cjs (a39415c) with next (644d7f9)1

Open in CodSpeed

Footnotes

  1. No successful run was found on next (a39415c) during the generation of this report, so 644d7f9 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

ryansolid added a commit that referenced this pull request Sep 9, 2026
Bump seroval and seroval-plugins from ~1.5.4 to ~1.6.7 (still minor-locked)
in the root, solid-js and @solidjs/web. Seroval 1.6 ships bundled
declarations with no extensionless relative imports, so the TS2834 gap in
the NodeNext-CJS packaged-types fixture is closed: it now type-checks all
13 public @solidjs/web specifiers, including server-functions/*,
serialization/* and frames/*.

The 34 regenerated hydration harness artifacts reflect seroval 1.6's
inline helper text only (tab indentation; the async-iterator helper
rewritten with method shorthand and braceless single-statement bodies) —
same logic, and the parity/truncated-stream specs replay them green.

@rollup/plugin-commonjs was referenced by no rollup config after #3318.

Verified: full build + every suite, type tests, test-integration
(import + require(esm) walk, packaged types), compiler runtime harness;
examples/rendering streaming mode smoke-tested in prod and dev builds
(true streaming, hydration, client navigation, zero console errors).

Co-authored-by: Cursor <cursoragent@cursor.com>
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