Repository: pmmmwh/react-refresh-webpack-plugin (js)

Issue:
Empty module import after upgrading from 0.5.15 to >=0.5.16
We recently upgraded our project to the latest version of the plugin, and began noticing errors preventing our application from loading.

Specifically, it appears that our application code that imports [the `express-useragent` library](https://www.npmjs.com/package/express-useragent) results in an empty object, as shown in the screenshot below:

![Image](https://github.com/user-attachments/assets/ebad4545-b3ca-4f56-8cd0-ff69fef7d199)

I've confirmed that this issue is only present while this plugin is loaded, works with the plugin at v0.5.15, and stops working with the plugin at v0.5.16, indicating that the issue may have begun as of https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/853 .

I'm not very familiar with what could be happening here, but if this plugin analyzes exports of a module, it may be an incompatibility with how the [impacted file](https://github.com/biggora/express-useragent/blob/master/lib/express-useragent.js) defines its exports in a rather unconventional manner.

Pull request:
fix: ensure `this` propagates through module execution interceptor
Fixes #916

Patch:
diff --git a/lib/utils/makeRefreshRuntimeModule.js b/lib/utils/makeRefreshRuntimeModule.js
index a4a6303..875b7fc 100644
--- a/lib/utils/makeRefreshRuntimeModule.js
+++ b/lib/utils/makeRefreshRuntimeModule.js
@@ -1,9 +1,9 @@
 /**
  * Makes a runtime module to intercept module execution for React Refresh.
- * This module creates an isolated __webpak_require__ function for each module
- * and injects a $Refresh$ object into it for use by react-refresh.
+ * This module creates an isolated `__webpack_require__` function for each module,
+ * and injects a `$Refresh$` object into it for use by React Refresh.
  * @param {import('webpack')} webpack The Webpack exports.
- * @returns {ReactRefreshRuntimeModule} The runtime module class.
+ * @returns {typeof import('webpack').RuntimeModule} The runtime module class.
  */
 function makeRefreshRuntimeModule(webpack) {
   return class ReactRefreshRuntimeModule extends webpack.RuntimeModule {
@@ -17,6 +17,8 @@ function makeRefreshRuntimeModule(webpack) {
      * @returns {string} runtime code
      */
     generate() {
+      if (!this.compilation) throw new Error('Webpack compilation missing!');
+
       const { runtimeTemplate } = this.compilation;
       const constDeclaration = runtimeTemplate.supportsConst() ? 'const' : 'var';
       return webpack.Template.asString([
@@ -48,45 +50,46 @@ function makeRefreshRuntimeModule(webpack) {
           'options',
           [
             `${constDeclaration} originalFactory = options.factory;`,
-            `options.factory = ${runtimeTemplate.basicFunction(
-              'moduleObject, moduleExports, webpackRequire',
-              [
-                // Our require function delegates to the original require function
-                `${constDeclaration} hotRequire = ${runtimeTemplate.returningFunction(
-                  'webpackRequire(request)',
-                  'request'
-                )};`,
-                // The propery descriptor factory below
-                // ensures all properties but $Refresh$ are proxied through to the original require function
-                `${constDeclaration} createPropertyDescriptor = ${runtimeTemplate.basicFunction(
-                  'name',
-                  [
-                    `return {`,
-                    webpack.Template.indent([
-                      `configurable: true,`,
-                      `enumerable: true,`,
-                      `get: ${runtimeTemplate.returningFunction('webpackRequire[name]')},`,
-                      `set: ${runtimeTemplate.basicFunction('value', [
-                        'webpackRequire[name] = value;',
-                      ])},`,
-                    ]),
-                    `};`,
-                  ]
-                )};`,
-                `for (${constDeclaration} name in webpackRequire) {`,
-                webpack.Template.indent([
-                  'if (name === "$Refresh$") continue;',
-                  'if (Object.prototype.hasOwnProperty.call(webpackRequire, name)) {',
+            // Using a function declaration -
+            // ensures `this` would propagate for modules relying on it
+            `options.factory = function(moduleObject, moduleExports, webpackRequire) {`,
+            webpack.Template.indent([
+              // Our require function delegates to the original require function
+              `${constDeclaration} hotRequire = ${runtimeTemplate.returningFunction(
+                'webpackRequire(request)',
+                'request'
+              )};`,
+              // The propery descriptor factory below ensures all properties but `$Refresh$`
+              // are proxied through to the original require function
+              `${constDeclaration} createPropertyDescriptor = ${runtimeTemplate.basicFunction(
+                'name',
+                [
+                  `return {`,
                   webpack.Template.indent([
-                    `Object.defineProperty(hotRequire, name, createPropertyDescriptor(name));`,
+                    `configurable: true,`,
+                    `enumerable: true,`,
+                    `get: ${runtimeTemplate.returningFunction('webpackRequire[name]')},`,
+                    `set: ${runtimeTemplate.basicFunction('value', [
+                      'webpackRequire[name] = value;',
+                    ])},`,
                   ]),
-                  `}`,
+                  `};`,
+                ]
+              )};`,
+              `for (${constDeclaration} name in webpackRequire) {`,
+              webpack.Template.indent([
+                'if (name === "$Refresh$") continue;',
+                'if (Object.prototype.hasOwnProperty.call(webpackRequire, name)) {',
+                webpack.Template.indent([
+                  `Object.defineProperty(hotRequire, name, createPropertyDescriptor(name));`,
                 ]),
                 `}`,
-                `hotRequire.$Refresh$ = setup(options.id);`,
-                `originalFactory.call(this, moduleObject, moduleExports, hotRequire);`,
-              ]
-            )};`,
+              ]),
+              `}`,
+              `hotRequire.$Refresh$ = setup(options.id);`,
+              `originalFactory.call(this, moduleObject, moduleExports, hotRequire);`,
+            ]),
+            '};',
           ]
         )});`,
       ]);


Test patch:
diff --git a/test/unit/makeRefreshRuntimeModule.test.js b/test/unit/makeRefreshRuntimeModule.test.js
index 4984413..602757b 100644
--- a/test/unit/makeRefreshRuntimeModule.test.js
+++ b/test/unit/makeRefreshRuntimeModule.test.js
@@ -118,7 +118,7 @@ __webpack_require__.i.push(function(options) {
 
 __webpack_require__.i.push((options) => {
 	const originalFactory = options.factory;
-	options.factory = (moduleObject, moduleExports, webpackRequire) => {
+	options.factory = function(moduleObject, moduleExports, webpackRequire) {
 		const hotRequire = (request) => (webpackRequire(request));
 		const createPropertyDescriptor = (name) => {
 			return {