Skip to content

feat: Publish dual CJS and ESM builds#85

Merged
mykola-mokhnach merged 4 commits into
masterfrom
dual
Jul 26, 2026
Merged

feat: Publish dual CJS and ESM builds#85
mykola-mokhnach merged 4 commits into
masterfrom
dual

Conversation

@mykola-mokhnach

Copy link
Copy Markdown
Contributor

Publish both a CommonJS and an ESM build from the same TypeScript source (build/cjs and build/esm, each with its own package.json marker), so existing require('asyncbox') consumers keep working unchanged while new consumers can import it natively. Wired up via a conditional exports map (plus main/module/types fallbacks) and a postbuild script that stamps each output directory with its own {"type": ...}.

p-limit (a runtime dependency) is ESM-only. The CJS build's require('p-limit') works because it relies on Node's native require(esm) support, available on the versions already required by engines (^20.19.0 || ^22.12.0 || >=24.0.0). tsconfig.cjs.json uses moduleResolution: "Bundler" (paired with module: "CommonJS") so tsc can resolve p-limit's types, which only exist under its exports field with no top-level main/types.

Also add test/cjs-interop.spec.cjs, which require()s the CJS build directly, so a future change that breaks CJS consumption (e.g. an ESM-only dependency bump) fails CI instead of only being caught by the ESM test suite.

Lint/format/release tooling changes are intentionally left out of this PR per #84 (comment).

Publish both a CommonJS and an ESM build from the same TypeScript source
(build/cjs and build/esm, each with its own package.json marker), so existing
require('asyncbox') consumers keep working unchanged while new consumers can
import it natively. Wired up via a conditional exports map (plus
main/module/types fallbacks) and a postbuild script that stamps each output
directory with its own {"type": ...}.

p-limit (a runtime dependency) is ESM-only. The CJS build's require('p-limit')
works because it relies on Node's native require(esm) support, available on
the versions already required by engines (^20.19.0 || ^22.12.0 || >=24.0.0).
tsconfig.cjs.json uses moduleResolution: "Bundler" (paired with module:
"CommonJS") so tsc can resolve p-limit's types, which only exist under its
exports field with no top-level main/types.

Also add test/cjs-interop.spec.cjs, which require()s the CJS build directly,
so a future change that breaks CJS consumption (e.g. an ESM-only dependency
bump) fails CI instead of only being caught by the ESM test suite.

Lint/format/release tooling changes are intentionally left out of this PR
per #84 (comment).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates asyncbox’s packaging and build pipeline to publish both ESM and CommonJS outputs from the same TypeScript sources, aiming to keep existing require('asyncbox') consumers working while enabling native import usage.

Changes:

  • Split TypeScript build outputs into build/esm and build/cjs via dedicated tsconfig files.
  • Add a postbuild step that stamps each build output directory with a package.json containing the correct "type".
  • Add a CommonJS interop test and update docs + package.json entrypoints/exports to support dual publishing.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tsconfig.json Removes shared outDir and excludes the new .cjs test from typechecking.
tsconfig.esm.json Adds ESM build output configuration (build/esm).
tsconfig.cjs.json Adds CJS build output configuration (build/cjs) with CJS module settings.
test/cjs-interop.spec.cjs Adds a CJS-focused test to protect require() consumption.
scripts/postbuild.mjs Stamps build output dirs with per-directory package.json "type" markers.
README.md Documents that both ESM and CJS consumption are supported.
package.json Wires up conditional exports + dual entrypoints and adjusts build/test scripts accordingly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package.json Outdated
Comment thread test/cjs-interop.spec.cjs Outdated
npm run clean ran npm run build -- --clean, which still fires the postbuild
lifecycle hook and regenerates build/esm and build/cjs (including the stamped
package.json markers) — the repo never actually ended up clean. Just rm -rf
the whole build directory instead.

Also have test/cjs-interop.spec.cjs require('asyncbox') by package name rather
than reaching into build/cjs/lib/asyncbox.js directly, so it actually exercises
the exports/main resolution real CommonJS consumers go through.

Addresses review comments from #85

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.

Comment thread package.json Outdated
rm -rf build doesn't work in default Windows shells. Add scripts/clean.mjs
(mirroring the existing scripts/postbuild.mjs pattern) using fs.rmSync instead.

Addresses #85 (comment)
Fold scripts/clean.mjs into a single node -e invocation directly in
package.json — same cross-platform fs.rmSync behavior, one less file.
Comment thread tsconfig.json
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"strict": true,
"outDir": "build",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be removed? The equivalent node-teen_process PR leaves this line untouched.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since outDir is set by the extended files, I guess it should be removed from the other PR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asyncbox could drop it because it has no lint:types script — only tsc -b via the esm/cjs configs, which both set their own outDir. teen_process's lint:types needs the base config to still emit somewhere sane, so outDir: "build" here is intentional, not leftover cruft.

@mykola-mokhnach
mykola-mokhnach merged commit 623bedd into master Jul 26, 2026
5 checks passed
@mykola-mokhnach
mykola-mokhnach deleted the dual branch July 26, 2026 21:08
github-actions Bot pushed a commit that referenced this pull request Jul 26, 2026
## [6.4.0](v6.3.5...v6.4.0) (2026-07-26)

### Features

* Publish dual CJS and ESM builds ([#85](#85)) ([623bedd](623bedd))
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 6.4.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants