Skip to content

Add support for Node.js 22 and 24 - #542

Merged
rosston merged 6 commits into
mainfrom
more-node-versions
Sep 21, 2026
Merged

rosston merged 6 commits into
mainfrom
more-node-versions

Conversation

@rosston

@rosston rosston commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Fixes #530.

Why

CI now runs against Node 22, 24 and current (26.x) in addition to 16, 18 and 20, on both Ubuntu and Windows. Adding those versions surfaced three separate problems, and this PR fixes all of them so the full matrix is green.

  1. quibble 0.9.x doesn't work reliably on Node 22+. Its ESM loader leaked a ?__quibble=N query string on Node 22 and later, which could make stub substitutions silently miss. Fixed upstream in quibble 0.10.0.
  2. The mocha-based examples crash on Node 26. mocha 10 bundles yargs 16, whose entry point is an extensionless file that Node 26's stricter require(esm) handling tries to load as ESM. The babel, node-esm and node-esm-auto-loader examples died on startup before running a single test.
  3. The auto-load gating in npm test was silently broken. Three scripts (test:esm-auto-loader, test:no-loader-esm, test:example-esm-auto-loader) decide what to run by calling test/esm-lib/supports-auto-load.js. That path has never existed in this repo; the helper lives at test/support/. The wrong path came in with the 3.19.0 release commit. Because node failed on the missing file, the || fallback always ran, so test:no-loader-esm ran on every Node version, and test:esm-auto-loader and the node-esm-auto-loader example had been dropped from npm test, so CI never exercised them.

What changed

  • quibble ^0.9.2 → ^0.10.0. The example lockfiles were refreshed too (the file: dependency stores a snapshot of this package's dependencies, so they had gone stale).
  • Examples moved off mocha to node:test. babel, node-esm and node-esm-auto-loader no longer use mocha (or chai). This is the last mocha usage in the repo. The babel example's shared helper now exports setup()/teardown() instead of registering top-level hooks, because node:test top-level hooks don't cascade into nested describe blocks. node-esm-auto-loader still runs without --loader, since testing auto-registration is its whole point.
  • Fixed the gating scripts. The three scripts now point at test/support/supports-auto-load.js. I restored cross-env on test:esm-auto-loader, which the 3.19.0 release commit had dropped and which is needed under Windows cmd.exe. test:esm-auto-loader is back in npm test and test:example-esm-auto-loader is back in test:example.

The gating now does what it was written to do:

Node Runs Skips
16 (can't auto-register a loader) test:no-loader-esm auto-loader script and example
18.19+ / 20.6+ (can) test:esm-auto-loader, auto-loader example test:no-loader-esm

The visible effect is that test:no-loader-esm no longer runs on Node 18 and later, which is what that script intends.

Alternatives considered

  • Bump mocha instead of removing it. mocha 11 needs Node ^18.18 || ^20.9 || >=21.1, and mocha 12 needs ^20.19 || >=22.12. We still support Node 16 (and test 18.20 and 20.16), and the babel and node-esm examples run on all of them, so no mocha release that works on Node 26 also works across the whole matrix. node:test does (Node 16.17+), and drops a large dependency tree.
  • Leave the gating scripts as they were. They passed, but only because the || fallback always ran, so nothing was testing what the scripts claimed to. Fixing the path is a one-line change per script and makes the gates real.
  • Hide the Node 26 deprecation warning (see below) by filtering it in our tests. Rejected: it only hides the problem in our own runs and does nothing for users.

Known follow-up (not in this PR)

On Node 26, replacing an ES module without --loader prints DEP0205: module.register() is deprecated. Use module.registerHooks() instead, once per process. It is harmless, doesn't fail anything, and doesn't appear on Node 16 through 24. The Module.register() call is inside quibble, not this repo, so it can't be fixed here. src/can-register-loader.js only checks whether Module.register exists, and it will need to change together with quibble.

Verification

  • CI: all 12 jobs (Node 16, 18, 20, 22, 24 and current, on Ubuntu and Windows) pass. The Windows logs show the gating behaving as in the table above, including the auto-loader example passing on Windows for Node 18 and current.

Notes for reviewers

  • The package-lock.json diffs for babel, node-esm and node-esm-auto-loader are huge (over 30,000 lines removed) because the mocha/chai dependency trees are gone. The substantive changes are the package.json files, the example test files and the root package.json scripts.
  • The unrelated jest, jest-broken, node and node-ava lockfile changes are only the refreshed testdouble and quibble snapshot lines.

mocha 10.x bundles yargs 16.2.0, whose CJS entry point is an
extensionless file reached via the exports "require" condition while
the package itself declares "type": "module". Node 26's stricter
require(esm) handling trusts that "type" field unconditionally and
tries to load the file as ESM, which crashes on its own bare
require() call. This broke test:example:babel and
test:example:node-esm under the newly added Node "current" CI job.

Since node:test has been available (experimentally) since Node 16.17,
swap both example projects to it instead, removing the mocha/chai/
yargs dependency chain entirely. This works uniformly across the
16-26 Node matrix. Mirrors the identical fix already applied to
quibble's own example projects.

Along the way, discovered that node:test's top-level beforeEach/
afterEach hooks do not cascade into nested describe() blocks (unlike
mocha), so the babel example's shared test/helper.js was changed from
registering global hooks to exporting a setup()/teardown() pair that
the spec's own local beforeEach/afterEach call explicitly.

Verified npm test passes fully (all example projects included) on
Node 20.16.0, 22.14.0, and 24.14.1 (asdf) and 26.9.0 "current"
(Docker node:current), with a locally-built fix for quibble's
separate Node 22+ ESM loader bug installed for the run. quibble's
fix (testdouble/quibble#more-node-versions) is not yet published to
npm, so testdouble.js's own package.json still resolves the published,
unfixed quibble@0.9.2 until that happens.
To bring Node 22+ compatibility.
@rosston rosston self-assigned this Sep 21, 2026
Mirrors the node-esm example conversion. mocha 10 crashes on Node 26
(yargs 16), and mocha 11+/12 constrain the supported Node range, so
dropping it removes the last mocha usage in the repo. The example still
runs without --loader to exercise auto-registration.
@rosston
rosston marked this pull request as ready for review September 21, 2026 19:04
@rosston
rosston merged commit cf44876 into main Sep 21, 2026
12 checks passed
@rosston
rosston deleted the more-node-versions branch September 21, 2026 19:05
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.

Suite test:esm fails under Node 21

1 participant