build: dual ESM+CJS output with integration test coverage#123
Merged
build: dual ESM+CJS output with integration test coverage#123
Conversation
Introduces tsconfig.base.json holding the shared compiler options, plus tsconfig.cjs.json and tsconfig.esm.json for the respective dual-build outputs (./dist/cjs and ./dist/esm). Root tsconfig.json now extends the base and targets the ESM layout so editors/IDEs default to the modern configuration while the explicit build variants drive the publishable artifacts. Uses moduleResolution: bundler in both variants because TypeScript 6 deprecated plain "node" and nodenext tripped on an upstream package shipping .d.ts files without .js-extension imports. Adds scripts/write-package-type.mjs to write the correct subfolder package.json type marker after each tsc run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ECMAScript module resolution in Node requires explicit file extensions on relative imports. Appending .js keeps the TypeScript source working under both the CJS and ESM outputs and aligns with the standard modern Node/TS pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Publishes both CJS and ESM entries via the conditional exports map so consumers get the correct format automatically while keeping type: commonjs at the root. Adds build:cjs / build:esm / build / clean scripts plus test:integration variants that drive the new runtime consumer tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Exercises the dual build outputs through native Node runtimes using node:assert/strict. Each test imports the respective dist entry and verifies the exported surface, singleton behavior, constructor option merging, and that resolved options carry no __proto__ own properties — mirroring the existing Deno scenario so regressions in either output format are caught. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Points the createRequire-based scenario at ./dist/cjs/index.js and extends the assertions with the __proto__ pollution checks that the new Node integration tests share. Adds deno-esm.ts so Deno also exercises the ESM dist natively, giving coverage of both output formats across the Deno matrix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The build job now runs the dual CJS+ESM build and invokes the CJS and ESM integration tests on every Node version ['20','22','24','25']. The Deno job also runs the new CJS and ESM Deno scenarios after building. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With the dual build output, the CJS entry lives at dist/cjs/index. Updating the require path keeps the example runnable from CI and local development against the published API surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deno's strict ESM loader rejects bare specifiers for Node built-ins. TypeScript preserves specifiers verbatim, so the source must use the node: prefix for both CJS and ESM output to work across Node, Deno, and other strict ESM runtimes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
dist/cjs/) and ESM (dist/esm/) outputs via separate tsconfigs, with subfolderpackage.jsonmarkers and a conditionalexportsmap so bothimportandrequireconsumers resolve correctly.test/integration/cjs.cjs), ESM (test/integration/esm.mjs), and updated Deno tests to cover both dist layouts — all run in CI.nodejs.ymlacross the existing Node matrix (20, 22, 24, 25); Deno job now exercises both CJS (viacreateRequire) and ESM consumer paths.Notable choices
moduleResolution: bundlerrather thannodenext— upstreamntp-packet-parser@0.6.0ships.d.tsfiles without.jsextensions on relative imports, which tripsnodenextduring our build.bundlerstill honors theexportsmap and resolves types correctly for consumers. Can revisit if upstream fixes the extensions.tsc+ a tiny post-build Node script.package.jsonkeeps"type": "commonjs"for safety; per-folder markers indist/cjsanddist/esmdo the dual-resolution work.Test plan
yarn clean && yarn buildproduces bothdist/cjs/anddist/esm/with type markers and declarationsyarn test:integration:cjspasses locallyyarn test:integration:esmpasses locallynode examples/example.jscontinues to work against the new CJS pathcreateRequire) and ESM (import) scenarios🤖 Generated with Claude Code