diff --git a/packages/react-devtools-extensions/src/main.js b/packages/react-devtools-extensions/src/main.js index f8d24a72118e5..4670e9a53fce3 100644 --- a/packages/react-devtools-extensions/src/main.js +++ b/packages/react-devtools-extensions/src/main.js @@ -287,6 +287,9 @@ function createPanelIfReactLoaded() { }; } + const hookNamesModuleLoaderFunction = () => + import('react-devtools-shared/src/hooks/parseHookNames'); + root = createRoot(document.createElement('div')); render = (overrideTab = mostRecentOverrideTab) => { @@ -298,6 +301,7 @@ function createPanelIfReactLoaded() { componentsPortalContainer, enabledInspectedElementContextMenu: true, fetchFileWithCaching, + hookNamesModuleLoaderFunction, overrideTab, profilerPortalContainer, showTabBar: false, diff --git a/packages/react-devtools-inline/hookNames.js b/packages/react-devtools-inline/hookNames.js new file mode 100644 index 0000000000000..6a319e2de30b5 --- /dev/null +++ b/packages/react-devtools-inline/hookNames.js @@ -0,0 +1 @@ +module.exports = require('./dist/hookNames'); diff --git a/packages/react-devtools-inline/src/hookNames.js b/packages/react-devtools-inline/src/hookNames.js new file mode 100644 index 0000000000000..e70424bcdaca3 --- /dev/null +++ b/packages/react-devtools-inline/src/hookNames.js @@ -0,0 +1,15 @@ +/** @flow */ + +import { + parseHookNames, + parseSourceAndMetadata, + prefetchSourceFiles, + purgeCachedMetadata, +} from 'react-devtools-shared/src/hooks/parseHookNames'; + +export { + parseHookNames, + parseSourceAndMetadata, + prefetchSourceFiles, + purgeCachedMetadata, +}; diff --git a/packages/react-devtools-inline/webpack.config.js b/packages/react-devtools-inline/webpack.config.js index 17b008c452d88..bc38a8792e4c4 100644 --- a/packages/react-devtools-inline/webpack.config.js +++ b/packages/react-devtools-inline/webpack.config.js @@ -37,6 +37,7 @@ module.exports = { entry: { backend: './src/backend.js', frontend: './src/frontend.js', + hookNames: './src/hookNames.js', }, output: { path: __dirname + '/dist', diff --git a/packages/react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext.js b/packages/react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext.js index 210a3ec83239b..4647ebe9bf76b 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext.js @@ -1,4 +1,11 @@ -// @flow +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ import {createContext} from 'react'; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext.js b/packages/react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext.js new file mode 100644 index 0000000000000..a42bc4c7c030e --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Thenable} from 'shared/ReactTypes'; + +import {createContext} from 'react'; +import typeof * as ParseHookNamesModule from 'react-devtools-shared/src/hooks/parseHookNames'; + +export type HookNamesModuleLoaderFunction = () => Thenable; +export type Context = HookNamesModuleLoaderFunction | null; + +const HookNamesModuleLoaderContext = createContext(null); +HookNamesModuleLoaderContext.displayName = 'HookNamesModuleLoaderContext'; + +export default HookNamesModuleLoaderContext; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js index c5ab69ada2264..54b15698614b9 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js @@ -32,6 +32,7 @@ import { } from 'react-devtools-shared/src/hookNamesCache'; import {loadModule} from 'react-devtools-shared/src/dynamicImportCache'; import FetchFileWithCachingContext from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext'; +import HookNamesModuleLoaderContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext'; import {SettingsContext} from '../Settings/SettingsContext'; import {enableNamedHooksFeature} from 'react-devtools-feature-flags'; @@ -60,13 +61,6 @@ export const InspectedElementContext = createContext( const POLL_INTERVAL = 1000; -// parseHookNames has a lot of code. -// Embedding it into a build makes the build large. -// This component uses Suspense to lazily import() it only if the feature will be used. -function loadHookNamesModuleLoaderFunction() { - return import('react-devtools-shared/src/hooks/parseHookNames'); -} - export type Props = {| children: ReactNodeList, |}; @@ -78,6 +72,11 @@ export function InspectedElementContextController({children}: Props) { const store = useContext(StoreContext); const {parseHookNames: parseHookNamesByDefault} = useContext(SettingsContext); + // parseHookNames has a lot of code. + // Embedding it into a build makes the build large. + // This function enables DevTools to make use of Suspense to lazily import() it only if the feature will be used. + const hookNamesModuleLoader = useContext(HookNamesModuleLoaderContext); + const refresh = useCacheRefresh(); // Temporarily stores most recently-inspected (hydrated) path. @@ -127,31 +126,31 @@ export function InspectedElementContextController({children}: Props) { inspectedElement = inspectElement(element, state.path, store, bridge); if (enableNamedHooksFeature) { - if (parseHookNames || alreadyLoadedHookNames) { - const loadHookNamesModule = loadModule( - loadHookNamesModuleLoaderFunction, - ); - if (loadHookNamesModule !== null) { - const { - parseHookNames: loadHookNamesFunction, - prefetchSourceFiles, - purgeCachedMetadata, - } = loadHookNamesModule; + if (typeof hookNamesModuleLoader === 'function') { + if (parseHookNames || alreadyLoadedHookNames) { + const hookNamesModule = loadModule(hookNamesModuleLoader); + if (hookNamesModule !== null) { + const { + parseHookNames: loadHookNamesFunction, + prefetchSourceFiles, + purgeCachedMetadata, + } = hookNamesModule; - purgeCachedMetadataRef.current = purgeCachedMetadata; - prefetchSourceFilesRef.current = prefetchSourceFiles; + purgeCachedMetadataRef.current = purgeCachedMetadata; + prefetchSourceFilesRef.current = prefetchSourceFiles; - if ( - inspectedElement !== null && - inspectedElement.hooks !== null && - loadHookNamesFunction !== null - ) { - hookNames = loadHookNames( - element, - inspectedElement.hooks, - loadHookNamesFunction, - fetchFileWithCaching, - ); + if ( + inspectedElement !== null && + inspectedElement.hooks !== null && + loadHookNamesFunction !== null + ) { + hookNames = loadHookNames( + element, + inspectedElement.hooks, + loadHookNamesFunction, + fetchFileWithCaching, + ); + } } } } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js index 2a9d8ef2f7588..fb80b0152968e 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js @@ -26,6 +26,7 @@ import { enableNamedHooksFeature, enableProfilerChangedHookIndices, } from 'react-devtools-feature-flags'; +import HookNamesModuleLoaderContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext'; import type {InspectedElement} from './types'; import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks'; @@ -65,6 +66,8 @@ export function InspectedElementHooksTree({ toggleParseHookNames(); }; + const hookNamesModuleLoader = useContext(HookNamesModuleLoaderContext); + const hookParsingFailed = parseHookNames && hookNames === null; let toggleTitle; @@ -85,16 +88,18 @@ export function InspectedElementHooksTree({
hooks
- {enableNamedHooksFeature && (!parseHookNames || hookParsingFailed) && ( - - - - )} + {enableNamedHooksFeature && + typeof hookNamesModuleLoader === 'function' && + (!parseHookNames || hookParsingFailed) && ( + + + + )} diff --git a/packages/react-devtools-shared/src/devtools/views/DevTools.js b/packages/react-devtools-shared/src/devtools/views/DevTools.js index f3507afaf4c97..5b5dfbfa02bee 100644 --- a/packages/react-devtools-shared/src/devtools/views/DevTools.js +++ b/packages/react-devtools-shared/src/devtools/views/DevTools.js @@ -28,6 +28,7 @@ import {SettingsContextController} from './Settings/SettingsContext'; import {TreeContextController} from './Components/TreeContext'; import ViewElementSourceContext from './Components/ViewElementSourceContext'; import FetchFileWithCachingContext from './Components/FetchFileWithCachingContext'; +import HookNamesModuleLoaderContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext'; import {ProfilerContextController} from './Profiler/ProfilerContext'; import {SchedulingProfilerContextController} from 'react-devtools-scheduling-profiler/src/SchedulingProfilerContext'; import {ModalDialogContextController} from './ModalDialog'; @@ -42,12 +43,10 @@ import styles from './DevTools.css'; import './root.css'; -import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks'; import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types'; import type {FetchFileWithCaching} from './Components/FetchFileWithCachingContext'; +import type {HookNamesModuleLoaderFunction} from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext'; import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; -import type {HookNames} from 'react-devtools-shared/src/types'; -import type {Thenable} from '../cache'; export type BrowserTheme = 'dark' | 'light'; export type TabID = 'components' | 'profiler'; @@ -56,9 +55,6 @@ export type ViewElementSource = ( id: number, inspectedElement: InspectedElement, ) => void; -export type LoadHookNamesFunction = ( - hooksTree: HooksTree, -) => Thenable; export type ViewAttributeSource = ( id: number, path: Array, @@ -102,6 +98,7 @@ export type Props = {| // and extracts hook "names" based on the variables the hook return values get assigned to. // Not every DevTools build can load source maps, so this property is optional. fetchFileWithCaching?: ?FetchFileWithCaching, + hookNamesModuleLoaderFunction?: ?HookNamesModuleLoaderFunction, |}; const componentsTab = { @@ -127,6 +124,7 @@ export default function DevTools({ defaultTab = 'components', enabledInspectedElementContextMenu = false, fetchFileWithCaching, + hookNamesModuleLoaderFunction, overrideTab, profilerPortalContainer, showTabBar = false, @@ -244,52 +242,55 @@ export default function DevTools({ componentsPortalContainer={componentsPortalContainer} profilerPortalContainer={profilerPortalContainer}> - - - - - -
- {showTabBar && ( -
- - - {process.env.DEVTOOLS_VERSION} - -
- + + + + + +
+ {showTabBar && ( +
+ + + {process.env.DEVTOOLS_VERSION} + +
+ +
+ )} + + - )} - - -
- - - - - + + + + + + diff --git a/packages/react-devtools-shell/src/devtools.js b/packages/react-devtools-shell/src/devtools.js index fda4c91b74ffe..8b172825357ec 100644 --- a/packages/react-devtools-shell/src/devtools.js +++ b/packages/react-devtools-shell/src/devtools.js @@ -10,6 +10,10 @@ import { import {initialize as initializeFrontend} from 'react-devtools-inline/frontend'; import {initDevTools} from 'react-devtools-shared/src/devtools'; +// This is a pretty gross hack to make the runtime loaded named-hooks-code work. +// $FlowFixMe +__webpack_public_path__ = '/dist/'; // eslint-disable-line no-undef + const iframe = ((document.getElementById('target'): any): HTMLIFrameElement); const {contentDocument, contentWindow} = iframe; @@ -50,6 +54,10 @@ mountButton.addEventListener('click', function() { } }); +function hookNamesModuleLoaderFunction() { + return import('react-devtools-inline/hookNames'); +} + inject('dist/app.js', () => { initDevTools({ connect(cb) { @@ -58,6 +66,7 @@ inject('dist/app.js', () => { createElement(DevTools, { browserTheme: 'light', enabledInspectedElementContextMenu: true, + hookNamesModuleLoaderFunction, showTabBar: true, warnIfLegacyBackendDetected: true, warnIfUnsupportedVersionDetected: true,