Repository: facebook/react
Pull request: feat: display message if user ended up opening hook script

In https://github.com/facebook/react/pull/30596 we've moved console patching to the global hook. Generally speaking, the patching happens even before React is loaded on the page.

If browser DevTools were opened after when `console.error` or `console.warn` were called, the source script will be `hook.js`, because of the patching.
![devtools-opened-after-the-message](https://github.com/user-attachments/assets/3d3dbc16-96b8-4234-b061-57b21b60cf2e)

This is because ignore listing is not applied retroactively by Chrome DevTools.
If you had it open before console calls, Hook script would be correctly filtered out from the stack:
![devtools-opened-before-the-message](https://github.com/user-attachments/assets/3e99cb22-97b0-4b49-9a76-f7bc948e6452)

I had hopes that the fix for https://issues.chromium.org/issues/345248263 will also apply ignore listing retroactively, but looks like we need to open a separate feature request for the Chrome DevTools team.

With these changes, if user attempts to open `hook.js` script, they are going to see this message:
![Screenshot 2024-09-19 at 11 30 59](https://github.com/user-attachments/assets/5850b74c-329f-4fbe-a3dd-33f9ac717ee9)


Changed files:
filename: packages/react-devtools-extensions/webpack.config.js
patch: @@ -154,6 +154,62 @@ module.exports = {
         );
       },
     }),
+    {
+      apply(compiler) {
+        if (__DEV__) {
+          return;
+        }
+
+        const {RawSource} = compiler.webpack.sources;
+        compiler.hooks.compilation.tap(
+          'CustomContentForHookScriptPlugin',
+          compilation => {
+            compilation.hooks.processAssets.tap(
+              {
+                name: 'CustomContentForHookScriptPlugin',
+                stage: Webpack.Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING,
+                additionalAssets: true,
+              },
+              assets => {
+                // eslint-disable-next-line no-for-of-loops/no-for-of-loops
+                for (const [name, asset] of Object.entries(assets)) {
+                  if (name !== 'installHook.js.map') {
+                    continue;
+                  }
+
+                  const mapContent = asset.source().toString();
+                  if (!mapContent) {
+                    continue;
+                  }
+
+                  const map = JSON.parse(mapContent);
+                  map.sourcesContent = map.sources.map(sourceName => {
+                    if (!sourceName.endsWith('/hook.js')) {
+                      return null;
+                    }
+
+                    return (
+                      '/*\n' +
+                      ' * This script is from React DevTools.\n' +
+                      " * You're likely here because you thought it sent an error or warning to the console.\n" +
+                      ' * React DevTools patches the console to support features like appending component stacks, \n' +
+                      ' * so this file appears as a source. However, the console call actually came from another script.\n' +
+                      " * To remove this script from stack traces, open your browser's DevTools (to enable source mapping) before these console calls happen.\n" +
+                      ' */'
+                    );
+                  });
+
+                  compilation.updateAsset(
+                    name,
+                    new RawSource(JSON.stringify(map)),
+                  );
+                }
+              },
+            );
+          },
+        );
+      },
+    },
   ],
   module: {
     defaultRules: [