Skip to content

Commit

Permalink
Added new dynamic import suspense cache
Browse files Browse the repository at this point in the history
And used it to import named hooks code.

Note this commit currently breaks the test shell.
  • Loading branch information
Brian Vaughn committed Sep 7, 2021
1 parent 778f57c commit 2b4b830
Show file tree
Hide file tree
Showing 14 changed files with 259 additions and 116 deletions.
37 changes: 15 additions & 22 deletions packages/react-devtools-extensions/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,28 +291,21 @@ function createPanelIfReactLoaded() {

render = (overrideTab = mostRecentOverrideTab) => {
mostRecentOverrideTab = overrideTab;
import('react-devtools-shared/src/hooks/parseHookNames').then(
({parseHookNames, prefetchSourceFiles, purgeCachedMetadata}) => {
root.render(
createElement(DevTools, {
bridge,
browserTheme: getBrowserTheme(),
componentsPortalContainer,
enabledInspectedElementContextMenu: true,
fetchFileWithCaching,
loadHookNames: parseHookNames,
overrideTab,
prefetchSourceFiles,
profilerPortalContainer,
purgeCachedHookNamesMetadata: purgeCachedMetadata,
showTabBar: false,
store,
warnIfUnsupportedVersionDetected: true,
viewAttributeSourceFunction,
viewElementSourceFunction,
}),
);
},
root.render(
createElement(DevTools, {
bridge,
browserTheme: getBrowserTheme(),
componentsPortalContainer,
enabledInspectedElementContextMenu: true,
fetchFileWithCaching,
overrideTab,
profilerPortalContainer,
showTabBar: false,
store,
warnIfUnsupportedVersionDetected: true,
viewAttributeSourceFunction,
viewElementSourceFunction,
}),
);
};

Expand Down
1 change: 1 addition & 0 deletions packages/react-devtools-extensions/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ module.exports = {
path: __dirname + '/build',
publicPath: '/build/',
filename: '[name].js',
chunkFilename: '[name].chunk.js',
},
node: {
// Don't define a polyfill on window.setImmediate
Expand Down
5 changes: 4 additions & 1 deletion packages/react-devtools-inline/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
"prepublish": "yarn run build",
"start": "cross-env NODE_ENV=development webpack --config webpack.config.js --watch"
},
"dependencies": {},
"dependencies": {
"source-map-js": "^0.6.2",
"sourcemap-codec": "^1.4.8"
},
"devDependencies": {
"@babel/core": "^7.11.1",
"@babel/plugin-proposal-class-properties": "^7.10.4",
Expand Down
1 change: 1 addition & 0 deletions packages/react-devtools-inline/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ module.exports = {
output: {
path: __dirname + '/dist',
filename: '[name].js',
chunkFilename: '[name].chunk.js',
library: '[name]',
libraryTarget: 'commonjs2',
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// @flow

import {createContext} from 'react';

export type FetchFileWithCaching = (url: string) => Promise<string>;
export type Context = FetchFileWithCaching | null;

const FetchFileWithCachingContext = createContext<Context>(null);
FetchFileWithCachingContext.displayName = 'FetchFileWithCachingContext';

export default FetchFileWithCachingContext;

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ import {
hasAlreadyLoadedHookNames,
loadHookNames,
} from 'react-devtools-shared/src/hookNamesCache';
import HookNamesContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesContext';
import {loadModule} from 'react-devtools-shared/src/dynamicImportCache';
import FetchFileWithCachingContext from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext';
import {SettingsContext} from '../Settings/SettingsContext';
import {enableNamedHooksFeature} from 'react-devtools-feature-flags';

import type {HookNames} from 'react-devtools-shared/src/types';
import type {ReactNodeList} from 'shared/ReactTypes';
Expand All @@ -58,18 +60,20 @@ export const InspectedElementContext = createContext<Context>(

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,
|};

export function InspectedElementContextController({children}: Props) {
const {selectedElementID} = useContext(TreeStateContext);
const {
fetchFileWithCaching,
loadHookNames: loadHookNamesFunction,
prefetchSourceFiles,
purgeCachedMetadata,
} = useContext(HookNamesContext);
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
const {parseHookNames: parseHookNamesByDefault} = useContext(SettingsContext);
Expand Down Expand Up @@ -113,24 +117,43 @@ export function InspectedElementContextController({children}: Props) {
setParseHookNames(parseHookNamesByDefault || alreadyLoadedHookNames);
}

const prefetchSourceFilesRef = useRef(null);
const purgeCachedMetadataRef = useRef(null);

// Don't load a stale element from the backend; it wastes bridge bandwidth.
let hookNames: HookNames | null = null;
let inspectedElement = null;
if (!elementHasChanged && element !== null) {
inspectedElement = inspectElement(element, state.path, store, bridge);

if (parseHookNames || alreadyLoadedHookNames) {
if (
inspectedElement !== null &&
inspectedElement.hooks !== null &&
loadHookNamesFunction !== null
) {
hookNames = loadHookNames(
element,
inspectedElement.hooks,
loadHookNamesFunction,
fetchFileWithCaching,
if (enableNamedHooksFeature) {
if (parseHookNames || alreadyLoadedHookNames) {
const loadHookNamesModule = loadModule(
loadHookNamesModuleLoaderFunction,
);
if (loadHookNamesModule !== null) {
const {
parseHookNames: loadHookNamesFunction,
prefetchSourceFiles,
purgeCachedMetadata,
} = loadHookNamesModule;

purgeCachedMetadataRef.current = purgeCachedMetadata;
prefetchSourceFilesRef.current = prefetchSourceFiles;

if (
inspectedElement !== null &&
inspectedElement.hooks !== null &&
loadHookNamesFunction !== null
) {
hookNames = loadHookNames(
element,
inspectedElement.hooks,
loadHookNamesFunction,
fetchFileWithCaching,
);
}
}
}
}
}
Expand Down Expand Up @@ -164,13 +187,15 @@ export function InspectedElementContextController({children}: Props) {
) {
inspectedElementRef.current = inspectedElement;

const prefetchSourceFiles = prefetchSourceFilesRef.current;
if (typeof prefetchSourceFiles === 'function') {
prefetchSourceFiles(inspectedElement.hooks, fetchFileWithCaching);
}
}
}, [inspectedElement, prefetchSourceFiles]);
}, [inspectedElement]);

useEffect(() => {
const purgeCachedMetadata = purgeCachedMetadataRef.current;
if (typeof purgeCachedMetadata === 'function') {
// When Fast Refresh updates a component, any cached AST metadata may be invalid.
const fastRefreshScheduled = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import styles from './InspectedElementHooksTree.css';
import useContextMenu from '../../ContextMenu/useContextMenu';
import {meta} from '../../../hydration';
import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache';
import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags';
import HookNamesContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesContext';
import {
enableNamedHooksFeature,
enableProfilerChangedHookIndices,
} from 'react-devtools-feature-flags';

import type {InspectedElement} from './types';
import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
Expand Down Expand Up @@ -53,8 +55,6 @@ export function InspectedElementHooksTree({
}: HooksTreeViewProps) {
const {hooks, id} = inspectedElement;

const {loadHookNames: loadHookNamesFunction} = useContext(HookNamesContext);

// Changing parseHookNames is done in a transition, because it suspends.
// This value is done outside of the transition, so the UI toggle feels responsive.
const [parseHookNamesOptimistic, setParseHookNamesOptimistic] = useState(
Expand Down Expand Up @@ -85,17 +85,16 @@ export function InspectedElementHooksTree({
<div className={styles.HooksTreeView}>
<div className={styles.HeaderRow}>
<div className={styles.Header}>hooks</div>
{loadHookNamesFunction !== null &&
(!parseHookNames || hookParsingFailed) && (
<Toggle
className={hookParsingFailed ? styles.ToggleError : null}
isChecked={parseHookNamesOptimistic}
isDisabled={parseHookNamesOptimistic || hookParsingFailed}
onChange={handleChange}
title={toggleTitle}>
<ButtonIcon type="parse-hook-names" />
</Toggle>
)}
{enableNamedHooksFeature && (!parseHookNames || hookParsingFailed) && (
<Toggle
className={hookParsingFailed ? styles.ToggleError : null}
isChecked={parseHookNamesOptimistic}
isDisabled={parseHookNamesOptimistic || hookParsingFailed}
onChange={handleChange}
title={toggleTitle}>
<ButtonIcon type="parse-hook-names" />
</Toggle>
)}
<Button onClick={handleCopy} title="Copy to clipboard">
<ButtonIcon type="copy" />
</Button>
Expand Down
35 changes: 5 additions & 30 deletions packages/react-devtools-shared/src/devtools/views/DevTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import TabBar from './TabBar';
import {SettingsContextController} from './Settings/SettingsContext';
import {TreeContextController} from './Components/TreeContext';
import ViewElementSourceContext from './Components/ViewElementSourceContext';
import HookNamesContext from './Components/HookNamesContext';
import FetchFileWithCachingContext from './Components/FetchFileWithCachingContext';
import {ProfilerContextController} from './Profiler/ProfilerContext';
import {SchedulingProfilerContextController} from 'react-devtools-scheduling-profiler/src/SchedulingProfilerContext';
import {ModalDialogContextController} from './ModalDialog';
Expand All @@ -44,26 +44,21 @@ 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 {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';

export type FetchFileWithCaching = (url: string) => Promise<string>;
export type PrefetchSourceFiles = (
hooksTree: HooksTree,
fetchFileWithCaching: FetchFileWithCaching | null,
) => void;
export type ViewElementSource = (
id: number,
inspectedElement: InspectedElement,
) => void;
export type LoadHookNamesFunction = (
hooksTree: HooksTree,
) => Thenable<HookNames>;
export type PurgeCachedHookNamesMetadata = () => void;
export type ViewAttributeSource = (
id: number,
path: Array<string | number>,
Expand Down Expand Up @@ -107,9 +102,6 @@ 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,
loadHookNames?: ?LoadHookNamesFunction,
prefetchSourceFiles?: ?PrefetchSourceFiles,
purgeCachedHookNamesMetadata?: ?PurgeCachedHookNamesMetadata,
|};

const componentsTab = {
Expand All @@ -135,11 +127,8 @@ export default function DevTools({
defaultTab = 'components',
enabledInspectedElementContextMenu = false,
fetchFileWithCaching,
loadHookNames,
overrideTab,
profilerPortalContainer,
prefetchSourceFiles,
purgeCachedHookNamesMetadata,
showTabBar = false,
store,
warnIfLegacyBackendDetected = false,
Expand Down Expand Up @@ -199,21 +188,6 @@ export default function DevTools({
[enabledInspectedElementContextMenu, viewAttributeSourceFunction],
);

const hookNamesContext = useMemo(
() => ({
fetchFileWithCaching: fetchFileWithCaching || null,
loadHookNames: loadHookNames || null,
prefetchSourceFiles: prefetchSourceFiles || null,
purgeCachedMetadata: purgeCachedHookNamesMetadata || null,
}),
[
fetchFileWithCaching,
loadHookNames,
prefetchSourceFiles,
purgeCachedHookNamesMetadata,
],
);

const devToolsRef = useRef<HTMLElement | null>(null);

useEffect(() => {
Expand Down Expand Up @@ -270,7 +244,8 @@ export default function DevTools({
componentsPortalContainer={componentsPortalContainer}
profilerPortalContainer={profilerPortalContainer}>
<ViewElementSourceContext.Provider value={viewElementSource}>
<HookNamesContext.Provider value={hookNamesContext}>
<FetchFileWithCachingContext.Provider
value={fetchFileWithCaching || null}>
<TreeContextController>
<ProfilerContextController>
<SchedulingProfilerContextController>
Expand Down Expand Up @@ -314,7 +289,7 @@ export default function DevTools({
</SchedulingProfilerContextController>
</ProfilerContextController>
</TreeContextController>
</HookNamesContext.Provider>
</FetchFileWithCachingContext.Provider>
</ViewElementSourceContext.Provider>
</SettingsContextController>
<UnsupportedBridgeProtocolDialog />
Expand Down
Loading

0 comments on commit 2b4b830

Please sign in to comment.