@samitouri / QOS-React / commits / c5cfa71948

DevTools: Show hook names based on variable usage (#21641)

Co-authored-by: Brian Vaughn <brian.david.vaughn@gmail.com> Co-authored-by: Saphal Patro <saphal1998@gmail.com> Co-authored-by: VibhorCodecianGupta <vibhordelgupta@gmail.com>

Brian Vaughn committed Jul 1, 2021 at 14:39 UTC c5cfa71948dbf4c2dd9ff64596ab663f8d559cd6
84 files changed +2966 -74
.eslintignore
+2
@@ -20,6 +20,8 @@ packages/react-devtools-core/dist
20 packages/react-devtools-extensions/chrome/build
21 packages/react-devtools-extensions/firefox/build
22 packages/react-devtools-extensions/shared/build
23 +packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/
24 +packages/react-devtools-extensions/src/ErrorTesterCompiled.js
25 packages/react-devtools-inline/dist
26 packages/react-devtools-shell/dist
27 packages/react-devtools-scheduling-profiler/dist
.prettierignore
+2
@@ -2,6 +2,8 @@ packages/react-devtools-core/dist
2 packages/react-devtools-extensions/chrome/build
3 packages/react-devtools-extensions/firefox/build
4 packages/react-devtools-extensions/shared/build
5 +packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/
6 +packages/react-devtools-extensions/src/ErrorTesterCompiled.js
7 packages/react-devtools-inline/dist
8 packages/react-devtools-shell/dist
9 packages/react-devtools-scheduling-profiler/dist
package.json
+1 -1
@@ -107,7 +107,7 @@
107 "scripts": {
108 "build": "node ./scripts/rollup/build.js",
109 "build-combined": "node ./scripts/rollup/build-all-release-channels.js",
110 - "build-for-devtools": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react-dom,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh --type=NODE && rm -rf build2 && mkdir build2 && cp -r ./build/node_modules build2/oss-experimental/",
110 + "build-for-devtools": "cross-env RELEASE_CHANNEL=experimental yarn build react/index,react/jsx,react-dom,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh --type=NODE && rm -rf build2 && mkdir build2 && cp -r ./build/node_modules build2/oss-experimental/",
111 "build-for-devtools-dev": "yarn build-for-devtools --type=NODE_DEV",
112 "build-for-devtools-prod": "yarn build-for-devtools --type=NODE_PROD",
113 "linc": "node ./scripts/tasks/linc.js",
packages/react-devtools-core/webpack.backend.js
+3
@@ -47,6 +47,9 @@ module.exports = {
47 scheduler: resolve(builtModulesDir, 'scheduler'),
48 },
49 },
50 + node: {
51 + fs: 'empty',
52 + },
53 plugins: [
54 new DefinePlugin({
55 __DEV__,
packages/react-devtools-extensions/build.js
+6
@@ -93,6 +93,12 @@ const build = async (tempPath, manifestPath) => {
93 STATIC_FILES.map(file => copy(join(__dirname, file), join(zipPath, file))),
94 );
95
96 + // The "source-map" library requires this chunk of WASM to be bundled at runtime.
97 + await copy(
98 + join(__dirname, 'node_modules', 'source-map', 'lib', 'mappings.wasm'),
99 + join(zipPath, 'mappings.wasm'),
100 + );
101 +
102 const commit = getGitCommit();
103 const dateString = new Date().toLocaleDateString();
104 const manifest = JSON.parse(readFileSync(copiedManifestPath).toString());
packages/react-devtools-extensions/chrome/manifest.json
+2 -1
@@ -32,7 +32,8 @@
32 "main.html",
33 "panel.html",
34 "build/react_devtools_backend.js",
35 - "build/renderer.js"
35 + "build/renderer.js",
36 + "mappings.wasm"
37 ],
38
39 "background": {
packages/react-devtools-extensions/edge/manifest.json
+2 -1
@@ -32,7 +32,8 @@
32 "main.html",
33 "panel.html",
34 "build/react_devtools_backend.js",
35 - "build/renderer.js"
35 + "build/renderer.js",
36 + "mappings.wasm"
37 ],
38
39 "background": {
packages/react-devtools-extensions/firefox/manifest.json
+2 -1
@@ -37,7 +37,8 @@
37 "main.html",
38 "panel.html",
39 "build/react_devtools_backend.js",
40 - "build/renderer.js"
40 + "build/renderer.js",
41 + "mappings.wasm"
42 ],
43
44 "background": {
packages/react-devtools-extensions/package.json
+7 -1
@@ -15,12 +15,14 @@
15 "build:edge:dev": "cross-env NODE_ENV=development node ./edge/build",
16 "test:chrome": "node ./chrome/test",
17 "test:firefox": "node ./firefox/test",
18 - "test:edge": "node ./edge/test"
18 + "test:edge": "node ./edge/test",
19 + "update-mock-source-maps": "node ./src/__tests__/updateMockSourceMaps.js"
20 },
21 "devDependencies": {
22 "@babel/core": "^7.11.1",
23 "@babel/plugin-proposal-class-properties": "^7.10.4",
24 "@babel/plugin-transform-flow-strip-types": "^7.10.4",
25 + "@babel/plugin-transform-modules-commonjs": "^7.10.4",
26 "@babel/plugin-transform-react-jsx-source": "^7.10.5",
27 "@babel/preset-env": "^7.11.0",
28 "@babel/preset-flow": "^7.10.4",
@@ -29,16 +31,20 @@
31 "babel-core": "^7.0.0-bridge",
32 "babel-eslint": "^9.0.0",
33 "babel-loader": "^8.0.4",
34 + "babel-preset-minify": "^0.5.1",
35 "child-process-promise": "^2.2.1",
36 "chrome-launch": "^1.1.4",
37 "crx": "^5.0.0",
38 "css-loader": "^1.0.1",
39 "firefox-profile": "^1.0.2",
40 + "fs-extra": "^4.0.2",
41 + "jest-fetch-mock": "^3.0.3",
42 "node-libs-browser": "0.5.3",
43 "nullthrows": "^1.0.0",
44 "open": "^7.0.2",
45 "os-name": "^3.1.0",
46 "raw-loader": "^3.1.0",
47 + "source-map": "^0.8.0-beta.0",
48 "style-loader": "^0.23.1",
49 "web-ext": "^3.0.0",
50 "webpack": "^4.43.0",
packages/react-devtools-extensions/src/ErrorTester.js new
+34
@@ -0,0 +1,34 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import ErrorStackParser from 'error-stack-parser';
11 +import testErrorStack, {
12 + SOURCE_STACK_FRAME_LINE_NUMBER,
13 +} from './ErrorTesterCompiled';
14 +
15 +let sourceMapsAreAppliedToErrors: boolean | null = null;
16 +
17 +// Source maps are automatically applied to Error stack frames.
18 +export function areSourceMapsAppliedToErrors(): boolean {
19 + if (sourceMapsAreAppliedToErrors === null) {
20 + try {
21 + testErrorStack();
22 + sourceMapsAreAppliedToErrors = false;
23 + } catch (error) {
24 + const parsed = ErrorStackParser.parse(error);
25 + const topStackFrame = parsed[0];
26 + const lineNumber = topStackFrame.lineNumber;
27 + if (lineNumber === SOURCE_STACK_FRAME_LINE_NUMBER) {
28 + sourceMapsAreAppliedToErrors = true;
29 + }
30 + }
31 + }
32 +
33 + return sourceMapsAreAppliedToErrors === true;
34 +}
packages/react-devtools-extensions/src/ErrorTesterCompiled.js new
+25
@@ -0,0 +1,25 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.default = testErrorStack;
7 +exports.SOURCE_STACK_FRAME_LINE_NUMBER = void 0;
8 +
9 +/**
10 + * Copyright (c) Facebook, Inc. and its affiliates.
11 + *
12 + * This source code is licensed under the MIT license found in the
13 + * LICENSE file in the root directory of this source tree.
14 + *
15 + * @flow
16 + */
17 +function testErrorStack() {
18 + // If source maps are applied, this Error will have a stack frame with line 12.
19 + throw Error('Test Error stack');
20 +}
21 +
22 +const SOURCE_STACK_FRAME_LINE_NUMBER = 12;
23 +exports.SOURCE_STACK_FRAME_LINE_NUMBER = SOURCE_STACK_FRAME_LINE_NUMBER;
24 +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3RFcm9yclN0YWNrLmpzIl0sIm5hbWVzIjpbInRlc3RFcm9yclN0YWNrIiwiRXJyb3IiLCJTT1VSQ0VfU1RBQ0tfRlJBTUVfTElORV9OVU1CRVIiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O0FBQUE7Ozs7Ozs7O0FBU2UsU0FBU0EsY0FBVCxHQUEwQjtBQUN2QztBQUNBLFFBQU1DLEtBQUssQ0FBQyxrQkFBRCxDQUFYO0FBQ0Q7O0FBRU0sTUFBTUMsOEJBQThCLEdBQUcsRUFBdkMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvcHlyaWdodCAoYykgRmFjZWJvb2ssIEluYy4gYW5kIGl0cyBhZmZpbGlhdGVzLlxuICpcbiAqIFRoaXMgc291cmNlIGNvZGUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlIGZvdW5kIGluIHRoZVxuICogTElDRU5TRSBmaWxlIGluIHRoZSByb290IGRpcmVjdG9yeSBvZiB0aGlzIHNvdXJjZSB0cmVlLlxuICpcbiAqIEBmbG93XG4gKi9cblxuZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gdGVzdEVyb3JyU3RhY2soKSB7XG4gIC8vIElmIHNvdXJjZSBtYXBzIGFyZSBhcHBsaWVkLCB0aGlzIEVycm9yIHdpbGwgaGF2ZSBhIHN0YWNrIGZyYW1lIHdpdGggbGluZSAxMi5cbiAgdGhyb3cgRXJyb3IoJ1Rlc3QgRXJyb3Igc3RhY2snKTtcbn1cblxuZXhwb3J0IGNvbnN0IFNPVVJDRV9TVEFDS19GUkFNRV9MSU5FX05VTUJFUiA9IDEyOyJdfQ==
25 +//# sourceURL=testErrorStack.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/README.md new
+1
@@ -0,0 +1 @@
1 +The JavaScript files and source maps in this directory are used to test DevTools hook name parsing code. The files here use source maps in different formats to mirror real world applications. The source for the files is located in the parnet `__tests__` folder. To regenerate these compiled files, run `yarn update-mock-source-maps` in the `react-devtools-extensions` directory.
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/ComponentWithCustomHook.js new
+39
@@ -0,0 +1,39 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import React, {useEffect, useState} from 'react';
11 +
12 +export function Component() {
13 + const [count, setCount] = useState(0);
14 + const isDarkMode = useIsDarkMode();
15 +
16 + useEffect(() => {
17 + // ...
18 + }, []);
19 +
20 + const handleClick = () => setCount(count + 1);
21 +
22 + return (
23 + <>
24 + <div>Dark mode? {isDarkMode}</div>
25 + <div>Count: {count}</div>
26 + <button onClick={handleClick}>Update count</button>
27 + </>
28 + );
29 +}
30 +
31 +function useIsDarkMode() {
32 + const [isDarkMode] = useState(false);
33 +
34 + useEffect(function useEffectCreate() {
35 + // Here is where we may listen to a "theme" event...
36 + }, []);
37 +
38 + return isDarkMode;
39 +}
packages/react-devtools-extensions/src/__tests__/__source__/ComponentWithExternalCustomHooks.js new
+17
@@ -0,0 +1,17 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import React from 'react';
11 +import useTheme from './useTheme';
12 +
13 +export function Component() {
14 + const theme = useTheme();
15 +
16 + return <div>theme: {theme}</div>;
17 +}
packages/react-devtools-extensions/src/__tests__/__source__/Example.js new
+21
@@ -0,0 +1,21 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import React, {useState} from 'react';
11 +
12 +export function Component() {
13 + const [count, setCount] = useState(0);
14 +
15 + return (
16 + <div>
17 + <p>You clicked {count} times</p>
18 + <button onClick={() => setCount(count + 1)}>Click me</button>
19 + </div>
20 + );
21 +}
packages/react-devtools-extensions/src/__tests__/__source__/InlineRequire.js new
+14
@@ -0,0 +1,14 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export function Component() {
11 + const [count] = require('react').useState(0);
12 +
13 + return count;
14 +}
packages/react-devtools-extensions/src/__tests__/__source__/ToDoList.js new
+128
@@ -0,0 +1,128 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import * as React from 'react';
11 +import {Fragment, useCallback, useState} from 'react';
12 +
13 +export function ListItem({item, removeItem, toggleItem}) {
14 + const handleDelete = useCallback(() => {
15 + removeItem(item);
16 + }, [item, removeItem]);
17 +
18 + const handleToggle = useCallback(() => {
19 + toggleItem(item);
20 + }, [item, toggleItem]);
21 +
22 + return (
23 + <li>
24 + <button onClick={handleDelete}>Delete</button>
25 + <label>
26 + <input
27 + checked={item.isComplete}
28 + onChange={handleToggle}
29 + type="checkbox"
30 + />{' '}
31 + {item.text}
32 + </label>
33 + </li>
34 + );
35 +}
36 +
37 +export function List(props) {
38 + const [newItemText, setNewItemText] = useState('');
39 + const [items, setItems] = useState([
40 + {id: 1, isComplete: true, text: 'First'},
41 + {id: 2, isComplete: true, text: 'Second'},
42 + {id: 3, isComplete: false, text: 'Third'},
43 + ]);
44 + const [uid, setUID] = useState(4);
45 +
46 + const handleClick = useCallback(() => {
47 + if (newItemText !== '') {
48 + setItems([
49 + ...items,
50 + {
51 + id: uid,
52 + isComplete: false,
53 + text: newItemText,
54 + },
55 + ]);
56 + setUID(uid + 1);
57 + setNewItemText('');
58 + }
59 + }, [newItemText, items, uid]);
60 +
61 + const handleKeyPress = useCallback(
62 + event => {
63 + if (event.key === 'Enter') {
64 + handleClick();
65 + }
66 + },
67 + [handleClick],
68 + );
69 +
70 + const handleChange = useCallback(
71 + event => {
72 + setNewItemText(event.currentTarget.value);
73 + },
74 + [setNewItemText],
75 + );
76 +
77 + const removeItem = useCallback(
78 + itemToRemove => setItems(items.filter(item => item !== itemToRemove)),
79 + [items],
80 + );
81 +
82 + const toggleItem = useCallback(
83 + itemToToggle => {
84 + // Dont use indexOf()
85 + // because editing props in DevTools creates a new Object.
86 + const index = items.findIndex(item => item.id === itemToToggle.id);
87 +
88 + setItems(
89 + items
90 + .slice(0, index)
91 + .concat({
92 + ...itemToToggle,
93 + isComplete: !itemToToggle.isComplete,
94 + })
95 + .concat(items.slice(index + 1)),
96 + );
97 + },
98 + [items],
99 + );
100 +
101 + return (
102 + <Fragment>
103 + <h1>List</h1>
104 + <input
105 + type="text"
106 + placeholder="New list item..."
107 + value={newItemText}
108 + onChange={handleChange}
109 + onKeyPress={handleKeyPress}
110 + />
111 + <button disabled={newItemText === ''} onClick={handleClick}>
112 + <span role="img" aria-label="Add item">
113 + Add
114 + </span>
115 + </button>
116 + <ul>
117 + {items.map(item => (
118 + <ListItem
119 + key={item.id}
120 + item={item}
121 + removeItem={removeItem}
122 + toggleItem={toggleItem}
123 + />
124 + ))}
125 + </ul>
126 + </Fragment>
127 + );
128 +}
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/ComponentWithCustomHook.js new
+42
@@ -0,0 +1,42 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.Component = Component;
7 +
8 +var _react = _interopRequireWildcard(require("react"));
9 +
10 +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
11 +
12 +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
13 +
14 +/**
15 + * Copyright (c) Facebook, Inc. and its affiliates.
16 + *
17 + * This source code is licensed under the MIT license found in the
18 + * LICENSE file in the root directory of this source tree.
19 + *
20 + * @flow
21 + */
22 +function Component() {
23 + const [count, setCount] = (0, _react.useState)(0);
24 + const isDarkMode = useIsDarkMode();
25 + (0, _react.useEffect)(() => {// ...
26 + }, []);
27 +
28 + const handleClick = () => setCount(count + 1);
29 +
30 + return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", null, "Dark mode? ", isDarkMode), /*#__PURE__*/_react.default.createElement("div", null, "Count: ", count), /*#__PURE__*/_react.default.createElement("button", {
31 + onClick: handleClick
32 + }, "Update count"));
33 +}
34 +
35 +function useIsDarkMode() {
36 + const [isDarkMode, setIsDarkMode] = (0, _react.useState)(0);
37 + (0, _react.useEffect)(function useEffectCreate() {// Here is where we may listen to a "theme" event...
38 + }, []);
39 + return isDarkMode;
40 +}
41 +//# sourceMappingURL=ComponentWithCustomHook.js.map
42 +//# sourceURL=ComponentWithCustomHook.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/ComponentWithCustomHook.js.map new
+1
@@ -0,0 +1 @@
1 +{"version":3,"sources":["ComponentWithCustomHook.js"],"names":["Component","count","setCount","isDarkMode","useIsDarkMode","handleClick","setIsDarkMode","useEffectCreate"],"mappings":";;;;;;;AASA;;;;;;AATA;;;;;;;;AAWO,SAASA,SAAT,GAAqB;AAC1B,QAAM,CAACC,KAAD,EAAQC,QAAR,IAAoB,qBAAS,CAAT,CAA1B;AACA,QAAMC,UAAU,GAAGC,aAAa,EAAhC;AAEA,wBAAU,MAAM,CACd;AACD,GAFD,EAEG,EAFH;;AAIA,QAAMC,WAAW,GAAG,MAAMH,QAAQ,CAACD,KAAK,GAAG,CAAT,CAAlC;;AAEA,sBACE,yEACE,yDAAiBE,UAAjB,CADF,eAEE,qDAAaF,KAAb,CAFF,eAGE;AAAQ,IAAA,OAAO,EAAEI;AAAjB,oBAHF,CADF;AAOD;;AAED,SAASD,aAAT,GAAyB;AACvB,QAAM,CAACD,UAAD,EAAaG,aAAb,IAA8B,qBAAS,CAAT,CAApC;AAEA,wBAAU,SAASC,eAAT,GAA2B,CACnC;AACD,GAFD,EAEG,EAFH;AAIA,SAAOJ,UAAP;AACD","sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @flow\n */\n\nimport React, {useEffect, useState} from 'react';\n\nexport function Component() {\n const [count, setCount] = useState(0);\n const isDarkMode = useIsDarkMode();\n\n useEffect(() => {\n // ...\n }, []);\n\n const handleClick = () => setCount(count + 1);\n\n return (\n <>\n <div>Dark mode? {isDarkMode}</div>\n <div>Count: {count}</div>\n <button onClick={handleClick}>Update count</button>\n </>\n );\n}\n\nfunction useIsDarkMode() {\n const [isDarkMode, setIsDarkMode] = useState(0);\n\n useEffect(function useEffectCreate() {\n // Here is where we may listen to a \"theme\" event...\n }, []);\n\n return isDarkMode;\n}"]}
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/ComponentWithExternalCustomHooks.js new
+27
@@ -0,0 +1,27 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.Component = Component;
7 +
8 +var _react = _interopRequireDefault(require("react"));
9 +
10 +var _useTheme = _interopRequireDefault(require("./useTheme"));
11 +
12 +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13 +
14 +/**
15 + * Copyright (c) Facebook, Inc. and its affiliates.
16 + *
17 + * This source code is licensed under the MIT license found in the
18 + * LICENSE file in the root directory of this source tree.
19 + *
20 + * @flow
21 + */
22 +function Component() {
23 + const theme = (0, _useTheme.default)();
24 + return /*#__PURE__*/_react.default.createElement("div", null, "theme: ", theme);
25 +}
26 +//# sourceMappingURL=ComponentWithExternalCustomHooks.js.map
27 +//# sourceURL=ComponentWithExternalCustomHooks.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/ComponentWithExternalCustomHooks.js.map new
+1
@@ -0,0 +1 @@
1 +{"version":3,"sources":["ComponentWithExternalCustomHooks.js"],"names":["Component","theme"],"mappings":";;;;;;;AASA;;AACA;;;;AAVA;;;;;;;;AAYO,SAASA,SAAT,GAAqB;AAC1B,QAAMC,KAAK,GAAG,wBAAd;AAEA,sBAAO,qDAAaA,KAAb,CAAP;AACD","sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @flow\n */\n\nimport React from 'react';\nimport useTheme from './useTheme';\n\nexport function Component() {\n const theme = useTheme();\n\n return <div>theme: {theme}</div>;\n}\n"]}
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/Example.js new
+29
@@ -0,0 +1,29 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.Component = Component;
7 +
8 +var _react = _interopRequireWildcard(require("react"));
9 +
10 +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
11 +
12 +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
13 +
14 +/**
15 + * Copyright (c) Facebook, Inc. and its affiliates.
16 + *
17 + * This source code is licensed under the MIT license found in the
18 + * LICENSE file in the root directory of this source tree.
19 + *
20 + * @flow
21 + */
22 +function Component() {
23 + const [count, setCount] = (0, _react.useState)(0);
24 + return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("p", null, "You clicked ", count, " times"), /*#__PURE__*/_react.default.createElement("button", {
25 + onClick: () => setCount(count + 1)
26 + }, "Click me"));
27 +}
28 +//# sourceMappingURL=Example.js.map
29 +//# sourceURL=Example.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/Example.js.map new
+1
@@ -0,0 +1 @@
1 +{"version":3,"sources":["Example.js"],"names":["Component","count","setCount"],"mappings":";;;;;;;AASA;;;;;;AATA;;;;;;;;AAWO,SAASA,SAAT,GAAqB;AAC1B,QAAM,CAACC,KAAD,EAAQC,QAAR,IAAoB,qBAAS,CAAT,CAA1B;AAEA,sBACE,uDACE,wDAAgBD,KAAhB,WADF,eAEE;AAAQ,IAAA,OAAO,EAAE,MAAMC,QAAQ,CAACD,KAAK,GAAG,CAAT;AAA/B,gBAFF,CADF;AAMD","sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @flow\n */\n\nimport React, {useState} from 'react';\n\nexport function Component() {\n const [count, setCount] = useState(0);\n\n return (\n <div>\n <p>You clicked {count} times</p>\n <button onClick={() => setCount(count + 1)}>Click me</button>\n </div>\n );\n}\n"]}
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/InlineRequire.js new
+22
@@ -0,0 +1,22 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.Component = Component;
7 +
8 +/**
9 + * Copyright (c) Facebook, Inc. and its affiliates.
10 + *
11 + * This source code is licensed under the MIT license found in the
12 + * LICENSE file in the root directory of this source tree.
13 + *
14 + * @flow
15 + */
16 +function Component() {
17 + const [count] = require('react').useState(0);
18 +
19 + return count;
20 +}
21 +//# sourceMappingURL=InlineRequire.js.map
22 +//# sourceURL=InlineRequire.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/InlineRequire.js.map new
+1
@@ -0,0 +1 @@
1 +{"version":3,"sources":["InlineRequire.js"],"names":["Component","count","require","useState"],"mappings":";;;;;;;AAAA;;;;;;;;AASO,SAASA,SAAT,GAAqB;AAC1B,QAAM,CAACC,KAAD,IAAUC,OAAO,CAAC,OAAD,CAAP,CAAiBC,QAAjB,CAA0B,CAA1B,CAAhB;;AAEA,SAAOF,KAAP;AACD","sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @flow\n */\n\nexport function Component() {\n const [count] = require('react').useState(0);\n\n return count;\n}\n"]}
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/ToDoList.js new
+107
@@ -0,0 +1,107 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.ListItem = ListItem;
7 +exports.List = List;
8 +
9 +var React = _interopRequireWildcard(require("react"));
10 +
11 +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
12 +
13 +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
14 +
15 +/**
16 + * Copyright (c) Facebook, Inc. and its affiliates.
17 + *
18 + * This source code is licensed under the MIT license found in the
19 + * LICENSE file in the root directory of this source tree.
20 + *
21 + * @flow
22 + */
23 +function ListItem({
24 + item,
25 + removeItem,
26 + toggleItem
27 +}) {
28 + const handleDelete = (0, React.useCallback)(() => {
29 + removeItem(item);
30 + }, [item, removeItem]);
31 + const handleToggle = (0, React.useCallback)(() => {
32 + toggleItem(item);
33 + }, [item, toggleItem]);
34 + return /*#__PURE__*/React.createElement("li", null, /*#__PURE__*/React.createElement("button", {
35 + onClick: handleDelete
36 + }, "Delete"), /*#__PURE__*/React.createElement("label", null, /*#__PURE__*/React.createElement("input", {
37 + checked: item.isComplete,
38 + onChange: handleToggle,
39 + type: "checkbox"
40 + }), ' ', item.text));
41 +}
42 +
43 +function List(props) {
44 + const [newItemText, setNewItemText] = (0, React.useState)('');
45 + const [items, setItems] = (0, React.useState)([{
46 + id: 1,
47 + isComplete: true,
48 + text: 'First'
49 + }, {
50 + id: 2,
51 + isComplete: true,
52 + text: 'Second'
53 + }, {
54 + id: 3,
55 + isComplete: false,
56 + text: 'Third'
57 + }]);
58 + const [uid, setUID] = (0, React.useState)(4);
59 + const handleClick = (0, React.useCallback)(() => {
60 + if (newItemText !== '') {
61 + setItems([...items, {
62 + id: uid,
63 + isComplete: false,
64 + text: newItemText
65 + }]);
66 + setUID(uid + 1);
67 + setNewItemText('');
68 + }
69 + }, [newItemText, items, uid]);
70 + const handleKeyPress = (0, React.useCallback)(event => {
71 + if (event.key === 'Enter') {
72 + handleClick();
73 + }
74 + }, [handleClick]);
75 + const handleChange = (0, React.useCallback)(event => {
76 + setNewItemText(event.currentTarget.value);
77 + }, [setNewItemText]);
78 + const removeItem = (0, React.useCallback)(itemToRemove => setItems(items.filter(item => item !== itemToRemove)), [items]);
79 + const toggleItem = (0, React.useCallback)(itemToToggle => {
80 + // Dont use indexOf()
81 + // because editing props in DevTools creates a new Object.
82 + const index = items.findIndex(item => item.id === itemToToggle.id);
83 + setItems(items.slice(0, index).concat({ ...itemToToggle,
84 + isComplete: !itemToToggle.isComplete
85 + }).concat(items.slice(index + 1)));
86 + }, [items]);
87 + return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "List"), /*#__PURE__*/React.createElement("input", {
88 + type: "text",
89 + placeholder: "New list item...",
90 + value: newItemText,
91 + onChange: handleChange,
92 + onKeyPress: handleKeyPress
93 + }), /*#__PURE__*/React.createElement("button", {
94 + disabled: newItemText === '',
95 + onClick: handleClick
96 + }, /*#__PURE__*/React.createElement("span", {
97 + role: "img",
98 + "aria-label": "Add item"
99 + }, "Add")), /*#__PURE__*/React.createElement("ul", null, items.map(item => /*#__PURE__*/React.createElement(ListItem, {
100 + key: item.id,
101 + item: item,
102 + removeItem: removeItem,
103 + toggleItem: toggleItem
104 + }))));
105 +}
106 +//# sourceMappingURL=ToDoList.js.map
107 +//# sourceURL=ToDoList.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/ToDoList.js.map new
+1
@@ -0,0 +1 @@
1 +{"version":3,"sources":["ToDoList.js"],"names":["ListItem","item","removeItem","toggleItem","handleDelete","handleToggle","isComplete","text","List","props","newItemText","setNewItemText","items","setItems","id","uid","setUID","handleClick","handleKeyPress","event","key","handleChange","currentTarget","value","itemToRemove","filter","itemToToggle","index","findIndex","slice","concat","map"],"mappings":";;;;;;;;AASA;;;;;;AATA;;;;;;;;AAYO,SAASA,QAAT,CAAkB;AAACC,EAAAA,IAAD;AAAOC,EAAAA,UAAP;AAAmBC,EAAAA;AAAnB,CAAlB,EAAkD;AACvD,QAAMC,YAAY,GAAG,uBAAY,MAAM;AACrCF,IAAAA,UAAU,CAACD,IAAD,CAAV;AACD,GAFoB,EAElB,CAACA,IAAD,EAAOC,UAAP,CAFkB,CAArB;AAIA,QAAMG,YAAY,GAAG,uBAAY,MAAM;AACrCF,IAAAA,UAAU,CAACF,IAAD,CAAV;AACD,GAFoB,EAElB,CAACA,IAAD,EAAOE,UAAP,CAFkB,CAArB;AAIA,sBACE,6CACE;AAAQ,IAAA,OAAO,EAAEC;AAAjB,cADF,eAEE,gDACE;AACE,IAAA,OAAO,EAAEH,IAAI,CAACK,UADhB;AAEE,IAAA,QAAQ,EAAED,YAFZ;AAGE,IAAA,IAAI,EAAC;AAHP,IADF,EAKK,GALL,EAMGJ,IAAI,CAACM,IANR,CAFF,CADF;AAaD;;AAEM,SAASC,IAAT,CAAcC,KAAd,EAAqB;AAC1B,QAAM,CAACC,WAAD,EAAcC,cAAd,IAAgC,oBAAS,EAAT,CAAtC;AACA,QAAM,CAACC,KAAD,EAAQC,QAAR,IAAoB,oBAAS,CACjC;AAACC,IAAAA,EAAE,EAAE,CAAL;AAAQR,IAAAA,UAAU,EAAE,IAApB;AAA0BC,IAAAA,IAAI,EAAE;AAAhC,GADiC,EAEjC;AAACO,IAAAA,EAAE,EAAE,CAAL;AAAQR,IAAAA,UAAU,EAAE,IAApB;AAA0BC,IAAAA,IAAI,EAAE;AAAhC,GAFiC,EAGjC;AAACO,IAAAA,EAAE,EAAE,CAAL;AAAQR,IAAAA,UAAU,EAAE,KAApB;AAA2BC,IAAAA,IAAI,EAAE;AAAjC,GAHiC,CAAT,CAA1B;AAKA,QAAM,CAACQ,GAAD,EAAMC,MAAN,IAAgB,oBAAS,CAAT,CAAtB;AAEA,QAAMC,WAAW,GAAG,uBAAY,MAAM;AACpC,QAAIP,WAAW,KAAK,EAApB,EAAwB;AACtBG,MAAAA,QAAQ,CAAC,CACP,GAAGD,KADI,EAEP;AACEE,QAAAA,EAAE,EAAEC,GADN;AAEET,QAAAA,UAAU,EAAE,KAFd;AAGEC,QAAAA,IAAI,EAAEG;AAHR,OAFO,CAAD,CAAR;AAQAM,MAAAA,MAAM,CAACD,GAAG,GAAG,CAAP,CAAN;AACAJ,MAAAA,cAAc,CAAC,EAAD,CAAd;AACD;AACF,GAbmB,EAajB,CAACD,WAAD,EAAcE,KAAd,EAAqBG,GAArB,CAbiB,CAApB;AAeA,QAAMG,cAAc,GAAG,uBACrBC,KAAK,IAAI;AACP,QAAIA,KAAK,CAACC,GAAN,KAAc,OAAlB,EAA2B;AACzBH,MAAAA,WAAW;AACZ;AACF,GALoB,EAMrB,CAACA,WAAD,CANqB,CAAvB;AASA,QAAMI,YAAY,GAAG,uBACnBF,KAAK,IAAI;AACPR,IAAAA,cAAc,CAACQ,KAAK,CAACG,aAAN,CAAoBC,KAArB,CAAd;AACD,GAHkB,EAInB,CAACZ,cAAD,CAJmB,CAArB;AAOA,QAAMT,UAAU,GAAG,uBACjBsB,YAAY,IAAIX,QAAQ,CAACD,KAAK,CAACa,MAAN,CAAaxB,IAAI,IAAIA,IAAI,KAAKuB,YAA9B,CAAD,CADP,EAEjB,CAACZ,KAAD,CAFiB,CAAnB;AAKA,QAAMT,UAAU,GAAG,uBACjBuB,YAAY,IAAI;AACd;AACA;AACA,UAAMC,KAAK,GAAGf,KAAK,CAACgB,SAAN,CAAgB3B,IAAI,IAAIA,IAAI,CAACa,EAAL,KAAYY,YAAY,CAACZ,EAAjD,CAAd;AAEAD,IAAAA,QAAQ,CACND,KAAK,CACFiB,KADH,CACS,CADT,EACYF,KADZ,EAEGG,MAFH,CAEU,EACN,GAAGJ,YADG;AAENpB,MAAAA,UAAU,EAAE,CAACoB,YAAY,CAACpB;AAFpB,KAFV,EAMGwB,MANH,CAMUlB,KAAK,CAACiB,KAAN,CAAYF,KAAK,GAAG,CAApB,CANV,CADM,CAAR;AASD,GAfgB,EAgBjB,CAACf,KAAD,CAhBiB,CAAnB;AAmBA,sBACE,oBAAC,cAAD,qBACE,uCADF,eAEE;AACE,IAAA,IAAI,EAAC,MADP;AAEE,IAAA,WAAW,EAAC,kBAFd;AAGE,IAAA,KAAK,EAAEF,WAHT;AAIE,IAAA,QAAQ,EAAEW,YAJZ;AAKE,IAAA,UAAU,EAAEH;AALd,IAFF,eASE;AAAQ,IAAA,QAAQ,EAAER,WAAW,KAAK,EAAlC;AAAsC,IAAA,OAAO,EAAEO;AAA/C,kBACE;AAAM,IAAA,IAAI,EAAC,KAAX;AAAiB,kBAAW;AAA5B,WADF,CATF,eAcE,gCACGL,KAAK,CAACmB,GAAN,CAAU9B,IAAI,iBACb,oBAAC,QAAD;AACE,IAAA,GAAG,EAAEA,IAAI,CAACa,EADZ;AAEE,IAAA,IAAI,EAAEb,IAFR;AAGE,IAAA,UAAU,EAAEC,UAHd;AAIE,IAAA,UAAU,EAAEC;AAJd,IADD,CADH,CAdF,CADF;AA2BD","sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @flow\n */\n\nimport * as React from 'react';\nimport {Fragment, useCallback, useState} from 'react';\n\nexport function ListItem({item, removeItem, toggleItem}) {\n const handleDelete = useCallback(() => {\n removeItem(item);\n }, [item, removeItem]);\n\n const handleToggle = useCallback(() => {\n toggleItem(item);\n }, [item, toggleItem]);\n\n return (\n <li>\n <button onClick={handleDelete}>Delete</button>\n <label>\n <input\n checked={item.isComplete}\n onChange={handleToggle}\n type=\"checkbox\"\n />{' '}\n {item.text}\n </label>\n </li>\n );\n}\n\nexport function List(props) {\n const [newItemText, setNewItemText] = useState('');\n const [items, setItems] = useState([\n {id: 1, isComplete: true, text: 'First'},\n {id: 2, isComplete: true, text: 'Second'},\n {id: 3, isComplete: false, text: 'Third'},\n ]);\n const [uid, setUID] = useState(4);\n\n const handleClick = useCallback(() => {\n if (newItemText !== '') {\n setItems([\n ...items,\n {\n id: uid,\n isComplete: false,\n text: newItemText,\n },\n ]);\n setUID(uid + 1);\n setNewItemText('');\n }\n }, [newItemText, items, uid]);\n\n const handleKeyPress = useCallback(\n event => {\n if (event.key === 'Enter') {\n handleClick();\n }\n },\n [handleClick],\n );\n\n const handleChange = useCallback(\n event => {\n setNewItemText(event.currentTarget.value);\n },\n [setNewItemText],\n );\n\n const removeItem = useCallback(\n itemToRemove => setItems(items.filter(item => item !== itemToRemove)),\n [items],\n );\n\n const toggleItem = useCallback(\n itemToToggle => {\n // Dont use indexOf()\n // because editing props in DevTools creates a new Object.\n const index = items.findIndex(item => item.id === itemToToggle.id);\n\n setItems(\n items\n .slice(0, index)\n .concat({\n ...itemToToggle,\n isComplete: !itemToToggle.isComplete,\n })\n .concat(items.slice(index + 1)),\n );\n },\n [items],\n );\n\n return (\n <Fragment>\n <h1>List</h1>\n <input\n type=\"text\"\n placeholder=\"New list item...\"\n value={newItemText}\n onChange={handleChange}\n onKeyPress={handleKeyPress}\n />\n <button disabled={newItemText === ''} onClick={handleClick}>\n <span role=\"img\" aria-label=\"Add item\">\n Add\n </span>\n </button>\n <ul>\n {items.map(item => (\n <ListItem\n key={item.id}\n item={item}\n removeItem={removeItem}\n toggleItem={toggleItem}\n />\n ))}\n </ul>\n </Fragment>\n );\n}\n"]}
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/useTheme.js new
+28
@@ -0,0 +1,28 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.default = useTheme;
7 +exports.ThemeContext = void 0;
8 +
9 +var _react = require("react");
10 +
11 +/**
12 + * Copyright (c) Facebook, Inc. and its affiliates.
13 + *
14 + * This source code is licensed under the MIT license found in the
15 + * LICENSE file in the root directory of this source tree.
16 + *
17 + * @flow
18 + */
19 +const ThemeContext = /*#__PURE__*/(0, _react.createContext)('bright');
20 +exports.ThemeContext = ThemeContext;
21 +
22 +function useTheme() {
23 + const theme = (0, _react.useContext)(ThemeContext);
24 + (0, _react.useDebugValue)(theme);
25 + return theme;
26 +}
27 +//# sourceMappingURL=useTheme.js.map
28 +//# sourceURL=useTheme.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/useTheme.js.map new
+1
@@ -0,0 +1 @@
1 +{"version":3,"sources":["useTheme.js"],"names":["ThemeContext","useTheme","theme"],"mappings":";;;;;;;;AASA;;AATA;;;;;;;;AAWO,MAAMA,YAAY,gBAAG,0BAAc,QAAd,CAArB;;;AAEQ,SAASC,QAAT,GAAoB;AACjC,QAAMC,KAAK,GAAG,uBAAWF,YAAX,CAAd;AACA,4BAAcE,KAAd;AACA,SAAOA,KAAP;AACD","sourcesContent":["/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * @flow\n */\n\nimport {createContext, useContext, useDebugValue} from 'react';\n\nexport const ThemeContext = createContext('bright');\n\nexport default function useTheme() {\n const theme = useContext(ThemeContext);\n useDebugValue(theme);\n return theme;\n}\n"]}
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/ComponentWithCustomHook.js new
+42
@@ -0,0 +1,42 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.Component = Component;
7 +
8 +var _react = _interopRequireWildcard(require("react"));
9 +
10 +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
11 +
12 +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
13 +
14 +/**
15 + * Copyright (c) Facebook, Inc. and its affiliates.
16 + *
17 + * This source code is licensed under the MIT license found in the
18 + * LICENSE file in the root directory of this source tree.
19 + *
20 + * @flow
21 + */
22 +function Component() {
23 + const [count, setCount] = (0, _react.useState)(0);
24 + const isDarkMode = useIsDarkMode();
25 + (0, _react.useEffect)(() => {// ...
26 + }, []);
27 +
28 + const handleClick = () => setCount(count + 1);
29 +
30 + return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", null, "Dark mode? ", isDarkMode), /*#__PURE__*/_react.default.createElement("div", null, "Count: ", count), /*#__PURE__*/_react.default.createElement("button", {
31 + onClick: handleClick
32 + }, "Update count"));
33 +}
34 +
35 +function useIsDarkMode() {
36 + const [isDarkMode, setIsDarkMode] = (0, _react.useState)(0);
37 + (0, _react.useEffect)(function useEffectCreate() {// Here is where we may listen to a "theme" event...
38 + }, []);
39 + return isDarkMode;
40 +}
41 +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkNvbXBvbmVudFdpdGhDdXN0b21Ib29rLmpzIl0sIm5hbWVzIjpbIkNvbXBvbmVudCIsImNvdW50Iiwic2V0Q291bnQiLCJpc0RhcmtNb2RlIiwidXNlSXNEYXJrTW9kZSIsImhhbmRsZUNsaWNrIiwic2V0SXNEYXJrTW9kZSIsInVzZUVmZmVjdENyZWF0ZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQVNBOzs7Ozs7QUFUQTs7Ozs7Ozs7QUFXTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU0sQ0FBQ0MsS0FBRCxFQUFRQyxRQUFSLElBQW9CLHFCQUFTLENBQVQsQ0FBMUI7QUFDQSxRQUFNQyxVQUFVLEdBQUdDLGFBQWEsRUFBaEM7QUFFQSx3QkFBVSxNQUFNLENBQ2Q7QUFDRCxHQUZELEVBRUcsRUFGSDs7QUFJQSxRQUFNQyxXQUFXLEdBQUcsTUFBTUgsUUFBUSxDQUFDRCxLQUFLLEdBQUcsQ0FBVCxDQUFsQzs7QUFFQSxzQkFDRSx5RUFDRSx5REFBaUJFLFVBQWpCLENBREYsZUFFRSxxREFBYUYsS0FBYixDQUZGLGVBR0U7QUFBUSxJQUFBLE9BQU8sRUFBRUk7QUFBakIsb0JBSEYsQ0FERjtBQU9EOztBQUVELFNBQVNELGFBQVQsR0FBeUI7QUFDdkIsUUFBTSxDQUFDRCxVQUFELEVBQWFHLGFBQWIsSUFBOEIscUJBQVMsQ0FBVCxDQUFwQztBQUVBLHdCQUFVLFNBQVNDLGVBQVQsR0FBMkIsQ0FDbkM7QUFDRCxHQUZELEVBRUcsRUFGSDtBQUlBLFNBQU9KLFVBQVA7QUFDRCIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQ29weXJpZ2h0IChjKSBGYWNlYm9vaywgSW5jLiBhbmQgaXRzIGFmZmlsaWF0ZXMuXG4gKlxuICogVGhpcyBzb3VyY2UgY29kZSBpcyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIGxpY2Vuc2UgZm91bmQgaW4gdGhlXG4gKiBMSUNFTlNFIGZpbGUgaW4gdGhlIHJvb3QgZGlyZWN0b3J5IG9mIHRoaXMgc291cmNlIHRyZWUuXG4gKlxuICogQGZsb3dcbiAqL1xuXG5pbXBvcnQgUmVhY3QsIHt1c2VFZmZlY3QsIHVzZVN0YXRlfSBmcm9tICdyZWFjdCc7XG5cbmV4cG9ydCBmdW5jdGlvbiBDb21wb25lbnQoKSB7XG4gIGNvbnN0IFtjb3VudCwgc2V0Q291bnRdID0gdXNlU3RhdGUoMCk7XG4gIGNvbnN0IGlzRGFya01vZGUgPSB1c2VJc0RhcmtNb2RlKCk7XG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICAvLyAuLi5cbiAgfSwgW10pO1xuXG4gIGNvbnN0IGhhbmRsZUNsaWNrID0gKCkgPT4gc2V0Q291bnQoY291bnQgKyAxKTtcblxuICByZXR1cm4gKFxuICAgIDw+XG4gICAgICA8ZGl2PkRhcmsgbW9kZT8ge2lzRGFya01vZGV9PC9kaXY+XG4gICAgICA8ZGl2PkNvdW50OiB7Y291bnR9PC9kaXY+XG4gICAgICA8YnV0dG9uIG9uQ2xpY2s9e2hhbmRsZUNsaWNrfT5VcGRhdGUgY291bnQ8L2J1dHRvbj5cbiAgICA8Lz5cbiAgKTtcbn1cblxuZnVuY3Rpb24gdXNlSXNEYXJrTW9kZSgpIHtcbiAgY29uc3QgW2lzRGFya01vZGUsIHNldElzRGFya01vZGVdID0gdXNlU3RhdGUoMCk7XG5cbiAgdXNlRWZmZWN0KGZ1bmN0aW9uIHVzZUVmZmVjdENyZWF0ZSgpIHtcbiAgICAvLyBIZXJlIGlzIHdoZXJlIHdlIG1heSBsaXN0ZW4gdG8gYSBcInRoZW1lXCIgZXZlbnQuLi5cbiAgfSwgW10pO1xuXG4gIHJldHVybiBpc0RhcmtNb2RlO1xufSJdfQ==
42 +//# sourceURL=ComponentWithCustomHook.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/ComponentWithExternalCustomHooks.js new
+27
@@ -0,0 +1,27 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.Component = Component;
7 +
8 +var _react = _interopRequireDefault(require("react"));
9 +
10 +var _useTheme = _interopRequireDefault(require("./useTheme"));
11 +
12 +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13 +
14 +/**
15 + * Copyright (c) Facebook, Inc. and its affiliates.
16 + *
17 + * This source code is licensed under the MIT license found in the
18 + * LICENSE file in the root directory of this source tree.
19 + *
20 + * @flow
21 + */
22 +function Component() {
23 + const theme = (0, _useTheme.default)();
24 + return /*#__PURE__*/_react.default.createElement("div", null, "theme: ", theme);
25 +}
26 +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkNvbXBvbmVudFdpdGhFeHRlcm5hbEN1c3RvbUhvb2tzLmpzIl0sIm5hbWVzIjpbIkNvbXBvbmVudCIsInRoZW1lIl0sIm1hcHBpbmdzIjoiOzs7Ozs7O0FBU0E7O0FBQ0E7Ozs7QUFWQTs7Ozs7Ozs7QUFZTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU1DLEtBQUssR0FBRyx3QkFBZDtBQUVBLHNCQUFPLHFEQUFhQSxLQUFiLENBQVA7QUFDRCIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQ29weXJpZ2h0IChjKSBGYWNlYm9vaywgSW5jLiBhbmQgaXRzIGFmZmlsaWF0ZXMuXG4gKlxuICogVGhpcyBzb3VyY2UgY29kZSBpcyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIGxpY2Vuc2UgZm91bmQgaW4gdGhlXG4gKiBMSUNFTlNFIGZpbGUgaW4gdGhlIHJvb3QgZGlyZWN0b3J5IG9mIHRoaXMgc291cmNlIHRyZWUuXG4gKlxuICogQGZsb3dcbiAqL1xuXG5pbXBvcnQgUmVhY3QgZnJvbSAncmVhY3QnO1xuaW1wb3J0IHVzZVRoZW1lIGZyb20gJy4vdXNlVGhlbWUnO1xuXG5leHBvcnQgZnVuY3Rpb24gQ29tcG9uZW50KCkge1xuICBjb25zdCB0aGVtZSA9IHVzZVRoZW1lKCk7XG5cbiAgcmV0dXJuIDxkaXY+dGhlbWU6IHt0aGVtZX08L2Rpdj47XG59XG4iXX0=
27 +//# sourceURL=ComponentWithExternalCustomHooks.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/Example.js new
+29
@@ -0,0 +1,29 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.Component = Component;
7 +
8 +var _react = _interopRequireWildcard(require("react"));
9 +
10 +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
11 +
12 +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
13 +
14 +/**
15 + * Copyright (c) Facebook, Inc. and its affiliates.
16 + *
17 + * This source code is licensed under the MIT license found in the
18 + * LICENSE file in the root directory of this source tree.
19 + *
20 + * @flow
21 + */
22 +function Component() {
23 + const [count, setCount] = (0, _react.useState)(0);
24 + return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("p", null, "You clicked ", count, " times"), /*#__PURE__*/_react.default.createElement("button", {
25 + onClick: () => setCount(count + 1)
26 + }, "Click me"));
27 +}
28 +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkV4YW1wbGUuanMiXSwibmFtZXMiOlsiQ29tcG9uZW50IiwiY291bnQiLCJzZXRDb3VudCJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQVNBOzs7Ozs7QUFUQTs7Ozs7Ozs7QUFXTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU0sQ0FBQ0MsS0FBRCxFQUFRQyxRQUFSLElBQW9CLHFCQUFTLENBQVQsQ0FBMUI7QUFFQSxzQkFDRSx1REFDRSx3REFBZ0JELEtBQWhCLFdBREYsZUFFRTtBQUFRLElBQUEsT0FBTyxFQUFFLE1BQU1DLFFBQVEsQ0FBQ0QsS0FBSyxHQUFHLENBQVQ7QUFBL0IsZ0JBRkYsQ0FERjtBQU1EIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb3B5cmlnaHQgKGMpIEZhY2Vib29rLCBJbmMuIGFuZCBpdHMgYWZmaWxpYXRlcy5cbiAqXG4gKiBUaGlzIHNvdXJjZSBjb2RlIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZSBmb3VuZCBpbiB0aGVcbiAqIExJQ0VOU0UgZmlsZSBpbiB0aGUgcm9vdCBkaXJlY3Rvcnkgb2YgdGhpcyBzb3VyY2UgdHJlZS5cbiAqXG4gKiBAZmxvd1xuICovXG5cbmltcG9ydCBSZWFjdCwge3VzZVN0YXRlfSBmcm9tICdyZWFjdCc7XG5cbmV4cG9ydCBmdW5jdGlvbiBDb21wb25lbnQoKSB7XG4gIGNvbnN0IFtjb3VudCwgc2V0Q291bnRdID0gdXNlU3RhdGUoMCk7XG5cbiAgcmV0dXJuIChcbiAgICA8ZGl2PlxuICAgICAgPHA+WW91IGNsaWNrZWQge2NvdW50fSB0aW1lczwvcD5cbiAgICAgIDxidXR0b24gb25DbGljaz17KCkgPT4gc2V0Q291bnQoY291bnQgKyAxKX0+Q2xpY2sgbWU8L2J1dHRvbj5cbiAgICA8L2Rpdj5cbiAgKTtcbn1cbiJdfQ==
29 +//# sourceURL=Example.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/InlineRequire.js new
+22
@@ -0,0 +1,22 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.Component = Component;
7 +
8 +/**
9 + * Copyright (c) Facebook, Inc. and its affiliates.
10 + *
11 + * This source code is licensed under the MIT license found in the
12 + * LICENSE file in the root directory of this source tree.
13 + *
14 + * @flow
15 + */
16 +function Component() {
17 + const [count] = require('react').useState(0);
18 +
19 + return count;
20 +}
21 +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIklubGluZVJlcXVpcmUuanMiXSwibmFtZXMiOlsiQ29tcG9uZW50IiwiY291bnQiLCJyZXF1aXJlIiwidXNlU3RhdGUiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7QUFBQTs7Ozs7Ozs7QUFTTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU0sQ0FBQ0MsS0FBRCxJQUFVQyxPQUFPLENBQUMsT0FBRCxDQUFQLENBQWlCQyxRQUFqQixDQUEwQixDQUExQixDQUFoQjs7QUFFQSxTQUFPRixLQUFQO0FBQ0QiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvcHlyaWdodCAoYykgRmFjZWJvb2ssIEluYy4gYW5kIGl0cyBhZmZpbGlhdGVzLlxuICpcbiAqIFRoaXMgc291cmNlIGNvZGUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlIGZvdW5kIGluIHRoZVxuICogTElDRU5TRSBmaWxlIGluIHRoZSByb290IGRpcmVjdG9yeSBvZiB0aGlzIHNvdXJjZSB0cmVlLlxuICpcbiAqIEBmbG93XG4gKi9cblxuZXhwb3J0IGZ1bmN0aW9uIENvbXBvbmVudCgpIHtcbiAgY29uc3QgW2NvdW50XSA9IHJlcXVpcmUoJ3JlYWN0JykudXNlU3RhdGUoMCk7XG5cbiAgcmV0dXJuIGNvdW50O1xufVxuIl19
22 +//# sourceURL=InlineRequire.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/ToDoList.js new
+107
@@ -0,0 +1,107 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.ListItem = ListItem;
7 +exports.List = List;
8 +
9 +var React = _interopRequireWildcard(require("react"));
10 +
11 +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
12 +
13 +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
14 +
15 +/**
16 + * Copyright (c) Facebook, Inc. and its affiliates.
17 + *
18 + * This source code is licensed under the MIT license found in the
19 + * LICENSE file in the root directory of this source tree.
20 + *
21 + * @flow
22 + */
23 +function ListItem({
24 + item,
25 + removeItem,
26 + toggleItem
27 +}) {
28 + const handleDelete = (0, React.useCallback)(() => {
29 + removeItem(item);
30 + }, [item, removeItem]);
31 + const handleToggle = (0, React.useCallback)(() => {
32 + toggleItem(item);
33 + }, [item, toggleItem]);
34 + return /*#__PURE__*/React.createElement("li", null, /*#__PURE__*/React.createElement("button", {
35 + onClick: handleDelete
36 + }, "Delete"), /*#__PURE__*/React.createElement("label", null, /*#__PURE__*/React.createElement("input", {
37 + checked: item.isComplete,
38 + onChange: handleToggle,
39 + type: "checkbox"
40 + }), ' ', item.text));
41 +}
42 +
43 +function List(props) {
44 + const [newItemText, setNewItemText] = (0, React.useState)('');
45 + const [items, setItems] = (0, React.useState)([{
46 + id: 1,
47 + isComplete: true,
48 + text: 'First'
49 + }, {
50 + id: 2,
51 + isComplete: true,
52 + text: 'Second'
53 + }, {
54 + id: 3,
55 + isComplete: false,
56 + text: 'Third'
57 + }]);
58 + const [uid, setUID] = (0, React.useState)(4);
59 + const handleClick = (0, React.useCallback)(() => {
60 + if (newItemText !== '') {
61 + setItems([...items, {
62 + id: uid,
63 + isComplete: false,
64 + text: newItemText
65 + }]);
66 + setUID(uid + 1);
67 + setNewItemText('');
68 + }
69 + }, [newItemText, items, uid]);
70 + const handleKeyPress = (0, React.useCallback)(event => {
71 + if (event.key === 'Enter') {
72 + handleClick();
73 + }
74 + }, [handleClick]);
75 + const handleChange = (0, React.useCallback)(event => {
76 + setNewItemText(event.currentTarget.value);
77 + }, [setNewItemText]);
78 + const removeItem = (0, React.useCallback)(itemToRemove => setItems(items.filter(item => item !== itemToRemove)), [items]);
79 + const toggleItem = (0, React.useCallback)(itemToToggle => {
80 + // Dont use indexOf()
81 + // because editing props in DevTools creates a new Object.
82 + const index = items.findIndex(item => item.id === itemToToggle.id);
83 + setItems(items.slice(0, index).concat({ ...itemToToggle,
84 + isComplete: !itemToToggle.isComplete
85 + }).concat(items.slice(index + 1)));
86 + }, [items]);
87 + return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "List"), /*#__PURE__*/React.createElement("input", {
88 + type: "text",
89 + placeholder: "New list item...",
90 + value: newItemText,
91 + onChange: handleChange,
92 + onKeyPress: handleKeyPress
93 + }), /*#__PURE__*/React.createElement("button", {
94 + disabled: newItemText === '',
95 + onClick: handleClick
96 + }, /*#__PURE__*/React.createElement("span", {
97 + role: "img",
98 + "aria-label": "Add item"
99 + }, "Add")), /*#__PURE__*/React.createElement("ul", null, items.map(item => /*#__PURE__*/React.createElement(ListItem, {
100 + key: item.id,
101 + item: item,
102 + removeItem: removeItem,
103 + toggleItem: toggleItem
104 + }))));
105 +}
106 +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIlRvRG9MaXN0LmpzIl0sIm5hbWVzIjpbIkxpc3RJdGVtIiwiaXRlbSIsInJlbW92ZUl0ZW0iLCJ0b2dnbGVJdGVtIiwiaGFuZGxlRGVsZXRlIiwiaGFuZGxlVG9nZ2xlIiwiaXNDb21wbGV0ZSIsInRleHQiLCJMaXN0IiwicHJvcHMiLCJuZXdJdGVtVGV4dCIsInNldE5ld0l0ZW1UZXh0IiwiaXRlbXMiLCJzZXRJdGVtcyIsImlkIiwidWlkIiwic2V0VUlEIiwiaGFuZGxlQ2xpY2siLCJoYW5kbGVLZXlQcmVzcyIsImV2ZW50Iiwia2V5IiwiaGFuZGxlQ2hhbmdlIiwiY3VycmVudFRhcmdldCIsInZhbHVlIiwiaXRlbVRvUmVtb3ZlIiwiZmlsdGVyIiwiaXRlbVRvVG9nZ2xlIiwiaW5kZXgiLCJmaW5kSW5kZXgiLCJzbGljZSIsImNvbmNhdCIsIm1hcCJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7QUFTQTs7Ozs7O0FBVEE7Ozs7Ozs7O0FBWU8sU0FBU0EsUUFBVCxDQUFrQjtBQUFDQyxFQUFBQSxJQUFEO0FBQU9DLEVBQUFBLFVBQVA7QUFBbUJDLEVBQUFBO0FBQW5CLENBQWxCLEVBQWtEO0FBQ3ZELFFBQU1DLFlBQVksR0FBRyx1QkFBWSxNQUFNO0FBQ3JDRixJQUFBQSxVQUFVLENBQUNELElBQUQsQ0FBVjtBQUNELEdBRm9CLEVBRWxCLENBQUNBLElBQUQsRUFBT0MsVUFBUCxDQUZrQixDQUFyQjtBQUlBLFFBQU1HLFlBQVksR0FBRyx1QkFBWSxNQUFNO0FBQ3JDRixJQUFBQSxVQUFVLENBQUNGLElBQUQsQ0FBVjtBQUNELEdBRm9CLEVBRWxCLENBQUNBLElBQUQsRUFBT0UsVUFBUCxDQUZrQixDQUFyQjtBQUlBLHNCQUNFLDZDQUNFO0FBQVEsSUFBQSxPQUFPLEVBQUVDO0FBQWpCLGNBREYsZUFFRSxnREFDRTtBQUNFLElBQUEsT0FBTyxFQUFFSCxJQUFJLENBQUNLLFVBRGhCO0FBRUUsSUFBQSxRQUFRLEVBQUVELFlBRlo7QUFHRSxJQUFBLElBQUksRUFBQztBQUhQLElBREYsRUFLSyxHQUxMLEVBTUdKLElBQUksQ0FBQ00sSUFOUixDQUZGLENBREY7QUFhRDs7QUFFTSxTQUFTQyxJQUFULENBQWNDLEtBQWQsRUFBcUI7QUFDMUIsUUFBTSxDQUFDQyxXQUFELEVBQWNDLGNBQWQsSUFBZ0Msb0JBQVMsRUFBVCxDQUF0QztBQUNBLFFBQU0sQ0FBQ0MsS0FBRCxFQUFRQyxRQUFSLElBQW9CLG9CQUFTLENBQ2pDO0FBQUNDLElBQUFBLEVBQUUsRUFBRSxDQUFMO0FBQVFSLElBQUFBLFVBQVUsRUFBRSxJQUFwQjtBQUEwQkMsSUFBQUEsSUFBSSxFQUFFO0FBQWhDLEdBRGlDLEVBRWpDO0FBQUNPLElBQUFBLEVBQUUsRUFBRSxDQUFMO0FBQVFSLElBQUFBLFVBQVUsRUFBRSxJQUFwQjtBQUEwQkMsSUFBQUEsSUFBSSxFQUFFO0FBQWhDLEdBRmlDLEVBR2pDO0FBQUNPLElBQUFBLEVBQUUsRUFBRSxDQUFMO0FBQVFSLElBQUFBLFVBQVUsRUFBRSxLQUFwQjtBQUEyQkMsSUFBQUEsSUFBSSxFQUFFO0FBQWpDLEdBSGlDLENBQVQsQ0FBMUI7QUFLQSxRQUFNLENBQUNRLEdBQUQsRUFBTUMsTUFBTixJQUFnQixvQkFBUyxDQUFULENBQXRCO0FBRUEsUUFBTUMsV0FBVyxHQUFHLHVCQUFZLE1BQU07QUFDcEMsUUFBSVAsV0FBVyxLQUFLLEVBQXBCLEVBQXdCO0FBQ3RCRyxNQUFBQSxRQUFRLENBQUMsQ0FDUCxHQUFHRCxLQURJLEVBRVA7QUFDRUUsUUFBQUEsRUFBRSxFQUFFQyxHQUROO0FBRUVULFFBQUFBLFVBQVUsRUFBRSxLQUZkO0FBR0VDLFFBQUFBLElBQUksRUFBRUc7QUFIUixPQUZPLENBQUQsQ0FBUjtBQVFBTSxNQUFBQSxNQUFNLENBQUNELEdBQUcsR0FBRyxDQUFQLENBQU47QUFDQUosTUFBQUEsY0FBYyxDQUFDLEVBQUQsQ0FBZDtBQUNEO0FBQ0YsR0FibUIsRUFhakIsQ0FBQ0QsV0FBRCxFQUFjRSxLQUFkLEVBQXFCRyxHQUFyQixDQWJpQixDQUFwQjtBQWVBLFFBQU1HLGNBQWMsR0FBRyx1QkFDckJDLEtBQUssSUFBSTtBQUNQLFFBQUlBLEtBQUssQ0FBQ0MsR0FBTixLQUFjLE9BQWxCLEVBQTJCO0FBQ3pCSCxNQUFBQSxXQUFXO0FBQ1o7QUFDRixHQUxvQixFQU1yQixDQUFDQSxXQUFELENBTnFCLENBQXZCO0FBU0EsUUFBTUksWUFBWSxHQUFHLHVCQUNuQkYsS0FBSyxJQUFJO0FBQ1BSLElBQUFBLGNBQWMsQ0FBQ1EsS0FBSyxDQUFDRyxhQUFOLENBQW9CQyxLQUFyQixDQUFkO0FBQ0QsR0FIa0IsRUFJbkIsQ0FBQ1osY0FBRCxDQUptQixDQUFyQjtBQU9BLFFBQU1ULFVBQVUsR0FBRyx1QkFDakJzQixZQUFZLElBQUlYLFFBQVEsQ0FBQ0QsS0FBSyxDQUFDYSxNQUFOLENBQWF4QixJQUFJLElBQUlBLElBQUksS0FBS3VCLFlBQTlCLENBQUQsQ0FEUCxFQUVqQixDQUFDWixLQUFELENBRmlCLENBQW5CO0FBS0EsUUFBTVQsVUFBVSxHQUFHLHVCQUNqQnVCLFlBQVksSUFBSTtBQUNkO0FBQ0E7QUFDQSxVQUFNQyxLQUFLLEdBQUdmLEtBQUssQ0FBQ2dCLFNBQU4sQ0FBZ0IzQixJQUFJLElBQUlBLElBQUksQ0FBQ2EsRUFBTCxLQUFZWSxZQUFZLENBQUNaLEVBQWpELENBQWQ7QUFFQUQsSUFBQUEsUUFBUSxDQUNORCxLQUFLLENBQ0ZpQixLQURILENBQ1MsQ0FEVCxFQUNZRixLQURaLEVBRUdHLE1BRkgsQ0FFVSxFQUNOLEdBQUdKLFlBREc7QUFFTnBCLE1BQUFBLFVBQVUsRUFBRSxDQUFDb0IsWUFBWSxDQUFDcEI7QUFGcEIsS0FGVixFQU1Hd0IsTUFOSCxDQU1VbEIsS0FBSyxDQUFDaUIsS0FBTixDQUFZRixLQUFLLEdBQUcsQ0FBcEIsQ0FOVixDQURNLENBQVI7QUFTRCxHQWZnQixFQWdCakIsQ0FBQ2YsS0FBRCxDQWhCaUIsQ0FBbkI7QUFtQkEsc0JBQ0Usb0JBQUMsY0FBRCxxQkFDRSx1Q0FERixlQUVFO0FBQ0UsSUFBQSxJQUFJLEVBQUMsTUFEUDtBQUVFLElBQUEsV0FBVyxFQUFDLGtCQUZkO0FBR0UsSUFBQSxLQUFLLEVBQUVGLFdBSFQ7QUFJRSxJQUFBLFFBQVEsRUFBRVcsWUFKWjtBQUtFLElBQUEsVUFBVSxFQUFFSDtBQUxkLElBRkYsZUFTRTtBQUFRLElBQUEsUUFBUSxFQUFFUixXQUFXLEtBQUssRUFBbEM7QUFBc0MsSUFBQSxPQUFPLEVBQUVPO0FBQS9DLGtCQUNFO0FBQU0sSUFBQSxJQUFJLEVBQUMsS0FBWDtBQUFpQixrQkFBVztBQUE1QixXQURGLENBVEYsZUFjRSxnQ0FDR0wsS0FBSyxDQUFDbUIsR0FBTixDQUFVOUIsSUFBSSxpQkFDYixvQkFBQyxRQUFEO0FBQ0UsSUFBQSxHQUFHLEVBQUVBLElBQUksQ0FBQ2EsRUFEWjtBQUVFLElBQUEsSUFBSSxFQUFFYixJQUZSO0FBR0UsSUFBQSxVQUFVLEVBQUVDLFVBSGQ7QUFJRSxJQUFBLFVBQVUsRUFBRUM7QUFKZCxJQURELENBREgsQ0FkRixDQURGO0FBMkJEIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb3B5cmlnaHQgKGMpIEZhY2Vib29rLCBJbmMuIGFuZCBpdHMgYWZmaWxpYXRlcy5cbiAqXG4gKiBUaGlzIHNvdXJjZSBjb2RlIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZSBmb3VuZCBpbiB0aGVcbiAqIExJQ0VOU0UgZmlsZSBpbiB0aGUgcm9vdCBkaXJlY3Rvcnkgb2YgdGhpcyBzb3VyY2UgdHJlZS5cbiAqXG4gKiBAZmxvd1xuICovXG5cbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0JztcbmltcG9ydCB7RnJhZ21lbnQsIHVzZUNhbGxiYWNrLCB1c2VTdGF0ZX0gZnJvbSAncmVhY3QnO1xuXG5leHBvcnQgZnVuY3Rpb24gTGlzdEl0ZW0oe2l0ZW0sIHJlbW92ZUl0ZW0sIHRvZ2dsZUl0ZW19KSB7XG4gIGNvbnN0IGhhbmRsZURlbGV0ZSA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICByZW1vdmVJdGVtKGl0ZW0pO1xuICB9LCBbaXRlbSwgcmVtb3ZlSXRlbV0pO1xuXG4gIGNvbnN0IGhhbmRsZVRvZ2dsZSA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICB0b2dnbGVJdGVtKGl0ZW0pO1xuICB9LCBbaXRlbSwgdG9nZ2xlSXRlbV0pO1xuXG4gIHJldHVybiAoXG4gICAgPGxpPlxuICAgICAgPGJ1dHRvbiBvbkNsaWNrPXtoYW5kbGVEZWxldGV9PkRlbGV0ZTwvYnV0dG9uPlxuICAgICAgPGxhYmVsPlxuICAgICAgICA8aW5wdXRcbiAgICAgICAgICBjaGVja2VkPXtpdGVtLmlzQ29tcGxldGV9XG4gICAgICAgICAgb25DaGFuZ2U9e2hhbmRsZVRvZ2dsZX1cbiAgICAgICAgICB0eXBlPVwiY2hlY2tib3hcIlxuICAgICAgICAvPnsnICd9XG4gICAgICAgIHtpdGVtLnRleHR9XG4gICAgICA8L2xhYmVsPlxuICAgIDwvbGk+XG4gICk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBMaXN0KHByb3BzKSB7XG4gIGNvbnN0IFtuZXdJdGVtVGV4dCwgc2V0TmV3SXRlbVRleHRdID0gdXNlU3RhdGUoJycpO1xuICBjb25zdCBbaXRlbXMsIHNldEl0ZW1zXSA9IHVzZVN0YXRlKFtcbiAgICB7aWQ6IDEsIGlzQ29tcGxldGU6IHRydWUsIHRleHQ6ICdGaXJzdCd9LFxuICAgIHtpZDogMiwgaXNDb21wbGV0ZTogdHJ1ZSwgdGV4dDogJ1NlY29uZCd9LFxuICAgIHtpZDogMywgaXNDb21wbGV0ZTogZmFsc2UsIHRleHQ6ICdUaGlyZCd9LFxuICBdKTtcbiAgY29uc3QgW3VpZCwgc2V0VUlEXSA9IHVzZVN0YXRlKDQpO1xuXG4gIGNvbnN0IGhhbmRsZUNsaWNrID0gdXNlQ2FsbGJhY2soKCkgPT4ge1xuICAgIGlmIChuZXdJdGVtVGV4dCAhPT0gJycpIHtcbiAgICAgIHNldEl0ZW1zKFtcbiAgICAgICAgLi4uaXRlbXMsXG4gICAgICAgIHtcbiAgICAgICAgICBpZDogdWlkLFxuICAgICAgICAgIGlzQ29tcGxldGU6IGZhbHNlLFxuICAgICAgICAgIHRleHQ6IG5ld0l0ZW1UZXh0LFxuICAgICAgICB9LFxuICAgICAgXSk7XG4gICAgICBzZXRVSUQodWlkICsgMSk7XG4gICAgICBzZXROZXdJdGVtVGV4dCgnJyk7XG4gICAgfVxuICB9LCBbbmV3SXRlbVRleHQsIGl0ZW1zLCB1aWRdKTtcblxuICBjb25zdCBoYW5kbGVLZXlQcmVzcyA9IHVzZUNhbGxiYWNrKFxuICAgIGV2ZW50ID0+IHtcbiAgICAgIGlmIChldmVudC5rZXkgPT09ICdFbnRlcicpIHtcbiAgICAgICAgaGFuZGxlQ2xpY2soKTtcbiAgICAgIH1cbiAgICB9LFxuICAgIFtoYW5kbGVDbGlja10sXG4gICk7XG5cbiAgY29uc3QgaGFuZGxlQ2hhbmdlID0gdXNlQ2FsbGJhY2soXG4gICAgZXZlbnQgPT4ge1xuICAgICAgc2V0TmV3SXRlbVRleHQoZXZlbnQuY3VycmVudFRhcmdldC52YWx1ZSk7XG4gICAgfSxcbiAgICBbc2V0TmV3SXRlbVRleHRdLFxuICApO1xuXG4gIGNvbnN0IHJlbW92ZUl0ZW0gPSB1c2VDYWxsYmFjayhcbiAgICBpdGVtVG9SZW1vdmUgPT4gc2V0SXRlbXMoaXRlbXMuZmlsdGVyKGl0ZW0gPT4gaXRlbSAhPT0gaXRlbVRvUmVtb3ZlKSksXG4gICAgW2l0ZW1zXSxcbiAgKTtcblxuICBjb25zdCB0b2dnbGVJdGVtID0gdXNlQ2FsbGJhY2soXG4gICAgaXRlbVRvVG9nZ2xlID0+IHtcbiAgICAgIC8vIERvbnQgdXNlIGluZGV4T2YoKVxuICAgICAgLy8gYmVjYXVzZSBlZGl0aW5nIHByb3BzIGluIERldlRvb2xzIGNyZWF0ZXMgYSBuZXcgT2JqZWN0LlxuICAgICAgY29uc3QgaW5kZXggPSBpdGVtcy5maW5kSW5kZXgoaXRlbSA9PiBpdGVtLmlkID09PSBpdGVtVG9Ub2dnbGUuaWQpO1xuXG4gICAgICBzZXRJdGVtcyhcbiAgICAgICAgaXRlbXNcbiAgICAgICAgICAuc2xpY2UoMCwgaW5kZXgpXG4gICAgICAgICAgLmNvbmNhdCh7XG4gICAgICAgICAgICAuLi5pdGVtVG9Ub2dnbGUsXG4gICAgICAgICAgICBpc0NvbXBsZXRlOiAhaXRlbVRvVG9nZ2xlLmlzQ29tcGxldGUsXG4gICAgICAgICAgfSlcbiAgICAgICAgICAuY29uY2F0KGl0ZW1zLnNsaWNlKGluZGV4ICsgMSkpLFxuICAgICAgKTtcbiAgICB9LFxuICAgIFtpdGVtc10sXG4gICk7XG5cbiAgcmV0dXJuIChcbiAgICA8RnJhZ21lbnQ+XG4gICAgICA8aDE+TGlzdDwvaDE+XG4gICAgICA8aW5wdXRcbiAgICAgICAgdHlwZT1cInRleHRcIlxuICAgICAgICBwbGFjZWhvbGRlcj1cIk5ldyBsaXN0IGl0ZW0uLi5cIlxuICAgICAgICB2YWx1ZT17bmV3SXRlbVRleHR9XG4gICAgICAgIG9uQ2hhbmdlPXtoYW5kbGVDaGFuZ2V9XG4gICAgICAgIG9uS2V5UHJlc3M9e2hhbmRsZUtleVByZXNzfVxuICAgICAgLz5cbiAgICAgIDxidXR0b24gZGlzYWJsZWQ9e25ld0l0ZW1UZXh0ID09PSAnJ30gb25DbGljaz17aGFuZGxlQ2xpY2t9PlxuICAgICAgICA8c3BhbiByb2xlPVwiaW1nXCIgYXJpYS1sYWJlbD1cIkFkZCBpdGVtXCI+XG4gICAgICAgICAgQWRkXG4gICAgICAgIDwvc3Bhbj5cbiAgICAgIDwvYnV0dG9uPlxuICAgICAgPHVsPlxuICAgICAgICB7aXRlbXMubWFwKGl0ZW0gPT4gKFxuICAgICAgICAgIDxMaXN0SXRlbVxuICAgICAgICAgICAga2V5PXtpdGVtLmlkfVxuICAgICAgICAgICAgaXRlbT17aXRlbX1cbiAgICAgICAgICAgIHJlbW92ZUl0ZW09e3JlbW92ZUl0ZW19XG4gICAgICAgICAgICB0b2dnbGVJdGVtPXt0b2dnbGVJdGVtfVxuICAgICAgICAgIC8+XG4gICAgICAgICkpfVxuICAgICAgPC91bD5cbiAgICA8L0ZyYWdtZW50PlxuICApO1xufVxuIl19
107 +//# sourceURL=ToDoList.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/useTheme.js new
+28
@@ -0,0 +1,28 @@
1 +"use strict";
2 +
3 +Object.defineProperty(exports, "__esModule", {
4 + value: true
5 +});
6 +exports.default = useTheme;
7 +exports.ThemeContext = void 0;
8 +
9 +var _react = require("react");
10 +
11 +/**
12 + * Copyright (c) Facebook, Inc. and its affiliates.
13 + *
14 + * This source code is licensed under the MIT license found in the
15 + * LICENSE file in the root directory of this source tree.
16 + *
17 + * @flow
18 + */
19 +const ThemeContext = /*#__PURE__*/(0, _react.createContext)('bright');
20 +exports.ThemeContext = ThemeContext;
21 +
22 +function useTheme() {
23 + const theme = (0, _react.useContext)(ThemeContext);
24 + (0, _react.useDebugValue)(theme);
25 + return theme;
26 +}
27 +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInVzZVRoZW1lLmpzIl0sIm5hbWVzIjpbIlRoZW1lQ29udGV4dCIsInVzZVRoZW1lIiwidGhlbWUiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O0FBU0E7O0FBVEE7Ozs7Ozs7O0FBV08sTUFBTUEsWUFBWSxnQkFBRywwQkFBYyxRQUFkLENBQXJCOzs7QUFFUSxTQUFTQyxRQUFULEdBQW9CO0FBQ2pDLFFBQU1DLEtBQUssR0FBRyx1QkFBV0YsWUFBWCxDQUFkO0FBQ0EsNEJBQWNFLEtBQWQ7QUFDQSxTQUFPQSxLQUFQO0FBQ0QiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvcHlyaWdodCAoYykgRmFjZWJvb2ssIEluYy4gYW5kIGl0cyBhZmZpbGlhdGVzLlxuICpcbiAqIFRoaXMgc291cmNlIGNvZGUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlIGZvdW5kIGluIHRoZVxuICogTElDRU5TRSBmaWxlIGluIHRoZSByb290IGRpcmVjdG9yeSBvZiB0aGlzIHNvdXJjZSB0cmVlLlxuICpcbiAqIEBmbG93XG4gKi9cblxuaW1wb3J0IHtjcmVhdGVDb250ZXh0LCB1c2VDb250ZXh0LCB1c2VEZWJ1Z1ZhbHVlfSBmcm9tICdyZWFjdCc7XG5cbmV4cG9ydCBjb25zdCBUaGVtZUNvbnRleHQgPSBjcmVhdGVDb250ZXh0KCdicmlnaHQnKTtcblxuZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gdXNlVGhlbWUoKSB7XG4gIGNvbnN0IHRoZW1lID0gdXNlQ29udGV4dChUaGVtZUNvbnRleHQpO1xuICB1c2VEZWJ1Z1ZhbHVlKHRoZW1lKTtcbiAgcmV0dXJuIHRoZW1lO1xufVxuIl19
28 +//# sourceURL=useTheme.js
\ No newline at end of file
packages/react-devtools-extensions/src/__tests__/__source__/useTheme.js new
+18
@@ -0,0 +1,18 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import {createContext, useContext, useDebugValue} from 'react';
11 +
12 +export const ThemeContext = createContext('bright');
13 +
14 +export default function useTheme() {
15 + const theme = useContext(ThemeContext);
16 + useDebugValue(theme);
17 + return theme;
18 +}
packages/react-devtools-extensions/src/__tests__/parseHookNames-test.js new
+287
@@ -0,0 +1,287 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +describe('parseHookNames', () => {
11 + let fetchMock;
12 + let inspectHooks;
13 + let parseHookNames;
14 +
15 + beforeEach(() => {
16 + jest.resetModules();
17 +
18 + jest.mock('source-map-support', () => {
19 + console.trace('source-map-support');
20 + });
21 +
22 + const {
23 + overrideFeatureFlags,
24 + } = require('react-devtools-shared/src/__tests__/utils');
25 + overrideFeatureFlags({enableHookNameParsing: true});
26 +
27 + fetchMock = require('jest-fetch-mock');
28 + fetchMock.enableMocks();
29 +
30 + inspectHooks = require('react-debug-tools/src/ReactDebugHooks')
31 + .inspectHooks;
32 + parseHookNames = require('../parseHookNames').default;
33 +
34 + // Jest (jest-runner?) configures Errors to automatically account for source maps.
35 + // This changes behavior between our tests and the browser.
36 + // To "fix" this, clear the prepareStackTrace() method on the Error object.
37 + delete Error.prepareStackTrace;
38 +
39 + fetchMock.mockIf(/.+$/, request => {
40 + const {resolve} = require('path');
41 + const url = request.url;
42 + if (url.endsWith('js.map')) {
43 + // Source maps are relative URLs (e.g. "path/to/Exmaple.js" specifies "Exmaple.js.map").
44 + const sourceMapURL = resolve(
45 + __dirname,
46 + '__source__',
47 + '__compiled__',
48 + 'external',
49 + url,
50 + );
51 + return Promise.resolve(requireText(sourceMapURL, 'utf8'));
52 + } else {
53 + return Promise.resolve(requireText(url, 'utf8'));
54 + }
55 + });
56 +
57 + // Mock out portion of browser API used by parseHookNames to initialize "source-map".
58 + global.chrome = {
59 + extension: {
60 + getURL: jest.fn((...args) => {
61 + const {join} = require('path');
62 + return join(
63 + __dirname,
64 + '..',
65 + '..',
66 + 'node_modules',
67 + 'source-map',
68 + 'lib',
69 + 'mappings.wasm',
70 + );
71 + }),
72 + },
73 + };
74 + });
75 +
76 + afterEach(() => {
77 + fetch.resetMocks();
78 + });
79 +
80 + function expectHookNamesToEqual(map, expectedNamesArray) {
81 + // Slightly hacky since it relies on the iterable order of values()
82 + expect(Array.from(map.values())).toEqual(expectedNamesArray);
83 + }
84 +
85 + function requireText(path, encoding) {
86 + const {readFileSync} = require('fs');
87 + return readFileSync(path, encoding);
88 + }
89 +
90 + async function getHookNamesForComponent(Component, props = {}) {
91 + const hooksTree = inspectHooks(Component, props, undefined, true);
92 + const hookNames = await parseHookNames(hooksTree);
93 + return hookNames;
94 + }
95 +
96 + it('should parse names for useState()', async () => {
97 + const React = require('react');
98 + const {useState} = React;
99 + function Component(props) {
100 + const [foo] = useState(true);
101 + const bar = useState(true);
102 + const [baz] = React.useState(true);
103 + return `${foo}-${bar}-${baz}`;
104 + }
105 +
106 + const hookNames = await getHookNamesForComponent(Component);
107 + expectHookNamesToEqual(hookNames, ['foo', 'bar', 'baz']);
108 + });
109 +
110 + it('should parse names for useReducer()', async () => {
111 + const React = require('react');
112 + const {useReducer} = React;
113 + function Component(props) {
114 + const [foo] = useReducer(true);
115 + const [bar] = useReducer(true);
116 + const [baz] = React.useReducer(true);
117 + return `${foo}-${bar}-${baz}`;
118 + }
119 +
120 + const hookNames = await getHookNamesForComponent(Component);
121 + expectHookNamesToEqual(hookNames, ['foo', 'bar', 'baz']);
122 + });
123 +
124 + it('should return null for hooks without names like useEffect', async () => {
125 + const React = require('react');
126 + const {useEffect} = React;
127 + function Component(props) {
128 + useEffect(() => {});
129 + React.useLayoutEffect(() => () => {});
130 + return null;
131 + }
132 +
133 + const hookNames = await getHookNamesForComponent(Component);
134 + expectHookNamesToEqual(hookNames, []); // No hooks with names
135 + });
136 +
137 + it('should parse names for custom hooks', async () => {
138 + const {useDebugValue, useState} = require('react');
139 + function useCustomHookOne() {
140 + // DebugValue hook should not appear in log.
141 + useDebugValue('example');
142 + return true;
143 + }
144 + function useCustomHookTwo() {
145 + const [baz, setBaz] = useState(true);
146 + return [baz, setBaz];
147 + }
148 + function Component(props) {
149 + const foo = useCustomHookOne();
150 + // This cae is ignored;
151 + // the meaning of a tuple assignment for a custom hook is unclear.
152 + const [bar] = useCustomHookTwo();
153 + return `${foo}-${bar}`;
154 + }
155 +
156 + const hookNames = await getHookNamesForComponent(Component);
157 + expectHookNamesToEqual(hookNames, [
158 + 'foo',
159 + null, // Custom hooks can have names, but not when using destructuring.
160 + 'baz',
161 + ]);
162 + });
163 +
164 + it('should return null for custom hooks without explicit names', async () => {
165 + const {useDebugValue} = require('react');
166 + function useCustomHookOne() {
167 + // DebugValue hook should not appear in log.
168 + useDebugValue('example');
169 + }
170 + function useCustomHookTwo() {
171 + // DebugValue hook should not appear in log.
172 + useDebugValue('example');
173 + return [true];
174 + }
175 + function Component(props) {
176 + useCustomHookOne();
177 + const [bar] = useCustomHookTwo();
178 + return bar;
179 + }
180 +
181 + const hookNames = await getHookNamesForComponent(Component);
182 + expectHookNamesToEqual(hookNames, [
183 + null, // Custom hooks can have names, but this one does not even return a value.
184 + null, // Custom hooks can have names, but not when using destructuring.
185 + ]);
186 + });
187 +
188 + describe('inline and external source maps', () => {
189 + it('should work for simple components', async () => {
190 + async function test(path) {
191 + const Component = require(path).Component;
192 + const hookNames = await getHookNamesForComponent(Component);
193 + expectHookNamesToEqual(hookNames, [
194 + 'count', // useState
195 + ]);
196 + }
197 +
198 + await test('./__source__/Example'); // original source (uncompiled)
199 + await test('./__source__/__compiled__/inline/Example'); // inline source map
200 + await test('./__source__/__compiled__/external/Example'); // external source map
201 + });
202 +
203 + it('should work with more complex files and components', async () => {
204 + async function test(path) {
205 + const components = require(path);
206 +
207 + let hookNames = await getHookNamesForComponent(components.List);
208 + expectHookNamesToEqual(hookNames, [
209 + 'newItemText', // useState
210 + 'items', // useState
211 + 'uid', // useState
212 + 'handleClick', // useCallback
213 + 'handleKeyPress', // useCallback
214 + 'handleChange', // useCallback
215 + 'removeItem', // useCallback
216 + 'toggleItem', // useCallback
217 + ]);
218 +
219 + hookNames = await getHookNamesForComponent(components.ListItem, {
220 + item: {},
221 + });
222 + expectHookNamesToEqual(hookNames, [
223 + 'handleDelete', // useCallback
224 + 'handleToggle', // useCallback
225 + ]);
226 + }
227 +
228 + await test('./__source__/ToDoList'); // original source (uncompiled)
229 + await test('./__source__/__compiled__/inline/ToDoList'); // inline source map
230 + await test('./__source__/__compiled__/external/ToDoList'); // external source map
231 + });
232 +
233 + it('should work for custom hook', async () => {
234 + async function test(path) {
235 + const Component = require(path).Component;
236 + const hookNames = await getHookNamesForComponent(Component);
237 + expectHookNamesToEqual(hookNames, [
238 + 'count', // useState()
239 + 'isDarkMode', // useIsDarkMode()
240 + 'isDarkMode', // useIsDarkMode -> useState()
241 + ]);
242 + }
243 +
244 + await test('./__source__/ComponentWithCustomHook'); // original source (uncompiled)
245 + await test('./__source__/__compiled__/inline/ComponentWithCustomHook'); // inline source map
246 + await test('./__source__/__compiled__/external/ComponentWithCustomHook'); // external source map
247 + });
248 +
249 + it('should work for external hooks', async () => {
250 + async function test(path) {
251 + const Component = require(path).Component;
252 + const hookNames = await getHookNamesForComponent(Component);
253 + expectHookNamesToEqual(hookNames, [
254 + 'theme', // useTheme()
255 + 'theme', // useContext()
256 + ]);
257 + }
258 +
259 + // We can't test the uncompiled source here, because it either needs to get transformed,
260 + // which would break the source mapping, or the import statements will fail.
261 +
262 + await test(
263 + './__source__/__compiled__/inline/ComponentWithExternalCustomHooks',
264 + ); // inline source map
265 + await test(
266 + './__source__/__compiled__/external/ComponentWithExternalCustomHooks',
267 + ); // external source map
268 + });
269 +
270 + // TODO Inline require (e.g. require("react").useState()) isn't supported yet.
271 + // Maybe this isn't an important use case to support,
272 + // since inline requires are most likely to exist in compiled source (if at all).
273 + xit('should work for inline requires', async () => {
274 + async function test(path) {
275 + const Component = require(path).Component;
276 + const hookNames = await getHookNamesForComponent(Component);
277 + expectHookNamesToEqual(hookNames, [
278 + 'count', // useState()
279 + ]);
280 + }
281 +
282 + await test('./__source__/InlineRequire'); // original source (uncompiled)
283 + await test('./__source__/__compiled__/inline/InlineRequire'); // inline source map
284 + await test('./__source__/__compiled__/external/InlineRequire'); // external source map
285 + });
286 + });
287 +});
packages/react-devtools-extensions/src/__tests__/updateMockSourceMaps.js new
+76
@@ -0,0 +1,76 @@
1 +const {transformSync} = require('@babel/core');
2 +const {btoa} = require('base64');
3 +const {
4 + lstatSync,
5 + mkdirSync,
6 + readdirSync,
7 + readFileSync,
8 + writeFileSync,
9 +} = require('fs');
10 +const {emptyDirSync} = require('fs-extra');
11 +const {resolve} = require('path');
12 +
13 +const sourceDir = resolve(__dirname, '__source__');
14 +const buildRoot = resolve(sourceDir, '__compiled__');
15 +const externalDir = resolve(buildRoot, 'external');
16 +const inlineDir = resolve(buildRoot, 'inline');
17 +
18 +// Remove previous builds
19 +emptyDirSync(buildRoot);
20 +mkdirSync(externalDir);
21 +mkdirSync(inlineDir);
22 +
23 +function compile(fileName) {
24 + const code = readFileSync(resolve(sourceDir, fileName), 'utf8');
25 +
26 + const transformed = transformSync(code, {
27 + plugins: ['@babel/plugin-transform-modules-commonjs'],
28 + presets: [
29 + // 'minify',
30 + [
31 + '@babel/react',
32 + // {
33 + // runtime: 'automatic',
34 + // development: false,
35 + // },
36 + ],
37 + ],
38 + sourceMap: true,
39 + });
40 +
41 + const sourceMap = transformed.map;
42 + sourceMap.sources = [fileName];
43 +
44 + // Generate compiled output with external source maps
45 + writeFileSync(
46 + resolve(externalDir, fileName),
47 + transformed.code +
48 + `\n//# sourceMappingURL=${fileName}.map` +
49 + `\n//# sourceURL=${fileName}`,
50 + 'utf8',
51 + );
52 + writeFileSync(
53 + resolve(externalDir, `${fileName}.map`),
54 + JSON.stringify(sourceMap),
55 + 'utf8',
56 + );
57 +
58 + // Generate compiled output with external inline base64 source maps
59 + writeFileSync(
60 + resolve(inlineDir, fileName),
61 + transformed.code +
62 + '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
63 + btoa(JSON.stringify(sourceMap)) +
64 + `\n//# sourceURL=${fileName}`,
65 + 'utf8',
66 + );
67 +}
68 +
69 +// Compile all files in the current directory
70 +const entries = readdirSync(sourceDir);
71 +entries.forEach(entry => {
72 + const stat = lstatSync(resolve(sourceDir, entry));
73 + if (!stat.isDirectory() && entry.endsWith('.js')) {
74 + compile(entry);
75 + }
76 +});
packages/react-devtools-extensions/src/astUtils.js new
+366
@@ -0,0 +1,366 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import traverse, {NodePath, Node} from '@babel/traverse';
11 +import {File} from '@babel/types';
12 +
13 +import type {HooksNode} from 'react-debug-tools/src/ReactDebugHooks';
14 +
15 +export type SourceConsumer = any;
16 +
17 +export type SourceFileASTWithHookDetails = {
18 + sourceFileAST: File,
19 + line: number,
20 + source: string,
21 +};
22 +
23 +export type SourceMap = {|
24 + mappings: string,
25 + names: Array<string>,
26 + sources: Array<string>,
27 + sourcesContent: Array<string>,
28 + version: number,
29 +|};
30 +
31 +const AST_NODE_TYPES = Object.freeze({
32 + CALL_EXPRESSION: 'CallExpression',
33 + MEMBER_EXPRESSION: 'MemberExpression',
34 + ARRAY_PATTERN: 'ArrayPattern',
35 + IDENTIFIER: 'Identifier',
36 + NUMERIC_LITERAL: 'NumericLiteral',
37 +});
38 +
39 +// Check if line number obtained from source map and the line number in hook node match
40 +function checkNodeLocation(path: NodePath, line: number): boolean {
41 + const {start, end} = path.node.loc;
42 + return line >= start.line && line <= end.line;
43 +}
44 +
45 +// Checks whether hookNode is a member of targetHookNode
46 +function filterMemberNodesOfTargetHook(
47 + targetHookNode: NodePath,
48 + hookNode: NodePath,
49 +): boolean {
50 + const targetHookName = targetHookNode.node.id.name;
51 + return (
52 + targetHookName ===
53 + (hookNode.node.init.object && hookNode.node.init.object.name) ||
54 + targetHookName === hookNode.node.init.name
55 + );
56 +}
57 +
58 +// Checks whether hook is the first member node of a state variable declaration node
59 +function filterMemberWithHookVariableName(hook: NodePath): boolean {
60 + return (
61 + hook.node.init.property.type === AST_NODE_TYPES.NUMERIC_LITERAL &&
62 + hook.node.init.property.value === 0
63 + );
64 +}
65 +
66 +// Returns all AST Nodes associated with 'potentialReactHookASTNode'
67 +function getFilteredHookASTNodes(
68 + potentialReactHookASTNode: NodePath,
69 + potentialHooksFound: Array<NodePath>,
70 + source: string,
71 +): Array<NodePath> {
72 + let nodesAssociatedWithReactHookASTNode: NodePath[] = [];
73 + if (nodeContainsHookVariableName(potentialReactHookASTNode)) {
74 + // made custom hooks to enter this, always
75 + // Case 1.
76 + // Directly usable Node -> const ref = useRef(null);
77 + // -> const [tick, setTick] = useState(1);
78 + // Case 2.
79 + // Custom Hooks -> const someVariable = useSomeCustomHook();
80 + // -> const [someVariable, someFunction] = useAnotherCustomHook();
81 + nodesAssociatedWithReactHookASTNode.unshift(potentialReactHookASTNode);
82 + } else {
83 + // Case 3.
84 + // Indirectly usable Node -> const tickState = useState(1);
85 + // [tick, setTick] = tickState;
86 + // -> const tickState = useState(1);
87 + // const tick = tickState[0];
88 + // const setTick = tickState[1];
89 + nodesAssociatedWithReactHookASTNode = potentialHooksFound.filter(hookNode =>
90 + filterMemberNodesOfTargetHook(potentialReactHookASTNode, hookNode),
91 + );
92 + }
93 + return nodesAssociatedWithReactHookASTNode;
94 +}
95 +
96 +// Returns Hook name
97 +export function getHookName(
98 + hook: HooksNode,
99 + originalSourceAST: mixed,
100 + originalSourceCode: string,
101 + originalSourceLineNumber: number,
102 +): string | null {
103 + const hooksFromAST = getPotentialHookDeclarationsFromAST(originalSourceAST);
104 +
105 + const potentialReactHookASTNode = hooksFromAST.find(node => {
106 + const nodeLocationCheck = checkNodeLocation(
107 + node,
108 + ((originalSourceLineNumber: any): number),
109 + );
110 + const hookDeclaractionCheck = isConfirmedHookDeclaration(node);
111 + return nodeLocationCheck && hookDeclaractionCheck;
112 + });
113 +
114 + if (!potentialReactHookASTNode) {
115 + return null;
116 + }
117 +
118 + // nodesAssociatedWithReactHookASTNode could directly be used to obtain the hook variable name
119 + // depending on the type of potentialReactHookASTNode
120 + try {
121 + const nodesAssociatedWithReactHookASTNode = getFilteredHookASTNodes(
122 + potentialReactHookASTNode,
123 + hooksFromAST,
124 + originalSourceCode,
125 + );
126 +
127 + return getHookNameFromNode(
128 + hook,
129 + nodesAssociatedWithReactHookASTNode,
130 + potentialReactHookASTNode,
131 + );
132 + } catch (error) {
133 + console.error(error);
134 + return null;
135 + }
136 +}
137 +
138 +function getHookNameFromNode(
139 + originalHook: HooksNode,
140 + nodesAssociatedWithReactHookASTNode: NodePath[],
141 + potentialReactHookASTNode: NodePath,
142 +): string | null {
143 + let hookVariableName: string | null;
144 + const isCustomHook = originalHook.id === null;
145 +
146 + switch (nodesAssociatedWithReactHookASTNode.length) {
147 + case 1:
148 + // CASE 1A (nodesAssociatedWithReactHookASTNode[0] !== potentialReactHookASTNode):
149 + // const flagState = useState(true); -> later referenced as
150 + // const [flag, setFlag] = flagState;
151 + //
152 + // CASE 1B (nodesAssociatedWithReactHookASTNode[0] === potentialReactHookASTNode):
153 + // const [flag, setFlag] = useState(true); -> we have access to the hook variable straight away
154 + //
155 + // CASE 1C (isCustomHook && nodesAssociatedWithReactHookASTNode[0] === potentialReactHookASTNode):
156 + // const someVariable = useSomeCustomHook(); -> we have access to hook variable straight away
157 + // const [someVariable, someFunction] = useAnotherCustomHook(); -> we ignore variable names in this case
158 + // as it is unclear what variable name to show
159 + if (
160 + isCustomHook &&
161 + nodesAssociatedWithReactHookASTNode[0] === potentialReactHookASTNode
162 + ) {
163 + hookVariableName = getHookVariableName(
164 + potentialReactHookASTNode,
165 + isCustomHook,
166 + );
167 + break;
168 + }
169 + hookVariableName = getHookVariableName(
170 + nodesAssociatedWithReactHookASTNode[0],
171 + );
172 + break;
173 +
174 + case 2:
175 + // const flagState = useState(true); -> later referenced as
176 + // const flag = flagState[0];
177 + // const setFlag = flagState[1];
178 + nodesAssociatedWithReactHookASTNode = nodesAssociatedWithReactHookASTNode.filter(
179 + hookPath => filterMemberWithHookVariableName(hookPath),
180 + );
181 +
182 + if (nodesAssociatedWithReactHookASTNode.length !== 1) {
183 + // Something went wrong, only a single desirable hook should remain here
184 + throw new Error("Couldn't isolate AST Node containing hook variable.");
185 + }
186 + hookVariableName = getHookVariableName(
187 + nodesAssociatedWithReactHookASTNode[0],
188 + );
189 + break;
190 +
191 + default:
192 + // Case 0:
193 + // const flagState = useState(true); -> which is not accessed anywhere
194 + //
195 + // Case > 2 (fallback):
196 + // const someState = React.useState(() => 0)
197 + //
198 + // const stateVariable = someState[0]
199 + // const setStateVariable = someState[1]
200 + //
201 + // const [number2, setNumber2] = someState
202 + //
203 + // We assign the state variable for 'someState' to multiple variables,
204 + // and hence cannot isolate a unique variable name. In such cases,
205 + // default to showing 'someState'
206 +
207 + hookVariableName = getHookVariableName(potentialReactHookASTNode);
208 + break;
209 + }
210 +
211 + return hookVariableName;
212 +}
213 +
214 +// Extracts the variable name from hook node path
215 +function getHookVariableName(
216 + hook: NodePath,
217 + isCustomHook: boolean = false,
218 +): string | null {
219 + const nodeType = hook.node.id.type;
220 + switch (nodeType) {
221 + case AST_NODE_TYPES.ARRAY_PATTERN:
222 + return !isCustomHook ? hook.node.id.elements[0].name : null;
223 +
224 + case AST_NODE_TYPES.IDENTIFIER:
225 + return hook.node.id.name;
226 +
227 + default:
228 + throw new Error(`Invalid node type: ${nodeType}`);
229 + }
230 +}
231 +
232 +function getPotentialHookDeclarationsFromAST(sourceAST: File): NodePath[] {
233 + const potentialHooksFound: NodePath[] = [];
234 + traverse(sourceAST, {
235 + enter(path) {
236 + if (path.isVariableDeclarator() && isPotentialHookDeclaration(path)) {
237 + potentialHooksFound.push(path);
238 + }
239 + },
240 + });
241 + return potentialHooksFound;
242 +}
243 +
244 +// Check if 'path' contains declaration of the form const X = useState(0);
245 +function isConfirmedHookDeclaration(path: NodePath): boolean {
246 + const node = path.node.init;
247 + if (node.type !== AST_NODE_TYPES.CALL_EXPRESSION) {
248 + return false;
249 + }
250 + const callee = node.callee;
251 + return isHook(callee);
252 +}
253 +
254 +// We consider hooks to be a hook name identifier or a member expression containing a hook name.
255 +function isHook(node: Node): boolean {
256 + if (node.type === AST_NODE_TYPES.IDENTIFIER) {
257 + return isHookName(node.name);
258 + } else if (
259 + node.type === AST_NODE_TYPES.MEMBER_EXPRESSION &&
260 + !node.computed &&
261 + isHook(node.property)
262 + ) {
263 + const obj = node.object;
264 + const isPascalCaseNameSpace = /^[A-Z].*/;
265 + return (
266 + obj.type === AST_NODE_TYPES.IDENTIFIER &&
267 + isPascalCaseNameSpace.test(obj.name)
268 + );
269 + } else {
270 + // TODO Possibly handle inline require statements e.g. require("useStable")(...)
271 + // This does not seem like a high priority, since inline requires are probably
272 + // not common and are also typically in compiled code rather than source code.
273 +
274 + return false;
275 + }
276 +}
277 +
278 +// Catch all identifiers that begin with "use"
279 +// followed by an uppercase Latin character to exclude identifiers like "user".
280 +// Copied from packages/eslint-plugin-react-hooks/src/RulesOfHooks
281 +function isHookName(name: string): boolean {
282 + return /^use[A-Z0-9].*$/.test(name);
283 +}
284 +
285 +// Determines whether incoming hook is a primitive hook that gets assigned to variables.
286 +export function isNonDeclarativePrimitiveHook(hook: HooksNode) {
287 + return ['Effect', 'ImperativeHandle', 'LayoutEffect', 'DebugValue'].includes(
288 + hook.name,
289 + );
290 +}
291 +
292 +// Check if the AST Node COULD be a React Hook
293 +function isPotentialHookDeclaration(path: NodePath): boolean {
294 + // The array potentialHooksFound will contain all potential hook declaration cases we support
295 + const nodePathInit = path.node.init;
296 + if (nodePathInit != null) {
297 + if (nodePathInit.type === AST_NODE_TYPES.CALL_EXPRESSION) {
298 + // CASE: CallExpression
299 + // 1. const [count, setCount] = useState(0); -> destructured pattern
300 + // 2. const [A, setA] = useState(0), const [B, setB] = useState(0); -> multiple inline declarations
301 + // 3. const [
302 + // count,
303 + // setCount
304 + // ] = useState(0); -> multiline hook declaration
305 + // 4. const ref = useRef(null); -> generic hooks
306 + const callee = nodePathInit.callee;
307 + return isHook(callee);
308 + } else if (
309 + nodePathInit.type === AST_NODE_TYPES.MEMBER_EXPRESSION ||
310 + nodePathInit.type === AST_NODE_TYPES.IDENTIFIER
311 + ) {
312 + // CASE: MemberExpression
313 + // const countState = React.useState(0);
314 + // const count = countState[0];
315 + // const setCount = countState[1]; -> Accessing members following hook declaration
316 +
317 + // CASE: Identifier
318 + // const countState = React.useState(0);
319 + // const [count, setCount] = countState; -> destructuring syntax following hook declaration
320 + return true;
321 + }
322 + }
323 + return false;
324 +}
325 +
326 +/// Check whether 'node' is hook decalration of form useState(0); OR React.useState(0);
327 +function isReactFunction(node: Node, functionName: string): boolean {
328 + return (
329 + node.name === functionName ||
330 + (node.type === 'MemberExpression' &&
331 + node.object.name === 'React' &&
332 + node.property.name === functionName)
333 + );
334 +}
335 +
336 +// Check if 'path' is either State or Reducer hook
337 +function isStateOrReducerHook(path: NodePath): boolean {
338 + const callee = path.node.init.callee;
339 + return (
340 + isReactFunction(callee, 'useState') || isReactFunction(callee, 'useReducer')
341 + );
342 +}
343 +
344 +// Check whether hookNode of a declaration contains obvious variable name
345 +function nodeContainsHookVariableName(hookNode: NodePath): boolean {
346 + // We determine cases where variable names are obvious in declarations. Examples:
347 + // const [tick, setTick] = useState(1); OR const ref = useRef(null);
348 + // Here tick/ref are obvious hook variables in the hook declaration node itself
349 + // 1. True for satisfying above cases
350 + // 2. False for everything else. Examples:
351 + // const countState = React.useState(0);
352 + // const count = countState[0];
353 + // const setCount = countState[1]; -> not obvious, hook variable can't be determined
354 + // from the hook declaration node alone
355 + // 3. For custom hooks we force pass true since we are only concerned with the AST node
356 + // regardless of how it is accessed in source code. (See: getHookVariableName)
357 +
358 + const node = hookNode.node.id;
359 + if (
360 + node.type === AST_NODE_TYPES.ARRAY_PATTERN ||
361 + (node.type === AST_NODE_TYPES.IDENTIFIER && !isStateOrReducerHook(hookNode))
362 + ) {
363 + return true;
364 + }
365 + return false;
366 +}
packages/react-devtools-extensions/src/main.js
+2
@@ -12,6 +12,7 @@ import {
12 getSavedComponentFilters,
13 getShowInlineWarningsAndErrors,
14 } from 'react-devtools-shared/src/utils';
15 +import parseHookNames from './parseHookNames';
16 import {
17 localStorageGetItem,
18 localStorageRemoveItem,
@@ -214,6 +215,7 @@ function createPanelIfReactLoaded() {
215 browserTheme: getBrowserTheme(),
216 componentsPortalContainer,
217 enabledInspectedElementContextMenu: true,
218 + loadHookNamesFunction: parseHookNames,
219 overrideTab,
220 profilerPortalContainer,
221 showTabBar: false,
packages/react-devtools-extensions/src/parseHookNames.js new
+501
@@ -0,0 +1,501 @@
1 +/* global chrome */
2 +
3 +/**
4 + * Copyright (c) Facebook, Inc. and its affiliates.
5 + *
6 + * This source code is licensed under the MIT license found in the
7 + * LICENSE file in the root directory of this source tree.
8 + *
9 + * @flow
10 + */
11 +
12 +import {parse} from '@babel/parser';
13 +import {enableHookNameParsing} from 'react-devtools-feature-flags';
14 +import LRU from 'lru-cache';
15 +import {SourceMapConsumer} from 'source-map';
16 +import {getHookName, isNonDeclarativePrimitiveHook} from './astUtils';
17 +import {areSourceMapsAppliedToErrors} from './ErrorTester';
18 +import {__DEBUG__} from 'react-devtools-shared/src/constants';
19 +
20 +import type {
21 + HooksNode,
22 + HookSource,
23 + HooksTree,
24 +} from 'react-debug-tools/src/ReactDebugHooks';
25 +import type {HookNames, LRUCache} from 'react-devtools-shared/src/types';
26 +import type {Thenable} from 'shared/ReactTypes';
27 +import type {SourceConsumer, SourceMap} from './astUtils';
28 +
29 +const SOURCE_MAP_REGEX = / ?sourceMappingURL=([^\s'"]+)/gm;
30 +const ABSOLUTE_URL_REGEX = /^https?:\/\//i;
31 +const MAX_SOURCE_LENGTH = 100_000_000;
32 +
33 +type AST = mixed;
34 +
35 +type HookSourceData = {|
36 + // Generated by react-debug-tools.
37 + hookSource: HookSource,
38 +
39 + // AST for original source code; typically comes from a consumed source map.
40 + originalSourceAST: AST | null,
41 +
42 + // Source code (React components or custom hooks) containing primitive hook calls.
43 + // If no source map has been provided, this code will be the same as runtimeSourceCode.
44 + originalSourceCode: string | null,
45 +
46 + // Compiled code (React components or custom hooks) containing primitive hook calls.
47 + runtimeSourceCode: string | null,
48 +
49 + // APIs from source-map for parsing source maps (if detected).
50 + sourceConsumer: SourceConsumer | null,
51 +
52 + // External URL of source map.
53 + // Sources without source maps (or with inline source maps) won't have this.
54 + sourceMapURL: string | null,
55 +
56 + // Parsed source map object.
57 + sourceMapContents: SourceMap | null,
58 +|};
59 +
60 +type CachedMetadata = {|
61 + originalSourceAST: AST,
62 + originalSourceCode: string,
63 + sourceConsumer: SourceConsumer | null,
64 +|};
65 +
66 +// On large trees, encoding takes significant time.
67 +// Try to reuse the already encoded strings.
68 +const fileNameToMetadataCache: LRUCache<string, CachedMetadata> = new LRU({
69 + max: 50,
70 + dispose: (fileName: string, metadata: CachedMetadata) => {
71 + if (__DEBUG__) {
72 + console.log(
73 + 'fileNameToHookSourceData.dispose() Evicting cached metadata for "' +
74 + fileName +
75 + '"',
76 + );
77 + }
78 +
79 + const sourceConsumer = metadata.sourceConsumer;
80 + if (sourceConsumer !== null) {
81 + sourceConsumer.destroy();
82 + }
83 + },
84 +});
85 +
86 +export default async function parseHookNames(
87 + hooksTree: HooksTree,
88 +): Thenable<HookNames | null> {
89 + if (!enableHookNameParsing) {
90 + return Promise.resolve(null);
91 + }
92 +
93 + const hooksList: Array<HooksNode> = [];
94 + flattenHooksList(hooksTree, hooksList);
95 +
96 + if (__DEBUG__) {
97 + console.log('parseHookNames() hooksList:', hooksList);
98 + }
99 +
100 + // Gather the unique set of source files to load for the built-in hooks.
101 + const fileNameToHookSourceData: Map<string, HookSourceData> = new Map();
102 + for (let i = 0; i < hooksList.length; i++) {
103 + const hook = hooksList[i];
104 +
105 + const hookSource = hook.hookSource;
106 + if (hookSource == null) {
107 + // Older versions of react-debug-tools don't include this information.
108 + // In this case, we can't continue.
109 + throw Error('Hook source code location not found.');
110 + }
111 +
112 + const fileName = hookSource.fileName;
113 + if (fileName == null) {
114 + throw Error('Hook source code location not found.');
115 + } else {
116 + if (!fileNameToHookSourceData.has(fileName)) {
117 + const hookSourceData: HookSourceData = {
118 + hookSource,
119 + originalSourceAST: null,
120 + originalSourceCode: null,
121 + runtimeSourceCode: null,
122 + sourceConsumer: null,
123 + sourceMapURL: null,
124 + sourceMapContents: null,
125 + };
126 +
127 + // If we've already loaded source/source map info for this file,
128 + // we can skip reloading it (and more importantly, re-parsing it).
129 + const metadata = fileNameToMetadataCache.get(fileName);
130 + if (metadata != null) {
131 + if (__DEBUG__) {
132 + console.groupCollapsed(
133 + 'parseHookNames() Found cached metadata for file "' +
134 + fileName +
135 + '"',
136 + );
137 + console.log(metadata);
138 + console.groupEnd();
139 + }
140 +
141 + hookSourceData.originalSourceAST = metadata.originalSourceAST;
142 + hookSourceData.originalSourceCode = metadata.originalSourceCode;
143 + hookSourceData.sourceConsumer = metadata.sourceConsumer;
144 + }
145 +
146 + fileNameToHookSourceData.set(fileName, hookSourceData);
147 + }
148 + }
149 + }
150 +
151 + return loadSourceFiles(fileNameToHookSourceData)
152 + .then(() => extractAndLoadSourceMaps(fileNameToHookSourceData))
153 + .then(() => parseSourceAST(fileNameToHookSourceData))
154 + .then(() => updateLruCache(fileNameToHookSourceData))
155 + .then(() => findHookNames(hooksList, fileNameToHookSourceData));
156 +}
157 +
158 +function decodeBase64String(encoded: string): Object {
159 + if (typeof atob === 'function') {
160 + return atob(encoded);
161 + } else if (
162 + typeof Buffer !== 'undefined' &&
163 + Buffer !== null &&
164 + typeof Buffer.from === 'function'
165 + ) {
166 + return Buffer.from(encoded, 'base64');
167 + } else {
168 + throw Error('Cannot decode base64 string');
169 + }
170 +}
171 +
172 +function extractAndLoadSourceMaps(
173 + fileNameToHookSourceData: Map<string, HookSourceData>,
174 +): Promise<*> {
175 + const promises = [];
176 + fileNameToHookSourceData.forEach(hookSourceData => {
177 + if (hookSourceData.originalSourceAST !== null) {
178 + // Use cached metadata.
179 + return;
180 + }
181 +
182 + const runtimeSourceCode = ((hookSourceData.runtimeSourceCode: any): string);
183 + const sourceMappingURLs = runtimeSourceCode.match(SOURCE_MAP_REGEX);
184 + if (sourceMappingURLs == null) {
185 + // Maybe file has not been transformed; we'll try to parse it as-is in parseSourceAST().
186 +
187 + if (__DEBUG__) {
188 + console.log('extractAndLoadSourceMaps() No source map found');
189 + }
190 + } else {
191 + for (let i = 0; i < sourceMappingURLs.length; i++) {
192 + const sourceMappingURL = sourceMappingURLs[i];
193 + const index = sourceMappingURL.indexOf('base64,');
194 + if (index >= 0) {
195 + // Web apps like Code Sandbox embed multiple inline source maps.
196 + // In this case, we need to loop through and find the right one.
197 + // We may also need to trim any part of this string that isn't based64 encoded data.
198 + const trimmed = ((sourceMappingURL.match(
199 + /base64,([a-zA-Z0-9+\/=]+)/,
200 + ): any): Array<string>)[1];
201 + const decoded = decodeBase64String(trimmed);
202 + const parsed = JSON.parse(decoded);
203 +
204 + if (__DEBUG__) {
205 + console.groupCollapsed(
206 + 'extractAndLoadSourceMaps() Inline source map',
207 + );
208 + console.log(parsed);
209 + console.groupEnd();
210 + }
211 +
212 + // Hook source might be a URL like "https://4syus.csb.app/src/App.js"
213 + // Parsed source map might be a partial path like "src/App.js"
214 + const fileName = ((hookSourceData.hookSource.fileName: any): string);
215 + const match = parsed.sources.find(
216 + source =>
217 + source === 'Inline Babel script' || fileName.includes(source),
218 + );
219 + if (match) {
220 + hookSourceData.sourceMapContents = parsed;
221 + break;
222 + }
223 + } else {
224 + if (sourceMappingURLs.length > 1) {
225 + console.warn(
226 + 'More than one external source map detected in the source file',
227 + );
228 + }
229 +
230 + let url = sourceMappingURLs[0].split('=')[1];
231 + if (ABSOLUTE_URL_REGEX.test(url)) {
232 + const baseURL = url.slice(0, url.lastIndexOf('/'));
233 + url = `${baseURL}/${url}`;
234 +
235 + if (!isValidUrl(url)) {
236 + throw new Error(`Invalid source map URL "${url}"`);
237 + }
238 + }
239 +
240 + hookSourceData.sourceMapURL = url;
241 +
242 + if (__DEBUG__) {
243 + console.log(
244 + 'extractAndLoadSourceMaps() External source map "' + url + '"',
245 + );
246 + }
247 +
248 + promises.push(
249 + fetchFile(url).then(sourceMapContents => {
250 + hookSourceData.sourceMapContents = JSON.parse(sourceMapContents);
251 + }),
252 + );
253 + break;
254 + }
255 + }
256 + }
257 + });
258 + return Promise.all(promises);
259 +}
260 +
261 +function fetchFile(url: string): Promise<string> {
262 + return new Promise((resolve, reject) => {
263 + fetch(url).then(response => {
264 + if (response.ok) {
265 + response
266 + .text()
267 + .then(text => {
268 + resolve(text);
269 + })
270 + .catch(error => {
271 + reject(null);
272 + });
273 + } else {
274 + reject(null);
275 + }
276 + });
277 + });
278 +}
279 +
280 +function findHookNames(
281 + hooksList: Array<HooksNode>,
282 + fileNameToHookSourceData: Map<string, HookSourceData>,
283 +): HookNames {
284 + const map: HookNames = new Map();
285 +
286 + hooksList.map(hook => {
287 + if (isNonDeclarativePrimitiveHook(hook)) {
288 + if (__DEBUG__) {
289 + console.log('findHookNames() Non declarative primitive hook');
290 + }
291 +
292 + // Not all hooks have names (e.g. useEffect or useLayoutEffect)
293 + return null;
294 + }
295 +
296 + // We already guard against a null HookSource in parseHookNames()
297 + const hookSource = ((hook.hookSource: any): HookSource);
298 + const fileName = hookSource.fileName;
299 + if (!fileName) {
300 + return null; // Should not be reachable.
301 + }
302 +
303 + const hookSourceData = fileNameToHookSourceData.get(fileName);
304 + if (!hookSourceData) {
305 + return null; // Should not be reachable.
306 + }
307 +
308 + const {lineNumber, columnNumber} = hookSource;
309 + if (!lineNumber || !columnNumber) {
310 + return null; // Should not be reachable.
311 + }
312 +
313 + const sourceConsumer = hookSourceData.sourceConsumer;
314 +
315 + let originalSourceLineNumber;
316 + if (areSourceMapsAppliedToErrors() || !sourceConsumer) {
317 + // Either the current environment automatically applies source maps to errors,
318 + // or the current code had no source map to begin with.
319 + // Either way, we don't need to convert the Error stack frame locations.
320 + originalSourceLineNumber = lineNumber;
321 + } else {
322 + originalSourceLineNumber = sourceConsumer.originalPositionFor({
323 + line: lineNumber,
324 + column: columnNumber,
325 + }).line;
326 + }
327 +
328 + if (__DEBUG__) {
329 + console.log(
330 + 'findHookNames() mapped line number',
331 + lineNumber,
332 + 'to',
333 + originalSourceLineNumber,
334 + );
335 + }
336 +
337 + if (originalSourceLineNumber === null) {
338 + return null;
339 + }
340 +
341 + const name = getHookName(
342 + hook,
343 + hookSourceData.originalSourceAST,
344 + ((hookSourceData.originalSourceCode: any): string),
345 + ((originalSourceLineNumber: any): number),
346 + );
347 +
348 + if (__DEBUG__) {
349 + console.log('findHookNames() Found name "' + (name || '-') + '"');
350 + }
351 +
352 + map.set(hook, name);
353 + });
354 +
355 + return map;
356 +}
357 +
358 +function isValidUrl(possibleURL: string): boolean {
359 + try {
360 + // eslint-disable-next-line no-new
361 + new URL(possibleURL);
362 + } catch (_) {
363 + return false;
364 + }
365 + return true;
366 +}
367 +
368 +function loadSourceFiles(
369 + fileNameToHookSourceData: Map<string, HookSourceData>,
370 +): Promise<*> {
371 + const promises = [];
372 + fileNameToHookSourceData.forEach((hookSourceData, fileName) => {
373 + promises.push(
374 + fetchFile(fileName).then(runtimeSourceCode => {
375 + if (runtimeSourceCode.length > MAX_SOURCE_LENGTH) {
376 + throw Error('Source code too large to parse');
377 + }
378 +
379 + if (__DEBUG__) {
380 + console.groupCollapsed(
381 + 'loadSourceFiles() fileName "' + fileName + '"',
382 + );
383 + console.log(runtimeSourceCode);
384 + console.groupEnd();
385 + }
386 +
387 + hookSourceData.runtimeSourceCode = runtimeSourceCode;
388 + }),
389 + );
390 + });
391 + return Promise.all(promises);
392 +}
393 +
394 +async function parseSourceAST(
395 + fileNameToHookSourceData: Map<string, HookSourceData>,
396 +): Promise<*> {
397 + // SourceMapConsumer.initialize() does nothing when running in Node (aka Jest)
398 + // because the wasm file is automatically read from the file system
399 + // so we can avoid triggering a warning message about this.
400 + if (!__TEST__) {
401 + if (__DEBUG__) {
402 + console.log('parseSourceAST() Initializing source-map library ...');
403 + }
404 +
405 + // $FlowFixMe
406 + const wasmMappingsURL = chrome.extension.getURL('mappings.wasm');
407 +
408 + SourceMapConsumer.initialize({'lib/mappings.wasm': wasmMappingsURL});
409 + }
410 +
411 + const promises = [];
412 + fileNameToHookSourceData.forEach(hookSourceData => {
413 + if (hookSourceData.originalSourceAST !== null) {
414 + // Use cached metadata.
415 + return;
416 + }
417 +
418 + const {runtimeSourceCode, sourceMapContents} = hookSourceData;
419 + if (sourceMapContents !== null) {
420 + // Parse and extract the AST from the source map.
421 + promises.push(
422 + SourceMapConsumer.with(
423 + sourceMapContents,
424 + null,
425 + (sourceConsumer: SourceConsumer) => {
426 + hookSourceData.sourceConsumer = sourceConsumer;
427 +
428 + // Now that the source map has been loaded,
429 + // extract the original source for later.
430 + const source = sourceMapContents.sources[0];
431 + const originalSourceCode = sourceConsumer.sourceContentFor(
432 + source,
433 + true,
434 + );
435 +
436 + if (__DEBUG__) {
437 + console.groupCollapsed(
438 + 'parseSourceAST() Extracted source code from source map',
439 + );
440 + console.log(originalSourceCode);
441 + console.groupEnd();
442 + }
443 +
444 + hookSourceData.originalSourceCode = originalSourceCode;
445 +
446 + // TODO Parsing should ideally be done off of the main thread.
447 + hookSourceData.originalSourceAST = parse(originalSourceCode, {
448 + sourceType: 'unambiguous',
449 + plugins: ['jsx', 'typescript'],
450 + });
451 + },
452 + ),
453 + );
454 + } else {
455 + // There's no source map to parse here so we can just parse the original source itself.
456 + hookSourceData.originalSourceCode = runtimeSourceCode;
457 +
458 + // TODO Parsing should ideally be done off of the main thread.
459 + hookSourceData.originalSourceAST = parse(runtimeSourceCode, {
460 + sourceType: 'unambiguous',
461 + plugins: ['jsx', 'typescript'],
462 + });
463 + }
464 + });
465 + return Promise.all(promises);
466 +}
467 +
468 +function flattenHooksList(
469 + hooksTree: HooksTree,
470 + hooksList: Array<HooksNode>,
471 +): void {
472 + for (let i = 0; i < hooksTree.length; i++) {
473 + const hook = hooksTree[i];
474 + hooksList.push(hook);
475 + if (hook.subHooks.length > 0) {
476 + flattenHooksList(hook.subHooks, hooksList);
477 + }
478 + }
479 +}
480 +
481 +function updateLruCache(
482 + fileNameToHookSourceData: Map<string, HookSourceData>,
483 +): Promise<*> {
484 + fileNameToHookSourceData.forEach(
485 + ({originalSourceAST, originalSourceCode, sourceConsumer}, fileName) => {
486 + // Only set once to avoid triggering eviction/cleanup code.
487 + if (!fileNameToMetadataCache.has(fileName)) {
488 + if (__DEBUG__) {
489 + console.log('updateLruCache() Caching metada for "' + fileName + '"');
490 + }
491 +
492 + fileNameToMetadataCache.set(fileName, {
493 + originalSourceAST,
494 + originalSourceCode: ((originalSourceCode: any): string),
495 + sourceConsumer,
496 + });
497 + }
498 + },
499 + );
500 + return Promise.resolve();
501 +}
packages/react-devtools-extensions/webpack.backend.js
+4
@@ -32,6 +32,10 @@ module.exports = {
32 node: {
33 // Don't define a polyfill on window.setImmediate
34 setImmediate: false,
35 +
36 + // source-maps package has a dependency on 'fs'
37 + // but this build won't trigger that code path
38 + fs: 'empty',
39 },
40 resolve: {
41 alias: {
packages/react-devtools-extensions/webpack.config.js
+15
@@ -37,6 +37,10 @@ module.exports = {
37 node: {
38 // Don't define a polyfill on window.setImmediate
39 setImmediate: false,
40 +
41 + // source-maps package has a dependency on 'fs'
42 + // but this build won't trigger that code path
43 + fs: 'empty',
44 },
45 resolve: {
46 alias: {
@@ -65,6 +69,17 @@ module.exports = {
69 }),
70 ],
71 module: {
72 + defaultRules: [
73 + {
74 + type: 'javascript/auto',
75 + resolve: {},
76 + },
77 + {
78 + test: /\.json$/i,
79 + type: 'json',
80 + },
81 + ],
82 +
83 rules: [
84 {
85 test: /\.js$/,
packages/react-devtools-inline/webpack.config.js
+5
@@ -37,6 +37,11 @@ module.exports = {
37 'react-is': 'react-is',
38 scheduler: 'scheduler',
39 },
40 + node: {
41 + // source-maps package has a dependency on 'fs'
42 + // but this build won't trigger that code path
43 + fs: 'empty',
44 + },
45 resolve: {
46 alias: {
47 'react-devtools-feature-flags': resolveFeatureFlags('inline'),
packages/react-devtools-shared/package.json
+2
@@ -7,7 +7,9 @@
7 "react-dom-15": "npm:react-dom@^15"
8 },
9 "dependencies": {
10 + "@babel/parser": "^7.12.5",
11 "@babel/runtime": "^7.11.2",
12 + "@babel/traverse": "^7.12.5",
13 "@reach/menu-button": "^0.1.17",
14 "@reach/tooltip": "^0.2.2",
15 "clipboard-js": "^0.3.6",
packages/react-devtools-shared/src/__tests__/hookSerializer.js new
+60
@@ -0,0 +1,60 @@
1 +function hasAbsoluteFileName(hook) {
2 + const fileName = hook.hookSource ? hook.hookSource.fileName : null;
3 + if (fileName == null) {
4 + return false;
5 + } else {
6 + return fileName.indexOf('/react-devtools-shared/') > 0;
7 + }
8 +}
9 +
10 +function serializeHook(hook) {
11 + if (!hook.hookSource) return hook;
12 +
13 + // Remove user-specific portions of this file path.
14 + let fileName = hook.hookSource.fileName;
15 + const index = fileName.lastIndexOf('/react-devtools-shared/');
16 + fileName = fileName.substring(index + 1);
17 +
18 + let subHooks = hook.subHooks;
19 + if (subHooks) {
20 + subHooks = subHooks.map(serializeHook);
21 + }
22 +
23 + return {
24 + ...hook,
25 + hookSource: {
26 + ...hook.hookSource,
27 + fileName,
28 +
29 + // Otherwise changes in any test case or formatting might invalidate other tests.
30 + columnNumber: 'removed by Jest serializer',
31 + lineNumber: 'removed by Jest serializer',
32 + },
33 + subHooks,
34 + };
35 +}
36 +
37 +// test() is part of Jest's serializer API
38 +export function test(maybeHook) {
39 + if (maybeHook === null || typeof maybeHook !== 'object') {
40 + return false;
41 + }
42 +
43 + const hasOwnProperty = Object.prototype.hasOwnProperty.bind(maybeHook);
44 +
45 + return (
46 + hasOwnProperty('id') &&
47 + hasOwnProperty('isStateEditable') &&
48 + hasOwnProperty('name') &&
49 + hasOwnProperty('subHooks') &&
50 + hasOwnProperty('value') &&
51 + // Don't re-process an already printed hook.
52 + hasAbsoluteFileName(maybeHook)
53 + );
54 +}
55 +
56 +// print() is part of Jest's serializer API
57 +export function print(hook, serialize, indent) {
58 + // Don't stringify this object; that would break nested serializers.
59 + return serialize(serializeHook(hook));
60 +}
packages/react-devtools-shared/src/__tests__/inspectedElement-test.js
+14 -9
@@ -25,6 +25,7 @@ describe('InspectedElement', () => {
25 let BridgeContext;
26 let InspectedElementContext;
27 let InspectedElementContextController;
28 + let SettingsContextController;
29 let StoreContext;
30 let TreeContextController;
31
@@ -57,6 +58,8 @@ describe('InspectedElement', () => {
58 .InspectedElementContext;
59 InspectedElementContextController = require('react-devtools-shared/src/devtools/views/Components/InspectedElementContext')
60 .InspectedElementContextController;
61 + SettingsContextController = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext')
62 + .SettingsContextController;
63 StoreContext = require('react-devtools-shared/src/devtools/views/context')
64 .StoreContext;
65 TreeContextController = require('react-devtools-shared/src/devtools/views/Components/TreeContext')
@@ -79,15 +82,17 @@ describe('InspectedElement', () => {
82 }) => (
83 <BridgeContext.Provider value={bridge}>
84 <StoreContext.Provider value={store}>
82 - <TreeContextController
83 - defaultSelectedElementID={defaultSelectedElementID}
84 - defaultSelectedElementIndex={defaultSelectedElementIndex}>
85 - <React.Suspense fallback="Loading...">
86 - <InspectedElementContextController>
87 - {children}
88 - </InspectedElementContextController>
89 - </React.Suspense>
90 - </TreeContextController>
85 + <SettingsContextController>
86 + <TreeContextController
87 + defaultSelectedElementID={defaultSelectedElementID}
88 + defaultSelectedElementIndex={defaultSelectedElementIndex}>
89 + <React.Suspense fallback="Loading...">
90 + <InspectedElementContextController>
91 + {children}
92 + </InspectedElementContextController>
93 + </React.Suspense>
94 + </TreeContextController>
95 + </SettingsContextController>
96 </StoreContext.Provider>
97 </BridgeContext.Provider>
98 );
packages/react-devtools-shared/src/__tests__/inspectedElementSerializer.js
+1
@@ -10,6 +10,7 @@ export function test(maybeInspectedElement) {
10 const hasOwnProperty = Object.prototype.hasOwnProperty.bind(
11 maybeInspectedElement,
12 );
13 +
14 return (
15 hasOwnProperty('canEditFunctionProps') &&
16 hasOwnProperty('canEditHooks') &&
packages/react-devtools-shared/src/__tests__/setupEnv.js
+1
@@ -7,3 +7,4 @@ if (!global.hasOwnProperty('localStorage')) {
7
8 // Mimic the global we set with Webpack's DefinePlugin
9 global.__DEV__ = process.env.NODE_ENV !== 'production';
10 +global.__TEST__ = true;
packages/react-devtools-shared/src/__tests__/setupTests.js
+10
@@ -122,6 +122,16 @@ env.beforeEach(() => {
122 global.agent = agent;
123 global.bridge = bridge;
124 global.store = store;
125 +
126 + const readFileSync = require('fs').readFileSync;
127 + async function mockFetch(url) {
128 + return {
129 + ok: true,
130 + status: 200,
131 + text: async () => readFileSync(__dirname + url, 'utf-8'),
132 + };
133 + }
134 + global.fetch = mockFetch;
135 });
136 env.afterEach(() => {
137 delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
packages/react-devtools-shared/src/__tests__/utils.js
+10
@@ -268,3 +268,13 @@ export function withErrorsOrWarningsIgnored<T: void | Promise<void>>(
268 }
269 }
270 }
271 +
272 +export function overrideFeatureFlags(overrideFlags) {
273 + jest.mock('react-devtools-feature-flags', () => {
274 + const actualFlags = jest.requireActual('react-devtools-feature-flags');
275 + return {
276 + ...actualFlags,
277 + ...overrideFlags,
278 + };
279 + });
280 +}
packages/react-devtools-shared/src/backend/renderer.js
+5 -1
@@ -80,7 +80,10 @@ import {
80 MEMO_SYMBOL_STRING,
81 } from './ReactSymbols';
82 import {format} from './utils';
83 -import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags';
83 +import {
84 + enableHookNameParsing,
85 + enableProfilerChangedHookIndices,
86 +} from 'react-devtools-feature-flags';
87 import is from 'shared/objectIs';
88 import isArray from 'shared/isArray';
89
@@ -3092,6 +3095,7 @@ export function attach(
3095 hooks = inspectHooksOfFiber(
3096 fiber,
3097 (renderer.currentDispatcherRef: any),
3098 + enableHookNameParsing, // Include source location info for hooks
3099 );
3100 } finally {
3101 // Restore original console functionality.
packages/react-devtools-shared/src/config/DevToolsFeatureFlags.default.js
+1
@@ -13,5 +13,6 @@
13 * It should always be imported from "react-devtools-feature-flags".
14 ************************************************************************/
15
16 +export const enableHookNameParsing = false;
17 export const enableProfilerChangedHookIndices = false;
18 export const isInternalFacebookBuild = false;
packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-fb.js
+1
@@ -13,6 +13,7 @@
13 * It should always be imported from "react-devtools-feature-flags".
14 ************************************************************************/
15
16 +export const enableHookNameParsing = true;
17 export const enableProfilerChangedHookIndices = true;
18 export const isInternalFacebookBuild = true;
19
packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-oss.js
+1
@@ -13,6 +13,7 @@
13 * It should always be imported from "react-devtools-feature-flags".
14 ************************************************************************/
15
16 +export const enableHookNameParsing = false;
17 export const enableProfilerChangedHookIndices = false;
18 export const isInternalFacebookBuild = false;
19
packages/react-devtools-shared/src/constants.js
+3
@@ -23,6 +23,9 @@ export const LOCAL_STORAGE_FILTER_PREFERENCES_KEY =
23 export const SESSION_STORAGE_LAST_SELECTION_KEY =
24 'React::DevTools::lastSelection';
25
26 +export const LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY =
27 + 'React::DevTools::parseHookNames';
28 +
29 export const SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY =
30 'React::DevTools::recordChangeDescriptions';
31
packages/react-devtools-shared/src/devtools/views/ButtonIcon.js
+18 -1
@@ -26,6 +26,7 @@ export type IconType =
26 | 'log-data'
27 | 'more'
28 | 'next'
29 + | 'parse-hook-names'
30 | 'previous'
31 | 'record'
32 | 'reload'
@@ -92,6 +93,9 @@ export default function ButtonIcon({className = '', type}: Props) {
93 case 'next':
94 pathData = PATH_NEXT;
95 break;
96 + case 'parse-hook-names':
97 + pathData = PATH_PARSE_HOOK_NAMES;
98 + break;
99 case 'previous':
100 pathData = PATH_PREVIOUS;
101 break;
@@ -141,7 +145,11 @@ export default function ButtonIcon({className = '', type}: Props) {
145 height="24"
146 viewBox="0 0 24 24">
147 <path d="M0 0h24v24H0z" fill="none" />
144 - <path fill="currentColor" d={pathData} />
148 + {typeof pathData === 'string' ? (
149 + <path fill="currentColor" d={pathData} />
150 + ) : (
151 + pathData
152 + )}
153 </svg>
154 );
155 }
@@ -197,6 +205,15 @@ const PATH_MORE = `
205
206 const PATH_NEXT = 'M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z';
207
208 +const PATH_PARSE_HOOK_NAMES = (
209 + <g>
210 + <polygon points="20,7 20.94,4.94 23,4 20.94,3.06 20,1 19.06,3.06 17,4 19.06,4.94" />
211 + <polygon points="8.5,7 9.44,4.94 11.5,4 9.44,3.06 8.5,1 7.56,3.06 5.5,4 7.56,4.94" />
212 + <polygon points="20,12.5 19.06,14.56 17,15.5 19.06,16.44 20,18.5 20.94,16.44 23,15.5 20.94,14.56" />
213 + <path d="M17.71,9.12l-2.83-2.83C14.68,6.1,14.43,6,14.17,6c-0.26,0-0.51,0.1-0.71,0.29L2.29,17.46c-0.39,0.39-0.39,1.02,0,1.41 l2.83,2.83C5.32,21.9,5.57,22,5.83,22s0.51-0.1,0.71-0.29l11.17-11.17C18.1,10.15,18.1,9.51,17.71,9.12z M14.17,8.42l1.41,1.41 L14.41,11L13,9.59L14.17,8.42z M5.83,19.59l-1.41-1.41L11.59,11L13,12.41L5.83,19.59z" />
214 + </g>
215 +);
216 +
217 const PATH_PREVIOUS =
218 'M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z';
219
packages/react-devtools-shared/src/devtools/views/Components/Badge.js
+1 -1
@@ -26,7 +26,7 @@ export default function Badge({
26 type,
27 children,
28 }: Props) {
29 - if (hocDisplayNames === null) {
29 + if (hocDisplayNames === null || hocDisplayNames.length === 0) {
30 return null;
31 }
32
packages/react-devtools-shared/src/devtools/views/Components/Components.css
+1
@@ -68,4 +68,5 @@
68 justify-content: center;
69 font-size: var(--font-size-sans-large);
70 color: var(--color-dim);
71 + border-left: 1px solid var(--color-border);
72 }
packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.css
+1 -1
@@ -64,5 +64,5 @@
64 padding: 0.25rem;
65 color: var(--color-dimmer);
66 font-style: italic;
67 + border-left: 1px solid var(--color-border);
68 }
68 -
packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js
+9 -1
@@ -39,7 +39,12 @@ export default function InspectedElementWrapper(_: Props) {
39 const store = useContext(StoreContext);
40 const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
41
42 - const {inspectedElement} = useContext(InspectedElementContext);
42 + const {
43 + hookNames,
44 + inspectedElement,
45 + parseHookNames,
46 + toggleParseHookNames,
47 + } = useContext(InspectedElementContext);
48
49 const element =
50 inspectedElementID !== null
@@ -268,7 +273,10 @@ export default function InspectedElementWrapper(_: Props) {
273 inspectedElementID /* Force reset when selected Element changes */
274 }
275 element={element}
276 + hookNames={hookNames}
277 inspectedElement={inspectedElement}
278 + parseHookNames={parseHookNames}
279 + toggleParseHookNames={toggleParseHookNames}
280 />
281 )}
282 </div>
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js
+57 -1
@@ -18,13 +18,19 @@ import {
18 useMemo,
19 useState,
20 } from 'react';
21 +import {enableHookNameParsing} from 'react-devtools-feature-flags';
22 import {TreeStateContext} from './TreeContext';
23 import {BridgeContext, StoreContext} from '../context';
24 import {
25 checkForUpdate,
26 inspectElement,
27 } from 'react-devtools-shared/src/inspectedElementCache';
28 +import {loadHookNames} from 'react-devtools-shared/src/hookNamesCache';
29 +import {ElementTypeFunction} from 'react-devtools-shared/src/types';
30 +import LoadHookNamesFunctionContext from 'react-devtools-shared/src/devtools/views/Components/LoadHookNamesFunctionContext';
31 +import {SettingsContext} from '../Settings/SettingsContext';
32
33 +import type {HookNames} from 'react-devtools-shared/src/types';
34 import type {ReactNodeList} from 'shared/ReactTypes';
35 import type {
36 Element,
@@ -33,10 +39,14 @@ import type {
39
40 type Path = Array<string | number>;
41 type InspectPathFunction = (path: Path) => void;
42 +export type ToggleParseHookNames = () => void;
43
44 type Context = {|
45 + hookNames: HookNames | null,
46 inspectedElement: InspectedElement | null,
47 inspectPaths: InspectPathFunction,
48 + parseHookNames: boolean,
49 + toggleParseHookNames: ToggleParseHookNames,
50 |};
51
52 export const InspectedElementContext = createContext<Context>(
@@ -51,8 +61,10 @@ export type Props = {|
61
62 export function InspectedElementContextController({children}: Props) {
63 const {selectedElementID} = useContext(TreeStateContext);
64 + const loadHookNamesFunction = useContext(LoadHookNamesFunctionContext);
65 const bridge = useContext(BridgeContext);
66 const store = useContext(StoreContext);
67 + const {parseHookNames: parseHookNamesByDefault} = useContext(SettingsContext);
68
69 const refresh = useCacheRefresh();
70
@@ -68,6 +80,13 @@ export function InspectedElementContextController({children}: Props) {
80 path: null,
81 });
82
83 + // Parse the currently inspected element's hook names.
84 + // This may be enabled by default (for all elements)
85 + // or it may be opted into on a per-element basis (if it's too slow to be on by default).
86 + const [parseHookNames, setParseHookNames] = useState<boolean>(
87 + parseHookNamesByDefault,
88 + );
89 +
90 const element =
91 selectedElementID !== null ? store.getElementByID(selectedElementID) : null;
92
@@ -79,14 +98,41 @@ export function InspectedElementContextController({children}: Props) {
98 element,
99 path: null,
100 });
101 +
102 + setParseHookNames(parseHookNamesByDefault);
103 }
104
105 // Don't load a stale element from the backend; it wastes bridge bandwidth.
106 let inspectedElement = null;
107 + let hookNames: HookNames | null = null;
108 if (!elementHasChanged && element !== null) {
109 inspectedElement = inspectElement(element, state.path, store, bridge);
110 +
111 + if (enableHookNameParsing) {
112 + if (parseHookNames) {
113 + if (
114 + inspectedElement !== null &&
115 + inspectedElement.type === ElementTypeFunction &&
116 + inspectedElement.hooks !== null &&
117 + loadHookNamesFunction !== null
118 + ) {
119 + hookNames = loadHookNames(
120 + element,
121 + inspectedElement.hooks,
122 + loadHookNamesFunction,
123 + );
124 + }
125 + }
126 + }
127 }
128
129 + const toggleParseHookNames: ToggleParseHookNames = useCallback<ToggleParseHookNames>(() => {
130 + startTransition(() => {
131 + setParseHookNames(value => !value);
132 + refresh();
133 + });
134 + }, [setParseHookNames]);
135 +
136 const inspectPaths: InspectPathFunction = useCallback<InspectPathFunction>(
137 (path: Path) => {
138 startTransition(() => {
@@ -125,6 +171,7 @@ export function InspectedElementContextController({children}: Props) {
171 }
172 }, [
173 element,
174 + hookNames,
175 // Reset this timer any time the element we're inspecting gets a new response.
176 // No sense to ping right away after e.g. inspecting/hydrating a path.
177 inspectedElement,
@@ -133,10 +180,19 @@ export function InspectedElementContextController({children}: Props) {
180
181 const value = useMemo<Context>(
182 () => ({
183 + hookNames,
184 inspectedElement,
185 inspectPaths,
186 + parseHookNames,
187 + toggleParseHookNames,
188 }),
139 - [inspectedElement, inspectPaths],
189 + [
190 + hookNames,
191 + inspectedElement,
192 + inspectPaths,
193 + parseHookNames,
194 + toggleParseHookNames,
195 + ],
196 );
197
198 return (
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementErrorBoundary.css
+1 -1
@@ -1,3 +1,3 @@
1 .Wrapper {
2 - border-left: 1px solid var(--color-border);
2 + height: 100%;
3 }
\ No newline at end of file
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.css
+4
@@ -77,4 +77,8 @@
77 margin-right: 0.25rem;
78 border-radius: 0.125rem;
79 padding: 0.125rem 0.25rem;
80 +}
81 +
82 +.HookName {
83 + color: var(--color-component-name);
84 }
\ No newline at end of file
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js
+66 -4
@@ -13,6 +13,7 @@ import {useCallback, useContext, useRef, useState} from 'react';
13 import {BridgeContext, StoreContext} from '../context';
14 import Button from '../Button';
15 import ButtonIcon from '../ButtonIcon';
16 +import Toggle from '../Toggle';
17 import ExpandCollapseToggle from './ExpandCollapseToggle';
18 import KeyValue from './KeyValue';
19 import {getMetaValueLabel, serializeHooksForCopy} from '../utils';
@@ -20,28 +21,49 @@ import Store from '../../store';
21 import styles from './InspectedElementHooksTree.css';
22 import useContextMenu from '../../ContextMenu/useContextMenu';
23 import {meta} from '../../../hydration';
23 -import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags';
24 +import {
25 + enableHookNameParsing,
26 + enableProfilerChangedHookIndices,
27 +} from 'react-devtools-feature-flags';
28
29 import type {InspectedElement} from './types';
30 import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
31 import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
32 +import type {HookNames} from 'react-devtools-shared/src/types';
33 import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
34 +import type {ToggleParseHookNames} from './InspectedElementContext';
35
36 type HooksTreeViewProps = {|
37 bridge: FrontendBridge,
38 element: Element,
39 + hookNames: HookNames | null,
40 inspectedElement: InspectedElement,
41 + parseHookNames: boolean,
42 store: Store,
43 + toggleParseHookNames: ToggleParseHookNames,
44 |};
45
46 export function InspectedElementHooksTree({
47 bridge,
48 element,
49 + hookNames,
50 inspectedElement,
51 + parseHookNames,
52 store,
53 + toggleParseHookNames,
54 }: HooksTreeViewProps) {
55 const {hooks, id} = inspectedElement;
56
57 + // Changing parseHookNames is done in a transition, because it suspends.
58 + // This value is done outside of the transition, so the UI toggle feels responsive.
59 + const [parseHookNamesOptimistic, setParseHookNamesOptimistic] = useState(
60 + parseHookNames,
61 + );
62 + const handleChange = () => {
63 + setParseHookNamesOptimistic(!parseHookNames);
64 + toggleParseHookNames();
65 + };
66 +
67 const handleCopy = () => copy(serializeHooksForCopy(hooks));
68
69 if (hooks === null) {
@@ -51,11 +73,25 @@ export function InspectedElementHooksTree({
73 <div className={styles.HooksTreeView}>
74 <div className={styles.HeaderRow}>
75 <div className={styles.Header}>hooks</div>
76 + {enableHookNameParsing && !parseHookNames && (
77 + <Toggle
78 + isChecked={parseHookNamesOptimistic}
79 + isDisabled={parseHookNamesOptimistic}
80 + onChange={handleChange}
81 + title={
82 + parseHookNames
83 + ? 'Parse hook names'
84 + : 'Parse hook names (may be slow)'
85 + }>
86 + <ButtonIcon type="parse-hook-names" />
87 + </Toggle>
88 + )}
89 <Button onClick={handleCopy} title="Copy to clipboard">
90 <ButtonIcon type="copy" />
91 </Button>
92 </div>
93 <InnerHooksTreeView
94 + hookNames={hookNames}
95 hooks={hooks}
96 id={id}
97 element={element}
@@ -69,6 +105,7 @@ export function InspectedElementHooksTree({
105
106 type InnerHooksTreeViewProps = {|
107 element: Element,
108 + hookNames: HookNames | null,
109 hooks: HooksTree,
110 id: number,
111 inspectedElement: InspectedElement,
@@ -77,6 +114,7 @@ type InnerHooksTreeViewProps = {|
114
115 export function InnerHooksTreeView({
116 element,
117 + hookNames,
118 hooks,
119 id,
120 inspectedElement,
@@ -88,6 +126,7 @@ export function InnerHooksTreeView({
126 key={index}
127 element={element}
128 hook={hooks[index]}
129 + hookNames={hookNames}
130 id={id}
131 inspectedElement={inspectedElement}
132 path={path.concat([index])}
@@ -98,12 +137,20 @@ export function InnerHooksTreeView({
137 type HookViewProps = {|
138 element: Element,
139 hook: HooksNode,
140 + hookNames: HookNames | null,
141 id: number,
142 inspectedElement: InspectedElement,
143 path: Array<string | number>,
144 |};
145
106 -function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
146 +function HookView({
147 + element,
148 + hook,
149 + hookNames,
150 + id,
151 + inspectedElement,
152 + path,
153 +}: HookViewProps) {
154 const {
155 canEditHooks,
156 canEditHooksAndDeletePaths,
@@ -180,6 +227,16 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
227 let displayValue;
228 let isComplexDisplayValue = false;
229
230 + const hookName = hookNames != null ? hookNames.get(hook) : null;
231 + const hookDisplayName = hookName ? (
232 + <>
233 + {name}
234 + {!!hookName && <span className={styles.HookName}>({hookName})</span>}
235 + </>
236 + ) : (
237 + name
238 + );
239 +
240 // Format data for display to mimic the props/state/context for now.
241 if (type === 'string') {
242 displayValue = `"${((value: any): string)}"`;
@@ -204,6 +261,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
261 <InnerHooksTreeView
262 element={element}
263 hooks={subHooks}
264 + hookNames={hookNames}
265 id={id}
266 inspectedElement={inspectedElement}
267 path={path.concat(['subHooks'])}
@@ -219,6 +277,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
277 depth={1}
278 element={element}
279 hookID={hookID}
280 + hookName={hookName}
281 inspectedElement={inspectedElement}
282 name="subHooks"
283 path={path.concat(['subHooks'])}
@@ -236,7 +295,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
295 <span
296 onClick={toggleIsOpen}
297 className={name !== '' ? styles.Name : styles.NameAnonymous}>
239 - {name || 'Anonymous'}
298 + {hookDisplayName || 'Anonymous'}
299 </span>
300 <span className={styles.Value} onClick={toggleIsOpen}>
301 {isOpen || getMetaValueLabel(value)}
@@ -253,6 +312,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
312 depth={1}
313 element={element}
314 hookID={hookID}
315 + hookName={hookName}
316 inspectedElement={inspectedElement}
317 name="DebugValue"
318 path={path.concat(['value'])}
@@ -272,7 +332,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
332 <span
333 onClick={toggleIsOpen}
334 className={name !== '' ? styles.Name : styles.NameAnonymous}>
275 - {name || 'Anonymous'}
335 + {hookDisplayName || 'Anonymous'}
336 </span>{' '}
337 {/* $FlowFixMe */}
338 <span className={styles.Value} onClick={toggleIsOpen}>
@@ -299,6 +359,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
359 depth={1}
360 element={element}
361 hookID={hookID}
362 + hookName={hookName}
363 inspectedElement={inspectedElement}
364 name={name}
365 path={path.concat(['value'])}
@@ -320,6 +381,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
381 depth={1}
382 element={element}
383 hookID={hookID}
384 + hookName={hookName}
385 inspectedElement={inspectedElement}
386 name={name}
387 path={[]}
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js
+11 -1
@@ -36,19 +36,26 @@ import styles from './InspectedElementView.css';
36
37 import type {ContextMenuContextType} from '../context';
38 import type {Element, InspectedElement, SerializedElement} from './types';
39 -import type {ElementType} from 'react-devtools-shared/src/types';
39 +import type {ElementType, HookNames} from 'react-devtools-shared/src/types';
40 +import type {ToggleParseHookNames} from './InspectedElementContext';
41
42 export type CopyPath = (path: Array<string | number>) => void;
43 export type InspectPath = (path: Array<string | number>) => void;
44
45 type Props = {|
46 element: Element,
47 + hookNames: HookNames | null,
48 inspectedElement: InspectedElement,
49 + parseHookNames: boolean,
50 + toggleParseHookNames: ToggleParseHookNames,
51 |};
52
53 export default function InspectedElementView({
54 element,
55 + hookNames,
56 inspectedElement,
57 + parseHookNames,
58 + toggleParseHookNames,
59 }: Props) {
60 const {id} = element;
61 const {
@@ -103,8 +110,11 @@ export default function InspectedElementView({
110 <InspectedElementHooksTree
111 bridge={bridge}
112 element={element}
113 + hookNames={hookNames}
114 inspectedElement={inspectedElement}
115 + parseHookNames={parseHookNames}
116 store={store}
117 + toggleParseHookNames={toggleParseHookNames}
118 />
119
120 <InspectedElementContextTree
packages/react-devtools-shared/src/devtools/views/Components/KeyValue.css
+5
@@ -11,6 +11,7 @@
11 flex: 0 0 auto;
12 cursor: default;
13 }
14 +
15 .EditableName {
16 color: var(--color-attribute-name);
17 flex: 0 0 auto;
@@ -48,4 +49,8 @@
49 .DeleteArrayItemButton {
50 padding: 0;
51 margin-right: 0.125rem;
52 +}
53 +
54 +.HookName {
55 + color: var(--color-component-name);
56 }
\ No newline at end of file
packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js
+14 -2
@@ -43,6 +43,7 @@ type KeyValueProps = {|
43 element: Element,
44 hidden: boolean,
45 hookID?: ?number,
46 + hookName?: ?string,
47 inspectedElement: InspectedElement,
48 isDirectChildOfAnArray?: boolean,
49 name: string,
@@ -65,6 +66,7 @@ export default function KeyValue({
66 isDirectChildOfAnArray,
67 hidden,
68 hookID,
69 + hookName,
70 name,
71 path,
72 pathRoot,
@@ -202,7 +204,12 @@ export default function KeyValue({
204 <DeleteToggle name={name} deletePath={deletePath} path={path} />
205 );
206 } else {
205 - renderedName = <span className={styles.Name}>{name}</span>;
207 + renderedName = (
208 + <span className={styles.Name}>
209 + {name}
210 + {!!hookName && <span className={styles.HookName}>({hookName})</span>}
211 + </span>
212 + );
213 }
214 } else if (canRenameTheCurrentPath) {
215 renderedName = (
@@ -215,7 +222,12 @@ export default function KeyValue({
222 />
223 );
224 } else {
218 - renderedName = <span className={styles.Name}>{name}</span>;
225 + renderedName = (
226 + <span className={styles.Name}>
227 + {name}
228 + {!!hookName && <span className={styles.HookName}>({hookName})</span>}
229 + </span>
230 + );
231 }
232
233 let children = null;
packages/react-devtools-shared/src/devtools/views/Components/LoadHookNamesFunctionContext.js new
+11
@@ -0,0 +1,11 @@
1 +// @flow
2 +
3 +import {createContext} from 'react';
4 +import type {LoadHookNamesFunction} from '../DevTools';
5 +
6 +export type Context = LoadHookNamesFunction | null;
7 +
8 +const LoadHookNamesFunctionContext = createContext<Context>(null);
9 +LoadHookNamesFunctionContext.displayName = 'LoadHookNamesFunctionContext';
10 +
11 +export default LoadHookNamesFunctionContext;
packages/react-devtools-shared/src/devtools/views/DevTools.js
+48 -32
@@ -22,6 +22,7 @@ import TabBar from './TabBar';
22 import {SettingsContextController} from './Settings/SettingsContext';
23 import {TreeContextController} from './Components/TreeContext';
24 import ViewElementSourceContext from './Components/ViewElementSourceContext';
25 +import LoadHookNamesFunctionContext from './Components/LoadHookNamesFunctionContext';
26 import {ProfilerContextController} from './Profiler/ProfilerContext';
27 import {ModalDialogContextController} from './ModalDialog';
28 import ReactLogo from './ReactLogo';
@@ -34,15 +35,22 @@ import styles from './DevTools.css';
35
36 import './root.css';
37
38 +import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
39 import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types';
40 import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
41 +import type {HookNames} from 'react-devtools-shared/src/types';
42 +import type {Thenable} from '../cache';
43
44 export type BrowserTheme = 'dark' | 'light';
45 export type TabID = 'components' | 'profiler';
46 +
47 export type ViewElementSource = (
48 id: number,
49 inspectedElement: InspectedElement,
50 ) => void;
51 +export type LoadHookNamesFunction = (
52 + hooksTree: HooksTree,
53 +) => Thenable<HookNames>;
54 export type ViewAttributeSource = (
55 id: number,
56 path: Array<string | number>,
@@ -75,6 +83,11 @@ export type Props = {|
83 // but individual tabs (e.g. Components, Profiling) can be rendered into portals within their browser panels.
84 componentsPortalContainer?: Element,
85 profilerPortalContainer?: Element,
86 +
87 + // Loads and parses source maps for function components
88 + // and extracts hook "names" based on the variables the hook return values get assigned to.
89 + // Not every DevTools build can load source maps, so this property is optional.
90 + loadHookNamesFunction?: ?LoadHookNamesFunction,
91 |};
92
93 const componentsTab = {
@@ -99,6 +112,7 @@ export default function DevTools({
112 componentsPortalContainer,
113 defaultTab = 'components',
114 enabledInspectedElementContextMenu = false,
115 + loadHookNamesFunction,
116 overrideTab,
117 profilerPortalContainer,
118 showTabBar = false,
@@ -180,7 +194,6 @@ export default function DevTools({
194 }
195 };
196 }, [bridge]);
183 -
197 return (
198 <BridgeContext.Provider value={bridge}>
199 <StoreContext.Provider value={store}>
@@ -191,40 +204,43 @@ export default function DevTools({
204 componentsPortalContainer={componentsPortalContainer}
205 profilerPortalContainer={profilerPortalContainer}>
206 <ViewElementSourceContext.Provider value={viewElementSource}>
194 - <TreeContextController>
195 - <ProfilerContextController>
196 - <div className={styles.DevTools} ref={devToolsRef}>
197 - {showTabBar && (
198 - <div className={styles.TabBar}>
199 - <ReactLogo />
200 - <span className={styles.DevToolsVersion}>
201 - {process.env.DEVTOOLS_VERSION}
202 - </span>
203 - <div className={styles.Spacer} />
204 - <TabBar
205 - currentTab={tab}
206 - id="DevTools"
207 - selectTab={setTab}
208 - tabs={tabs}
209 - type="navigation"
207 + <LoadHookNamesFunctionContext.Provider
208 + value={loadHookNamesFunction || null}>
209 + <TreeContextController>
210 + <ProfilerContextController>
211 + <div className={styles.DevTools} ref={devToolsRef}>
212 + {showTabBar && (
213 + <div className={styles.TabBar}>
214 + <ReactLogo />
215 + <span className={styles.DevToolsVersion}>
216 + {process.env.DEVTOOLS_VERSION}
217 + </span>
218 + <div className={styles.Spacer} />
219 + <TabBar
220 + currentTab={tab}
221 + id="DevTools"
222 + selectTab={setTab}
223 + tabs={tabs}
224 + type="navigation"
225 + />
226 + </div>
227 + )}
228 + <div
229 + className={styles.TabContent}
230 + hidden={tab !== 'components'}>
231 + <Components
232 + portalContainer={componentsPortalContainer}
233 />
234 </div>
212 - )}
213 - <div
214 - className={styles.TabContent}
215 - hidden={tab !== 'components'}>
216 - <Components
217 - portalContainer={componentsPortalContainer}
218 - />
219 - </div>
220 - <div
221 - className={styles.TabContent}
222 - hidden={tab !== 'profiler'}>
223 - <Profiler portalContainer={profilerPortalContainer} />
235 + <div
236 + className={styles.TabContent}
237 + hidden={tab !== 'profiler'}>
238 + <Profiler portalContainer={profilerPortalContainer} />
239 + </div>
240 </div>
225 - </div>
226 - </ProfilerContextController>
227 - </TreeContextController>
241 + </ProfilerContextController>
242 + </TreeContextController>
243 + </LoadHookNamesFunctionContext.Provider>
244 </ViewElementSourceContext.Provider>
245 </SettingsContextController>
246 <UnsupportedBridgeProtocolDialog />
packages/react-devtools-shared/src/devtools/views/Settings/ComponentsSettings.js
+22
@@ -16,11 +16,13 @@ import {
16 useRef,
17 useState,
18 } from 'react';
19 +import {enableHookNameParsing} from 'react-devtools-feature-flags';
20 import {useSubscription} from '../hooks';
21 import {StoreContext} from '../context';
22 import Button from '../Button';
23 import ButtonIcon from '../ButtonIcon';
24 import Toggle from '../Toggle';
25 +import {SettingsContext} from '../Settings/SettingsContext';
26 import {
27 ComponentFilterDisplayName,
28 ComponentFilterElementType,
@@ -50,6 +52,7 @@ import type {
52
53 export default function ComponentsSettings(_: {||}) {
54 const store = useContext(StoreContext);
55 + const {parseHookNames, setParseHookNames} = useContext(SettingsContext);
56
57 const collapseNodesByDefaultSubscription = useMemo(
58 () => ({
@@ -72,6 +75,13 @@ export default function ComponentsSettings(_: {||}) {
75 [store],
76 );
77
78 + const updateParseHookNames = useCallback(
79 + ({currentTarget}) => {
80 + setParseHookNames(currentTarget.checked);
81 + },
82 + [setParseHookNames],
83 + );
84 +
85 const [componentFilters, setComponentFilters] = useState<
86 Array<ComponentFilter>,
87 >(() => [...store.componentFilters]);
@@ -252,6 +262,18 @@ export default function ComponentsSettings(_: {||}) {
262 Expand component tree by default
263 </label>
264
265 + {enableHookNameParsing && (
266 + <label className={styles.Setting}>
267 + <input
268 + type="checkbox"
269 + checked={parseHookNames}
270 + onChange={updateParseHookNames}
271 + />{' '}
272 + Always parse hook names from source{' '}
273 + <span className={styles.Warning}>(may be slow)</span>
274 + </label>
275 + )}
276 +
277 <div className={styles.Header}>Hide components where...</div>
278
279 <table className={styles.Table}>
packages/react-devtools-shared/src/devtools/views/Settings/SettingsContext.js
+12
@@ -18,6 +18,7 @@ import {
18 import {
19 COMFORTABLE_LINE_HEIGHT,
20 COMPACT_LINE_HEIGHT,
21 + LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY,
22 LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
23 LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
24 LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY,
@@ -45,6 +46,9 @@ type Context = {|
46 breakOnConsoleErrors: boolean,
47 setBreakOnConsoleErrors: (value: boolean) => void,
48
49 + parseHookNames: boolean,
50 + setParseHookNames: (value: boolean) => void,
51 +
52 showInlineWarningsAndErrors: boolean,
53 setShowInlineWarningsAndErrors: (value: boolean) => void,
54
@@ -94,6 +98,10 @@ function SettingsContextController({
98 LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
99 false,
100 );
101 + const [parseHookNames, setParseHookNames] = useLocalStorage<boolean>(
102 + LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY,
103 + false,
104 + );
105 const [
106 showInlineWarningsAndErrors,
107 setShowInlineWarningsAndErrors,
@@ -180,9 +188,11 @@ function SettingsContextController({
188 displayDensity === 'compact'
189 ? COMPACT_LINE_HEIGHT
190 : COMFORTABLE_LINE_HEIGHT,
191 + parseHookNames,
192 setAppendComponentStack,
193 setBreakOnConsoleErrors,
194 setDisplayDensity,
195 + setParseHookNames,
196 setTheme,
197 setTraceUpdatesEnabled,
198 setShowInlineWarningsAndErrors,
@@ -194,9 +204,11 @@ function SettingsContextController({
204 appendComponentStack,
205 breakOnConsoleErrors,
206 displayDensity,
207 + parseHookNames,
208 setAppendComponentStack,
209 setBreakOnConsoleErrors,
210 setDisplayDensity,
211 + setParseHookNames,
212 setTheme,
213 setTraceUpdatesEnabled,
214 setShowInlineWarningsAndErrors,
packages/react-devtools-shared/src/devtools/views/Settings/SettingsShared.css
+4
@@ -139,3 +139,7 @@
139 .ReleaseNotesLink {
140 color: var(--color-button-active);
141 }
142 +
143 +.Warning {
144 + color: var(--color-error-text);
145 +}
\ No newline at end of file
packages/react-devtools-shared/src/hookNamesCache.js new
+154
@@ -0,0 +1,154 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import {unstable_getCacheForType as getCacheForType} from 'react';
11 +import {enableHookNameParsing} from 'react-devtools-feature-flags';
12 +
13 +import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
14 +import type {Thenable, Wakeable} from 'shared/ReactTypes';
15 +import type {Element} from './devtools/views/Components/types';
16 +import type {HookNames} from 'react-devtools-shared/src/types';
17 +
18 +const TIMEOUT = 3000;
19 +
20 +const Pending = 0;
21 +const Resolved = 1;
22 +const Rejected = 2;
23 +
24 +type PendingRecord = {|
25 + status: 0,
26 + value: Wakeable,
27 +|};
28 +
29 +type ResolvedRecord<T> = {|
30 + status: 1,
31 + value: T,
32 +|};
33 +
34 +type RejectedRecord = {|
35 + status: 2,
36 + value: null,
37 +|};
38 +
39 +type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
40 +
41 +function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
42 + if (record.status === Resolved) {
43 + // This is just a type refinement.
44 + return record;
45 + } else if (record.status === Rejected) {
46 + // This is just a type refinement.
47 + return record;
48 + } else {
49 + throw record.value;
50 + }
51 +}
52 +
53 +type HookNamesMap = WeakMap<Element, Record<HookNames>>;
54 +
55 +function createMap(): HookNamesMap {
56 + return new WeakMap();
57 +}
58 +
59 +function getRecordMap(): WeakMap<Element, Record<HookNames>> {
60 + return getCacheForType(createMap);
61 +}
62 +
63 +export function loadHookNames(
64 + element: Element,
65 + hooksTree: HooksTree,
66 + loadHookNamesFunction: (hookLog: HooksTree) => Thenable<HookNames>,
67 +): HookNames | null {
68 + if (!enableHookNameParsing) {
69 + return null;
70 + }
71 +
72 + const map = getRecordMap();
73 +
74 + let record = map.get(element);
75 + if (record) {
76 + // TODO Do we need to update the Map to use new the hooks list objects as keys
77 + // or will these be stable between inspections as a component updates?
78 + // It seems like they're stable.
79 + } else {
80 + const callbacks = new Set();
81 + const wakeable: Wakeable = {
82 + then(callback) {
83 + callbacks.add(callback);
84 + },
85 + };
86 +
87 + const wake = () => {
88 + if (timeoutID) {
89 + clearTimeout(timeoutID);
90 + timeoutID = null;
91 + }
92 +
93 + // This assumes they won't throw.
94 + callbacks.forEach(callback => callback());
95 + callbacks.clear();
96 + };
97 +
98 + const newRecord: Record<HookNames> = (record = {
99 + status: Pending,
100 + value: wakeable,
101 + });
102 +
103 + let didTimeout = false;
104 +
105 + loadHookNamesFunction(hooksTree).then(
106 + function onSuccess(hookNames) {
107 + if (didTimeout) {
108 + return;
109 + }
110 +
111 + if (hookNames) {
112 + const resolvedRecord = ((newRecord: any): ResolvedRecord<HookNames>);
113 + resolvedRecord.status = Resolved;
114 + resolvedRecord.value = hookNames;
115 + } else {
116 + const notFoundRecord = ((newRecord: any): RejectedRecord);
117 + notFoundRecord.status = Rejected;
118 + notFoundRecord.value = null;
119 + }
120 +
121 + wake();
122 + },
123 + function onError(error) {
124 + if (didTimeout) {
125 + return;
126 + }
127 +
128 + const thrownRecord = ((newRecord: any): RejectedRecord);
129 + thrownRecord.status = Rejected;
130 + thrownRecord.value = null;
131 +
132 + wake();
133 + },
134 + );
135 +
136 + // Eventually timeout and stop trying to load names.
137 + let timeoutID = setTimeout(() => {
138 + timeoutID = null;
139 +
140 + didTimeout = true;
141 +
142 + const timedoutRecord = ((newRecord: any): RejectedRecord);
143 + timedoutRecord.status = Rejected;
144 + timedoutRecord.value = null;
145 +
146 + wake();
147 + }, TIMEOUT);
148 +
149 + map.set(element, record);
150 + }
151 +
152 + const response = readRecord(record).value;
153 + return response;
154 +}
packages/react-devtools-shared/src/types.js
+11
@@ -7,6 +7,8 @@
7 * @flow
8 */
9
10 +import type {HooksNode} from 'react-debug-tools/src/ReactDebugHooks';
11 +
12 export type Wall = {|
13 // `listen` returns the "unlisten" function.
14 listen: (fn: Function) => Function,
@@ -76,3 +78,12 @@ export type ComponentFilter =
78 | BooleanComponentFilter
79 | ElementTypeComponentFilter
80 | RegExpComponentFilter;
81 +
82 +export type HookName = string | null;
83 +export type HookNames = Map<HooksNode, HookName>;
84 +
85 +export type LRUCache<K, V> = {|
86 + get: (key: K) => V,
87 + has: (key: K) => boolean,
88 + set: (key: K, value: V) => void,
89 +|};
packages/react-devtools-shared/src/utils.js
+5 -1
@@ -47,13 +47,17 @@ import {
47 } from 'react-devtools-shared/src/types';
48 import {localStorageGetItem, localStorageSetItem} from './storage';
49 import {meta} from './hydration';
50 +
51 import type {ComponentFilter, ElementType} from './types';
52 +import type {LRUCache} from 'react-devtools-shared/src/types';
53
54 const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
55
56 // On large trees, encoding takes significant time.
57 // Try to reuse the already encoded strings.
56 -const encodedStringCache = new LRU({max: 1000});
58 +const encodedStringCache: LRUCache<string, Array<number>> = new LRU({
59 + max: 1000,
60 +});
61
62 export function alphaSortKeys(
63 a: string | number | Symbol,
packages/react-devtools-shell/webpack.config.js
+5
@@ -31,6 +31,11 @@ const config = {
31 app: './src/app/index.js',
32 devtools: './src/devtools.js',
33 },
34 + node: {
35 + // source-maps package has a dependency on 'fs'
36 + // but this build won't trigger that code path
37 + fs: 'empty',
38 + },
39 resolve: {
40 alias: {
41 react: resolve(builtModulesDir, 'react'),
scripts/jest/config.build-devtools.js
+10 -2
@@ -55,12 +55,20 @@ module.exports = Object.assign({}, baseConfig, {
55 // Don't run bundle tests on -test.internal.* files
56 testPathIgnorePatterns: ['/node_modules/', '-test.internal.js$'],
57 // Exclude the build output from transforms
58 - transformIgnorePatterns: ['/node_modules/', '<rootDir>/build2/'],
59 - testRegex: 'packages/react-devtools-shared/src/__tests__/[^]+.test.js$',
58 + transformIgnorePatterns: [
59 + '/node_modules/',
60 + '<rootDir>/build2/',
61 + '/__compiled__/',
62 + ],
63 + testRegex:
64 + 'packages/react-devtools-(extensions|shared)/src/__tests__/[^]+.test.js$',
65 snapshotSerializers: [
66 require.resolve(
67 '../../packages/react-devtools-shared/src/__tests__/dehydratedValueSerializer.js'
68 ),
69 + require.resolve(
70 + '../../packages/react-devtools-shared/src/__tests__/hookSerializer.js'
71 + ),
72 require.resolve(
73 '../../packages/react-devtools-shared/src/__tests__/inspectedElementSerializer.js'
74 ),
scripts/jest/config.build.js
+1
@@ -52,6 +52,7 @@ module.exports = Object.assign({}, baseConfig, {
52 moduleNameMapper,
53 modulePathIgnorePatterns: [
54 ...baseConfig.modulePathIgnorePatterns,
55 + 'packages/react-devtools-extensions',
56 'packages/react-devtools-shared',
57 ],
58 // Don't run bundle tests on -test.internal.* files
scripts/jest/config.source-persistent.js
+1
@@ -5,6 +5,7 @@ const baseConfig = require('./config.base');
5 module.exports = Object.assign({}, baseConfig, {
6 modulePathIgnorePatterns: [
7 ...baseConfig.modulePathIgnorePatterns,
8 + 'packages/react-devtools-extensions',
9 'packages/react-devtools-shared',
10 'ReactIncrementalPerf',
11 'ReactIncrementalUpdatesMinimalism',
scripts/jest/config.source-www.js
+1
@@ -5,6 +5,7 @@ const baseConfig = require('./config.base');
5 module.exports = Object.assign({}, baseConfig, {
6 modulePathIgnorePatterns: [
7 ...baseConfig.modulePathIgnorePatterns,
8 + 'packages/react-devtools-extensions',
9 'packages/react-devtools-shared',
10 ],
11 setupFiles: [
scripts/jest/config.source.js
+1
@@ -5,6 +5,7 @@ const baseConfig = require('./config.base');
5 module.exports = Object.assign({}, baseConfig, {
6 modulePathIgnorePatterns: [
7 ...baseConfig.modulePathIgnorePatterns,
8 + 'packages/react-devtools-extensions',
9 'packages/react-devtools-shared',
10 ],
11 setupFiles: [
scripts/prettier/index.js
+8 -1
@@ -25,7 +25,14 @@ let didWarn = false;
25 let didError = false;
26
27 const files = glob
28 - .sync('**/*.js', {ignore: ['**/node_modules/**', '**/cjs/**']})
28 + .sync('**/*.js', {
29 + ignore: [
30 + '**/node_modules/**',
31 + '**/cjs/**',
32 + '**/__compiled__/**',
33 + 'packages/react-devtools-extensions/src/ErrorTesterCompiled.js',
34 + ],
35 + })
36 .filter(f => !onlyChanged || changedFiles.has(f));
37
38 if (!files.length) {
yarn.lock
+279 -10
@@ -124,6 +124,15 @@
124 jsesc "^2.5.1"
125 source-map "^0.5.0"
126
127 +"@babel/generator@^7.12.5":
128 + version "7.12.5"
129 + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.5.tgz#a2c50de5c8b6d708ab95be5e6053936c1884a4de"
130 + integrity sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==
131 + dependencies:
132 + "@babel/types" "^7.12.5"
133 + jsesc "^2.5.1"
134 + source-map "^0.5.0"
135 +
136 "@babel/generator@^7.8.3":
137 version "7.8.3"
138 resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03"
@@ -481,7 +490,7 @@
490 dependencies:
491 "@babel/types" "^7.8.3"
492
484 -"@babel/helper-validator-identifier@^7.14.0":
493 +"@babel/helper-validator-identifier@^7.10.4", "@babel/helper-validator-identifier@^7.14.0":
494 version "7.14.0"
495 resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288"
496 integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==
@@ -547,6 +556,11 @@
556 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.3.tgz#9b530eecb071fd0c93519df25c5ff9f14759f298"
557 integrity sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ==
558
559 +"@babel/parser@^7.12.5":
560 + version "7.12.5"
561 + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.5.tgz#b4af32ddd473c0bfa643bd7ff0728b8e71b81ea0"
562 + integrity sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==
563 +
564 "@babel/parser@^7.8.6", "@babel/parser@^7.9.0":
565 version "7.9.4"
566 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8"
@@ -1507,6 +1521,21 @@
1521 globals "^11.1.0"
1522 lodash "^4.17.19"
1523
1524 +"@babel/traverse@^7.12.5":
1525 + version "7.12.5"
1526 + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.5.tgz#78a0c68c8e8a35e4cacfd31db8bb303d5606f095"
1527 + integrity sha512-xa15FbQnias7z9a62LwYAA5SZZPkHIXpd42C6uW68o8uTuua96FHZy1y61Va5P/i83FAAcMpW8+A/QayntzuqA==
1528 + dependencies:
1529 + "@babel/code-frame" "^7.10.4"
1530 + "@babel/generator" "^7.12.5"
1531 + "@babel/helper-function-name" "^7.10.4"
1532 + "@babel/helper-split-export-declaration" "^7.11.0"
1533 + "@babel/parser" "^7.12.5"
1534 + "@babel/types" "^7.12.5"
1535 + debug "^4.1.0"
1536 + globals "^11.1.0"
1537 + lodash "^4.17.19"
1538 +
1539 "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0":
1540 version "7.9.5"
1541 resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2"
@@ -1539,6 +1568,15 @@
1568 "@babel/helper-validator-identifier" "^7.14.0"
1569 to-fast-properties "^2.0.0"
1570
1571 +"@babel/types@^7.12.5":
1572 + version "7.12.6"
1573 + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.6.tgz#ae0e55ef1cce1fbc881cd26f8234eb3e657edc96"
1574 + integrity sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==
1575 + dependencies:
1576 + "@babel/helper-validator-identifier" "^7.10.4"
1577 + lodash "^4.17.19"
1578 + to-fast-properties "^2.0.0"
1579 +
1580 "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5":
1581 version "7.9.5"
1582 resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444"
@@ -3353,6 +3391,41 @@ babel-eslint@^9.0.0:
3391 eslint-scope "3.7.1"
3392 eslint-visitor-keys "^1.0.0"
3393
3394 +babel-helper-evaluate-path@^0.5.0:
3395 + version "0.5.0"
3396 + resolved "https://registry.yarnpkg.com/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.5.0.tgz#a62fa9c4e64ff7ea5cea9353174ef023a900a67c"
3397 + integrity sha512-mUh0UhS607bGh5wUMAQfOpt2JX2ThXMtppHRdRU1kL7ZLRWIXxoV2UIV1r2cAeeNeU1M5SB5/RSUgUxrK8yOkA==
3398 +
3399 +babel-helper-flip-expressions@^0.4.3:
3400 + version "0.4.3"
3401 + resolved "https://registry.yarnpkg.com/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.4.3.tgz#3696736a128ac18bc25254b5f40a22ceb3c1d3fd"
3402 + integrity sha1-NpZzahKKwYvCUlS19AoizrPB0/0=
3403 +
3404 +babel-helper-is-nodes-equiv@^0.0.1:
3405 + version "0.0.1"
3406 + resolved "https://registry.yarnpkg.com/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz#34e9b300b1479ddd98ec77ea0bbe9342dfe39684"
3407 + integrity sha1-NOmzALFHnd2Y7HfqC76TQt/jloQ=
3408 +
3409 +babel-helper-is-void-0@^0.4.3:
3410 + version "0.4.3"
3411 + resolved "https://registry.yarnpkg.com/babel-helper-is-void-0/-/babel-helper-is-void-0-0.4.3.tgz#7d9c01b4561e7b95dbda0f6eee48f5b60e67313e"
3412 + integrity sha1-fZwBtFYee5Xb2g9u7kj1tg5nMT4=
3413 +
3414 +babel-helper-mark-eval-scopes@^0.4.3:
3415 + version "0.4.3"
3416 + resolved "https://registry.yarnpkg.com/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.4.3.tgz#d244a3bef9844872603ffb46e22ce8acdf551562"
3417 + integrity sha1-0kSjvvmESHJgP/tG4izorN9VFWI=
3418 +
3419 +babel-helper-remove-or-void@^0.4.3:
3420 + version "0.4.3"
3421 + resolved "https://registry.yarnpkg.com/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.4.3.tgz#a4f03b40077a0ffe88e45d07010dee241ff5ae60"
3422 + integrity sha1-pPA7QAd6D/6I5F0HAQ3uJB/1rmA=
3423 +
3424 +babel-helper-to-multiple-sequence-expressions@^0.5.0:
3425 + version "0.5.0"
3426 + resolved "https://registry.yarnpkg.com/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz#a3f924e3561882d42fcf48907aa98f7979a4588d"
3427 + integrity sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==
3428 +
3429 babel-jest@^26.6.3:
3430 version "26.6.3"
3431 resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056"
@@ -3423,6 +3496,82 @@ babel-plugin-jest-hoist@^26.6.2:
3496 "@types/babel__core" "^7.0.0"
3497 "@types/babel__traverse" "^7.0.6"
3498
3499 +babel-plugin-minify-builtins@^0.5.0:
3500 + version "0.5.0"
3501 + resolved "https://registry.yarnpkg.com/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.5.0.tgz#31eb82ed1a0d0efdc31312f93b6e4741ce82c36b"
3502 + integrity sha512-wpqbN7Ov5hsNwGdzuzvFcjgRlzbIeVv1gMIlICbPj0xkexnfoIDe7q+AZHMkQmAE/F9R5jkrB6TLfTegImlXag==
3503 +
3504 +babel-plugin-minify-constant-folding@^0.5.0:
3505 + version "0.5.0"
3506 + resolved "https://registry.yarnpkg.com/babel-plugin-minify-constant-folding/-/babel-plugin-minify-constant-folding-0.5.0.tgz#f84bc8dbf6a561e5e350ff95ae216b0ad5515b6e"
3507 + integrity sha512-Vj97CTn/lE9hR1D+jKUeHfNy+m1baNiJ1wJvoGyOBUx7F7kJqDZxr9nCHjO/Ad+irbR3HzR6jABpSSA29QsrXQ==
3508 + dependencies:
3509 + babel-helper-evaluate-path "^0.5.0"
3510 +
3511 +babel-plugin-minify-dead-code-elimination@^0.5.1:
3512 + version "0.5.1"
3513 + resolved "https://registry.yarnpkg.com/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.1.tgz#1a0c68e44be30de4976ca69ffc535e08be13683f"
3514 + integrity sha512-x8OJOZIrRmQBcSqxBcLbMIK8uPmTvNWPXH2bh5MDCW1latEqYiRMuUkPImKcfpo59pTUB2FT7HfcgtG8ZlR5Qg==
3515 + dependencies:
3516 + babel-helper-evaluate-path "^0.5.0"
3517 + babel-helper-mark-eval-scopes "^0.4.3"
3518 + babel-helper-remove-or-void "^0.4.3"
3519 + lodash "^4.17.11"
3520 +
3521 +babel-plugin-minify-flip-comparisons@^0.4.3:
3522 + version "0.4.3"
3523 + resolved "https://registry.yarnpkg.com/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.4.3.tgz#00ca870cb8f13b45c038b3c1ebc0f227293c965a"
3524 + integrity sha1-AMqHDLjxO0XAOLPB68DyJyk8llo=
3525 + dependencies:
3526 + babel-helper-is-void-0 "^0.4.3"
3527 +
3528 +babel-plugin-minify-guarded-expressions@^0.4.4:
3529 + version "0.4.4"
3530 + resolved "https://registry.yarnpkg.com/babel-plugin-minify-guarded-expressions/-/babel-plugin-minify-guarded-expressions-0.4.4.tgz#818960f64cc08aee9d6c75bec6da974c4d621135"
3531 + integrity sha512-RMv0tM72YuPPfLT9QLr3ix9nwUIq+sHT6z8Iu3sLbqldzC1Dls8DPCywzUIzkTx9Zh1hWX4q/m9BPoPed9GOfA==
3532 + dependencies:
3533 + babel-helper-evaluate-path "^0.5.0"
3534 + babel-helper-flip-expressions "^0.4.3"
3535 +
3536 +babel-plugin-minify-infinity@^0.4.3:
3537 + version "0.4.3"
3538 + resolved "https://registry.yarnpkg.com/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.4.3.tgz#dfb876a1b08a06576384ef3f92e653ba607b39ca"
3539 + integrity sha1-37h2obCKBldjhO8/kuZTumB7Oco=
3540 +
3541 +babel-plugin-minify-mangle-names@^0.5.0:
3542 + version "0.5.0"
3543 + resolved "https://registry.yarnpkg.com/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.0.tgz#bcddb507c91d2c99e138bd6b17a19c3c271e3fd3"
3544 + integrity sha512-3jdNv6hCAw6fsX1p2wBGPfWuK69sfOjfd3zjUXkbq8McbohWy23tpXfy5RnToYWggvqzuMOwlId1PhyHOfgnGw==
3545 + dependencies:
3546 + babel-helper-mark-eval-scopes "^0.4.3"
3547 +
3548 +babel-plugin-minify-numeric-literals@^0.4.3:
3549 + version "0.4.3"
3550 + resolved "https://registry.yarnpkg.com/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.4.3.tgz#8e4fd561c79f7801286ff60e8c5fd9deee93c0bc"
3551 + integrity sha1-jk/VYcefeAEob/YOjF/Z3u6TwLw=
3552 +
3553 +babel-plugin-minify-replace@^0.5.0:
3554 + version "0.5.0"
3555 + resolved "https://registry.yarnpkg.com/babel-plugin-minify-replace/-/babel-plugin-minify-replace-0.5.0.tgz#d3e2c9946c9096c070efc96761ce288ec5c3f71c"
3556 + integrity sha512-aXZiaqWDNUbyNNNpWs/8NyST+oU7QTpK7J9zFEFSA0eOmtUNMU3fczlTTTlnCxHmq/jYNFEmkkSG3DDBtW3Y4Q==
3557 +
3558 +babel-plugin-minify-simplify@^0.5.1:
3559 + version "0.5.1"
3560 + resolved "https://registry.yarnpkg.com/babel-plugin-minify-simplify/-/babel-plugin-minify-simplify-0.5.1.tgz#f21613c8b95af3450a2ca71502fdbd91793c8d6a"
3561 + integrity sha512-OSYDSnoCxP2cYDMk9gxNAed6uJDiDz65zgL6h8d3tm8qXIagWGMLWhqysT6DY3Vs7Fgq7YUDcjOomhVUb+xX6A==
3562 + dependencies:
3563 + babel-helper-evaluate-path "^0.5.0"
3564 + babel-helper-flip-expressions "^0.4.3"
3565 + babel-helper-is-nodes-equiv "^0.0.1"
3566 + babel-helper-to-multiple-sequence-expressions "^0.5.0"
3567 +
3568 +babel-plugin-minify-type-constructors@^0.4.3:
3569 + version "0.4.3"
3570 + resolved "https://registry.yarnpkg.com/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.4.3.tgz#1bc6f15b87f7ab1085d42b330b717657a2156500"
3571 + integrity sha1-G8bxW4f3qxCF1CszC3F2V6IVZQA=
3572 + dependencies:
3573 + babel-helper-is-void-0 "^0.4.3"
3574 +
3575 babel-plugin-syntax-trailing-function-commas@^6.5.0:
3576 version "6.22.0"
3577 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
@@ -3433,6 +3582,65 @@ babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0:
3582 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz#aa213c1435e2bffeb6fca842287ef534ad05d5cf"
3583 integrity sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==
3584
3585 +babel-plugin-transform-inline-consecutive-adds@^0.4.3:
3586 + version "0.4.3"
3587 + resolved "https://registry.yarnpkg.com/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz#323d47a3ea63a83a7ac3c811ae8e6941faf2b0d1"
3588 + integrity sha1-Mj1Ho+pjqDp6w8gRro5pQfrysNE=
3589 +
3590 +babel-plugin-transform-member-expression-literals@^6.9.4:
3591 + version "6.9.4"
3592 + resolved "https://registry.yarnpkg.com/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz#37039c9a0c3313a39495faac2ff3a6b5b9d038bf"
3593 + integrity sha1-NwOcmgwzE6OUlfqsL/OmtbnQOL8=
3594 +
3595 +babel-plugin-transform-merge-sibling-variables@^6.9.4:
3596 + version "6.9.4"
3597 + resolved "https://registry.yarnpkg.com/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.4.tgz#85b422fc3377b449c9d1cde44087203532401dae"
3598 + integrity sha1-hbQi/DN3tEnJ0c3kQIcgNTJAHa4=
3599 +
3600 +babel-plugin-transform-minify-booleans@^6.9.4:
3601 + version "6.9.4"
3602 + resolved "https://registry.yarnpkg.com/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz#acbb3e56a3555dd23928e4b582d285162dd2b198"
3603 + integrity sha1-rLs+VqNVXdI5KOS1gtKFFi3SsZg=
3604 +
3605 +babel-plugin-transform-property-literals@^6.9.4:
3606 + version "6.9.4"
3607 + resolved "https://registry.yarnpkg.com/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz#98c1d21e255736573f93ece54459f6ce24985d39"
3608 + integrity sha1-mMHSHiVXNlc/k+zlRFn2ziSYXTk=
3609 + dependencies:
3610 + esutils "^2.0.2"
3611 +
3612 +babel-plugin-transform-regexp-constructors@^0.4.3:
3613 + version "0.4.3"
3614 + resolved "https://registry.yarnpkg.com/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.4.3.tgz#58b7775b63afcf33328fae9a5f88fbd4fb0b4965"
3615 + integrity sha1-WLd3W2OvzzMyj66aX4j71PsLSWU=
3616 +
3617 +babel-plugin-transform-remove-console@^6.9.4:
3618 + version "6.9.4"
3619 + resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz#b980360c067384e24b357a588d807d3c83527780"
3620 + integrity sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A=
3621 +
3622 +babel-plugin-transform-remove-debugger@^6.9.4:
3623 + version "6.9.4"
3624 + resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz#42b727631c97978e1eb2d199a7aec84a18339ef2"
3625 + integrity sha1-QrcnYxyXl44estGZp67IShgznvI=
3626 +
3627 +babel-plugin-transform-remove-undefined@^0.5.0:
3628 + version "0.5.0"
3629 + resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-undefined/-/babel-plugin-transform-remove-undefined-0.5.0.tgz#80208b31225766c630c97fa2d288952056ea22dd"
3630 + integrity sha512-+M7fJYFaEE/M9CXa0/IRkDbiV3wRELzA1kKQFCJ4ifhrzLKn/9VCCgj9OFmYWwBd8IB48YdgPkHYtbYq+4vtHQ==
3631 + dependencies:
3632 + babel-helper-evaluate-path "^0.5.0"
3633 +
3634 +babel-plugin-transform-simplify-comparison-operators@^6.9.4:
3635 + version "6.9.4"
3636 + resolved "https://registry.yarnpkg.com/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz#f62afe096cab0e1f68a2d753fdf283888471ceb9"
3637 + integrity sha1-9ir+CWyrDh9ootdT/fKDiIRxzrk=
3638 +
3639 +babel-plugin-transform-undefined-to-void@^6.9.4:
3640 + version "6.9.4"
3641 + resolved "https://registry.yarnpkg.com/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz#be241ca81404030678b748717322b89d0c8fe280"
3642 + integrity sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA=
3643 +
3644 babel-preset-current-node-syntax@^1.0.0:
3645 version "1.0.1"
3646 resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b"
@@ -3492,6 +3700,35 @@ babel-preset-jest@^26.6.2:
3700 babel-plugin-jest-hoist "^26.6.2"
3701 babel-preset-current-node-syntax "^1.0.0"
3702
3703 +babel-preset-minify@^0.5.1:
3704 + version "0.5.1"
3705 + resolved "https://registry.yarnpkg.com/babel-preset-minify/-/babel-preset-minify-0.5.1.tgz#25f5d0bce36ec818be80338d0e594106e21eaa9f"
3706 + integrity sha512-1IajDumYOAPYImkHbrKeiN5AKKP9iOmRoO2IPbIuVp0j2iuCcj0n7P260z38siKMZZ+85d3mJZdtW8IgOv+Tzg==
3707 + dependencies:
3708 + babel-plugin-minify-builtins "^0.5.0"
3709 + babel-plugin-minify-constant-folding "^0.5.0"
3710 + babel-plugin-minify-dead-code-elimination "^0.5.1"
3711 + babel-plugin-minify-flip-comparisons "^0.4.3"
3712 + babel-plugin-minify-guarded-expressions "^0.4.4"
3713 + babel-plugin-minify-infinity "^0.4.3"
3714 + babel-plugin-minify-mangle-names "^0.5.0"
3715 + babel-plugin-minify-numeric-literals "^0.4.3"
3716 + babel-plugin-minify-replace "^0.5.0"
3717 + babel-plugin-minify-simplify "^0.5.1"
3718 + babel-plugin-minify-type-constructors "^0.4.3"
3719 + babel-plugin-transform-inline-consecutive-adds "^0.4.3"
3720 + babel-plugin-transform-member-expression-literals "^6.9.4"
3721 + babel-plugin-transform-merge-sibling-variables "^6.9.4"
3722 + babel-plugin-transform-minify-booleans "^6.9.4"
3723 + babel-plugin-transform-property-literals "^6.9.4"
3724 + babel-plugin-transform-regexp-constructors "^0.4.3"
3725 + babel-plugin-transform-remove-console "^6.9.4"
3726 + babel-plugin-transform-remove-debugger "^6.9.4"
3727 + babel-plugin-transform-remove-undefined "^0.5.0"
3728 + babel-plugin-transform-simplify-comparison-operators "^6.9.4"
3729 + babel-plugin-transform-undefined-to-void "^6.9.4"
3730 + lodash "^4.17.11"
3731 +
3732 balanced-match@^1.0.0:
3733 version "1.0.0"
3734 resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -4754,6 +4991,13 @@ cross-env@^3.1.4:
4991 cross-spawn "^5.1.0"
4992 is-windows "^1.0.0"
4993
4994 +cross-fetch@^3.0.4:
4995 + version "3.1.4"
4996 + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
4997 + integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==
4998 + dependencies:
4999 + node-fetch "2.6.1"
5000 +
5001 cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
5002 version "6.0.5"
5003 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -6780,6 +7024,15 @@ fs-exists-sync@^0.1.0:
7024 resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
7025 integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=
7026
7027 +fs-extra@^4.0.2, fs-extra@~4.0.2:
7028 + version "4.0.3"
7029 + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
7030 + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
7031 + dependencies:
7032 + graceful-fs "^4.1.2"
7033 + jsonfile "^4.0.0"
7034 + universalify "^0.1.0"
7035 +
7036 fs-extra@^8.1.0:
7037 version "8.1.0"
7038 resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@@ -6789,15 +7042,6 @@ fs-extra@^8.1.0:
7042 jsonfile "^4.0.0"
7043 universalify "^0.1.0"
7044
6792 -fs-extra@~4.0.2:
6793 - version "4.0.3"
6794 - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
6795 - integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
6796 - dependencies:
6797 - graceful-fs "^4.1.2"
6798 - jsonfile "^4.0.0"
6799 - universalify "^0.1.0"
6800 -
7045 fs-minipass@^2.0.0:
7046 version "2.1.0"
7047 resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
@@ -8512,6 +8756,14 @@ jest-environment-node@^26.6.2:
8756 jest-mock "^26.6.2"
8757 jest-util "^26.6.2"
8758
8759 +jest-fetch-mock@^3.0.3:
8760 + version "3.0.3"
8761 + resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b"
8762 + integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==
8763 + dependencies:
8764 + cross-fetch "^3.0.4"
8765 + promise-polyfill "^8.1.3"
8766 +
8767 jest-get-type@^26.3.0:
8768 version "26.3.0"
8769 resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
@@ -10041,6 +10293,11 @@ node-cleanup@^2.1.2:
10293 resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c"
10294 integrity sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=
10295
10296 +node-fetch@2.6.1:
10297 + version "2.6.1"
10298 + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
10299 + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
10300 +
10301 node-fetch@^1.0.1:
10302 version "1.7.3"
10303 resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
@@ -11267,6 +11524,11 @@ promise-polyfill@^6.0.1:
11524 resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-6.1.0.tgz#dfa96943ea9c121fca4de9b5868cb39d3472e057"
11525 integrity sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=
11526
11527 +promise-polyfill@^8.1.3:
11528 + version "8.2.0"
11529 + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0"
11530 + integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==
11531 +
11532 promise-retry@^1.1.1:
11533 version "1.1.1"
11534 resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d"
@@ -12778,6 +13040,13 @@ source-map@^0.7.3:
13040 resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
13041 integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
13042
13043 +source-map@^0.8.0-beta.0:
13044 + version "0.8.0-beta.0"
13045 + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11"
13046 + integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==
13047 + dependencies:
13048 + whatwg-url "^7.0.0"
13049 +
13050 sourcemap-codec@^1.4.1, sourcemap-codec@^1.4.4:
13051 version "1.4.8"
13052 resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"