[GH-ISSUE #6797] [Bug] esbuild CommonJS factories lose exports when the module parameter is present #4148

Closed
opened 2026-10-31 00:47:10 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 23, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6797

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

The public analyze-javascript-application CLI recovers an esbuild CommonJS factory but reports exports: [] when its callback declares (exports, module). Its exports.parse assignment remains present in the source. A generated (exports) control reports ["parse"]. Both observations are observed, exact, and have complete coverage; both CLI calls exit 0.

Both bundles were generated with the checkout's locked esbuild 0.28.2. Executing these source-owned bundles separately in Node's VM confirmed that both export parse. REA itself performed static analysis; no browser was used. Each case was analyzed once through the CLI before this draft.

Reproduction

Save this complete generated bundle as fixture/bundle.js:

(() => {
  var __getOwnPropNames = Object.getOwnPropertyNames;
  var __commonJS = (cb, mod) => function __require() {
    try {
      return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
    } catch (e) {
      throw mod = 0, e;
    }
  };
  // dep.cjs
  var require_dep = __commonJS({
    "dep.cjs"(exports, module) {
      exports.parse = function parse() {
        return module.id;
      };
    }
  });
  require_dep();
})();

Run the prepared source build:

node "$REA_ROOT/scripts/rea.mjs" analyze-javascript-application "$PWD/fixture" --format json

Inspect normalized_result.graph.nodes: the javascript-module observation with module_key: "dep.cjs" and bundler: "esbuild" has exports: [].

The generated control changes the callback to "dep.cjs"(exports) and its return expression to 42; REA reports ["parse"]. The VM control replaced the final require_dep(); with globalThis.observed = require_dep();, evaluated the bundle in a fresh sandbox, and read Object.keys(sandbox.observed): ["parse"] in both cases.

Expected behavior

Both factories should retain the directly assigned parse export. esbuild's included helper visibly passes mod.exports first and mod second; referencing module.id does not remove exports.parse.

Impact and scope

Ordinary esbuild output that needs both CommonJS parameters loses exported API names in successful application-analysis results. The demonstrated consequence is incorrect module metadata; downstream effects and prevalence across third-party bundles were not measured. No practical result-preserving workaround was verified.

REA version

6.7.0; source revision bd4f5e9c197ba942ed026c9fed6ff3fae663901d, prepared build from that checkout.

Runtime and platform

Node v26.12.1; Linux x86_64. Fixture generation: esbuild 0.28.2 from the repository lockfile.

Target and artifact context

Local directory containing one generated JavaScript IIFE bundle; no target application or browser attachment.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Generated callback VM export names CLI module export names Observation coverage Exit
(exports) ["parse"] ["parse"] complete 0
(exports, module) ["parse"] [] complete 0

The esbuild adapter recovers the callback using the shared collector. That collector unconditionally identifies the second parameter as the exports binding. This source evidence explains the observed loss; the helper's argument order differs from Webpack's.

Additional context

Related #3551 concerns renaming Webpack's first module parameter and a literal module.exports check. This case needs no renaming: esbuild's ordinary argument order makes the collector reject the actual first exports binding. #3535 and #4490 concern Electron loader/member recovery, which this fixture does not use.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Two CLI controls and separate VM execution establish that a supported bundler callback loses an actual export while presenting its module observation as exact and complete.
  • Workaround status: unverified
  • Workaround basis: The one-parameter control succeeds, but changing the target's use of its module parameter changes the analyzed artifact; no result-preserving workaround was tested.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6797 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Evidence, artifacts, or reconstruction ## Observed behavior The public `analyze-javascript-application` CLI recovers an esbuild CommonJS factory but reports `exports: []` when its callback declares `(exports, module)`. Its `exports.parse` assignment remains present in the source. A generated `(exports)` control reports `["parse"]`. Both observations are `observed`, `exact`, and have complete coverage; both CLI calls exit 0. Both bundles were generated with the checkout's locked esbuild 0.28.2. Executing these source-owned bundles separately in Node's VM confirmed that both export `parse`. REA itself performed static analysis; no browser was used. Each case was analyzed once through the CLI before this draft. ## Reproduction Save this complete generated bundle as `fixture/bundle.js`: ```js (() => { var __getOwnPropNames = Object.getOwnPropertyNames; var __commonJS = (cb, mod) => function __require() { try { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; } catch (e) { throw mod = 0, e; } }; // dep.cjs var require_dep = __commonJS({ "dep.cjs"(exports, module) { exports.parse = function parse() { return module.id; }; } }); require_dep(); })(); ``` Run the prepared source build: ```sh node "$REA_ROOT/scripts/rea.mjs" analyze-javascript-application "$PWD/fixture" --format json ``` Inspect `normalized_result.graph.nodes`: the `javascript-module` observation with `module_key: "dep.cjs"` and `bundler: "esbuild"` has `exports: []`. The generated control changes the callback to `"dep.cjs"(exports)` and its return expression to `42`; REA reports `["parse"]`. The VM control replaced the final `require_dep();` with `globalThis.observed = require_dep();`, evaluated the bundle in a fresh sandbox, and read `Object.keys(sandbox.observed)`: `["parse"]` in both cases. ## Expected behavior Both factories should retain the directly assigned `parse` export. esbuild's included helper visibly passes `mod.exports` first and `mod` second; referencing `module.id` does not remove `exports.parse`. ## Impact and scope Ordinary esbuild output that needs both CommonJS parameters loses exported API names in successful application-analysis results. The demonstrated consequence is incorrect module metadata; downstream effects and prevalence across third-party bundles were not measured. No practical result-preserving workaround was verified. ## REA version 6.7.0; source revision `bd4f5e9c197ba942ed026c9fed6ff3fae663901d`, prepared build from that checkout. ## Runtime and platform Node v26.12.1; Linux x86_64. Fixture generation: esbuild 0.28.2 from the repository lockfile. ## Target and artifact context Local directory containing one generated JavaScript IIFE bundle; no target application or browser attachment. ## Interface CLI ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence | Generated callback | VM export names | CLI module export names | Observation coverage | Exit | | --- | --- | --- | --- | --- | | `(exports)` | `["parse"]` | `["parse"]` | complete | 0 | | `(exports, module)` | `["parse"]` | `[]` | complete | 0 | The esbuild adapter [recovers the callback using the shared collector](https://github.com/morluto/rea/blob/bd4f5e9c197ba942ed026c9fed6ff3fae663901d/src/domain/javascript/javascriptStaticAnalysisBundler.ts#L253-L259). That collector [unconditionally identifies the second parameter as the exports binding](https://github.com/morluto/rea/blob/bd4f5e9c197ba942ed026c9fed6ff3fae663901d/src/domain/javascript/javascriptAstFingerprint.ts#L40-L55). This source evidence explains the observed loss; the helper's argument order differs from Webpack's. ## Additional context Related #3551 concerns renaming Webpack's first `module` parameter and a literal `module.exports` check. This case needs no renaming: esbuild's ordinary argument order makes the collector reject the actual first `exports` binding. #3535 and #4490 concern Electron loader/member recovery, which this fixture does not use. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Two CLI controls and separate VM execution establish that a supported bundler callback loses an actual export while presenting its module observation as exact and complete. - Workaround status: unverified - Workaround basis: The one-parameter control succeeds, but changing the target's use of its module parameter changes the analyzed artifact; no result-preserving workaround was tested.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4148
No description provided.