Skip to content

Fix unusable CJS build by scoping module type per dist directory#34

Open
NullVoxPopuli with Copilot wants to merge 5 commits into
masterfrom
copilot/fix-cjs-build-issue
Open

Fix unusable CJS build by scoping module type per dist directory#34
NullVoxPopuli with Copilot wants to merge 5 commits into
masterfrom
copilot/fix-cjs-build-issue

Conversation

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown

The package declares "type": "module" at the root but ships CommonJS output as dist/cjs/*.js, so Node parses the CJS entry as ESM and every require('@handlebars/parser') throws ReferenceError: exports is not defined in ES module scope.

Change

  • package.json — extend build:cjs and build:esm to emit a nested package.json in each output directory, overriding the root type on a per-directory basis. No changes to exports, main, module, or file extensions.
"build:cjs": "tsc ... --outDir dist/cjs && node -e \"...writeFileSync('dist/cjs/package.json', ...{type:'commonjs'}...)\"",
"build:esm": "tsc ... --outDir dist/esm && node -e \"...writeFileSync('dist/esm/package.json', ...{type:'module'}...)\"",

Resulting layout:

dist/cjs/package.json  → {"type":"commonjs"}   # CJS consumers via require
dist/esm/package.json  → {"type":"module"}     # ESM consumers via import (defensive; matches root)

This is option #2 from the issue — the least invasive of the three suggested fixes, requiring no rename of build artifacts and no rewrite of the exports map.

Copilot AI changed the title [WIP] Fix CJS build usability issue with ESM package Fix unusable CJS build by scoping module type per dist directory Jul 5, 2026
Copilot AI requested a review from NullVoxPopuli July 5, 2026 14:53
@NullVoxPopuli

Copy link
Copy Markdown
Contributor

@copilot this is the worst approach you've ever done to a thing. you cannot ship additional package.jsons like this as it will break loads of other tools.

Emit the cjs output correctly, as cjs. this should be very simple

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Author

@copilot this is the worst approach you've ever done to a thing. you cannot ship additional package.jsons like this as it will break loads of other tools.

Emit the cjs output correctly, as cjs. thi...

Switched approach: dropped the nested package.json and now emit the CJS build as .cjs (option #1 from the issue). A small scripts/rename-cjs.cjs runs after tsc, renaming dist/cjs/*.js*.cjs and rewriting intra-package require("./x.js")require("./x.cjs"). main and the require/default exports conditions now point at ./dist/cjs/index.cjs. Verified with require('@handlebars/parser') from CJS and import('@handlebars/parser') from ESM; test suite passes. See 8f16dae (or the latest push on this branch).

@NullVoxPopuli

Copy link
Copy Markdown
Contributor

@copilot do we need a second tsconfig for cjs output? writing a whole node script for file renames is silly

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Author

@copilot do we need a second tsconfig for cjs output? writing a whole node script for file renames is silly

A second tsconfig alone won't do it — tsc doesn't provide a way to change the emitted extension for .js input, so the dist/cjs/*.js*.cjs rename (plus rewriting the intra-package require("./x.js") calls it emits) has to happen somewhere after tsc.

Dropped the standalone script and inlined the rename step directly into build:cjs as a short node -e one-liner in package.json — no extra file. See the latest commit on the branch.

Happy to take a different direction if you'd prefer, e.g. renaming lib/*.js sources to .cjs/.mjs variants, or a bundler like esbuild/rollup.

@NullVoxPopuli

Copy link
Copy Markdown
Contributor

@copilot yea, time for rolldown

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Author

@copilot yea, time for rolldown

Done — swapped tsc + rename hack for a rolldown build. build:esm / build:cjs are collapsed into a single build:bundle: rolldown -c, driven by a tiny rolldown.config.js that emits dist/esm/index.js (esm) and dist/cjs/index.cjs (cjs) from lib/index.js with source maps. main/module/exports already point at those files. CJS require() and ESM import() both resolve; 106 tests pass. See the latest commit on the branch.

@NullVoxPopuli
NullVoxPopuli marked this pull request as ready for review July 5, 2026 16:09
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.

CJS build is unusable: dist/cjs/*.js are CommonJS but package "type":"module" makes Node parse them as ESM ("exports is not defined")

2 participants