Repository: rolldown/rolldown
Pull request #4132: fix(hmr): should register cjs exports differing from esm
Author: login: hyf0
Created: 2025-04-16T08:00:37Z | Merged: 2025-04-16T08:55:27Z

<!-- Thank you for contributing! -->

### Description

<!-- Please insert your description here and provide especially info about the "what" this PR is solving -->



Diff:
diff --git a/crates/rolldown/src/hmr/hmr_ast_finalizer.rs b/crates/rolldown/src/hmr/hmr_ast_finalizer.rs
index 02a1f547202a..971d5502f33d 100644
--- a/crates/rolldown/src/hmr/hmr_ast_finalizer.rs
+++ b/crates/rolldown/src/hmr/hmr_ast_finalizer.rs
@@ -100,7 +100,7 @@ impl<'ast> HmrAstFinalizer<'_, 'ast> {
     };
 
     // __rolldown_runtime__.register(moduleId, module)
-    let arguments = self.snippet.builder.vec_from_array([
+    let mut arguments = self.snippet.builder.vec_from_array([
       ast::Argument::StringLiteral(self.snippet.builder.alloc_string_literal(
         SPAN,
         &self.module.stable_id,
@@ -108,6 +108,29 @@ impl<'ast> HmrAstFinalizer<'_, 'ast> {
       )),
       module_exports,
     ]);
+
+    if self.module.exports_kind.is_commonjs() {
+      // __rolldown_runtime__.register(moduleId, module, { cjs: true })
+      arguments.push(ast::Argument::ObjectExpression(
+        self.snippet.builder.alloc_object_expression(
+          SPAN,
+          self.snippet.builder.vec1(ast::ObjectPropertyKind::ObjectProperty(
+            ast::ObjectProperty {
+              key: ast::PropertyKey::StaticIdentifier(
+                self.snippet.id_name("cjs", SPAN).into_in(self.alloc),
+              ),
+              value: ast::Expression::BooleanLiteral(
+                self.snippet.builder.alloc_boolean_literal(SPAN, true),
+              ),
+              ..ast::ObjectProperty::dummy(self.alloc)
+            }
+            .into_in(self.alloc),
+          )),
+          None,
+        ),
+      ));
+    }
+
     let register_call = self.snippet.builder.alloc_call_expression(
       SPAN,
       self.snippet.id_ref_expr("__rolldown_runtime__.registerModule", SPAN),
diff --git a/crates/rolldown/src/module_finalizers/scope_hoisting/hmr.rs b/crates/rolldown/src/module_finalizers/scope_hoisting/hmr.rs
index e4a5d6198878..75bf62c0162b 100644
--- a/crates/rolldown/src/module_finalizers/scope_hoisting/hmr.rs
+++ b/crates/rolldown/src/module_finalizers/scope_hoisting/hmr.rs
@@ -74,7 +74,7 @@ impl<'ast> ScopeHoistingFinalizer<'_, 'ast> {
     };
 
     // __rolldown_runtime__.register(moduleId, module)
-    let arguments = self.snippet.builder.vec_from_array([
+    let mut arguments = self.snippet.builder.vec_from_array([
       ast::Argument::StringLiteral(self.snippet.builder.alloc_string_literal(
         SPAN,
         &self.ctx.module.stable_id,
@@ -82,6 +82,29 @@ impl<'ast> ScopeHoistingFinalizer<'_, 'ast> {
       )),
       module_exports,
     ]);
+
+    if self.ctx.module.exports_kind.is_commonjs() {
+      // __rolldown_runtime__.register(moduleId, module, { cjs: true })
+      arguments.push(ast::Argument::ObjectExpression(
+        self.snippet.builder.alloc_object_expression(
+          SPAN,
+          self.snippet.builder.vec1(ast::ObjectPropertyKind::ObjectProperty(
+            ast::ObjectProperty {
+              key: ast::PropertyKey::StaticIdentifier(
+                self.snippet.id_name("cjs", SPAN).into_in(self.alloc),
+              ),
+              value: ast::Expression::BooleanLiteral(
+                self.snippet.builder.alloc_boolean_literal(SPAN, true),
+              ),
+              ..ast::ObjectProperty::dummy(self.alloc)
+            }
+            .into_in(self.alloc),
+          )),
+          None,
+        ),
+      ));
+    }
+
     let register_call = self.snippet.builder.alloc_call_expression(
       SPAN,
       self.snippet.id_ref_expr("__rolldown_runtime__.registerModule", SPAN),
diff --git a/crates/rolldown/src/runtime/runtime-extra-dev.js b/crates/rolldown/src/runtime/runtime-extra-dev.js
index 4e59f4693a0a..e908a009c737 100644
--- a/crates/rolldown/src/runtime/runtime-extra-dev.js
+++ b/crates/rolldown/src/runtime/runtime-extra-dev.js
@@ -88,14 +88,21 @@ class DevRuntime {
     this.moduleHotContextsToBeUpdated.clear()
     // swap new contexts
   }
-  registerModule(id, exportGetters) {
+  registerModule(id, esmExportGettersOrCjsExports, meta = {}) {
     const exports = {};
-    Object.keys(exportGetters).forEach((key) => {
-      if (Object.prototype.hasOwnProperty.call(exportGetters, key)) {
-        Object.defineProperty(exports, key, {
-          enumerable: true,
-          get: exportGetters[key],
-        });
+    Object.keys(esmExportGettersOrCjsExports).forEach((key) => {
+      if (Object.prototype.hasOwnProperty.call(esmExportGettersOrCjsExports, key)) {
+        if (meta.cjs) {
+          Object.defineProperty(exports, key, {
+            enumerable: true,
+            get: () => esmExportGettersOrCjsExports[key],
+          });
+        } else {
+          Object.defineProperty(exports, key, {
+            enumerable: true,
+            get: esmExportGettersOrCjsExports[key],
+          });
+        }
       }
     })
     console.debug('Registering module', id, exports);
diff --git a/crates/rolldown/tests/rolldown/issues/4129/artifacts.snap b/crates/rolldown/tests/rolldown/issues/4129/artifacts.snap
index 81da610ac916..6c860d64f4ce 100644
--- a/crates/rolldown/tests/rolldown/issues/4129/artifacts.snap
+++ b/crates/rolldown/tests/rolldown/issues/4129/artifacts.snap
@@ -12,7 +12,7 @@ import assert from "node:assert";
 //#region lib.js
 var require_lib = __commonJS({ "lib.js"(exports, module) {
 	import.meta.hot = __rolldown_runtime__.createModuleHotContext("lib.js");
-	__rolldown_runtime__.registerModule("lib.js", module.exports);
+	__rolldown_runtime__.registerModule("lib.js", module.exports, { cjs: true });
 	exports.a = 1;
 } });
 var import_lib = __toESM(require_lib());
diff --git a/crates/rolldown/tests/rolldown/topics/hmr/register_exports/artifacts.snap b/crates/rolldown/tests/rolldown/topics/hmr/register_exports/artifacts.snap
index 7d7ba39a356e..e8d0c65b014c 100644
--- a/crates/rolldown/tests/rolldown/topics/hmr/register_exports/artifacts.snap
+++ b/crates/rolldown/tests/rolldown/topics/hmr/register_exports/artifacts.snap
@@ -11,7 +11,7 @@ source: crates/rolldown_testing/src/integration_test.rs
 //#region cjs.js
 var require_cjs = __commonJS({ "cjs.js"(exports, module) {
 	import.meta.hot = __rolldown_runtime__.createModuleHotContext("cjs.js");
-	__rolldown_runtime__.registerModule("cjs.js", module.exports);
+	__rolldown_runtime__.registerModule("cjs.js", module.exports, { cjs: true });
 	module.exports.value = "cjs";
 } });
 var import_cjs = __toESM(require_cjs());
diff --git a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap
index 0913c09d537b..229a77e29f17 100644
--- a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap
+++ b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap
@@ -4668,7 +4668,7 @@ expression: output
 
 # tests/rolldown/issues/4129
 
-- main-!~{000}~.js => main-pcCHAJja.js
+- main-!~{000}~.js => main-Bl27OrDc.js
 
 # tests/rolldown/misc/ambiguous_star_export
 
@@ -5193,7 +5193,7 @@ expression: output
 
 # tests/rolldown/topics/hmr/register_exports
 
-- main-!~{000}~.js => main-BJ9L-p0c.js
+- main-!~{000}~.js => main-2OjAHm2E.js
 
 # tests/rolldown/topics/import_meta_url_dirname_filename_polyfill/node_cjs
 


Reviews:
user: login: hyf0
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T08:01:04Z
user: login: hyf0
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T08:11:00Z
user: login: underfin
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T08:17:27Z

Review comments:
diff_hunk: @@ -88,14 +88,21 @@ class DevRuntime {
     this.moduleHotContextsToBeUpdated.clear()
     // swap new contexts
   }
-  registerModule(id, exportGetters) {
+  registerModule(id, esmExportGettersOrCjsExports, meta = {}) {
path: crates/rolldown/src/runtime/runtime-extra-dev.js
user: login: hyf0
body: @underfin cc
created_at: 2025-04-16T08:01:03Z
author_association: MEMBER
diff_hunk: @@ -12,7 +12,7 @@ import assert from "node:assert";
 //#region lib.js
 var require_lib = __commonJS({ "lib.js"(exports, module) {
 	import.meta.hot = __rolldown_runtime__.createModuleHotContext("lib.js");
-	__rolldown_runtime__.registerModule("lib.js", module.exports);
+	__rolldown_runtime__.registerModule("lib.js", module.exports, { cjs: true });
path: crates/rolldown/tests/rolldown/issues/4129/artifacts.snap
user: login: hyf0
body: This still fails case like

```
module.exports = ...
```

We should probably move the `registerModule` call to the bottom.
created_at: 2025-04-16T08:10:59Z
author_association: MEMBER

PR comments:
user: login: hyf0
created_at: 2025-04-16T08:00:55Z
body: * **#4132** <a href="https://app.graphite.dev/github/pr/rolldown/rolldown/4132?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/rolldown/rolldown/4132?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* **#4131** <a href="https://app.graphite.dev/github/pr/rolldown/rolldown/4131?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* `main`


<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-04-16T08:21:08Z
body: ### Benchmarks Rust
- target: `main`(afae76571b3730a8484341936c4e1d1b2b670579)
- pr: `04-16-fix_hmr_should_register_cjs_exports_differing_from_esm`(7f9510b9b07e90fb648863ba24545321f7b0bd67)
```
group                                                               pr                                     target
-----                                                               --                                     ------
bundle/bundle@multi-duplicated-top-level-symbol                     1.00     72.7±1.15ms        ? ?/sec    1.04     75.7±3.77ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-minify              1.00     95.7±2.03ms        ? ?/sec    1.00     95.8±3.48ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-minify-sourcemap    1.01    108.7±2.25ms        ? ?/sec    1.00    107.7±1.06ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-sourcemap           1.00     83.0±1.20ms        ? ?/sec    1.01     84.2±1.24ms        ? ?/sec
bundle/bundle@rome-ts                                               1.00    121.5±1.29ms        ? ?/sec    1.04    125.8±2.72ms        ? ?/sec
bundle/bundle@rome-ts-minify                                        1.00    201.4±4.27ms        ? ?/sec    1.02    206.4±4.87ms        ? ?/sec
bundle/bundle@rome-ts-minify-sourcemap                              1.00    241.1±3.98ms        ? ?/sec    1.01    243.2±5.68ms        ? ?/sec
bundle/bundle@rome-ts-sourcemap                                     1.00    134.3±1.86ms        ? ?/sec    1.02    136.5±1.76ms        ? ?/sec
bundle/bundle@threejs                                               1.00     42.5±1.10ms        ? ?/sec    1.00     42.4±1.65ms        ? ?/sec
bundle/bundle@threejs-minify                                        1.00     86.4±1.59ms        ? ?/sec    1.01     87.4±2.06ms        ? ?/sec
bundle/bundle@threejs-minify-sourcemap                              1.00    100.5±0.86ms        ? ?/sec    1.11    111.8±3.23ms        ? ?/sec
bundle/bundle@threejs-sourcemap                                     1.00     48.4±0.88ms        ? ?/sec    1.01     48.8±0.62ms        ? ?/sec
bundle/bundle@threejs10x                                            1.00    430.0±3.95ms        ? ?/sec    1.01    434.4±8.09ms        ? ?/sec
bundle/bundle@threejs10x-minify                                     1.00  1062.8±11.74ms        ? ?/sec    1.00   1059.5±9.56ms        ? ?/sec
bundle/bundle@threejs10x-minify-sourcemap                           1.00   1234.9±8.05ms        ? ?/sec    1.02  1261.1±19.38ms        ? ?/sec
bundle/bundle@threejs10x-sourcemap                                  1.00    498.8±5.27ms        ? ?/sec    1.01    503.5±5.53ms        ? ?/sec
remapping/remapping                                                 1.01     26.7±0.77ms        ? ?/sec    1.00     26.5±1.04ms        ? ?/sec
remapping/render-chunk-remapping                                    1.00     64.4±0.59ms        ? ?/sec    1.00     64.5±2.50ms        ? ?/sec
scan/scan@rome-ts                                                   1.00     95.6±0.84ms        ? ?/sec    1.02     97.6±1.51ms        ? ?/sec
scan/scan@threejs                                                   1.00     31.9±1.06ms        ? ?/sec    1.00     32.0±0.68ms        ? ?/sec
scan/scan@threejs10x                                                1.00    315.4±3.14ms        ? ?/sec    1.02    321.9±3.32ms        ? ?/sec
```
author_association: CONTRIBUTOR
user: login: graphite-app[bot]
created_at: 2025-04-16T08:26:16Z
body: ### Merge activity

* **Apr 16, 4:26 AM EDT**: [Graphite](https://app.graphite.dev/github/pr/rolldown/rolldown/4132) rebased this pull request after merging its parent, because this pull request is set to merge when ready.

author_association: CONTRIBUTOR
user: login: netlify[bot]
created_at: 2025-04-16T08:26:17Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *rolldown-rs* canceled.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 7f9510b9b07e90fb648863ba24545321f7b0bd67 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/rolldown-rs/deploys/67ff69898e088d00088a3f28 |
author_association: NONE