Repository navigation
fix(cjs): make module/exports reassignable so var module = X works (#3527) - #3764
Merged
Merged
Conversation
…3527) The CJS→ESM wrapper declared `const module = { exports: {} }` and `const exports = module.exports`. A CJS body that rebinds either — `var module = X`, `module = X`, or `exports = X` — silently failed: the reassignment collided with the `const` and was dropped, so reads still saw the wrapper's object. iconv-lite's `encodings/index.js` does exactly this: for (var i = 0; i < modules.length; i++) { var module = modules[i] mergeModules(exports, module) // reads module.<key> → undefined } `module` stayed the wrapper object (no `cesu8`), so `module[key]` read `undefined`, throwing `Cannot read properties of undefined (reading 'cesu8')` during init — a hard blocker compiling Express (#3527). In Node, `module`/`exports` are wrapper-function *parameters*, freely reassignable; rebinding the local is harmless because Node reads the real exports from its own module reference. Mirror that: introduce a stable `__cjs_module` (read back by `return __cjs_module.exports`) and make `module` and `exports` reassignable `var`s aliasing it. A body reassigning its `module` local can no longer clobber what the module exports, and `var module = X` now binds as expected. Verified: the iconv-shape repro and the Express tree advance past it; normal `module.exports = X`, `exports.foo = …`, and `module.exports.bar = …` are unchanged. Unit test asserts the reassignable-var + stable-ref wrapper shape.
This was referenced May 31, 2026
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
A CJS module that rebinds
module/exports—var module = X,module = X, orexports = X— silently failed under Perry's CJS→ESM wrapper, which declared them asconst. The reassignment collided with theconstand was dropped, so reads still saw the wrapper's original object. This fixes that, advancing the #3527 Express compile past iconv-lite.Root cause
The wrapper template was:
iconv-lite's
encodings/index.jsdoes:modulestayed the wrapper object (nocesu8key), somodule["cesu8"]readundefined→TypeError: Cannot read properties of undefined (reading 'cesu8')during init. (Minimal repro:var module = src; module.cesu8→ undefined.)Fix
In Node,
module/exportsare wrapper-function parameters — freely reassignable, and rebinding the local is harmless because Node reads the real exports from its own module reference. Mirror that:A body reassigning its
modulelocal can no longer clobber what the module exports, andvar module = Xbinds as expected.Verification
var module = X; module.cesu8repro and the iconv-lite shape now correct; the Express tree advances past iconv-lite.module.exports = X,exports.foo = …,module.exports.bar = …all unchanged (thereturn __cjs_module.exportsreads the stable ref).cjs_wraptests pass.cargo fmt --checkclean.Part of the #3527 chain (prior: #3537/#3543/#3546 codegen, #3551 wildcard, #3733 SIGBUS, #3751 Object.hasOwn). No version bump / changelog — folded in at merge per the worktree-PR convention.