Skip to content

fix(cjs): make require() a synchronous wrapper around the ESM build - #80

Merged
Shinrai merged 1 commit into
nextfrom
fix/cjs-sync-require
Oct 3, 2026
Merged

Shinrai merged 1 commit into
nextfrom
fix/cjs-sync-require

Conversation

@cldmv-bot

@cldmv-bot cldmv-bot Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

🚀 What's Changed

💥 Breaking Changes

No breaking changes

✨ Features

No new features

🐛 Bug Fixes

  • fix(cjs): make require() a synchronous wrapper around the ESM build (2e82985)

📦 Dependencies

No dependency updates

🔧 Other Changes

No other changes

👥 Contributors

The CommonJS entry went through a generated loader.cjs that did
`module.exports = (async () => await import("../index.mjs"))()`, so
require("@cldmv/jsonv") - and every require("@cldmv/jsonv/<year>") -
returned a Promise of the ESM namespace instead of the API.

- scripts/build-cjs.mjs: every generated .cjs file is now a thin wrapper,
  `module.exports = require("<esm file>")`, loaded through Node's
  synchronous require(esm). require() returns the same exports as import.
  The async loader.cjs is no longer generated, dist/cjs is rebuilt from
  scratch, and dist/years/year-resolver.mjs now gets a CJS wrapper too
  (the "./*" export already pointed require at one that did not exist).
- Where Node.js has no require(esm) (before 20.19 / 22.12) the wrappers
  throw ERR_REQUIRE_ESM with a message pointing to import() instead of a
  bare loader error. engines is unchanged.
- tests/cjs: node:test checks against the built dist/, run by `npm test`
  and `npm run coverage` after Vitest: require() of the entry and of a
  year module returns the same exports as import, and the version check
  fires when require(esm) is off.
- docs: note that require() is synchronous and its Node.js requirement.

Existing `await require("@cldmv/jsonv")` code keeps working, since
awaiting a non-promise returns it unchanged; only code that chained
.then() on the require() result changes.
@cldmv-bot cldmv-bot Bot added ! fix → next v4 flow: fix contributor PR targeting the next integration branch area: tests Touches test files, fixtures, or test infrastructure type: dependencies Relates to dependency updates, version bumps, or package management type: documentation Relates to docs, README updates, guides, or inline code comments labels Oct 3, 2026
@Shinrai
Shinrai merged commit ed3849d into next Oct 3, 2026
31 checks passed
@cldmv-bot
cldmv-bot Bot deleted the fix/cjs-sync-require branch October 3, 2026 18:12
cldmv-bot Bot added a commit that referenced this pull request Oct 5, 2026
# @cldmv/jsonv v1.1.4 Changelog

**Release Date**: October 2026
**Release Type**: Patch
**Branch**: `release/1.1.4`

---

## Overview

Version 1.1.4 fixes the CommonJS entry points. Until now `require("@cldmv/jsonv")`, and every `require("@cldmv/jsonv/<year>")`, returned a Promise of the ESM module rather than the API, so CommonJS code had to `await` it. `require()` now returns the same exports as `import`, synchronously, loaded through Node's `require(esm)`.

Existing `await require("@cldmv/jsonv")` code keeps working unchanged. Two cases do change and are covered in the upgrade notes below: code that chained `.then()` on the `require()` result, and CommonJS code on Node.js versions without `require(esm)`. The ESM API is untouched. The dev toolchain is also refreshed (`@cldmv/fix-headers` 2.2.0, `@cldmv/configs` 1.2.4, `@cldmv/vitest-runner` 1.5.1, `typescript-eslint` 8.71.0); none of it ships in the package.

---

## 🐛 Bug Fixes

### `require()` is a synchronous wrapper around the ESM build ([#80](#80))

The CommonJS build went through a generated `loader.cjs` that did `module.exports = (async () => await import("../index.mjs"))()`, so `require()` handed back a Promise. Every generated `.cjs` file is now a thin wrapper, `module.exports = require("<esm file>")`, so `require()` returns the same module namespace object as `import`:

```js
const { parse } = require("@cldmv/jsonv");
parse("{ a: 1 }"); // { a: 1 }

const jsonv2021 = require("@cldmv/jsonv/2021");
```

The rest of the CommonJS build was tidied along the way:

- The async `loader.cjs` is no longer generated, and `dist/cjs` is rebuilt from scratch on every build so a removed wrapper can't linger.
- `@cldmv/jsonv/year-resolver` now has a CommonJS wrapper. The `"./*"` export already pointed `require` at `dist/cjs/years/year-resolver.cjs`, but that file was never built, so `require("@cldmv/jsonv/year-resolver")` failed outright. `@cldmv/jsonv/loader` and the year modules are wrapped the same way.
- On Node.js without `require(esm)` (before 20.19.0, or 22.0–22.11), the wrappers throw an `ERR_REQUIRE_ESM` error whose message names the required Node.js versions and points to `import()`, instead of failing with a bare loader error.

---

## 🔧 CI & tooling

- New `tests/cjs/entry.test.cjs` (`node:test`), run by `npm test` and `npm run coverage` after Vitest through a new `test:cjs` script. It checks against the built `dist/` that `require()` of the entry and of a year module returns the same exports as `import`, and that the Node.js version check fires when `require(esm)` is unavailable.

---

## 📚 Documentation

- **NEW:** [docs/changelog/v1/v1.1.4.md](./v1.1.4.md) — this changelog.
- [docs/versioning-and-exports.md](https://github.com/CLDMV/jsonv/blob/master/docs/versioning-and-exports.md) — notes that `require()` is synchronous and states its Node.js requirement.
- README — restructured to the CLDMV README layout (badges, What's New, Installation with Node.js requirements, Documentation index, Links), with the changelog history backfilled for every earlier release under `docs/changelog/v1/`.

---

## 🔧 Dependencies

All development-only:

- `@cldmv/vitest-runner` 1.4.3 → 1.5.1 ([#77](#77))
- `typescript-eslint` 8.70.1 → 8.71.0 ([#77](#77))
- `@cldmv/fix-headers` `^2.1.1` → `^2.2.0`, resolved to 2.2.0. The range was first raised to `^2.1.4` ([#83](#83)) and then to `^2.2.0` ([#85](#85)). Version 2.1.4 no longer writes a JavaScript comment into JSON or Markdown files, processes every repeated `--input`, and never walks dependency folders such as `node_modules`. Version 2.2.0 changes `@Last modified by` only when a file's content was edited, so header-only rewrites keep the recorded editor.
- `@cldmv/configs` `^1.2.0` → `^1.2.4`, resolved to 1.2.4. The shared fix-headers config now sets `forceAuthorUpdate` and `forceLastModifiedAuthorUpdate` to false ([#85](#85)).
- No file headers were restamped: each fix-headers bump changed only `package.json` and the lockfile.

---

## Upgrade notes

- **`await require(...)` keeps working.** Awaiting a non-Promise returns it unchanged, so CommonJS code written against the old Promise-returning entry needs no change.
- **`.then()` on the `require()` result no longer works.** `require("@cldmv/jsonv").then((jsonv) => ...)` now throws `TypeError: ... .then is not a function`, because the result is the module itself. Use the result directly (`const jsonv = require("@cldmv/jsonv")`), or `await` it.
- **CommonJS needs Node.js ^20.19.0 or >=22.12.0.** On older Node.js, where `await require("@cldmv/jsonv")` used to resolve through the async loader, `require()` now throws `ERR_REQUIRE_ESM` with a message pointing to `import()`. Load the package with `await import("@cldmv/jsonv")` there. The `engines` field (`>=18.0.0`) and ESM `import` support are unchanged.



<details>
<summary>👥 Contributors</summary>

- @Shinrai

</details>

---

<!-- coverage-start -->

![coverage](https://img.shields.io/badge/coverage-98.7%25-brightgreen?style=for-the-badge&logo=vitest&logoColor=white)

| Metric | Coverage |
|--------|----------|
| Statements | 98.7% |
| Branches   | 97.3% |
| Functions  | 100.0% |
| Lines      | 98.8% |

*Avg: **98.7%** · `640792e` · Node lts/**

<!-- coverage-end -->

<!-- co-authors -->

Co-authored-by: Shinrai <Shinrai@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: tests Touches test files, fixtures, or test infrastructure ! fix → next v4 flow: fix contributor PR targeting the next integration branch type: dependencies Relates to dependency updates, version bumps, or package management type: documentation Relates to docs, README updates, guides, or inline code comments

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant