Repository: facebook/react
Pull request: make parseHookNames a separate bundle

Currently, the `main.js` bundle for React Devtools that Webpack outputs is too large (>7MB) to upload to Firefox. One of the reasons is because `parseHookNames` imports `babel` and `source-map`, which are both large packages. This PR uses dynamic imports to split `parseHookNames` into a separate webpack bundle and also always loads `parseHookNames` via web worker to reduce the size of `main.js`.

Changed files:
filename: packages/react-devtools-extensions/src/__tests__/parseHookNames-test.js
patch: @@ -62,7 +62,7 @@ describe('parseHookNames', () => {
 
     inspectHooks = require('react-debug-tools/src/ReactDebugHooks')
       .inspectHooks;
-    parseHookNames = require('../parseHookNames').parseHookNames;
+    parseHookNames = require('../parseHookNames/parseHookNames').parseHookNames;
 
     // Jest (jest-runner?) configures Errors to automatically account for source maps.
     // This changes behavior between our tests and the browser.
@@ -718,22 +718,13 @@ describe('parseHookNames', () => {
 describe('parseHookNames worker', () => {
   let inspectHooks;
   let parseHookNames;
-  let originalParseHookNamesMock;
   let workerizedParseHookNamesMock;
 
   beforeEach(() => {
     window.Worker = undefined;
 
-    originalParseHookNamesMock = jest.fn();
     workerizedParseHookNamesMock = jest.fn();
 
-    jest.mock('../parseHookNames/parseHookNames.js', () => {
-      return {
-        __esModule: true,
-        parseHookNames: originalParseHookNamesMock,
-      };
-    });
-
     jest.mock('../parseHookNames/parseHookNames.worker.js', () => {
       return {
         __esModule: true,
@@ -757,7 +748,7 @@ describe('parseHookNames worker', () => {
     return hookNames;
   }
 
-  it('should use worker when available', async () => {
+  it('should use worker', async () => {
     const Component = require('./__source__/__untransformed__/ComponentWithUseState')
       .Component;
 
@@ -769,13 +760,4 @@ describe('parseHookNames worker', () => {
     await getHookNamesForComponent(Component);
     expect(workerizedParseHookNamesMock).toHaveBeenCalledTimes(1);
   });
-
-  it('should use main thread when worker is not available', async () => {
-    const Component = require('./__source__/__untransformed__/ComponentWithUseState')
-      .Component;
-
-    await getHookNamesForComponent(Component);
-    expect(workerizedParseHookNamesMock).toHaveBeenCalledTimes(0);
-    expect(originalParseHookNamesMock).toHaveBeenCalledTimes(1);
-  });
 });

filename: packages/react-devtools-extensions/src/main.js
patch: @@ -12,7 +12,6 @@ import {
   getSavedComponentFilters,
   getShowInlineWarningsAndErrors,
 } from 'react-devtools-shared/src/utils';
-import {parseHookNames, purgeCachedMetadata} from './parseHookNames';
 import {
   localStorageGetItem,
   localStorageRemoveItem,
@@ -210,23 +209,26 @@ function createPanelIfReactLoaded() {
 
         render = (overrideTab = mostRecentOverrideTab) => {
           mostRecentOverrideTab = overrideTab;
-
-          root.render(
-            createElement(DevTools, {
-              bridge,
-              browserTheme: getBrowserTheme(),
-              componentsPortalContainer,
-              enabledInspectedElementContextMenu: true,
-              loadHookNames: parseHookNames,
-              overrideTab,
-              profilerPortalContainer,
-              purgeCachedHookNamesMetadata: purgeCachedMetadata,
-              showTabBar: false,
-              store,
-              warnIfUnsupportedVersionDetected: true,
-              viewAttributeSourceFunction,
-              viewElementSourceFunction,
-            }),
+          import('./parseHookNames').then(
+            ({parseHookNames, purgeCachedMetadata}) => {
+              root.render(
+                createElement(DevTools, {
+                  bridge,
+                  browserTheme: getBrowserTheme(),
+                  componentsPortalContainer,
+                  enabledInspectedElementContextMenu: true,
+                  loadHookNames: parseHookNames,
+                  overrideTab,
+                  profilerPortalContainer,
+                  purgeCachedHookNamesMetadata: purgeCachedMetadata,
+                  showTabBar: false,
+                  store,
+                  warnIfUnsupportedVersionDetected: true,
+                  viewAttributeSourceFunction,
+                  viewElementSourceFunction,
+                }),
+              );
+            },
           );
         };
 

filename: packages/react-devtools-extensions/src/parseHookNames/index.js
patch: @@ -12,17 +12,13 @@
 // This file uses workerize to load ./parseHookNames.worker as a webworker and instanciates it,
 // exposing flow typed functions that can be used on other files.
 
-import * as parseHookNamesModule from './parseHookNames';
 import WorkerizedParseHookNames from './parseHookNames.worker';
-
-type ParseHookNamesModule = typeof parseHookNamesModule;
+import typeof * as ParseHookNamesModule from './parseHookNames';
 
 // $FlowFixMe
 const wasmMappingsURL = chrome.extension.getURL('mappings.wasm');
 
-const workerizedParseHookNames: ParseHookNamesModule = window.Worker
-  ? WorkerizedParseHookNames()
-  : parseHookNamesModule;
+const workerizedParseHookNames: ParseHookNamesModule = WorkerizedParseHookNames();
 
 type ParseHookNames = $PropertyType<ParseHookNamesModule, 'parseHookNames'>;
 

filename: packages/react-devtools-extensions/webpack.config.js
patch: @@ -41,6 +41,7 @@ module.exports = {
   },
   output: {
     path: __dirname + '/build',
+    publicPath: '/build/',
     filename: '[name].js',
   },
   node: {