Skip to content

Commit d5dd630

Browse files
author
Brian Vaughn
committed
Added new dynamic import suspense cache
And used it to import named hooks code. Note this commit currently breaks the test shell.
1 parent 5167518 commit d5dd630

File tree

14 files changed

+259
-116
lines changed

14 files changed

+259
-116
lines changed

packages/react-devtools-extensions/src/main.js

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -291,28 +291,21 @@ function createPanelIfReactLoaded() {
291291

292292
render = (overrideTab = mostRecentOverrideTab) => {
293293
mostRecentOverrideTab = overrideTab;
294-
import('react-devtools-shared/src/hooks/parseHookNames').then(
295-
({parseHookNames, prefetchSourceFiles, purgeCachedMetadata}) => {
296-
root.render(
297-
createElement(DevTools, {
298-
bridge,
299-
browserTheme: getBrowserTheme(),
300-
componentsPortalContainer,
301-
enabledInspectedElementContextMenu: true,
302-
fetchFileWithCaching,
303-
loadHookNames: parseHookNames,
304-
overrideTab,
305-
prefetchSourceFiles,
306-
profilerPortalContainer,
307-
purgeCachedHookNamesMetadata: purgeCachedMetadata,
308-
showTabBar: false,
309-
store,
310-
warnIfUnsupportedVersionDetected: true,
311-
viewAttributeSourceFunction,
312-
viewElementSourceFunction,
313-
}),
314-
);
315-
},
294+
root.render(
295+
createElement(DevTools, {
296+
bridge,
297+
browserTheme: getBrowserTheme(),
298+
componentsPortalContainer,
299+
enabledInspectedElementContextMenu: true,
300+
fetchFileWithCaching,
301+
overrideTab,
302+
profilerPortalContainer,
303+
showTabBar: false,
304+
store,
305+
warnIfUnsupportedVersionDetected: true,
306+
viewAttributeSourceFunction,
307+
viewElementSourceFunction,
308+
}),
316309
);
317310
};
318311

packages/react-devtools-extensions/webpack.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ module.exports = {
5252
path: __dirname + '/build',
5353
publicPath: '/build/',
5454
filename: '[name].js',
55+
chunkFilename: '[name].chunk.js',
5556
},
5657
node: {
5758
// Don't define a polyfill on window.setImmediate

packages/react-devtools-inline/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
"prepublish": "yarn run build",
2121
"start": "cross-env NODE_ENV=development webpack --config webpack.config.js --watch"
2222
},
23-
"dependencies": {},
23+
"dependencies": {
24+
"source-map-js": "^0.6.2",
25+
"sourcemap-codec": "^1.4.8"
26+
},
2427
"devDependencies": {
2528
"@babel/core": "^7.11.1",
2629
"@babel/plugin-proposal-class-properties": "^7.10.4",

packages/react-devtools-inline/webpack.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module.exports = {
4141
output: {
4242
path: __dirname + '/dist',
4343
filename: '[name].js',
44+
chunkFilename: '[name].chunk.js',
4445
library: '[name]',
4546
libraryTarget: 'commonjs2',
4647
},
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @flow
2+
3+
import {createContext} from 'react';
4+
5+
export type FetchFileWithCaching = (url: string) => Promise<string>;
6+
export type Context = FetchFileWithCaching | null;
7+
8+
const FetchFileWithCachingContext = createContext<Context>(null);
9+
FetchFileWithCachingContext.displayName = 'FetchFileWithCachingContext';
10+
11+
export default FetchFileWithCachingContext;

packages/react-devtools-shared/src/devtools/views/Components/HookNamesContext.js

Lines changed: 0 additions & 26 deletions
This file was deleted.

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ import {
3030
hasAlreadyLoadedHookNames,
3131
loadHookNames,
3232
} from 'react-devtools-shared/src/hookNamesCache';
33-
import HookNamesContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesContext';
33+
import {loadModule} from 'react-devtools-shared/src/dynamicImportCache';
34+
import FetchFileWithCachingContext from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext';
3435
import {SettingsContext} from '../Settings/SettingsContext';
36+
import {enableNamedHooksFeature} from 'react-devtools-feature-flags';
3537

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

5961
const POLL_INTERVAL = 1000;
6062

63+
// parseHookNames has a lot of code.
64+
// Embedding it into a build makes the build large.
65+
// This component uses Suspense to lazily import() it only if the feature will be used.
66+
function loadHookNamesModuleLoaderFunction() {
67+
return import('react-devtools-shared/src/hooks/parseHookNames');
68+
}
69+
6170
export type Props = {|
6271
children: ReactNodeList,
6372
|};
6473

6574
export function InspectedElementContextController({children}: Props) {
6675
const {selectedElementID} = useContext(TreeStateContext);
67-
const {
68-
fetchFileWithCaching,
69-
loadHookNames: loadHookNamesFunction,
70-
prefetchSourceFiles,
71-
purgeCachedMetadata,
72-
} = useContext(HookNamesContext);
76+
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
7377
const bridge = useContext(BridgeContext);
7478
const store = useContext(StoreContext);
7579
const {parseHookNames: parseHookNamesByDefault} = useContext(SettingsContext);
@@ -113,24 +117,43 @@ export function InspectedElementContextController({children}: Props) {
113117
setParseHookNames(parseHookNamesByDefault || alreadyLoadedHookNames);
114118
}
115119

120+
const prefetchSourceFilesRef = useRef(null);
121+
const purgeCachedMetadataRef = useRef(null);
122+
116123
// Don't load a stale element from the backend; it wastes bridge bandwidth.
117124
let hookNames: HookNames | null = null;
118125
let inspectedElement = null;
119126
if (!elementHasChanged && element !== null) {
120127
inspectedElement = inspectElement(element, state.path, store, bridge);
121128

122-
if (parseHookNames || alreadyLoadedHookNames) {
123-
if (
124-
inspectedElement !== null &&
125-
inspectedElement.hooks !== null &&
126-
loadHookNamesFunction !== null
127-
) {
128-
hookNames = loadHookNames(
129-
element,
130-
inspectedElement.hooks,
131-
loadHookNamesFunction,
132-
fetchFileWithCaching,
129+
if (enableNamedHooksFeature) {
130+
if (parseHookNames || alreadyLoadedHookNames) {
131+
const loadHookNamesModule = loadModule(
132+
loadHookNamesModuleLoaderFunction,
133133
);
134+
if (loadHookNamesModule !== null) {
135+
const {
136+
parseHookNames: loadHookNamesFunction,
137+
prefetchSourceFiles,
138+
purgeCachedMetadata,
139+
} = loadHookNamesModule;
140+
141+
purgeCachedMetadataRef.current = purgeCachedMetadata;
142+
prefetchSourceFilesRef.current = prefetchSourceFiles;
143+
144+
if (
145+
inspectedElement !== null &&
146+
inspectedElement.hooks !== null &&
147+
loadHookNamesFunction !== null
148+
) {
149+
hookNames = loadHookNames(
150+
element,
151+
inspectedElement.hooks,
152+
loadHookNamesFunction,
153+
fetchFileWithCaching,
154+
);
155+
}
156+
}
134157
}
135158
}
136159
}
@@ -164,13 +187,15 @@ export function InspectedElementContextController({children}: Props) {
164187
) {
165188
inspectedElementRef.current = inspectedElement;
166189

190+
const prefetchSourceFiles = prefetchSourceFilesRef.current;
167191
if (typeof prefetchSourceFiles === 'function') {
168192
prefetchSourceFiles(inspectedElement.hooks, fetchFileWithCaching);
169193
}
170194
}
171-
}, [inspectedElement, prefetchSourceFiles]);
195+
}, [inspectedElement]);
172196

173197
useEffect(() => {
198+
const purgeCachedMetadata = purgeCachedMetadataRef.current;
174199
if (typeof purgeCachedMetadata === 'function') {
175200
// When Fast Refresh updates a component, any cached AST metadata may be invalid.
176201
const fastRefreshScheduled = () => {

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import styles from './InspectedElementHooksTree.css';
2222
import useContextMenu from '../../ContextMenu/useContextMenu';
2323
import {meta} from '../../../hydration';
2424
import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache';
25-
import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags';
26-
import HookNamesContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesContext';
25+
import {
26+
enableNamedHooksFeature,
27+
enableProfilerChangedHookIndices,
28+
} from 'react-devtools-feature-flags';
2729

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

56-
const {loadHookNames: loadHookNamesFunction} = useContext(HookNamesContext);
57-
5858
// Changing parseHookNames is done in a transition, because it suspends.
5959
// This value is done outside of the transition, so the UI toggle feels responsive.
6060
const [parseHookNamesOptimistic, setParseHookNamesOptimistic] = useState(
@@ -85,17 +85,16 @@ export function InspectedElementHooksTree({
8585
<div className={styles.HooksTreeView}>
8686
<div className={styles.HeaderRow}>
8787
<div className={styles.Header}>hooks</div>
88-
{loadHookNamesFunction !== null &&
89-
(!parseHookNames || hookParsingFailed) && (
90-
<Toggle
91-
className={hookParsingFailed ? styles.ToggleError : null}
92-
isChecked={parseHookNamesOptimistic}
93-
isDisabled={parseHookNamesOptimistic || hookParsingFailed}
94-
onChange={handleChange}
95-
title={toggleTitle}>
96-
<ButtonIcon type="parse-hook-names" />
97-
</Toggle>
98-
)}
88+
{enableNamedHooksFeature && (!parseHookNames || hookParsingFailed) && (
89+
<Toggle
90+
className={hookParsingFailed ? styles.ToggleError : null}
91+
isChecked={parseHookNamesOptimistic}
92+
isDisabled={parseHookNamesOptimistic || hookParsingFailed}
93+
onChange={handleChange}
94+
title={toggleTitle}>
95+
<ButtonIcon type="parse-hook-names" />
96+
</Toggle>
97+
)}
9998
<Button onClick={handleCopy} title="Copy to clipboard">
10099
<ButtonIcon type="copy" />
101100
</Button>

packages/react-devtools-shared/src/devtools/views/DevTools.js

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import TabBar from './TabBar';
2727
import {SettingsContextController} from './Settings/SettingsContext';
2828
import {TreeContextController} from './Components/TreeContext';
2929
import ViewElementSourceContext from './Components/ViewElementSourceContext';
30-
import HookNamesContext from './Components/HookNamesContext';
30+
import FetchFileWithCachingContext from './Components/FetchFileWithCachingContext';
3131
import {ProfilerContextController} from './Profiler/ProfilerContext';
3232
import {SchedulingProfilerContextController} from 'react-devtools-scheduling-profiler/src/SchedulingProfilerContext';
3333
import {ModalDialogContextController} from './ModalDialog';
@@ -44,26 +44,21 @@ import './root.css';
4444

4545
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
4646
import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types';
47+
import type {FetchFileWithCaching} from './Components/FetchFileWithCachingContext';
4748
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
4849
import type {HookNames} from 'react-devtools-shared/src/types';
4950
import type {Thenable} from '../cache';
5051

5152
export type BrowserTheme = 'dark' | 'light';
5253
export type TabID = 'components' | 'profiler';
5354

54-
export type FetchFileWithCaching = (url: string) => Promise<string>;
55-
export type PrefetchSourceFiles = (
56-
hooksTree: HooksTree,
57-
fetchFileWithCaching: FetchFileWithCaching | null,
58-
) => void;
5955
export type ViewElementSource = (
6056
id: number,
6157
inspectedElement: InspectedElement,
6258
) => void;
6359
export type LoadHookNamesFunction = (
6460
hooksTree: HooksTree,
6561
) => Thenable<HookNames>;
66-
export type PurgeCachedHookNamesMetadata = () => void;
6762
export type ViewAttributeSource = (
6863
id: number,
6964
path: Array<string | number>,
@@ -107,9 +102,6 @@ export type Props = {|
107102
// and extracts hook "names" based on the variables the hook return values get assigned to.
108103
// Not every DevTools build can load source maps, so this property is optional.
109104
fetchFileWithCaching?: ?FetchFileWithCaching,
110-
loadHookNames?: ?LoadHookNamesFunction,
111-
prefetchSourceFiles?: ?PrefetchSourceFiles,
112-
purgeCachedHookNamesMetadata?: ?PurgeCachedHookNamesMetadata,
113105
|};
114106

115107
const componentsTab = {
@@ -135,11 +127,8 @@ export default function DevTools({
135127
defaultTab = 'components',
136128
enabledInspectedElementContextMenu = false,
137129
fetchFileWithCaching,
138-
loadHookNames,
139130
overrideTab,
140131
profilerPortalContainer,
141-
prefetchSourceFiles,
142-
purgeCachedHookNamesMetadata,
143132
showTabBar = false,
144133
store,
145134
warnIfLegacyBackendDetected = false,
@@ -199,21 +188,6 @@ export default function DevTools({
199188
[enabledInspectedElementContextMenu, viewAttributeSourceFunction],
200189
);
201190

202-
const hookNamesContext = useMemo(
203-
() => ({
204-
fetchFileWithCaching: fetchFileWithCaching || null,
205-
loadHookNames: loadHookNames || null,
206-
prefetchSourceFiles: prefetchSourceFiles || null,
207-
purgeCachedMetadata: purgeCachedHookNamesMetadata || null,
208-
}),
209-
[
210-
fetchFileWithCaching,
211-
loadHookNames,
212-
prefetchSourceFiles,
213-
purgeCachedHookNamesMetadata,
214-
],
215-
);
216-
217191
const devToolsRef = useRef<HTMLElement | null>(null);
218192

219193
useEffect(() => {
@@ -270,7 +244,8 @@ export default function DevTools({
270244
componentsPortalContainer={componentsPortalContainer}
271245
profilerPortalContainer={profilerPortalContainer}>
272246
<ViewElementSourceContext.Provider value={viewElementSource}>
273-
<HookNamesContext.Provider value={hookNamesContext}>
247+
<FetchFileWithCachingContext.Provider
248+
value={fetchFileWithCaching || null}>
274249
<TreeContextController>
275250
<ProfilerContextController>
276251
<SchedulingProfilerContextController>
@@ -314,7 +289,7 @@ export default function DevTools({
314289
</SchedulingProfilerContextController>
315290
</ProfilerContextController>
316291
</TreeContextController>
317-
</HookNamesContext.Provider>
292+
</FetchFileWithCachingContext.Provider>
318293
</ViewElementSourceContext.Provider>
319294
</SettingsContextController>
320295
<UnsupportedBridgeProtocolDialog />

0 commit comments

Comments
 (0)