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>
This commit is contained in:
parent
ab390c65ee
commit
c5cfa71948
|
@ -20,6 +20,8 @@ packages/react-devtools-core/dist
|
|||
packages/react-devtools-extensions/chrome/build
|
||||
packages/react-devtools-extensions/firefox/build
|
||||
packages/react-devtools-extensions/shared/build
|
||||
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/
|
||||
packages/react-devtools-extensions/src/ErrorTesterCompiled.js
|
||||
packages/react-devtools-inline/dist
|
||||
packages/react-devtools-shell/dist
|
||||
packages/react-devtools-scheduling-profiler/dist
|
||||
|
|
|
@ -2,6 +2,8 @@ packages/react-devtools-core/dist
|
|||
packages/react-devtools-extensions/chrome/build
|
||||
packages/react-devtools-extensions/firefox/build
|
||||
packages/react-devtools-extensions/shared/build
|
||||
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/
|
||||
packages/react-devtools-extensions/src/ErrorTesterCompiled.js
|
||||
packages/react-devtools-inline/dist
|
||||
packages/react-devtools-shell/dist
|
||||
packages/react-devtools-scheduling-profiler/dist
|
||||
|
|
|
@ -107,7 +107,7 @@
|
|||
"scripts": {
|
||||
"build": "node ./scripts/rollup/build.js",
|
||||
"build-combined": "node ./scripts/rollup/build-all-release-channels.js",
|
||||
"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/",
|
||||
"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/",
|
||||
"build-for-devtools-dev": "yarn build-for-devtools --type=NODE_DEV",
|
||||
"build-for-devtools-prod": "yarn build-for-devtools --type=NODE_PROD",
|
||||
"linc": "node ./scripts/tasks/linc.js",
|
||||
|
|
|
@ -47,6 +47,9 @@ module.exports = {
|
|||
scheduler: resolve(builtModulesDir, 'scheduler'),
|
||||
},
|
||||
},
|
||||
node: {
|
||||
fs: 'empty',
|
||||
},
|
||||
plugins: [
|
||||
new DefinePlugin({
|
||||
__DEV__,
|
||||
|
|
|
@ -93,6 +93,12 @@ const build = async (tempPath, manifestPath) => {
|
|||
STATIC_FILES.map(file => copy(join(__dirname, file), join(zipPath, file))),
|
||||
);
|
||||
|
||||
// The "source-map" library requires this chunk of WASM to be bundled at runtime.
|
||||
await copy(
|
||||
join(__dirname, 'node_modules', 'source-map', 'lib', 'mappings.wasm'),
|
||||
join(zipPath, 'mappings.wasm'),
|
||||
);
|
||||
|
||||
const commit = getGitCommit();
|
||||
const dateString = new Date().toLocaleDateString();
|
||||
const manifest = JSON.parse(readFileSync(copiedManifestPath).toString());
|
||||
|
|
|
@ -32,7 +32,8 @@
|
|||
"main.html",
|
||||
"panel.html",
|
||||
"build/react_devtools_backend.js",
|
||||
"build/renderer.js"
|
||||
"build/renderer.js",
|
||||
"mappings.wasm"
|
||||
],
|
||||
|
||||
"background": {
|
||||
|
|
|
@ -32,7 +32,8 @@
|
|||
"main.html",
|
||||
"panel.html",
|
||||
"build/react_devtools_backend.js",
|
||||
"build/renderer.js"
|
||||
"build/renderer.js",
|
||||
"mappings.wasm"
|
||||
],
|
||||
|
||||
"background": {
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
"main.html",
|
||||
"panel.html",
|
||||
"build/react_devtools_backend.js",
|
||||
"build/renderer.js"
|
||||
"build/renderer.js",
|
||||
"mappings.wasm"
|
||||
],
|
||||
|
||||
"background": {
|
||||
|
|
|
@ -15,12 +15,14 @@
|
|||
"build:edge:dev": "cross-env NODE_ENV=development node ./edge/build",
|
||||
"test:chrome": "node ./chrome/test",
|
||||
"test:firefox": "node ./firefox/test",
|
||||
"test:edge": "node ./edge/test"
|
||||
"test:edge": "node ./edge/test",
|
||||
"update-mock-source-maps": "node ./src/__tests__/updateMockSourceMaps.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.1",
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.10.4",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.10.4",
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.10.5",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@babel/preset-flow": "^7.10.4",
|
||||
|
@ -29,16 +31,20 @@
|
|||
"babel-core": "^7.0.0-bridge",
|
||||
"babel-eslint": "^9.0.0",
|
||||
"babel-loader": "^8.0.4",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"chrome-launch": "^1.1.4",
|
||||
"crx": "^5.0.0",
|
||||
"css-loader": "^1.0.1",
|
||||
"firefox-profile": "^1.0.2",
|
||||
"fs-extra": "^4.0.2",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"node-libs-browser": "0.5.3",
|
||||
"nullthrows": "^1.0.0",
|
||||
"open": "^7.0.2",
|
||||
"os-name": "^3.1.0",
|
||||
"raw-loader": "^3.1.0",
|
||||
"source-map": "^0.8.0-beta.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"web-ext": "^3.0.0",
|
||||
"webpack": "^4.43.0",
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import ErrorStackParser from 'error-stack-parser';
|
||||
import testErrorStack, {
|
||||
SOURCE_STACK_FRAME_LINE_NUMBER,
|
||||
} from './ErrorTesterCompiled';
|
||||
|
||||
let sourceMapsAreAppliedToErrors: boolean | null = null;
|
||||
|
||||
// Source maps are automatically applied to Error stack frames.
|
||||
export function areSourceMapsAppliedToErrors(): boolean {
|
||||
if (sourceMapsAreAppliedToErrors === null) {
|
||||
try {
|
||||
testErrorStack();
|
||||
sourceMapsAreAppliedToErrors = false;
|
||||
} catch (error) {
|
||||
const parsed = ErrorStackParser.parse(error);
|
||||
const topStackFrame = parsed[0];
|
||||
const lineNumber = topStackFrame.lineNumber;
|
||||
if (lineNumber === SOURCE_STACK_FRAME_LINE_NUMBER) {
|
||||
sourceMapsAreAppliedToErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sourceMapsAreAppliedToErrors === true;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = testErrorStack;
|
||||
exports.SOURCE_STACK_FRAME_LINE_NUMBER = void 0;
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
function testErrorStack() {
|
||||
// If source maps are applied, this Error will have a stack frame with line 12.
|
||||
throw Error('Test Error stack');
|
||||
}
|
||||
|
||||
const SOURCE_STACK_FRAME_LINE_NUMBER = 12;
|
||||
exports.SOURCE_STACK_FRAME_LINE_NUMBER = SOURCE_STACK_FRAME_LINE_NUMBER;
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3RFcm9yclN0YWNrLmpzIl0sIm5hbWVzIjpbInRlc3RFcm9yclN0YWNrIiwiRXJyb3IiLCJTT1VSQ0VfU1RBQ0tfRlJBTUVfTElORV9OVU1CRVIiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O0FBQUE7Ozs7Ozs7O0FBU2UsU0FBU0EsY0FBVCxHQUEwQjtBQUN2QztBQUNBLFFBQU1DLEtBQUssQ0FBQyxrQkFBRCxDQUFYO0FBQ0Q7O0FBRU0sTUFBTUMsOEJBQThCLEdBQUcsRUFBdkMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvcHlyaWdodCAoYykgRmFjZWJvb2ssIEluYy4gYW5kIGl0cyBhZmZpbGlhdGVzLlxuICpcbiAqIFRoaXMgc291cmNlIGNvZGUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlIGZvdW5kIGluIHRoZVxuICogTElDRU5TRSBmaWxlIGluIHRoZSByb290IGRpcmVjdG9yeSBvZiB0aGlzIHNvdXJjZSB0cmVlLlxuICpcbiAqIEBmbG93XG4gKi9cblxuZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gdGVzdEVyb3JyU3RhY2soKSB7XG4gIC8vIElmIHNvdXJjZSBtYXBzIGFyZSBhcHBsaWVkLCB0aGlzIEVycm9yIHdpbGwgaGF2ZSBhIHN0YWNrIGZyYW1lIHdpdGggbGluZSAxMi5cbiAgdGhyb3cgRXJyb3IoJ1Rlc3QgRXJyb3Igc3RhY2snKTtcbn1cblxuZXhwb3J0IGNvbnN0IFNPVVJDRV9TVEFDS19GUkFNRV9MSU5FX05VTUJFUiA9IDEyOyJdfQ==
|
||||
//# sourceURL=testErrorStack.js
|
|
@ -0,0 +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.
|
39
packages/react-devtools-extensions/src/__tests__/__source__/ComponentWithCustomHook.js
vendored
Normal file
39
packages/react-devtools-extensions/src/__tests__/__source__/ComponentWithCustomHook.js
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React, {useEffect, useState} from 'react';
|
||||
|
||||
export function Component() {
|
||||
const [count, setCount] = useState(0);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
useEffect(() => {
|
||||
// ...
|
||||
}, []);
|
||||
|
||||
const handleClick = () => setCount(count + 1);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>Dark mode? {isDarkMode}</div>
|
||||
<div>Count: {count}</div>
|
||||
<button onClick={handleClick}>Update count</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function useIsDarkMode() {
|
||||
const [isDarkMode] = useState(false);
|
||||
|
||||
useEffect(function useEffectCreate() {
|
||||
// Here is where we may listen to a "theme" event...
|
||||
}, []);
|
||||
|
||||
return isDarkMode;
|
||||
}
|
17
packages/react-devtools-extensions/src/__tests__/__source__/ComponentWithExternalCustomHooks.js
vendored
Normal file
17
packages/react-devtools-extensions/src/__tests__/__source__/ComponentWithExternalCustomHooks.js
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import useTheme from './useTheme';
|
||||
|
||||
export function Component() {
|
||||
const theme = useTheme();
|
||||
|
||||
return <div>theme: {theme}</div>;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React, {useState} from 'react';
|
||||
|
||||
export function Component() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>You clicked {count} times</p>
|
||||
<button onClick={() => setCount(count + 1)}>Click me</button>
|
||||
</div>
|
||||
);
|
||||
}
|
14
packages/react-devtools-extensions/src/__tests__/__source__/InlineRequire.js
vendored
Normal file
14
packages/react-devtools-extensions/src/__tests__/__source__/InlineRequire.js
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export function Component() {
|
||||
const [count] = require('react').useState(0);
|
||||
|
||||
return count;
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Fragment, useCallback, useState} from 'react';
|
||||
|
||||
export function ListItem({item, removeItem, toggleItem}) {
|
||||
const handleDelete = useCallback(() => {
|
||||
removeItem(item);
|
||||
}, [item, removeItem]);
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
toggleItem(item);
|
||||
}, [item, toggleItem]);
|
||||
|
||||
return (
|
||||
<li>
|
||||
<button onClick={handleDelete}>Delete</button>
|
||||
<label>
|
||||
<input
|
||||
checked={item.isComplete}
|
||||
onChange={handleToggle}
|
||||
type="checkbox"
|
||||
/>{' '}
|
||||
{item.text}
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export function List(props) {
|
||||
const [newItemText, setNewItemText] = useState('');
|
||||
const [items, setItems] = useState([
|
||||
{id: 1, isComplete: true, text: 'First'},
|
||||
{id: 2, isComplete: true, text: 'Second'},
|
||||
{id: 3, isComplete: false, text: 'Third'},
|
||||
]);
|
||||
const [uid, setUID] = useState(4);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (newItemText !== '') {
|
||||
setItems([
|
||||
...items,
|
||||
{
|
||||
id: uid,
|
||||
isComplete: false,
|
||||
text: newItemText,
|
||||
},
|
||||
]);
|
||||
setUID(uid + 1);
|
||||
setNewItemText('');
|
||||
}
|
||||
}, [newItemText, items, uid]);
|
||||
|
||||
const handleKeyPress = useCallback(
|
||||
event => {
|
||||
if (event.key === 'Enter') {
|
||||
handleClick();
|
||||
}
|
||||
},
|
||||
[handleClick],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
event => {
|
||||
setNewItemText(event.currentTarget.value);
|
||||
},
|
||||
[setNewItemText],
|
||||
);
|
||||
|
||||
const removeItem = useCallback(
|
||||
itemToRemove => setItems(items.filter(item => item !== itemToRemove)),
|
||||
[items],
|
||||
);
|
||||
|
||||
const toggleItem = useCallback(
|
||||
itemToToggle => {
|
||||
// Dont use indexOf()
|
||||
// because editing props in DevTools creates a new Object.
|
||||
const index = items.findIndex(item => item.id === itemToToggle.id);
|
||||
|
||||
setItems(
|
||||
items
|
||||
.slice(0, index)
|
||||
.concat({
|
||||
...itemToToggle,
|
||||
isComplete: !itemToToggle.isComplete,
|
||||
})
|
||||
.concat(items.slice(index + 1)),
|
||||
);
|
||||
},
|
||||
[items],
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h1>List</h1>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="New list item..."
|
||||
value={newItemText}
|
||||
onChange={handleChange}
|
||||
onKeyPress={handleKeyPress}
|
||||
/>
|
||||
<button disabled={newItemText === ''} onClick={handleClick}>
|
||||
<span role="img" aria-label="Add item">
|
||||
Add
|
||||
</span>
|
||||
</button>
|
||||
<ul>
|
||||
{items.map(item => (
|
||||
<ListItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
removeItem={removeItem}
|
||||
toggleItem={toggleItem}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.Component = Component;
|
||||
|
||||
var _react = _interopRequireWildcard(require("react"));
|
||||
|
||||
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
||||
|
||||
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; }
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
function Component() {
|
||||
const [count, setCount] = (0, _react.useState)(0);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
(0, _react.useEffect)(() => {// ...
|
||||
}, []);
|
||||
|
||||
const handleClick = () => setCount(count + 1);
|
||||
|
||||
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", {
|
||||
onClick: handleClick
|
||||
}, "Update count"));
|
||||
}
|
||||
|
||||
function useIsDarkMode() {
|
||||
const [isDarkMode, setIsDarkMode] = (0, _react.useState)(0);
|
||||
(0, _react.useEffect)(function useEffectCreate() {// Here is where we may listen to a "theme" event...
|
||||
}, []);
|
||||
return isDarkMode;
|
||||
}
|
||||
//# sourceMappingURL=ComponentWithCustomHook.js.map
|
||||
//# sourceURL=ComponentWithCustomHook.js
|
|
@ -0,0 +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}"]}
|
|
@ -0,0 +1,27 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.Component = Component;
|
||||
|
||||
var _react = _interopRequireDefault(require("react"));
|
||||
|
||||
var _useTheme = _interopRequireDefault(require("./useTheme"));
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
function Component() {
|
||||
const theme = (0, _useTheme.default)();
|
||||
return /*#__PURE__*/_react.default.createElement("div", null, "theme: ", theme);
|
||||
}
|
||||
//# sourceMappingURL=ComponentWithExternalCustomHooks.js.map
|
||||
//# sourceURL=ComponentWithExternalCustomHooks.js
|
|
@ -0,0 +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"]}
|
29
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/Example.js
vendored
Normal file
29
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/Example.js
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.Component = Component;
|
||||
|
||||
var _react = _interopRequireWildcard(require("react"));
|
||||
|
||||
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
||||
|
||||
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; }
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
function Component() {
|
||||
const [count, setCount] = (0, _react.useState)(0);
|
||||
return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("p", null, "You clicked ", count, " times"), /*#__PURE__*/_react.default.createElement("button", {
|
||||
onClick: () => setCount(count + 1)
|
||||
}, "Click me"));
|
||||
}
|
||||
//# sourceMappingURL=Example.js.map
|
||||
//# sourceURL=Example.js
|
|
@ -0,0 +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"]}
|
|
@ -0,0 +1,22 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.Component = Component;
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
function Component() {
|
||||
const [count] = require('react').useState(0);
|
||||
|
||||
return count;
|
||||
}
|
||||
//# sourceMappingURL=InlineRequire.js.map
|
||||
//# sourceURL=InlineRequire.js
|
|
@ -0,0 +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"]}
|
107
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/ToDoList.js
vendored
Normal file
107
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/ToDoList.js
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.ListItem = ListItem;
|
||||
exports.List = List;
|
||||
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
|
||||
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
||||
|
||||
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; }
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
function ListItem({
|
||||
item,
|
||||
removeItem,
|
||||
toggleItem
|
||||
}) {
|
||||
const handleDelete = (0, React.useCallback)(() => {
|
||||
removeItem(item);
|
||||
}, [item, removeItem]);
|
||||
const handleToggle = (0, React.useCallback)(() => {
|
||||
toggleItem(item);
|
||||
}, [item, toggleItem]);
|
||||
return /*#__PURE__*/React.createElement("li", null, /*#__PURE__*/React.createElement("button", {
|
||||
onClick: handleDelete
|
||||
}, "Delete"), /*#__PURE__*/React.createElement("label", null, /*#__PURE__*/React.createElement("input", {
|
||||
checked: item.isComplete,
|
||||
onChange: handleToggle,
|
||||
type: "checkbox"
|
||||
}), ' ', item.text));
|
||||
}
|
||||
|
||||
function List(props) {
|
||||
const [newItemText, setNewItemText] = (0, React.useState)('');
|
||||
const [items, setItems] = (0, React.useState)([{
|
||||
id: 1,
|
||||
isComplete: true,
|
||||
text: 'First'
|
||||
}, {
|
||||
id: 2,
|
||||
isComplete: true,
|
||||
text: 'Second'
|
||||
}, {
|
||||
id: 3,
|
||||
isComplete: false,
|
||||
text: 'Third'
|
||||
}]);
|
||||
const [uid, setUID] = (0, React.useState)(4);
|
||||
const handleClick = (0, React.useCallback)(() => {
|
||||
if (newItemText !== '') {
|
||||
setItems([...items, {
|
||||
id: uid,
|
||||
isComplete: false,
|
||||
text: newItemText
|
||||
}]);
|
||||
setUID(uid + 1);
|
||||
setNewItemText('');
|
||||
}
|
||||
}, [newItemText, items, uid]);
|
||||
const handleKeyPress = (0, React.useCallback)(event => {
|
||||
if (event.key === 'Enter') {
|
||||
handleClick();
|
||||
}
|
||||
}, [handleClick]);
|
||||
const handleChange = (0, React.useCallback)(event => {
|
||||
setNewItemText(event.currentTarget.value);
|
||||
}, [setNewItemText]);
|
||||
const removeItem = (0, React.useCallback)(itemToRemove => setItems(items.filter(item => item !== itemToRemove)), [items]);
|
||||
const toggleItem = (0, React.useCallback)(itemToToggle => {
|
||||
// Dont use indexOf()
|
||||
// because editing props in DevTools creates a new Object.
|
||||
const index = items.findIndex(item => item.id === itemToToggle.id);
|
||||
setItems(items.slice(0, index).concat({ ...itemToToggle,
|
||||
isComplete: !itemToToggle.isComplete
|
||||
}).concat(items.slice(index + 1)));
|
||||
}, [items]);
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "List"), /*#__PURE__*/React.createElement("input", {
|
||||
type: "text",
|
||||
placeholder: "New list item...",
|
||||
value: newItemText,
|
||||
onChange: handleChange,
|
||||
onKeyPress: handleKeyPress
|
||||
}), /*#__PURE__*/React.createElement("button", {
|
||||
disabled: newItemText === '',
|
||||
onClick: handleClick
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
role: "img",
|
||||
"aria-label": "Add item"
|
||||
}, "Add")), /*#__PURE__*/React.createElement("ul", null, items.map(item => /*#__PURE__*/React.createElement(ListItem, {
|
||||
key: item.id,
|
||||
item: item,
|
||||
removeItem: removeItem,
|
||||
toggleItem: toggleItem
|
||||
}))));
|
||||
}
|
||||
//# sourceMappingURL=ToDoList.js.map
|
||||
//# sourceURL=ToDoList.js
|
File diff suppressed because one or more lines are too long
28
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/useTheme.js
vendored
Normal file
28
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/external/useTheme.js
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useTheme;
|
||||
exports.ThemeContext = void 0;
|
||||
|
||||
var _react = require("react");
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
const ThemeContext = /*#__PURE__*/(0, _react.createContext)('bright');
|
||||
exports.ThemeContext = ThemeContext;
|
||||
|
||||
function useTheme() {
|
||||
const theme = (0, _react.useContext)(ThemeContext);
|
||||
(0, _react.useDebugValue)(theme);
|
||||
return theme;
|
||||
}
|
||||
//# sourceMappingURL=useTheme.js.map
|
||||
//# sourceURL=useTheme.js
|
|
@ -0,0 +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"]}
|
|
@ -0,0 +1,42 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.Component = Component;
|
||||
|
||||
var _react = _interopRequireWildcard(require("react"));
|
||||
|
||||
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
||||
|
||||
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; }
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
function Component() {
|
||||
const [count, setCount] = (0, _react.useState)(0);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
(0, _react.useEffect)(() => {// ...
|
||||
}, []);
|
||||
|
||||
const handleClick = () => setCount(count + 1);
|
||||
|
||||
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", {
|
||||
onClick: handleClick
|
||||
}, "Update count"));
|
||||
}
|
||||
|
||||
function useIsDarkMode() {
|
||||
const [isDarkMode, setIsDarkMode] = (0, _react.useState)(0);
|
||||
(0, _react.useEffect)(function useEffectCreate() {// Here is where we may listen to a "theme" event...
|
||||
}, []);
|
||||
return isDarkMode;
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkNvbXBvbmVudFdpdGhDdXN0b21Ib29rLmpzIl0sIm5hbWVzIjpbIkNvbXBvbmVudCIsImNvdW50Iiwic2V0Q291bnQiLCJpc0RhcmtNb2RlIiwidXNlSXNEYXJrTW9kZSIsImhhbmRsZUNsaWNrIiwic2V0SXNEYXJrTW9kZSIsInVzZUVmZmVjdENyZWF0ZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQVNBOzs7Ozs7QUFUQTs7Ozs7Ozs7QUFXTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU0sQ0FBQ0MsS0FBRCxFQUFRQyxRQUFSLElBQW9CLHFCQUFTLENBQVQsQ0FBMUI7QUFDQSxRQUFNQyxVQUFVLEdBQUdDLGFBQWEsRUFBaEM7QUFFQSx3QkFBVSxNQUFNLENBQ2Q7QUFDRCxHQUZELEVBRUcsRUFGSDs7QUFJQSxRQUFNQyxXQUFXLEdBQUcsTUFBTUgsUUFBUSxDQUFDRCxLQUFLLEdBQUcsQ0FBVCxDQUFsQzs7QUFFQSxzQkFDRSx5RUFDRSx5REFBaUJFLFVBQWpCLENBREYsZUFFRSxxREFBYUYsS0FBYixDQUZGLGVBR0U7QUFBUSxJQUFBLE9BQU8sRUFBRUk7QUFBakIsb0JBSEYsQ0FERjtBQU9EOztBQUVELFNBQVNELGFBQVQsR0FBeUI7QUFDdkIsUUFBTSxDQUFDRCxVQUFELEVBQWFHLGFBQWIsSUFBOEIscUJBQVMsQ0FBVCxDQUFwQztBQUVBLHdCQUFVLFNBQVNDLGVBQVQsR0FBMkIsQ0FDbkM7QUFDRCxHQUZELEVBRUcsRUFGSDtBQUlBLFNBQU9KLFVBQVA7QUFDRCIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQ29weXJpZ2h0IChjKSBGYWNlYm9vaywgSW5jLiBhbmQgaXRzIGFmZmlsaWF0ZXMuXG4gKlxuICogVGhpcyBzb3VyY2UgY29kZSBpcyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIGxpY2Vuc2UgZm91bmQgaW4gdGhlXG4gKiBMSUNFTlNFIGZpbGUgaW4gdGhlIHJvb3QgZGlyZWN0b3J5IG9mIHRoaXMgc291cmNlIHRyZWUuXG4gKlxuICogQGZsb3dcbiAqL1xuXG5pbXBvcnQgUmVhY3QsIHt1c2VFZmZlY3QsIHVzZVN0YXRlfSBmcm9tICdyZWFjdCc7XG5cbmV4cG9ydCBmdW5jdGlvbiBDb21wb25lbnQoKSB7XG4gIGNvbnN0IFtjb3VudCwgc2V0Q291bnRdID0gdXNlU3RhdGUoMCk7XG4gIGNvbnN0IGlzRGFya01vZGUgPSB1c2VJc0RhcmtNb2RlKCk7XG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICAvLyAuLi5cbiAgfSwgW10pO1xuXG4gIGNvbnN0IGhhbmRsZUNsaWNrID0gKCkgPT4gc2V0Q291bnQoY291bnQgKyAxKTtcblxuICByZXR1cm4gKFxuICAgIDw+XG4gICAgICA8ZGl2PkRhcmsgbW9kZT8ge2lzRGFya01vZGV9PC9kaXY+XG4gICAgICA8ZGl2PkNvdW50OiB7Y291bnR9PC9kaXY+XG4gICAgICA8YnV0dG9uIG9uQ2xpY2s9e2hhbmRsZUNsaWNrfT5VcGRhdGUgY291bnQ8L2J1dHRvbj5cbiAgICA8Lz5cbiAgKTtcbn1cblxuZnVuY3Rpb24gdXNlSXNEYXJrTW9kZSgpIHtcbiAgY29uc3QgW2lzRGFya01vZGUsIHNldElzRGFya01vZGVdID0gdXNlU3RhdGUoMCk7XG5cbiAgdXNlRWZmZWN0KGZ1bmN0aW9uIHVzZUVmZmVjdENyZWF0ZSgpIHtcbiAgICAvLyBIZXJlIGlzIHdoZXJlIHdlIG1heSBsaXN0ZW4gdG8gYSBcInRoZW1lXCIgZXZlbnQuLi5cbiAgfSwgW10pO1xuXG4gIHJldHVybiBpc0RhcmtNb2RlO1xufSJdfQ==
|
||||
//# sourceURL=ComponentWithCustomHook.js
|
|
@ -0,0 +1,27 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.Component = Component;
|
||||
|
||||
var _react = _interopRequireDefault(require("react"));
|
||||
|
||||
var _useTheme = _interopRequireDefault(require("./useTheme"));
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
function Component() {
|
||||
const theme = (0, _useTheme.default)();
|
||||
return /*#__PURE__*/_react.default.createElement("div", null, "theme: ", theme);
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkNvbXBvbmVudFdpdGhFeHRlcm5hbEN1c3RvbUhvb2tzLmpzIl0sIm5hbWVzIjpbIkNvbXBvbmVudCIsInRoZW1lIl0sIm1hcHBpbmdzIjoiOzs7Ozs7O0FBU0E7O0FBQ0E7Ozs7QUFWQTs7Ozs7Ozs7QUFZTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU1DLEtBQUssR0FBRyx3QkFBZDtBQUVBLHNCQUFPLHFEQUFhQSxLQUFiLENBQVA7QUFDRCIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQ29weXJpZ2h0IChjKSBGYWNlYm9vaywgSW5jLiBhbmQgaXRzIGFmZmlsaWF0ZXMuXG4gKlxuICogVGhpcyBzb3VyY2UgY29kZSBpcyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIGxpY2Vuc2UgZm91bmQgaW4gdGhlXG4gKiBMSUNFTlNFIGZpbGUgaW4gdGhlIHJvb3QgZGlyZWN0b3J5IG9mIHRoaXMgc291cmNlIHRyZWUuXG4gKlxuICogQGZsb3dcbiAqL1xuXG5pbXBvcnQgUmVhY3QgZnJvbSAncmVhY3QnO1xuaW1wb3J0IHVzZVRoZW1lIGZyb20gJy4vdXNlVGhlbWUnO1xuXG5leHBvcnQgZnVuY3Rpb24gQ29tcG9uZW50KCkge1xuICBjb25zdCB0aGVtZSA9IHVzZVRoZW1lKCk7XG5cbiAgcmV0dXJuIDxkaXY+dGhlbWU6IHt0aGVtZX08L2Rpdj47XG59XG4iXX0=
|
||||
//# sourceURL=ComponentWithExternalCustomHooks.js
|
29
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/Example.js
vendored
Normal file
29
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/Example.js
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.Component = Component;
|
||||
|
||||
var _react = _interopRequireWildcard(require("react"));
|
||||
|
||||
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
||||
|
||||
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; }
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
function Component() {
|
||||
const [count, setCount] = (0, _react.useState)(0);
|
||||
return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("p", null, "You clicked ", count, " times"), /*#__PURE__*/_react.default.createElement("button", {
|
||||
onClick: () => setCount(count + 1)
|
||||
}, "Click me"));
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkV4YW1wbGUuanMiXSwibmFtZXMiOlsiQ29tcG9uZW50IiwiY291bnQiLCJzZXRDb3VudCJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQVNBOzs7Ozs7QUFUQTs7Ozs7Ozs7QUFXTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU0sQ0FBQ0MsS0FBRCxFQUFRQyxRQUFSLElBQW9CLHFCQUFTLENBQVQsQ0FBMUI7QUFFQSxzQkFDRSx1REFDRSx3REFBZ0JELEtBQWhCLFdBREYsZUFFRTtBQUFRLElBQUEsT0FBTyxFQUFFLE1BQU1DLFFBQVEsQ0FBQ0QsS0FBSyxHQUFHLENBQVQ7QUFBL0IsZ0JBRkYsQ0FERjtBQU1EIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb3B5cmlnaHQgKGMpIEZhY2Vib29rLCBJbmMuIGFuZCBpdHMgYWZmaWxpYXRlcy5cbiAqXG4gKiBUaGlzIHNvdXJjZSBjb2RlIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZSBmb3VuZCBpbiB0aGVcbiAqIExJQ0VOU0UgZmlsZSBpbiB0aGUgcm9vdCBkaXJlY3Rvcnkgb2YgdGhpcyBzb3VyY2UgdHJlZS5cbiAqXG4gKiBAZmxvd1xuICovXG5cbmltcG9ydCBSZWFjdCwge3VzZVN0YXRlfSBmcm9tICdyZWFjdCc7XG5cbmV4cG9ydCBmdW5jdGlvbiBDb21wb25lbnQoKSB7XG4gIGNvbnN0IFtjb3VudCwgc2V0Q291bnRdID0gdXNlU3RhdGUoMCk7XG5cbiAgcmV0dXJuIChcbiAgICA8ZGl2PlxuICAgICAgPHA+WW91IGNsaWNrZWQge2NvdW50fSB0aW1lczwvcD5cbiAgICAgIDxidXR0b24gb25DbGljaz17KCkgPT4gc2V0Q291bnQoY291bnQgKyAxKX0+Q2xpY2sgbWU8L2J1dHRvbj5cbiAgICA8L2Rpdj5cbiAgKTtcbn1cbiJdfQ==
|
||||
//# sourceURL=Example.js
|
22
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/InlineRequire.js
vendored
Normal file
22
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/InlineRequire.js
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.Component = Component;
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
function Component() {
|
||||
const [count] = require('react').useState(0);
|
||||
|
||||
return count;
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIklubGluZVJlcXVpcmUuanMiXSwibmFtZXMiOlsiQ29tcG9uZW50IiwiY291bnQiLCJyZXF1aXJlIiwidXNlU3RhdGUiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7QUFBQTs7Ozs7Ozs7QUFTTyxTQUFTQSxTQUFULEdBQXFCO0FBQzFCLFFBQU0sQ0FBQ0MsS0FBRCxJQUFVQyxPQUFPLENBQUMsT0FBRCxDQUFQLENBQWlCQyxRQUFqQixDQUEwQixDQUExQixDQUFoQjs7QUFFQSxTQUFPRixLQUFQO0FBQ0QiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvcHlyaWdodCAoYykgRmFjZWJvb2ssIEluYy4gYW5kIGl0cyBhZmZpbGlhdGVzLlxuICpcbiAqIFRoaXMgc291cmNlIGNvZGUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlIGZvdW5kIGluIHRoZVxuICogTElDRU5TRSBmaWxlIGluIHRoZSByb290IGRpcmVjdG9yeSBvZiB0aGlzIHNvdXJjZSB0cmVlLlxuICpcbiAqIEBmbG93XG4gKi9cblxuZXhwb3J0IGZ1bmN0aW9uIENvbXBvbmVudCgpIHtcbiAgY29uc3QgW2NvdW50XSA9IHJlcXVpcmUoJ3JlYWN0JykudXNlU3RhdGUoMCk7XG5cbiAgcmV0dXJuIGNvdW50O1xufVxuIl19
|
||||
//# sourceURL=InlineRequire.js
|
107
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/ToDoList.js
vendored
Normal file
107
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/ToDoList.js
vendored
Normal file
File diff suppressed because one or more lines are too long
28
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/useTheme.js
vendored
Normal file
28
packages/react-devtools-extensions/src/__tests__/__source__/__compiled__/inline/useTheme.js
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useTheme;
|
||||
exports.ThemeContext = void 0;
|
||||
|
||||
var _react = require("react");
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
const ThemeContext = /*#__PURE__*/(0, _react.createContext)('bright');
|
||||
exports.ThemeContext = ThemeContext;
|
||||
|
||||
function useTheme() {
|
||||
const theme = (0, _react.useContext)(ThemeContext);
|
||||
(0, _react.useDebugValue)(theme);
|
||||
return theme;
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInVzZVRoZW1lLmpzIl0sIm5hbWVzIjpbIlRoZW1lQ29udGV4dCIsInVzZVRoZW1lIiwidGhlbWUiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O0FBU0E7O0FBVEE7Ozs7Ozs7O0FBV08sTUFBTUEsWUFBWSxnQkFBRywwQkFBYyxRQUFkLENBQXJCOzs7QUFFUSxTQUFTQyxRQUFULEdBQW9CO0FBQ2pDLFFBQU1DLEtBQUssR0FBRyx1QkFBV0YsWUFBWCxDQUFkO0FBQ0EsNEJBQWNFLEtBQWQ7QUFDQSxTQUFPQSxLQUFQO0FBQ0QiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvcHlyaWdodCAoYykgRmFjZWJvb2ssIEluYy4gYW5kIGl0cyBhZmZpbGlhdGVzLlxuICpcbiAqIFRoaXMgc291cmNlIGNvZGUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlIGZvdW5kIGluIHRoZVxuICogTElDRU5TRSBmaWxlIGluIHRoZSByb290IGRpcmVjdG9yeSBvZiB0aGlzIHNvdXJjZSB0cmVlLlxuICpcbiAqIEBmbG93XG4gKi9cblxuaW1wb3J0IHtjcmVhdGVDb250ZXh0LCB1c2VDb250ZXh0LCB1c2VEZWJ1Z1ZhbHVlfSBmcm9tICdyZWFjdCc7XG5cbmV4cG9ydCBjb25zdCBUaGVtZUNvbnRleHQgPSBjcmVhdGVDb250ZXh0KCdicmlnaHQnKTtcblxuZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gdXNlVGhlbWUoKSB7XG4gIGNvbnN0IHRoZW1lID0gdXNlQ29udGV4dChUaGVtZUNvbnRleHQpO1xuICB1c2VEZWJ1Z1ZhbHVlKHRoZW1lKTtcbiAgcmV0dXJuIHRoZW1lO1xufVxuIl19
|
||||
//# sourceURL=useTheme.js
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {createContext, useContext, useDebugValue} from 'react';
|
||||
|
||||
export const ThemeContext = createContext('bright');
|
||||
|
||||
export default function useTheme() {
|
||||
const theme = useContext(ThemeContext);
|
||||
useDebugValue(theme);
|
||||
return theme;
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
describe('parseHookNames', () => {
|
||||
let fetchMock;
|
||||
let inspectHooks;
|
||||
let parseHookNames;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
jest.mock('source-map-support', () => {
|
||||
console.trace('source-map-support');
|
||||
});
|
||||
|
||||
const {
|
||||
overrideFeatureFlags,
|
||||
} = require('react-devtools-shared/src/__tests__/utils');
|
||||
overrideFeatureFlags({enableHookNameParsing: true});
|
||||
|
||||
fetchMock = require('jest-fetch-mock');
|
||||
fetchMock.enableMocks();
|
||||
|
||||
inspectHooks = require('react-debug-tools/src/ReactDebugHooks')
|
||||
.inspectHooks;
|
||||
parseHookNames = require('../parseHookNames').default;
|
||||
|
||||
// Jest (jest-runner?) configures Errors to automatically account for source maps.
|
||||
// This changes behavior between our tests and the browser.
|
||||
// To "fix" this, clear the prepareStackTrace() method on the Error object.
|
||||
delete Error.prepareStackTrace;
|
||||
|
||||
fetchMock.mockIf(/.+$/, request => {
|
||||
const {resolve} = require('path');
|
||||
const url = request.url;
|
||||
if (url.endsWith('js.map')) {
|
||||
// Source maps are relative URLs (e.g. "path/to/Exmaple.js" specifies "Exmaple.js.map").
|
||||
const sourceMapURL = resolve(
|
||||
__dirname,
|
||||
'__source__',
|
||||
'__compiled__',
|
||||
'external',
|
||||
url,
|
||||
);
|
||||
return Promise.resolve(requireText(sourceMapURL, 'utf8'));
|
||||
} else {
|
||||
return Promise.resolve(requireText(url, 'utf8'));
|
||||
}
|
||||
});
|
||||
|
||||
// Mock out portion of browser API used by parseHookNames to initialize "source-map".
|
||||
global.chrome = {
|
||||
extension: {
|
||||
getURL: jest.fn((...args) => {
|
||||
const {join} = require('path');
|
||||
return join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'node_modules',
|
||||
'source-map',
|
||||
'lib',
|
||||
'mappings.wasm',
|
||||
);
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fetch.resetMocks();
|
||||
});
|
||||
|
||||
function expectHookNamesToEqual(map, expectedNamesArray) {
|
||||
// Slightly hacky since it relies on the iterable order of values()
|
||||
expect(Array.from(map.values())).toEqual(expectedNamesArray);
|
||||
}
|
||||
|
||||
function requireText(path, encoding) {
|
||||
const {readFileSync} = require('fs');
|
||||
return readFileSync(path, encoding);
|
||||
}
|
||||
|
||||
async function getHookNamesForComponent(Component, props = {}) {
|
||||
const hooksTree = inspectHooks(Component, props, undefined, true);
|
||||
const hookNames = await parseHookNames(hooksTree);
|
||||
return hookNames;
|
||||
}
|
||||
|
||||
it('should parse names for useState()', async () => {
|
||||
const React = require('react');
|
||||
const {useState} = React;
|
||||
function Component(props) {
|
||||
const [foo] = useState(true);
|
||||
const bar = useState(true);
|
||||
const [baz] = React.useState(true);
|
||||
return `${foo}-${bar}-${baz}`;
|
||||
}
|
||||
|
||||
const hookNames = await getHookNamesForComponent(Component);
|
||||
expectHookNamesToEqual(hookNames, ['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
it('should parse names for useReducer()', async () => {
|
||||
const React = require('react');
|
||||
const {useReducer} = React;
|
||||
function Component(props) {
|
||||
const [foo] = useReducer(true);
|
||||
const [bar] = useReducer(true);
|
||||
const [baz] = React.useReducer(true);
|
||||
return `${foo}-${bar}-${baz}`;
|
||||
}
|
||||
|
||||
const hookNames = await getHookNamesForComponent(Component);
|
||||
expectHookNamesToEqual(hookNames, ['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
it('should return null for hooks without names like useEffect', async () => {
|
||||
const React = require('react');
|
||||
const {useEffect} = React;
|
||||
function Component(props) {
|
||||
useEffect(() => {});
|
||||
React.useLayoutEffect(() => () => {});
|
||||
return null;
|
||||
}
|
||||
|
||||
const hookNames = await getHookNamesForComponent(Component);
|
||||
expectHookNamesToEqual(hookNames, []); // No hooks with names
|
||||
});
|
||||
|
||||
it('should parse names for custom hooks', async () => {
|
||||
const {useDebugValue, useState} = require('react');
|
||||
function useCustomHookOne() {
|
||||
// DebugValue hook should not appear in log.
|
||||
useDebugValue('example');
|
||||
return true;
|
||||
}
|
||||
function useCustomHookTwo() {
|
||||
const [baz, setBaz] = useState(true);
|
||||
return [baz, setBaz];
|
||||
}
|
||||
function Component(props) {
|
||||
const foo = useCustomHookOne();
|
||||
// This cae is ignored;
|
||||
// the meaning of a tuple assignment for a custom hook is unclear.
|
||||
const [bar] = useCustomHookTwo();
|
||||
return `${foo}-${bar}`;
|
||||
}
|
||||
|
||||
const hookNames = await getHookNamesForComponent(Component);
|
||||
expectHookNamesToEqual(hookNames, [
|
||||
'foo',
|
||||
null, // Custom hooks can have names, but not when using destructuring.
|
||||
'baz',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return null for custom hooks without explicit names', async () => {
|
||||
const {useDebugValue} = require('react');
|
||||
function useCustomHookOne() {
|
||||
// DebugValue hook should not appear in log.
|
||||
useDebugValue('example');
|
||||
}
|
||||
function useCustomHookTwo() {
|
||||
// DebugValue hook should not appear in log.
|
||||
useDebugValue('example');
|
||||
return [true];
|
||||
}
|
||||
function Component(props) {
|
||||
useCustomHookOne();
|
||||
const [bar] = useCustomHookTwo();
|
||||
return bar;
|
||||
}
|
||||
|
||||
const hookNames = await getHookNamesForComponent(Component);
|
||||
expectHookNamesToEqual(hookNames, [
|
||||
null, // Custom hooks can have names, but this one does not even return a value.
|
||||
null, // Custom hooks can have names, but not when using destructuring.
|
||||
]);
|
||||
});
|
||||
|
||||
describe('inline and external source maps', () => {
|
||||
it('should work for simple components', async () => {
|
||||
async function test(path) {
|
||||
const Component = require(path).Component;
|
||||
const hookNames = await getHookNamesForComponent(Component);
|
||||
expectHookNamesToEqual(hookNames, [
|
||||
'count', // useState
|
||||
]);
|
||||
}
|
||||
|
||||
await test('./__source__/Example'); // original source (uncompiled)
|
||||
await test('./__source__/__compiled__/inline/Example'); // inline source map
|
||||
await test('./__source__/__compiled__/external/Example'); // external source map
|
||||
});
|
||||
|
||||
it('should work with more complex files and components', async () => {
|
||||
async function test(path) {
|
||||
const components = require(path);
|
||||
|
||||
let hookNames = await getHookNamesForComponent(components.List);
|
||||
expectHookNamesToEqual(hookNames, [
|
||||
'newItemText', // useState
|
||||
'items', // useState
|
||||
'uid', // useState
|
||||
'handleClick', // useCallback
|
||||
'handleKeyPress', // useCallback
|
||||
'handleChange', // useCallback
|
||||
'removeItem', // useCallback
|
||||
'toggleItem', // useCallback
|
||||
]);
|
||||
|
||||
hookNames = await getHookNamesForComponent(components.ListItem, {
|
||||
item: {},
|
||||
});
|
||||
expectHookNamesToEqual(hookNames, [
|
||||
'handleDelete', // useCallback
|
||||
'handleToggle', // useCallback
|
||||
]);
|
||||
}
|
||||
|
||||
await test('./__source__/ToDoList'); // original source (uncompiled)
|
||||
await test('./__source__/__compiled__/inline/ToDoList'); // inline source map
|
||||
await test('./__source__/__compiled__/external/ToDoList'); // external source map
|
||||
});
|
||||
|
||||
it('should work for custom hook', async () => {
|
||||
async function test(path) {
|
||||
const Component = require(path).Component;
|
||||
const hookNames = await getHookNamesForComponent(Component);
|
||||
expectHookNamesToEqual(hookNames, [
|
||||
'count', // useState()
|
||||
'isDarkMode', // useIsDarkMode()
|
||||
'isDarkMode', // useIsDarkMode -> useState()
|
||||
]);
|
||||
}
|
||||
|
||||
await test('./__source__/ComponentWithCustomHook'); // original source (uncompiled)
|
||||
await test('./__source__/__compiled__/inline/ComponentWithCustomHook'); // inline source map
|
||||
await test('./__source__/__compiled__/external/ComponentWithCustomHook'); // external source map
|
||||
});
|
||||
|
||||
it('should work for external hooks', async () => {
|
||||
async function test(path) {
|
||||
const Component = require(path).Component;
|
||||
const hookNames = await getHookNamesForComponent(Component);
|
||||
expectHookNamesToEqual(hookNames, [
|
||||
'theme', // useTheme()
|
||||
'theme', // useContext()
|
||||
]);
|
||||
}
|
||||
|
||||
// We can't test the uncompiled source here, because it either needs to get transformed,
|
||||
// which would break the source mapping, or the import statements will fail.
|
||||
|
||||
await test(
|
||||
'./__source__/__compiled__/inline/ComponentWithExternalCustomHooks',
|
||||
); // inline source map
|
||||
await test(
|
||||
'./__source__/__compiled__/external/ComponentWithExternalCustomHooks',
|
||||
); // external source map
|
||||
});
|
||||
|
||||
// TODO Inline require (e.g. require("react").useState()) isn't supported yet.
|
||||
// Maybe this isn't an important use case to support,
|
||||
// since inline requires are most likely to exist in compiled source (if at all).
|
||||
xit('should work for inline requires', async () => {
|
||||
async function test(path) {
|
||||
const Component = require(path).Component;
|
||||
const hookNames = await getHookNamesForComponent(Component);
|
||||
expectHookNamesToEqual(hookNames, [
|
||||
'count', // useState()
|
||||
]);
|
||||
}
|
||||
|
||||
await test('./__source__/InlineRequire'); // original source (uncompiled)
|
||||
await test('./__source__/__compiled__/inline/InlineRequire'); // inline source map
|
||||
await test('./__source__/__compiled__/external/InlineRequire'); // external source map
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
const {transformSync} = require('@babel/core');
|
||||
const {btoa} = require('base64');
|
||||
const {
|
||||
lstatSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
} = require('fs');
|
||||
const {emptyDirSync} = require('fs-extra');
|
||||
const {resolve} = require('path');
|
||||
|
||||
const sourceDir = resolve(__dirname, '__source__');
|
||||
const buildRoot = resolve(sourceDir, '__compiled__');
|
||||
const externalDir = resolve(buildRoot, 'external');
|
||||
const inlineDir = resolve(buildRoot, 'inline');
|
||||
|
||||
// Remove previous builds
|
||||
emptyDirSync(buildRoot);
|
||||
mkdirSync(externalDir);
|
||||
mkdirSync(inlineDir);
|
||||
|
||||
function compile(fileName) {
|
||||
const code = readFileSync(resolve(sourceDir, fileName), 'utf8');
|
||||
|
||||
const transformed = transformSync(code, {
|
||||
plugins: ['@babel/plugin-transform-modules-commonjs'],
|
||||
presets: [
|
||||
// 'minify',
|
||||
[
|
||||
'@babel/react',
|
||||
// {
|
||||
// runtime: 'automatic',
|
||||
// development: false,
|
||||
// },
|
||||
],
|
||||
],
|
||||
sourceMap: true,
|
||||
});
|
||||
|
||||
const sourceMap = transformed.map;
|
||||
sourceMap.sources = [fileName];
|
||||
|
||||
// Generate compiled output with external source maps
|
||||
writeFileSync(
|
||||
resolve(externalDir, fileName),
|
||||
transformed.code +
|
||||
`\n//# sourceMappingURL=${fileName}.map` +
|
||||
`\n//# sourceURL=${fileName}`,
|
||||
'utf8',
|
||||
);
|
||||
writeFileSync(
|
||||
resolve(externalDir, `${fileName}.map`),
|
||||
JSON.stringify(sourceMap),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
// Generate compiled output with external inline base64 source maps
|
||||
writeFileSync(
|
||||
resolve(inlineDir, fileName),
|
||||
transformed.code +
|
||||
'\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
|
||||
btoa(JSON.stringify(sourceMap)) +
|
||||
`\n//# sourceURL=${fileName}`,
|
||||
'utf8',
|
||||
);
|
||||
}
|
||||
|
||||
// Compile all files in the current directory
|
||||
const entries = readdirSync(sourceDir);
|
||||
entries.forEach(entry => {
|
||||
const stat = lstatSync(resolve(sourceDir, entry));
|
||||
if (!stat.isDirectory() && entry.endsWith('.js')) {
|
||||
compile(entry);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,366 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import traverse, {NodePath, Node} from '@babel/traverse';
|
||||
import {File} from '@babel/types';
|
||||
|
||||
import type {HooksNode} from 'react-debug-tools/src/ReactDebugHooks';
|
||||
|
||||
export type SourceConsumer = any;
|
||||
|
||||
export type SourceFileASTWithHookDetails = {
|
||||
sourceFileAST: File,
|
||||
line: number,
|
||||
source: string,
|
||||
};
|
||||
|
||||
export type SourceMap = {|
|
||||
mappings: string,
|
||||
names: Array<string>,
|
||||
sources: Array<string>,
|
||||
sourcesContent: Array<string>,
|
||||
version: number,
|
||||
|};
|
||||
|
||||
const AST_NODE_TYPES = Object.freeze({
|
||||
CALL_EXPRESSION: 'CallExpression',
|
||||
MEMBER_EXPRESSION: 'MemberExpression',
|
||||
ARRAY_PATTERN: 'ArrayPattern',
|
||||
IDENTIFIER: 'Identifier',
|
||||
NUMERIC_LITERAL: 'NumericLiteral',
|
||||
});
|
||||
|
||||
// Check if line number obtained from source map and the line number in hook node match
|
||||
function checkNodeLocation(path: NodePath, line: number): boolean {
|
||||
const {start, end} = path.node.loc;
|
||||
return line >= start.line && line <= end.line;
|
||||
}
|
||||
|
||||
// Checks whether hookNode is a member of targetHookNode
|
||||
function filterMemberNodesOfTargetHook(
|
||||
targetHookNode: NodePath,
|
||||
hookNode: NodePath,
|
||||
): boolean {
|
||||
const targetHookName = targetHookNode.node.id.name;
|
||||
return (
|
||||
targetHookName ===
|
||||
(hookNode.node.init.object && hookNode.node.init.object.name) ||
|
||||
targetHookName === hookNode.node.init.name
|
||||
);
|
||||
}
|
||||
|
||||
// Checks whether hook is the first member node of a state variable declaration node
|
||||
function filterMemberWithHookVariableName(hook: NodePath): boolean {
|
||||
return (
|
||||
hook.node.init.property.type === AST_NODE_TYPES.NUMERIC_LITERAL &&
|
||||
hook.node.init.property.value === 0
|
||||
);
|
||||
}
|
||||
|
||||
// Returns all AST Nodes associated with 'potentialReactHookASTNode'
|
||||
function getFilteredHookASTNodes(
|
||||
potentialReactHookASTNode: NodePath,
|
||||
potentialHooksFound: Array<NodePath>,
|
||||
source: string,
|
||||
): Array<NodePath> {
|
||||
let nodesAssociatedWithReactHookASTNode: NodePath[] = [];
|
||||
if (nodeContainsHookVariableName(potentialReactHookASTNode)) {
|
||||
// made custom hooks to enter this, always
|
||||
// Case 1.
|
||||
// Directly usable Node -> const ref = useRef(null);
|
||||
// -> const [tick, setTick] = useState(1);
|
||||
// Case 2.
|
||||
// Custom Hooks -> const someVariable = useSomeCustomHook();
|
||||
// -> const [someVariable, someFunction] = useAnotherCustomHook();
|
||||
nodesAssociatedWithReactHookASTNode.unshift(potentialReactHookASTNode);
|
||||
} else {
|
||||
// Case 3.
|
||||
// Indirectly usable Node -> const tickState = useState(1);
|
||||
// [tick, setTick] = tickState;
|
||||
// -> const tickState = useState(1);
|
||||
// const tick = tickState[0];
|
||||
// const setTick = tickState[1];
|
||||
nodesAssociatedWithReactHookASTNode = potentialHooksFound.filter(hookNode =>
|
||||
filterMemberNodesOfTargetHook(potentialReactHookASTNode, hookNode),
|
||||
);
|
||||
}
|
||||
return nodesAssociatedWithReactHookASTNode;
|
||||
}
|
||||
|
||||
// Returns Hook name
|
||||
export function getHookName(
|
||||
hook: HooksNode,
|
||||
originalSourceAST: mixed,
|
||||
originalSourceCode: string,
|
||||
originalSourceLineNumber: number,
|
||||
): string | null {
|
||||
const hooksFromAST = getPotentialHookDeclarationsFromAST(originalSourceAST);
|
||||
|
||||
const potentialReactHookASTNode = hooksFromAST.find(node => {
|
||||
const nodeLocationCheck = checkNodeLocation(
|
||||
node,
|
||||
((originalSourceLineNumber: any): number),
|
||||
);
|
||||
const hookDeclaractionCheck = isConfirmedHookDeclaration(node);
|
||||
return nodeLocationCheck && hookDeclaractionCheck;
|
||||
});
|
||||
|
||||
if (!potentialReactHookASTNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// nodesAssociatedWithReactHookASTNode could directly be used to obtain the hook variable name
|
||||
// depending on the type of potentialReactHookASTNode
|
||||
try {
|
||||
const nodesAssociatedWithReactHookASTNode = getFilteredHookASTNodes(
|
||||
potentialReactHookASTNode,
|
||||
hooksFromAST,
|
||||
originalSourceCode,
|
||||
);
|
||||
|
||||
return getHookNameFromNode(
|
||||
hook,
|
||||
nodesAssociatedWithReactHookASTNode,
|
||||
potentialReactHookASTNode,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getHookNameFromNode(
|
||||
originalHook: HooksNode,
|
||||
nodesAssociatedWithReactHookASTNode: NodePath[],
|
||||
potentialReactHookASTNode: NodePath,
|
||||
): string | null {
|
||||
let hookVariableName: string | null;
|
||||
const isCustomHook = originalHook.id === null;
|
||||
|
||||
switch (nodesAssociatedWithReactHookASTNode.length) {
|
||||
case 1:
|
||||
// CASE 1A (nodesAssociatedWithReactHookASTNode[0] !== potentialReactHookASTNode):
|
||||
// const flagState = useState(true); -> later referenced as
|
||||
// const [flag, setFlag] = flagState;
|
||||
//
|
||||
// CASE 1B (nodesAssociatedWithReactHookASTNode[0] === potentialReactHookASTNode):
|
||||
// const [flag, setFlag] = useState(true); -> we have access to the hook variable straight away
|
||||
//
|
||||
// CASE 1C (isCustomHook && nodesAssociatedWithReactHookASTNode[0] === potentialReactHookASTNode):
|
||||
// const someVariable = useSomeCustomHook(); -> we have access to hook variable straight away
|
||||
// const [someVariable, someFunction] = useAnotherCustomHook(); -> we ignore variable names in this case
|
||||
// as it is unclear what variable name to show
|
||||
if (
|
||||
isCustomHook &&
|
||||
nodesAssociatedWithReactHookASTNode[0] === potentialReactHookASTNode
|
||||
) {
|
||||
hookVariableName = getHookVariableName(
|
||||
potentialReactHookASTNode,
|
||||
isCustomHook,
|
||||
);
|
||||
break;
|
||||
}
|
||||
hookVariableName = getHookVariableName(
|
||||
nodesAssociatedWithReactHookASTNode[0],
|
||||
);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// const flagState = useState(true); -> later referenced as
|
||||
// const flag = flagState[0];
|
||||
// const setFlag = flagState[1];
|
||||
nodesAssociatedWithReactHookASTNode = nodesAssociatedWithReactHookASTNode.filter(
|
||||
hookPath => filterMemberWithHookVariableName(hookPath),
|
||||
);
|
||||
|
||||
if (nodesAssociatedWithReactHookASTNode.length !== 1) {
|
||||
// Something went wrong, only a single desirable hook should remain here
|
||||
throw new Error("Couldn't isolate AST Node containing hook variable.");
|
||||
}
|
||||
hookVariableName = getHookVariableName(
|
||||
nodesAssociatedWithReactHookASTNode[0],
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Case 0:
|
||||
// const flagState = useState(true); -> which is not accessed anywhere
|
||||
//
|
||||
// Case > 2 (fallback):
|
||||
// const someState = React.useState(() => 0)
|
||||
//
|
||||
// const stateVariable = someState[0]
|
||||
// const setStateVariable = someState[1]
|
||||
//
|
||||
// const [number2, setNumber2] = someState
|
||||
//
|
||||
// We assign the state variable for 'someState' to multiple variables,
|
||||
// and hence cannot isolate a unique variable name. In such cases,
|
||||
// default to showing 'someState'
|
||||
|
||||
hookVariableName = getHookVariableName(potentialReactHookASTNode);
|
||||
break;
|
||||
}
|
||||
|
||||
return hookVariableName;
|
||||
}
|
||||
|
||||
// Extracts the variable name from hook node path
|
||||
function getHookVariableName(
|
||||
hook: NodePath,
|
||||
isCustomHook: boolean = false,
|
||||
): string | null {
|
||||
const nodeType = hook.node.id.type;
|
||||
switch (nodeType) {
|
||||
case AST_NODE_TYPES.ARRAY_PATTERN:
|
||||
return !isCustomHook ? hook.node.id.elements[0].name : null;
|
||||
|
||||
case AST_NODE_TYPES.IDENTIFIER:
|
||||
return hook.node.id.name;
|
||||
|
||||
default:
|
||||
throw new Error(`Invalid node type: ${nodeType}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getPotentialHookDeclarationsFromAST(sourceAST: File): NodePath[] {
|
||||
const potentialHooksFound: NodePath[] = [];
|
||||
traverse(sourceAST, {
|
||||
enter(path) {
|
||||
if (path.isVariableDeclarator() && isPotentialHookDeclaration(path)) {
|
||||
potentialHooksFound.push(path);
|
||||
}
|
||||
},
|
||||
});
|
||||
return potentialHooksFound;
|
||||
}
|
||||
|
||||
// Check if 'path' contains declaration of the form const X = useState(0);
|
||||
function isConfirmedHookDeclaration(path: NodePath): boolean {
|
||||
const node = path.node.init;
|
||||
if (node.type !== AST_NODE_TYPES.CALL_EXPRESSION) {
|
||||
return false;
|
||||
}
|
||||
const callee = node.callee;
|
||||
return isHook(callee);
|
||||
}
|
||||
|
||||
// We consider hooks to be a hook name identifier or a member expression containing a hook name.
|
||||
function isHook(node: Node): boolean {
|
||||
if (node.type === AST_NODE_TYPES.IDENTIFIER) {
|
||||
return isHookName(node.name);
|
||||
} else if (
|
||||
node.type === AST_NODE_TYPES.MEMBER_EXPRESSION &&
|
||||
!node.computed &&
|
||||
isHook(node.property)
|
||||
) {
|
||||
const obj = node.object;
|
||||
const isPascalCaseNameSpace = /^[A-Z].*/;
|
||||
return (
|
||||
obj.type === AST_NODE_TYPES.IDENTIFIER &&
|
||||
isPascalCaseNameSpace.test(obj.name)
|
||||
);
|
||||
} else {
|
||||
// TODO Possibly handle inline require statements e.g. require("useStable")(...)
|
||||
// This does not seem like a high priority, since inline requires are probably
|
||||
// not common and are also typically in compiled code rather than source code.
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Catch all identifiers that begin with "use"
|
||||
// followed by an uppercase Latin character to exclude identifiers like "user".
|
||||
// Copied from packages/eslint-plugin-react-hooks/src/RulesOfHooks
|
||||
function isHookName(name: string): boolean {
|
||||
return /^use[A-Z0-9].*$/.test(name);
|
||||
}
|
||||
|
||||
// Determines whether incoming hook is a primitive hook that gets assigned to variables.
|
||||
export function isNonDeclarativePrimitiveHook(hook: HooksNode) {
|
||||
return ['Effect', 'ImperativeHandle', 'LayoutEffect', 'DebugValue'].includes(
|
||||
hook.name,
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the AST Node COULD be a React Hook
|
||||
function isPotentialHookDeclaration(path: NodePath): boolean {
|
||||
// The array potentialHooksFound will contain all potential hook declaration cases we support
|
||||
const nodePathInit = path.node.init;
|
||||
if (nodePathInit != null) {
|
||||
if (nodePathInit.type === AST_NODE_TYPES.CALL_EXPRESSION) {
|
||||
// CASE: CallExpression
|
||||
// 1. const [count, setCount] = useState(0); -> destructured pattern
|
||||
// 2. const [A, setA] = useState(0), const [B, setB] = useState(0); -> multiple inline declarations
|
||||
// 3. const [
|
||||
// count,
|
||||
// setCount
|
||||
// ] = useState(0); -> multiline hook declaration
|
||||
// 4. const ref = useRef(null); -> generic hooks
|
||||
const callee = nodePathInit.callee;
|
||||
return isHook(callee);
|
||||
} else if (
|
||||
nodePathInit.type === AST_NODE_TYPES.MEMBER_EXPRESSION ||
|
||||
nodePathInit.type === AST_NODE_TYPES.IDENTIFIER
|
||||
) {
|
||||
// CASE: MemberExpression
|
||||
// const countState = React.useState(0);
|
||||
// const count = countState[0];
|
||||
// const setCount = countState[1]; -> Accessing members following hook declaration
|
||||
|
||||
// CASE: Identifier
|
||||
// const countState = React.useState(0);
|
||||
// const [count, setCount] = countState; -> destructuring syntax following hook declaration
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Check whether 'node' is hook decalration of form useState(0); OR React.useState(0);
|
||||
function isReactFunction(node: Node, functionName: string): boolean {
|
||||
return (
|
||||
node.name === functionName ||
|
||||
(node.type === 'MemberExpression' &&
|
||||
node.object.name === 'React' &&
|
||||
node.property.name === functionName)
|
||||
);
|
||||
}
|
||||
|
||||
// Check if 'path' is either State or Reducer hook
|
||||
function isStateOrReducerHook(path: NodePath): boolean {
|
||||
const callee = path.node.init.callee;
|
||||
return (
|
||||
isReactFunction(callee, 'useState') || isReactFunction(callee, 'useReducer')
|
||||
);
|
||||
}
|
||||
|
||||
// Check whether hookNode of a declaration contains obvious variable name
|
||||
function nodeContainsHookVariableName(hookNode: NodePath): boolean {
|
||||
// We determine cases where variable names are obvious in declarations. Examples:
|
||||
// const [tick, setTick] = useState(1); OR const ref = useRef(null);
|
||||
// Here tick/ref are obvious hook variables in the hook declaration node itself
|
||||
// 1. True for satisfying above cases
|
||||
// 2. False for everything else. Examples:
|
||||
// const countState = React.useState(0);
|
||||
// const count = countState[0];
|
||||
// const setCount = countState[1]; -> not obvious, hook variable can't be determined
|
||||
// from the hook declaration node alone
|
||||
// 3. For custom hooks we force pass true since we are only concerned with the AST node
|
||||
// regardless of how it is accessed in source code. (See: getHookVariableName)
|
||||
|
||||
const node = hookNode.node.id;
|
||||
if (
|
||||
node.type === AST_NODE_TYPES.ARRAY_PATTERN ||
|
||||
(node.type === AST_NODE_TYPES.IDENTIFIER && !isStateOrReducerHook(hookNode))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -12,6 +12,7 @@ import {
|
|||
getSavedComponentFilters,
|
||||
getShowInlineWarningsAndErrors,
|
||||
} from 'react-devtools-shared/src/utils';
|
||||
import parseHookNames from './parseHookNames';
|
||||
import {
|
||||
localStorageGetItem,
|
||||
localStorageRemoveItem,
|
||||
|
@ -214,6 +215,7 @@ function createPanelIfReactLoaded() {
|
|||
browserTheme: getBrowserTheme(),
|
||||
componentsPortalContainer,
|
||||
enabledInspectedElementContextMenu: true,
|
||||
loadHookNamesFunction: parseHookNames,
|
||||
overrideTab,
|
||||
profilerPortalContainer,
|
||||
showTabBar: false,
|
||||
|
|
|
@ -0,0 +1,501 @@
|
|||
/* global chrome */
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {parse} from '@babel/parser';
|
||||
import {enableHookNameParsing} from 'react-devtools-feature-flags';
|
||||
import LRU from 'lru-cache';
|
||||
import {SourceMapConsumer} from 'source-map';
|
||||
import {getHookName, isNonDeclarativePrimitiveHook} from './astUtils';
|
||||
import {areSourceMapsAppliedToErrors} from './ErrorTester';
|
||||
import {__DEBUG__} from 'react-devtools-shared/src/constants';
|
||||
|
||||
import type {
|
||||
HooksNode,
|
||||
HookSource,
|
||||
HooksTree,
|
||||
} from 'react-debug-tools/src/ReactDebugHooks';
|
||||
import type {HookNames, LRUCache} from 'react-devtools-shared/src/types';
|
||||
import type {Thenable} from 'shared/ReactTypes';
|
||||
import type {SourceConsumer, SourceMap} from './astUtils';
|
||||
|
||||
const SOURCE_MAP_REGEX = / ?sourceMappingURL=([^\s'"]+)/gm;
|
||||
const ABSOLUTE_URL_REGEX = /^https?:\/\//i;
|
||||
const MAX_SOURCE_LENGTH = 100_000_000;
|
||||
|
||||
type AST = mixed;
|
||||
|
||||
type HookSourceData = {|
|
||||
// Generated by react-debug-tools.
|
||||
hookSource: HookSource,
|
||||
|
||||
// AST for original source code; typically comes from a consumed source map.
|
||||
originalSourceAST: AST | null,
|
||||
|
||||
// Source code (React components or custom hooks) containing primitive hook calls.
|
||||
// If no source map has been provided, this code will be the same as runtimeSourceCode.
|
||||
originalSourceCode: string | null,
|
||||
|
||||
// Compiled code (React components or custom hooks) containing primitive hook calls.
|
||||
runtimeSourceCode: string | null,
|
||||
|
||||
// APIs from source-map for parsing source maps (if detected).
|
||||
sourceConsumer: SourceConsumer | null,
|
||||
|
||||
// External URL of source map.
|
||||
// Sources without source maps (or with inline source maps) won't have this.
|
||||
sourceMapURL: string | null,
|
||||
|
||||
// Parsed source map object.
|
||||
sourceMapContents: SourceMap | null,
|
||||
|};
|
||||
|
||||
type CachedMetadata = {|
|
||||
originalSourceAST: AST,
|
||||
originalSourceCode: string,
|
||||
sourceConsumer: SourceConsumer | null,
|
||||
|};
|
||||
|
||||
// On large trees, encoding takes significant time.
|
||||
// Try to reuse the already encoded strings.
|
||||
const fileNameToMetadataCache: LRUCache<string, CachedMetadata> = new LRU({
|
||||
max: 50,
|
||||
dispose: (fileName: string, metadata: CachedMetadata) => {
|
||||
if (__DEBUG__) {
|
||||
console.log(
|
||||
'fileNameToHookSourceData.dispose() Evicting cached metadata for "' +
|
||||
fileName +
|
||||
'"',
|
||||
);
|
||||
}
|
||||
|
||||
const sourceConsumer = metadata.sourceConsumer;
|
||||
if (sourceConsumer !== null) {
|
||||
sourceConsumer.destroy();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default async function parseHookNames(
|
||||
hooksTree: HooksTree,
|
||||
): Thenable<HookNames | null> {
|
||||
if (!enableHookNameParsing) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const hooksList: Array<HooksNode> = [];
|
||||
flattenHooksList(hooksTree, hooksList);
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.log('parseHookNames() hooksList:', hooksList);
|
||||
}
|
||||
|
||||
// Gather the unique set of source files to load for the built-in hooks.
|
||||
const fileNameToHookSourceData: Map<string, HookSourceData> = new Map();
|
||||
for (let i = 0; i < hooksList.length; i++) {
|
||||
const hook = hooksList[i];
|
||||
|
||||
const hookSource = hook.hookSource;
|
||||
if (hookSource == null) {
|
||||
// Older versions of react-debug-tools don't include this information.
|
||||
// In this case, we can't continue.
|
||||
throw Error('Hook source code location not found.');
|
||||
}
|
||||
|
||||
const fileName = hookSource.fileName;
|
||||
if (fileName == null) {
|
||||
throw Error('Hook source code location not found.');
|
||||
} else {
|
||||
if (!fileNameToHookSourceData.has(fileName)) {
|
||||
const hookSourceData: HookSourceData = {
|
||||
hookSource,
|
||||
originalSourceAST: null,
|
||||
originalSourceCode: null,
|
||||
runtimeSourceCode: null,
|
||||
sourceConsumer: null,
|
||||
sourceMapURL: null,
|
||||
sourceMapContents: null,
|
||||
};
|
||||
|
||||
// If we've already loaded source/source map info for this file,
|
||||
// we can skip reloading it (and more importantly, re-parsing it).
|
||||
const metadata = fileNameToMetadataCache.get(fileName);
|
||||
if (metadata != null) {
|
||||
if (__DEBUG__) {
|
||||
console.groupCollapsed(
|
||||
'parseHookNames() Found cached metadata for file "' +
|
||||
fileName +
|
||||
'"',
|
||||
);
|
||||
console.log(metadata);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
hookSourceData.originalSourceAST = metadata.originalSourceAST;
|
||||
hookSourceData.originalSourceCode = metadata.originalSourceCode;
|
||||
hookSourceData.sourceConsumer = metadata.sourceConsumer;
|
||||
}
|
||||
|
||||
fileNameToHookSourceData.set(fileName, hookSourceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loadSourceFiles(fileNameToHookSourceData)
|
||||
.then(() => extractAndLoadSourceMaps(fileNameToHookSourceData))
|
||||
.then(() => parseSourceAST(fileNameToHookSourceData))
|
||||
.then(() => updateLruCache(fileNameToHookSourceData))
|
||||
.then(() => findHookNames(hooksList, fileNameToHookSourceData));
|
||||
}
|
||||
|
||||
function decodeBase64String(encoded: string): Object {
|
||||
if (typeof atob === 'function') {
|
||||
return atob(encoded);
|
||||
} else if (
|
||||
typeof Buffer !== 'undefined' &&
|
||||
Buffer !== null &&
|
||||
typeof Buffer.from === 'function'
|
||||
) {
|
||||
return Buffer.from(encoded, 'base64');
|
||||
} else {
|
||||
throw Error('Cannot decode base64 string');
|
||||
}
|
||||
}
|
||||
|
||||
function extractAndLoadSourceMaps(
|
||||
fileNameToHookSourceData: Map<string, HookSourceData>,
|
||||
): Promise<*> {
|
||||
const promises = [];
|
||||
fileNameToHookSourceData.forEach(hookSourceData => {
|
||||
if (hookSourceData.originalSourceAST !== null) {
|
||||
// Use cached metadata.
|
||||
return;
|
||||
}
|
||||
|
||||
const runtimeSourceCode = ((hookSourceData.runtimeSourceCode: any): string);
|
||||
const sourceMappingURLs = runtimeSourceCode.match(SOURCE_MAP_REGEX);
|
||||
if (sourceMappingURLs == null) {
|
||||
// Maybe file has not been transformed; we'll try to parse it as-is in parseSourceAST().
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.log('extractAndLoadSourceMaps() No source map found');
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < sourceMappingURLs.length; i++) {
|
||||
const sourceMappingURL = sourceMappingURLs[i];
|
||||
const index = sourceMappingURL.indexOf('base64,');
|
||||
if (index >= 0) {
|
||||
// Web apps like Code Sandbox embed multiple inline source maps.
|
||||
// In this case, we need to loop through and find the right one.
|
||||
// We may also need to trim any part of this string that isn't based64 encoded data.
|
||||
const trimmed = ((sourceMappingURL.match(
|
||||
/base64,([a-zA-Z0-9+\/=]+)/,
|
||||
): any): Array<string>)[1];
|
||||
const decoded = decodeBase64String(trimmed);
|
||||
const parsed = JSON.parse(decoded);
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.groupCollapsed(
|
||||
'extractAndLoadSourceMaps() Inline source map',
|
||||
);
|
||||
console.log(parsed);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
// Hook source might be a URL like "https://4syus.csb.app/src/App.js"
|
||||
// Parsed source map might be a partial path like "src/App.js"
|
||||
const fileName = ((hookSourceData.hookSource.fileName: any): string);
|
||||
const match = parsed.sources.find(
|
||||
source =>
|
||||
source === 'Inline Babel script' || fileName.includes(source),
|
||||
);
|
||||
if (match) {
|
||||
hookSourceData.sourceMapContents = parsed;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (sourceMappingURLs.length > 1) {
|
||||
console.warn(
|
||||
'More than one external source map detected in the source file',
|
||||
);
|
||||
}
|
||||
|
||||
let url = sourceMappingURLs[0].split('=')[1];
|
||||
if (ABSOLUTE_URL_REGEX.test(url)) {
|
||||
const baseURL = url.slice(0, url.lastIndexOf('/'));
|
||||
url = `${baseURL}/${url}`;
|
||||
|
||||
if (!isValidUrl(url)) {
|
||||
throw new Error(`Invalid source map URL "${url}"`);
|
||||
}
|
||||
}
|
||||
|
||||
hookSourceData.sourceMapURL = url;
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.log(
|
||||
'extractAndLoadSourceMaps() External source map "' + url + '"',
|
||||
);
|
||||
}
|
||||
|
||||
promises.push(
|
||||
fetchFile(url).then(sourceMapContents => {
|
||||
hookSourceData.sourceMapContents = JSON.parse(sourceMapContents);
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function fetchFile(url: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(url).then(response => {
|
||||
if (response.ok) {
|
||||
response
|
||||
.text()
|
||||
.then(text => {
|
||||
resolve(text);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(null);
|
||||
});
|
||||
} else {
|
||||
reject(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function findHookNames(
|
||||
hooksList: Array<HooksNode>,
|
||||
fileNameToHookSourceData: Map<string, HookSourceData>,
|
||||
): HookNames {
|
||||
const map: HookNames = new Map();
|
||||
|
||||
hooksList.map(hook => {
|
||||
if (isNonDeclarativePrimitiveHook(hook)) {
|
||||
if (__DEBUG__) {
|
||||
console.log('findHookNames() Non declarative primitive hook');
|
||||
}
|
||||
|
||||
// Not all hooks have names (e.g. useEffect or useLayoutEffect)
|
||||
return null;
|
||||
}
|
||||
|
||||
// We already guard against a null HookSource in parseHookNames()
|
||||
const hookSource = ((hook.hookSource: any): HookSource);
|
||||
const fileName = hookSource.fileName;
|
||||
if (!fileName) {
|
||||
return null; // Should not be reachable.
|
||||
}
|
||||
|
||||
const hookSourceData = fileNameToHookSourceData.get(fileName);
|
||||
if (!hookSourceData) {
|
||||
return null; // Should not be reachable.
|
||||
}
|
||||
|
||||
const {lineNumber, columnNumber} = hookSource;
|
||||
if (!lineNumber || !columnNumber) {
|
||||
return null; // Should not be reachable.
|
||||
}
|
||||
|
||||
const sourceConsumer = hookSourceData.sourceConsumer;
|
||||
|
||||
let originalSourceLineNumber;
|
||||
if (areSourceMapsAppliedToErrors() || !sourceConsumer) {
|
||||
// Either the current environment automatically applies source maps to errors,
|
||||
// or the current code had no source map to begin with.
|
||||
// Either way, we don't need to convert the Error stack frame locations.
|
||||
originalSourceLineNumber = lineNumber;
|
||||
} else {
|
||||
originalSourceLineNumber = sourceConsumer.originalPositionFor({
|
||||
line: lineNumber,
|
||||
column: columnNumber,
|
||||
}).line;
|
||||
}
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.log(
|
||||
'findHookNames() mapped line number',
|
||||
lineNumber,
|
||||
'to',
|
||||
originalSourceLineNumber,
|
||||
);
|
||||
}
|
||||
|
||||
if (originalSourceLineNumber === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = getHookName(
|
||||
hook,
|
||||
hookSourceData.originalSourceAST,
|
||||
((hookSourceData.originalSourceCode: any): string),
|
||||
((originalSourceLineNumber: any): number),
|
||||
);
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.log('findHookNames() Found name "' + (name || '-') + '"');
|
||||
}
|
||||
|
||||
map.set(hook, name);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function isValidUrl(possibleURL: string): boolean {
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(possibleURL);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function loadSourceFiles(
|
||||
fileNameToHookSourceData: Map<string, HookSourceData>,
|
||||
): Promise<*> {
|
||||
const promises = [];
|
||||
fileNameToHookSourceData.forEach((hookSourceData, fileName) => {
|
||||
promises.push(
|
||||
fetchFile(fileName).then(runtimeSourceCode => {
|
||||
if (runtimeSourceCode.length > MAX_SOURCE_LENGTH) {
|
||||
throw Error('Source code too large to parse');
|
||||
}
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.groupCollapsed(
|
||||
'loadSourceFiles() fileName "' + fileName + '"',
|
||||
);
|
||||
console.log(runtimeSourceCode);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
hookSourceData.runtimeSourceCode = runtimeSourceCode;
|
||||
}),
|
||||
);
|
||||
});
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
async function parseSourceAST(
|
||||
fileNameToHookSourceData: Map<string, HookSourceData>,
|
||||
): Promise<*> {
|
||||
// SourceMapConsumer.initialize() does nothing when running in Node (aka Jest)
|
||||
// because the wasm file is automatically read from the file system
|
||||
// so we can avoid triggering a warning message about this.
|
||||
if (!__TEST__) {
|
||||
if (__DEBUG__) {
|
||||
console.log('parseSourceAST() Initializing source-map library ...');
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
const wasmMappingsURL = chrome.extension.getURL('mappings.wasm');
|
||||
|
||||
SourceMapConsumer.initialize({'lib/mappings.wasm': wasmMappingsURL});
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
fileNameToHookSourceData.forEach(hookSourceData => {
|
||||
if (hookSourceData.originalSourceAST !== null) {
|
||||
// Use cached metadata.
|
||||
return;
|
||||
}
|
||||
|
||||
const {runtimeSourceCode, sourceMapContents} = hookSourceData;
|
||||
if (sourceMapContents !== null) {
|
||||
// Parse and extract the AST from the source map.
|
||||
promises.push(
|
||||
SourceMapConsumer.with(
|
||||
sourceMapContents,
|
||||
null,
|
||||
(sourceConsumer: SourceConsumer) => {
|
||||
hookSourceData.sourceConsumer = sourceConsumer;
|
||||
|
||||
// Now that the source map has been loaded,
|
||||
// extract the original source for later.
|
||||
const source = sourceMapContents.sources[0];
|
||||
const originalSourceCode = sourceConsumer.sourceContentFor(
|
||||
source,
|
||||
true,
|
||||
);
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.groupCollapsed(
|
||||
'parseSourceAST() Extracted source code from source map',
|
||||
);
|
||||
console.log(originalSourceCode);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
hookSourceData.originalSourceCode = originalSourceCode;
|
||||
|
||||
// TODO Parsing should ideally be done off of the main thread.
|
||||
hookSourceData.originalSourceAST = parse(originalSourceCode, {
|
||||
sourceType: 'unambiguous',
|
||||
plugins: ['jsx', 'typescript'],
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// There's no source map to parse here so we can just parse the original source itself.
|
||||
hookSourceData.originalSourceCode = runtimeSourceCode;
|
||||
|
||||
// TODO Parsing should ideally be done off of the main thread.
|
||||
hookSourceData.originalSourceAST = parse(runtimeSourceCode, {
|
||||
sourceType: 'unambiguous',
|
||||
plugins: ['jsx', 'typescript'],
|
||||
});
|
||||
}
|
||||
});
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function flattenHooksList(
|
||||
hooksTree: HooksTree,
|
||||
hooksList: Array<HooksNode>,
|
||||
): void {
|
||||
for (let i = 0; i < hooksTree.length; i++) {
|
||||
const hook = hooksTree[i];
|
||||
hooksList.push(hook);
|
||||
if (hook.subHooks.length > 0) {
|
||||
flattenHooksList(hook.subHooks, hooksList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateLruCache(
|
||||
fileNameToHookSourceData: Map<string, HookSourceData>,
|
||||
): Promise<*> {
|
||||
fileNameToHookSourceData.forEach(
|
||||
({originalSourceAST, originalSourceCode, sourceConsumer}, fileName) => {
|
||||
// Only set once to avoid triggering eviction/cleanup code.
|
||||
if (!fileNameToMetadataCache.has(fileName)) {
|
||||
if (__DEBUG__) {
|
||||
console.log('updateLruCache() Caching metada for "' + fileName + '"');
|
||||
}
|
||||
|
||||
fileNameToMetadataCache.set(fileName, {
|
||||
originalSourceAST,
|
||||
originalSourceCode: ((originalSourceCode: any): string),
|
||||
sourceConsumer,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
return Promise.resolve();
|
||||
}
|
|
@ -32,6 +32,10 @@ module.exports = {
|
|||
node: {
|
||||
// Don't define a polyfill on window.setImmediate
|
||||
setImmediate: false,
|
||||
|
||||
// source-maps package has a dependency on 'fs'
|
||||
// but this build won't trigger that code path
|
||||
fs: 'empty',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
|
|
@ -37,6 +37,10 @@ module.exports = {
|
|||
node: {
|
||||
// Don't define a polyfill on window.setImmediate
|
||||
setImmediate: false,
|
||||
|
||||
// source-maps package has a dependency on 'fs'
|
||||
// but this build won't trigger that code path
|
||||
fs: 'empty',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
@ -65,6 +69,17 @@ module.exports = {
|
|||
}),
|
||||
],
|
||||
module: {
|
||||
defaultRules: [
|
||||
{
|
||||
type: 'javascript/auto',
|
||||
resolve: {},
|
||||
},
|
||||
{
|
||||
test: /\.json$/i,
|
||||
type: 'json',
|
||||
},
|
||||
],
|
||||
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
|
|
|
@ -37,6 +37,11 @@ module.exports = {
|
|||
'react-is': 'react-is',
|
||||
scheduler: 'scheduler',
|
||||
},
|
||||
node: {
|
||||
// source-maps package has a dependency on 'fs'
|
||||
// but this build won't trigger that code path
|
||||
fs: 'empty',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'react-devtools-feature-flags': resolveFeatureFlags('inline'),
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
"react-dom-15": "npm:react-dom@^15"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.12.5",
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@babel/traverse": "^7.12.5",
|
||||
"@reach/menu-button": "^0.1.17",
|
||||
"@reach/tooltip": "^0.2.2",
|
||||
"clipboard-js": "^0.3.6",
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
function hasAbsoluteFileName(hook) {
|
||||
const fileName = hook.hookSource ? hook.hookSource.fileName : null;
|
||||
if (fileName == null) {
|
||||
return false;
|
||||
} else {
|
||||
return fileName.indexOf('/react-devtools-shared/') > 0;
|
||||
}
|
||||
}
|
||||
|
||||
function serializeHook(hook) {
|
||||
if (!hook.hookSource) return hook;
|
||||
|
||||
// Remove user-specific portions of this file path.
|
||||
let fileName = hook.hookSource.fileName;
|
||||
const index = fileName.lastIndexOf('/react-devtools-shared/');
|
||||
fileName = fileName.substring(index + 1);
|
||||
|
||||
let subHooks = hook.subHooks;
|
||||
if (subHooks) {
|
||||
subHooks = subHooks.map(serializeHook);
|
||||
}
|
||||
|
||||
return {
|
||||
...hook,
|
||||
hookSource: {
|
||||
...hook.hookSource,
|
||||
fileName,
|
||||
|
||||
// Otherwise changes in any test case or formatting might invalidate other tests.
|
||||
columnNumber: 'removed by Jest serializer',
|
||||
lineNumber: 'removed by Jest serializer',
|
||||
},
|
||||
subHooks,
|
||||
};
|
||||
}
|
||||
|
||||
// test() is part of Jest's serializer API
|
||||
export function test(maybeHook) {
|
||||
if (maybeHook === null || typeof maybeHook !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty.bind(maybeHook);
|
||||
|
||||
return (
|
||||
hasOwnProperty('id') &&
|
||||
hasOwnProperty('isStateEditable') &&
|
||||
hasOwnProperty('name') &&
|
||||
hasOwnProperty('subHooks') &&
|
||||
hasOwnProperty('value') &&
|
||||
// Don't re-process an already printed hook.
|
||||
hasAbsoluteFileName(maybeHook)
|
||||
);
|
||||
}
|
||||
|
||||
// print() is part of Jest's serializer API
|
||||
export function print(hook, serialize, indent) {
|
||||
// Don't stringify this object; that would break nested serializers.
|
||||
return serialize(serializeHook(hook));
|
||||
}
|
|
@ -25,6 +25,7 @@ describe('InspectedElement', () => {
|
|||
let BridgeContext;
|
||||
let InspectedElementContext;
|
||||
let InspectedElementContextController;
|
||||
let SettingsContextController;
|
||||
let StoreContext;
|
||||
let TreeContextController;
|
||||
|
||||
|
@ -57,6 +58,8 @@ describe('InspectedElement', () => {
|
|||
.InspectedElementContext;
|
||||
InspectedElementContextController = require('react-devtools-shared/src/devtools/views/Components/InspectedElementContext')
|
||||
.InspectedElementContextController;
|
||||
SettingsContextController = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext')
|
||||
.SettingsContextController;
|
||||
StoreContext = require('react-devtools-shared/src/devtools/views/context')
|
||||
.StoreContext;
|
||||
TreeContextController = require('react-devtools-shared/src/devtools/views/Components/TreeContext')
|
||||
|
@ -79,15 +82,17 @@ describe('InspectedElement', () => {
|
|||
}) => (
|
||||
<BridgeContext.Provider value={bridge}>
|
||||
<StoreContext.Provider value={store}>
|
||||
<TreeContextController
|
||||
defaultSelectedElementID={defaultSelectedElementID}
|
||||
defaultSelectedElementIndex={defaultSelectedElementIndex}>
|
||||
<React.Suspense fallback="Loading...">
|
||||
<InspectedElementContextController>
|
||||
{children}
|
||||
</InspectedElementContextController>
|
||||
</React.Suspense>
|
||||
</TreeContextController>
|
||||
<SettingsContextController>
|
||||
<TreeContextController
|
||||
defaultSelectedElementID={defaultSelectedElementID}
|
||||
defaultSelectedElementIndex={defaultSelectedElementIndex}>
|
||||
<React.Suspense fallback="Loading...">
|
||||
<InspectedElementContextController>
|
||||
{children}
|
||||
</InspectedElementContextController>
|
||||
</React.Suspense>
|
||||
</TreeContextController>
|
||||
</SettingsContextController>
|
||||
</StoreContext.Provider>
|
||||
</BridgeContext.Provider>
|
||||
);
|
||||
|
|
|
@ -10,6 +10,7 @@ export function test(maybeInspectedElement) {
|
|||
const hasOwnProperty = Object.prototype.hasOwnProperty.bind(
|
||||
maybeInspectedElement,
|
||||
);
|
||||
|
||||
return (
|
||||
hasOwnProperty('canEditFunctionProps') &&
|
||||
hasOwnProperty('canEditHooks') &&
|
||||
|
|
|
@ -7,3 +7,4 @@ if (!global.hasOwnProperty('localStorage')) {
|
|||
|
||||
// Mimic the global we set with Webpack's DefinePlugin
|
||||
global.__DEV__ = process.env.NODE_ENV !== 'production';
|
||||
global.__TEST__ = true;
|
||||
|
|
|
@ -122,6 +122,16 @@ env.beforeEach(() => {
|
|||
global.agent = agent;
|
||||
global.bridge = bridge;
|
||||
global.store = store;
|
||||
|
||||
const readFileSync = require('fs').readFileSync;
|
||||
async function mockFetch(url) {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => readFileSync(__dirname + url, 'utf-8'),
|
||||
};
|
||||
}
|
||||
global.fetch = mockFetch;
|
||||
});
|
||||
env.afterEach(() => {
|
||||
delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
|
|
|
@ -268,3 +268,13 @@ export function withErrorsOrWarningsIgnored<T: void | Promise<void>>(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function overrideFeatureFlags(overrideFlags) {
|
||||
jest.mock('react-devtools-feature-flags', () => {
|
||||
const actualFlags = jest.requireActual('react-devtools-feature-flags');
|
||||
return {
|
||||
...actualFlags,
|
||||
...overrideFlags,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -80,7 +80,10 @@ import {
|
|||
MEMO_SYMBOL_STRING,
|
||||
} from './ReactSymbols';
|
||||
import {format} from './utils';
|
||||
import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags';
|
||||
import {
|
||||
enableHookNameParsing,
|
||||
enableProfilerChangedHookIndices,
|
||||
} from 'react-devtools-feature-flags';
|
||||
import is from 'shared/objectIs';
|
||||
import isArray from 'shared/isArray';
|
||||
|
||||
|
@ -3092,6 +3095,7 @@ export function attach(
|
|||
hooks = inspectHooksOfFiber(
|
||||
fiber,
|
||||
(renderer.currentDispatcherRef: any),
|
||||
enableHookNameParsing, // Include source location info for hooks
|
||||
);
|
||||
} finally {
|
||||
// Restore original console functionality.
|
||||
|
|
|
@ -13,5 +13,6 @@
|
|||
* It should always be imported from "react-devtools-feature-flags".
|
||||
************************************************************************/
|
||||
|
||||
export const enableHookNameParsing = false;
|
||||
export const enableProfilerChangedHookIndices = false;
|
||||
export const isInternalFacebookBuild = false;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
* It should always be imported from "react-devtools-feature-flags".
|
||||
************************************************************************/
|
||||
|
||||
export const enableHookNameParsing = true;
|
||||
export const enableProfilerChangedHookIndices = true;
|
||||
export const isInternalFacebookBuild = true;
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
* It should always be imported from "react-devtools-feature-flags".
|
||||
************************************************************************/
|
||||
|
||||
export const enableHookNameParsing = false;
|
||||
export const enableProfilerChangedHookIndices = false;
|
||||
export const isInternalFacebookBuild = false;
|
||||
|
||||
|
|
|
@ -23,6 +23,9 @@ export const LOCAL_STORAGE_FILTER_PREFERENCES_KEY =
|
|||
export const SESSION_STORAGE_LAST_SELECTION_KEY =
|
||||
'React::DevTools::lastSelection';
|
||||
|
||||
export const LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY =
|
||||
'React::DevTools::parseHookNames';
|
||||
|
||||
export const SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY =
|
||||
'React::DevTools::recordChangeDescriptions';
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ export type IconType =
|
|||
| 'log-data'
|
||||
| 'more'
|
||||
| 'next'
|
||||
| 'parse-hook-names'
|
||||
| 'previous'
|
||||
| 'record'
|
||||
| 'reload'
|
||||
|
@ -92,6 +93,9 @@ export default function ButtonIcon({className = '', type}: Props) {
|
|||
case 'next':
|
||||
pathData = PATH_NEXT;
|
||||
break;
|
||||
case 'parse-hook-names':
|
||||
pathData = PATH_PARSE_HOOK_NAMES;
|
||||
break;
|
||||
case 'previous':
|
||||
pathData = PATH_PREVIOUS;
|
||||
break;
|
||||
|
@ -141,7 +145,11 @@ export default function ButtonIcon({className = '', type}: Props) {
|
|||
height="24"
|
||||
viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
<path fill="currentColor" d={pathData} />
|
||||
{typeof pathData === 'string' ? (
|
||||
<path fill="currentColor" d={pathData} />
|
||||
) : (
|
||||
pathData
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
@ -197,6 +205,15 @@ const PATH_MORE = `
|
|||
|
||||
const PATH_NEXT = 'M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z';
|
||||
|
||||
const PATH_PARSE_HOOK_NAMES = (
|
||||
<g>
|
||||
<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" />
|
||||
<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" />
|
||||
<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" />
|
||||
<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" />
|
||||
</g>
|
||||
);
|
||||
|
||||
const PATH_PREVIOUS =
|
||||
'M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z';
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ export default function Badge({
|
|||
type,
|
||||
children,
|
||||
}: Props) {
|
||||
if (hocDisplayNames === null) {
|
||||
if (hocDisplayNames === null || hocDisplayNames.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -68,4 +68,5 @@
|
|||
justify-content: center;
|
||||
font-size: var(--font-size-sans-large);
|
||||
color: var(--color-dim);
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
|
|
@ -64,5 +64,5 @@
|
|||
padding: 0.25rem;
|
||||
color: var(--color-dimmer);
|
||||
font-style: italic;
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,12 @@ export default function InspectedElementWrapper(_: Props) {
|
|||
const store = useContext(StoreContext);
|
||||
const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
|
||||
|
||||
const {inspectedElement} = useContext(InspectedElementContext);
|
||||
const {
|
||||
hookNames,
|
||||
inspectedElement,
|
||||
parseHookNames,
|
||||
toggleParseHookNames,
|
||||
} = useContext(InspectedElementContext);
|
||||
|
||||
const element =
|
||||
inspectedElementID !== null
|
||||
|
@ -268,7 +273,10 @@ export default function InspectedElementWrapper(_: Props) {
|
|||
inspectedElementID /* Force reset when selected Element changes */
|
||||
}
|
||||
element={element}
|
||||
hookNames={hookNames}
|
||||
inspectedElement={inspectedElement}
|
||||
parseHookNames={parseHookNames}
|
||||
toggleParseHookNames={toggleParseHookNames}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -18,13 +18,19 @@ import {
|
|||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {enableHookNameParsing} from 'react-devtools-feature-flags';
|
||||
import {TreeStateContext} from './TreeContext';
|
||||
import {BridgeContext, StoreContext} from '../context';
|
||||
import {
|
||||
checkForUpdate,
|
||||
inspectElement,
|
||||
} from 'react-devtools-shared/src/inspectedElementCache';
|
||||
import {loadHookNames} from 'react-devtools-shared/src/hookNamesCache';
|
||||
import {ElementTypeFunction} from 'react-devtools-shared/src/types';
|
||||
import LoadHookNamesFunctionContext from 'react-devtools-shared/src/devtools/views/Components/LoadHookNamesFunctionContext';
|
||||
import {SettingsContext} from '../Settings/SettingsContext';
|
||||
|
||||
import type {HookNames} from 'react-devtools-shared/src/types';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
import type {
|
||||
Element,
|
||||
|
@ -33,10 +39,14 @@ import type {
|
|||
|
||||
type Path = Array<string | number>;
|
||||
type InspectPathFunction = (path: Path) => void;
|
||||
export type ToggleParseHookNames = () => void;
|
||||
|
||||
type Context = {|
|
||||
hookNames: HookNames | null,
|
||||
inspectedElement: InspectedElement | null,
|
||||
inspectPaths: InspectPathFunction,
|
||||
parseHookNames: boolean,
|
||||
toggleParseHookNames: ToggleParseHookNames,
|
||||
|};
|
||||
|
||||
export const InspectedElementContext = createContext<Context>(
|
||||
|
@ -51,8 +61,10 @@ export type Props = {|
|
|||
|
||||
export function InspectedElementContextController({children}: Props) {
|
||||
const {selectedElementID} = useContext(TreeStateContext);
|
||||
const loadHookNamesFunction = useContext(LoadHookNamesFunctionContext);
|
||||
const bridge = useContext(BridgeContext);
|
||||
const store = useContext(StoreContext);
|
||||
const {parseHookNames: parseHookNamesByDefault} = useContext(SettingsContext);
|
||||
|
||||
const refresh = useCacheRefresh();
|
||||
|
||||
|
@ -68,6 +80,13 @@ export function InspectedElementContextController({children}: Props) {
|
|||
path: null,
|
||||
});
|
||||
|
||||
// Parse the currently inspected element's hook names.
|
||||
// This may be enabled by default (for all elements)
|
||||
// or it may be opted into on a per-element basis (if it's too slow to be on by default).
|
||||
const [parseHookNames, setParseHookNames] = useState<boolean>(
|
||||
parseHookNamesByDefault,
|
||||
);
|
||||
|
||||
const element =
|
||||
selectedElementID !== null ? store.getElementByID(selectedElementID) : null;
|
||||
|
||||
|
@ -79,14 +98,41 @@ export function InspectedElementContextController({children}: Props) {
|
|||
element,
|
||||
path: null,
|
||||
});
|
||||
|
||||
setParseHookNames(parseHookNamesByDefault);
|
||||
}
|
||||
|
||||
// Don't load a stale element from the backend; it wastes bridge bandwidth.
|
||||
let inspectedElement = null;
|
||||
let hookNames: HookNames | null = null;
|
||||
if (!elementHasChanged && element !== null) {
|
||||
inspectedElement = inspectElement(element, state.path, store, bridge);
|
||||
|
||||
if (enableHookNameParsing) {
|
||||
if (parseHookNames) {
|
||||
if (
|
||||
inspectedElement !== null &&
|
||||
inspectedElement.type === ElementTypeFunction &&
|
||||
inspectedElement.hooks !== null &&
|
||||
loadHookNamesFunction !== null
|
||||
) {
|
||||
hookNames = loadHookNames(
|
||||
element,
|
||||
inspectedElement.hooks,
|
||||
loadHookNamesFunction,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toggleParseHookNames: ToggleParseHookNames = useCallback<ToggleParseHookNames>(() => {
|
||||
startTransition(() => {
|
||||
setParseHookNames(value => !value);
|
||||
refresh();
|
||||
});
|
||||
}, [setParseHookNames]);
|
||||
|
||||
const inspectPaths: InspectPathFunction = useCallback<InspectPathFunction>(
|
||||
(path: Path) => {
|
||||
startTransition(() => {
|
||||
|
@ -125,6 +171,7 @@ export function InspectedElementContextController({children}: Props) {
|
|||
}
|
||||
}, [
|
||||
element,
|
||||
hookNames,
|
||||
// Reset this timer any time the element we're inspecting gets a new response.
|
||||
// No sense to ping right away after e.g. inspecting/hydrating a path.
|
||||
inspectedElement,
|
||||
|
@ -133,10 +180,19 @@ export function InspectedElementContextController({children}: Props) {
|
|||
|
||||
const value = useMemo<Context>(
|
||||
() => ({
|
||||
hookNames,
|
||||
inspectedElement,
|
||||
inspectPaths,
|
||||
parseHookNames,
|
||||
toggleParseHookNames,
|
||||
}),
|
||||
[inspectedElement, inspectPaths],
|
||||
[
|
||||
hookNames,
|
||||
inspectedElement,
|
||||
inspectPaths,
|
||||
parseHookNames,
|
||||
toggleParseHookNames,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
.Wrapper {
|
||||
border-left: 1px solid var(--color-border);
|
||||
height: 100%;
|
||||
}
|
|
@ -77,4 +77,8 @@
|
|||
margin-right: 0.25rem;
|
||||
border-radius: 0.125rem;
|
||||
padding: 0.125rem 0.25rem;
|
||||
}
|
||||
|
||||
.HookName {
|
||||
color: var(--color-component-name);
|
||||
}
|
|
@ -13,6 +13,7 @@ import {useCallback, useContext, useRef, useState} from 'react';
|
|||
import {BridgeContext, StoreContext} from '../context';
|
||||
import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import Toggle from '../Toggle';
|
||||
import ExpandCollapseToggle from './ExpandCollapseToggle';
|
||||
import KeyValue from './KeyValue';
|
||||
import {getMetaValueLabel, serializeHooksForCopy} from '../utils';
|
||||
|
@ -20,28 +21,49 @@ import Store from '../../store';
|
|||
import styles from './InspectedElementHooksTree.css';
|
||||
import useContextMenu from '../../ContextMenu/useContextMenu';
|
||||
import {meta} from '../../../hydration';
|
||||
import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags';
|
||||
import {
|
||||
enableHookNameParsing,
|
||||
enableProfilerChangedHookIndices,
|
||||
} from 'react-devtools-feature-flags';
|
||||
|
||||
import type {InspectedElement} from './types';
|
||||
import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
|
||||
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
||||
import type {HookNames} from 'react-devtools-shared/src/types';
|
||||
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
|
||||
import type {ToggleParseHookNames} from './InspectedElementContext';
|
||||
|
||||
type HooksTreeViewProps = {|
|
||||
bridge: FrontendBridge,
|
||||
element: Element,
|
||||
hookNames: HookNames | null,
|
||||
inspectedElement: InspectedElement,
|
||||
parseHookNames: boolean,
|
||||
store: Store,
|
||||
toggleParseHookNames: ToggleParseHookNames,
|
||||
|};
|
||||
|
||||
export function InspectedElementHooksTree({
|
||||
bridge,
|
||||
element,
|
||||
hookNames,
|
||||
inspectedElement,
|
||||
parseHookNames,
|
||||
store,
|
||||
toggleParseHookNames,
|
||||
}: HooksTreeViewProps) {
|
||||
const {hooks, id} = inspectedElement;
|
||||
|
||||
// Changing parseHookNames is done in a transition, because it suspends.
|
||||
// This value is done outside of the transition, so the UI toggle feels responsive.
|
||||
const [parseHookNamesOptimistic, setParseHookNamesOptimistic] = useState(
|
||||
parseHookNames,
|
||||
);
|
||||
const handleChange = () => {
|
||||
setParseHookNamesOptimistic(!parseHookNames);
|
||||
toggleParseHookNames();
|
||||
};
|
||||
|
||||
const handleCopy = () => copy(serializeHooksForCopy(hooks));
|
||||
|
||||
if (hooks === null) {
|
||||
|
@ -51,11 +73,25 @@ export function InspectedElementHooksTree({
|
|||
<div className={styles.HooksTreeView}>
|
||||
<div className={styles.HeaderRow}>
|
||||
<div className={styles.Header}>hooks</div>
|
||||
{enableHookNameParsing && !parseHookNames && (
|
||||
<Toggle
|
||||
isChecked={parseHookNamesOptimistic}
|
||||
isDisabled={parseHookNamesOptimistic}
|
||||
onChange={handleChange}
|
||||
title={
|
||||
parseHookNames
|
||||
? 'Parse hook names'
|
||||
: 'Parse hook names (may be slow)'
|
||||
}>
|
||||
<ButtonIcon type="parse-hook-names" />
|
||||
</Toggle>
|
||||
)}
|
||||
<Button onClick={handleCopy} title="Copy to clipboard">
|
||||
<ButtonIcon type="copy" />
|
||||
</Button>
|
||||
</div>
|
||||
<InnerHooksTreeView
|
||||
hookNames={hookNames}
|
||||
hooks={hooks}
|
||||
id={id}
|
||||
element={element}
|
||||
|
@ -69,6 +105,7 @@ export function InspectedElementHooksTree({
|
|||
|
||||
type InnerHooksTreeViewProps = {|
|
||||
element: Element,
|
||||
hookNames: HookNames | null,
|
||||
hooks: HooksTree,
|
||||
id: number,
|
||||
inspectedElement: InspectedElement,
|
||||
|
@ -77,6 +114,7 @@ type InnerHooksTreeViewProps = {|
|
|||
|
||||
export function InnerHooksTreeView({
|
||||
element,
|
||||
hookNames,
|
||||
hooks,
|
||||
id,
|
||||
inspectedElement,
|
||||
|
@ -88,6 +126,7 @@ export function InnerHooksTreeView({
|
|||
key={index}
|
||||
element={element}
|
||||
hook={hooks[index]}
|
||||
hookNames={hookNames}
|
||||
id={id}
|
||||
inspectedElement={inspectedElement}
|
||||
path={path.concat([index])}
|
||||
|
@ -98,12 +137,20 @@ export function InnerHooksTreeView({
|
|||
type HookViewProps = {|
|
||||
element: Element,
|
||||
hook: HooksNode,
|
||||
hookNames: HookNames | null,
|
||||
id: number,
|
||||
inspectedElement: InspectedElement,
|
||||
path: Array<string | number>,
|
||||
|};
|
||||
|
||||
function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
||||
function HookView({
|
||||
element,
|
||||
hook,
|
||||
hookNames,
|
||||
id,
|
||||
inspectedElement,
|
||||
path,
|
||||
}: HookViewProps) {
|
||||
const {
|
||||
canEditHooks,
|
||||
canEditHooksAndDeletePaths,
|
||||
|
@ -180,6 +227,16 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
|||
let displayValue;
|
||||
let isComplexDisplayValue = false;
|
||||
|
||||
const hookName = hookNames != null ? hookNames.get(hook) : null;
|
||||
const hookDisplayName = hookName ? (
|
||||
<>
|
||||
{name}
|
||||
{!!hookName && <span className={styles.HookName}>({hookName})</span>}
|
||||
</>
|
||||
) : (
|
||||
name
|
||||
);
|
||||
|
||||
// Format data for display to mimic the props/state/context for now.
|
||||
if (type === 'string') {
|
||||
displayValue = `"${((value: any): string)}"`;
|
||||
|
@ -204,6 +261,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
|||
<InnerHooksTreeView
|
||||
element={element}
|
||||
hooks={subHooks}
|
||||
hookNames={hookNames}
|
||||
id={id}
|
||||
inspectedElement={inspectedElement}
|
||||
path={path.concat(['subHooks'])}
|
||||
|
@ -219,6 +277,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
|||
depth={1}
|
||||
element={element}
|
||||
hookID={hookID}
|
||||
hookName={hookName}
|
||||
inspectedElement={inspectedElement}
|
||||
name="subHooks"
|
||||
path={path.concat(['subHooks'])}
|
||||
|
@ -236,7 +295,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
|||
<span
|
||||
onClick={toggleIsOpen}
|
||||
className={name !== '' ? styles.Name : styles.NameAnonymous}>
|
||||
{name || 'Anonymous'}
|
||||
{hookDisplayName || 'Anonymous'}
|
||||
</span>
|
||||
<span className={styles.Value} onClick={toggleIsOpen}>
|
||||
{isOpen || getMetaValueLabel(value)}
|
||||
|
@ -253,6 +312,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
|||
depth={1}
|
||||
element={element}
|
||||
hookID={hookID}
|
||||
hookName={hookName}
|
||||
inspectedElement={inspectedElement}
|
||||
name="DebugValue"
|
||||
path={path.concat(['value'])}
|
||||
|
@ -272,7 +332,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
|||
<span
|
||||
onClick={toggleIsOpen}
|
||||
className={name !== '' ? styles.Name : styles.NameAnonymous}>
|
||||
{name || 'Anonymous'}
|
||||
{hookDisplayName || 'Anonymous'}
|
||||
</span>{' '}
|
||||
{/* $FlowFixMe */}
|
||||
<span className={styles.Value} onClick={toggleIsOpen}>
|
||||
|
@ -299,6 +359,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
|||
depth={1}
|
||||
element={element}
|
||||
hookID={hookID}
|
||||
hookName={hookName}
|
||||
inspectedElement={inspectedElement}
|
||||
name={name}
|
||||
path={path.concat(['value'])}
|
||||
|
@ -320,6 +381,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
|||
depth={1}
|
||||
element={element}
|
||||
hookID={hookID}
|
||||
hookName={hookName}
|
||||
inspectedElement={inspectedElement}
|
||||
name={name}
|
||||
path={[]}
|
||||
|
|
|
@ -36,19 +36,26 @@ import styles from './InspectedElementView.css';
|
|||
|
||||
import type {ContextMenuContextType} from '../context';
|
||||
import type {Element, InspectedElement, SerializedElement} from './types';
|
||||
import type {ElementType} from 'react-devtools-shared/src/types';
|
||||
import type {ElementType, HookNames} from 'react-devtools-shared/src/types';
|
||||
import type {ToggleParseHookNames} from './InspectedElementContext';
|
||||
|
||||
export type CopyPath = (path: Array<string | number>) => void;
|
||||
export type InspectPath = (path: Array<string | number>) => void;
|
||||
|
||||
type Props = {|
|
||||
element: Element,
|
||||
hookNames: HookNames | null,
|
||||
inspectedElement: InspectedElement,
|
||||
parseHookNames: boolean,
|
||||
toggleParseHookNames: ToggleParseHookNames,
|
||||
|};
|
||||
|
||||
export default function InspectedElementView({
|
||||
element,
|
||||
hookNames,
|
||||
inspectedElement,
|
||||
parseHookNames,
|
||||
toggleParseHookNames,
|
||||
}: Props) {
|
||||
const {id} = element;
|
||||
const {
|
||||
|
@ -103,8 +110,11 @@ export default function InspectedElementView({
|
|||
<InspectedElementHooksTree
|
||||
bridge={bridge}
|
||||
element={element}
|
||||
hookNames={hookNames}
|
||||
inspectedElement={inspectedElement}
|
||||
parseHookNames={parseHookNames}
|
||||
store={store}
|
||||
toggleParseHookNames={toggleParseHookNames}
|
||||
/>
|
||||
|
||||
<InspectedElementContextTree
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
flex: 0 0 auto;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.EditableName {
|
||||
color: var(--color-attribute-name);
|
||||
flex: 0 0 auto;
|
||||
|
@ -48,4 +49,8 @@
|
|||
.DeleteArrayItemButton {
|
||||
padding: 0;
|
||||
margin-right: 0.125rem;
|
||||
}
|
||||
|
||||
.HookName {
|
||||
color: var(--color-component-name);
|
||||
}
|
|
@ -43,6 +43,7 @@ type KeyValueProps = {|
|
|||
element: Element,
|
||||
hidden: boolean,
|
||||
hookID?: ?number,
|
||||
hookName?: ?string,
|
||||
inspectedElement: InspectedElement,
|
||||
isDirectChildOfAnArray?: boolean,
|
||||
name: string,
|
||||
|
@ -65,6 +66,7 @@ export default function KeyValue({
|
|||
isDirectChildOfAnArray,
|
||||
hidden,
|
||||
hookID,
|
||||
hookName,
|
||||
name,
|
||||
path,
|
||||
pathRoot,
|
||||
|
@ -202,7 +204,12 @@ export default function KeyValue({
|
|||
<DeleteToggle name={name} deletePath={deletePath} path={path} />
|
||||
);
|
||||
} else {
|
||||
renderedName = <span className={styles.Name}>{name}</span>;
|
||||
renderedName = (
|
||||
<span className={styles.Name}>
|
||||
{name}
|
||||
{!!hookName && <span className={styles.HookName}>({hookName})</span>}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
} else if (canRenameTheCurrentPath) {
|
||||
renderedName = (
|
||||
|
@ -215,7 +222,12 @@ export default function KeyValue({
|
|||
/>
|
||||
);
|
||||
} else {
|
||||
renderedName = <span className={styles.Name}>{name}</span>;
|
||||
renderedName = (
|
||||
<span className={styles.Name}>
|
||||
{name}
|
||||
{!!hookName && <span className={styles.HookName}>({hookName})</span>}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
let children = null;
|
||||
|
|
11
packages/react-devtools-shared/src/devtools/views/Components/LoadHookNamesFunctionContext.js
vendored
Normal file
11
packages/react-devtools-shared/src/devtools/views/Components/LoadHookNamesFunctionContext.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
// @flow
|
||||
|
||||
import {createContext} from 'react';
|
||||
import type {LoadHookNamesFunction} from '../DevTools';
|
||||
|
||||
export type Context = LoadHookNamesFunction | null;
|
||||
|
||||
const LoadHookNamesFunctionContext = createContext<Context>(null);
|
||||
LoadHookNamesFunctionContext.displayName = 'LoadHookNamesFunctionContext';
|
||||
|
||||
export default LoadHookNamesFunctionContext;
|
|
@ -22,6 +22,7 @@ import TabBar from './TabBar';
|
|||
import {SettingsContextController} from './Settings/SettingsContext';
|
||||
import {TreeContextController} from './Components/TreeContext';
|
||||
import ViewElementSourceContext from './Components/ViewElementSourceContext';
|
||||
import LoadHookNamesFunctionContext from './Components/LoadHookNamesFunctionContext';
|
||||
import {ProfilerContextController} from './Profiler/ProfilerContext';
|
||||
import {ModalDialogContextController} from './ModalDialog';
|
||||
import ReactLogo from './ReactLogo';
|
||||
|
@ -34,15 +35,22 @@ import styles from './DevTools.css';
|
|||
|
||||
import './root.css';
|
||||
|
||||
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
|
||||
import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types';
|
||||
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
||||
import type {HookNames} from 'react-devtools-shared/src/types';
|
||||
import type {Thenable} from '../cache';
|
||||
|
||||
export type BrowserTheme = 'dark' | 'light';
|
||||
export type TabID = 'components' | 'profiler';
|
||||
|
||||
export type ViewElementSource = (
|
||||
id: number,
|
||||
inspectedElement: InspectedElement,
|
||||
) => void;
|
||||
export type LoadHookNamesFunction = (
|
||||
hooksTree: HooksTree,
|
||||
) => Thenable<HookNames>;
|
||||
export type ViewAttributeSource = (
|
||||
id: number,
|
||||
path: Array<string | number>,
|
||||
|
@ -75,6 +83,11 @@ export type Props = {|
|
|||
// but individual tabs (e.g. Components, Profiling) can be rendered into portals within their browser panels.
|
||||
componentsPortalContainer?: Element,
|
||||
profilerPortalContainer?: Element,
|
||||
|
||||
// Loads and parses source maps for function components
|
||||
// and extracts hook "names" based on the variables the hook return values get assigned to.
|
||||
// Not every DevTools build can load source maps, so this property is optional.
|
||||
loadHookNamesFunction?: ?LoadHookNamesFunction,
|
||||
|};
|
||||
|
||||
const componentsTab = {
|
||||
|
@ -99,6 +112,7 @@ export default function DevTools({
|
|||
componentsPortalContainer,
|
||||
defaultTab = 'components',
|
||||
enabledInspectedElementContextMenu = false,
|
||||
loadHookNamesFunction,
|
||||
overrideTab,
|
||||
profilerPortalContainer,
|
||||
showTabBar = false,
|
||||
|
@ -180,7 +194,6 @@ export default function DevTools({
|
|||
}
|
||||
};
|
||||
}, [bridge]);
|
||||
|
||||
return (
|
||||
<BridgeContext.Provider value={bridge}>
|
||||
<StoreContext.Provider value={store}>
|
||||
|
@ -191,40 +204,43 @@ export default function DevTools({
|
|||
componentsPortalContainer={componentsPortalContainer}
|
||||
profilerPortalContainer={profilerPortalContainer}>
|
||||
<ViewElementSourceContext.Provider value={viewElementSource}>
|
||||
<TreeContextController>
|
||||
<ProfilerContextController>
|
||||
<div className={styles.DevTools} ref={devToolsRef}>
|
||||
{showTabBar && (
|
||||
<div className={styles.TabBar}>
|
||||
<ReactLogo />
|
||||
<span className={styles.DevToolsVersion}>
|
||||
{process.env.DEVTOOLS_VERSION}
|
||||
</span>
|
||||
<div className={styles.Spacer} />
|
||||
<TabBar
|
||||
currentTab={tab}
|
||||
id="DevTools"
|
||||
selectTab={setTab}
|
||||
tabs={tabs}
|
||||
type="navigation"
|
||||
<LoadHookNamesFunctionContext.Provider
|
||||
value={loadHookNamesFunction || null}>
|
||||
<TreeContextController>
|
||||
<ProfilerContextController>
|
||||
<div className={styles.DevTools} ref={devToolsRef}>
|
||||
{showTabBar && (
|
||||
<div className={styles.TabBar}>
|
||||
<ReactLogo />
|
||||
<span className={styles.DevToolsVersion}>
|
||||
{process.env.DEVTOOLS_VERSION}
|
||||
</span>
|
||||
<div className={styles.Spacer} />
|
||||
<TabBar
|
||||
currentTab={tab}
|
||||
id="DevTools"
|
||||
selectTab={setTab}
|
||||
tabs={tabs}
|
||||
type="navigation"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={styles.TabContent}
|
||||
hidden={tab !== 'components'}>
|
||||
<Components
|
||||
portalContainer={componentsPortalContainer}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={styles.TabContent}
|
||||
hidden={tab !== 'components'}>
|
||||
<Components
|
||||
portalContainer={componentsPortalContainer}
|
||||
/>
|
||||
<div
|
||||
className={styles.TabContent}
|
||||
hidden={tab !== 'profiler'}>
|
||||
<Profiler portalContainer={profilerPortalContainer} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={styles.TabContent}
|
||||
hidden={tab !== 'profiler'}>
|
||||
<Profiler portalContainer={profilerPortalContainer} />
|
||||
</div>
|
||||
</div>
|
||||
</ProfilerContextController>
|
||||
</TreeContextController>
|
||||
</ProfilerContextController>
|
||||
</TreeContextController>
|
||||
</LoadHookNamesFunctionContext.Provider>
|
||||
</ViewElementSourceContext.Provider>
|
||||
</SettingsContextController>
|
||||
<UnsupportedBridgeProtocolDialog />
|
||||
|
|
|
@ -16,11 +16,13 @@ import {
|
|||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {enableHookNameParsing} from 'react-devtools-feature-flags';
|
||||
import {useSubscription} from '../hooks';
|
||||
import {StoreContext} from '../context';
|
||||
import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import Toggle from '../Toggle';
|
||||
import {SettingsContext} from '../Settings/SettingsContext';
|
||||
import {
|
||||
ComponentFilterDisplayName,
|
||||
ComponentFilterElementType,
|
||||
|
@ -50,6 +52,7 @@ import type {
|
|||
|
||||
export default function ComponentsSettings(_: {||}) {
|
||||
const store = useContext(StoreContext);
|
||||
const {parseHookNames, setParseHookNames} = useContext(SettingsContext);
|
||||
|
||||
const collapseNodesByDefaultSubscription = useMemo(
|
||||
() => ({
|
||||
|
@ -72,6 +75,13 @@ export default function ComponentsSettings(_: {||}) {
|
|||
[store],
|
||||
);
|
||||
|
||||
const updateParseHookNames = useCallback(
|
||||
({currentTarget}) => {
|
||||
setParseHookNames(currentTarget.checked);
|
||||
},
|
||||
[setParseHookNames],
|
||||
);
|
||||
|
||||
const [componentFilters, setComponentFilters] = useState<
|
||||
Array<ComponentFilter>,
|
||||
>(() => [...store.componentFilters]);
|
||||
|
@ -252,6 +262,18 @@ export default function ComponentsSettings(_: {||}) {
|
|||
Expand component tree by default
|
||||
</label>
|
||||
|
||||
{enableHookNameParsing && (
|
||||
<label className={styles.Setting}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={parseHookNames}
|
||||
onChange={updateParseHookNames}
|
||||
/>{' '}
|
||||
Always parse hook names from source{' '}
|
||||
<span className={styles.Warning}>(may be slow)</span>
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className={styles.Header}>Hide components where...</div>
|
||||
|
||||
<table className={styles.Table}>
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
import {
|
||||
COMFORTABLE_LINE_HEIGHT,
|
||||
COMPACT_LINE_HEIGHT,
|
||||
LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY,
|
||||
LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
|
||||
LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
|
||||
LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY,
|
||||
|
@ -45,6 +46,9 @@ type Context = {|
|
|||
breakOnConsoleErrors: boolean,
|
||||
setBreakOnConsoleErrors: (value: boolean) => void,
|
||||
|
||||
parseHookNames: boolean,
|
||||
setParseHookNames: (value: boolean) => void,
|
||||
|
||||
showInlineWarningsAndErrors: boolean,
|
||||
setShowInlineWarningsAndErrors: (value: boolean) => void,
|
||||
|
||||
|
@ -94,6 +98,10 @@ function SettingsContextController({
|
|||
LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
|
||||
false,
|
||||
);
|
||||
const [parseHookNames, setParseHookNames] = useLocalStorage<boolean>(
|
||||
LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY,
|
||||
false,
|
||||
);
|
||||
const [
|
||||
showInlineWarningsAndErrors,
|
||||
setShowInlineWarningsAndErrors,
|
||||
|
@ -180,9 +188,11 @@ function SettingsContextController({
|
|||
displayDensity === 'compact'
|
||||
? COMPACT_LINE_HEIGHT
|
||||
: COMFORTABLE_LINE_HEIGHT,
|
||||
parseHookNames,
|
||||
setAppendComponentStack,
|
||||
setBreakOnConsoleErrors,
|
||||
setDisplayDensity,
|
||||
setParseHookNames,
|
||||
setTheme,
|
||||
setTraceUpdatesEnabled,
|
||||
setShowInlineWarningsAndErrors,
|
||||
|
@ -194,9 +204,11 @@ function SettingsContextController({
|
|||
appendComponentStack,
|
||||
breakOnConsoleErrors,
|
||||
displayDensity,
|
||||
parseHookNames,
|
||||
setAppendComponentStack,
|
||||
setBreakOnConsoleErrors,
|
||||
setDisplayDensity,
|
||||
setParseHookNames,
|
||||
setTheme,
|
||||
setTraceUpdatesEnabled,
|
||||
setShowInlineWarningsAndErrors,
|
||||
|
|
|
@ -139,3 +139,7 @@
|
|||
.ReleaseNotesLink {
|
||||
color: var(--color-button-active);
|
||||
}
|
||||
|
||||
.Warning {
|
||||
color: var(--color-error-text);
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {unstable_getCacheForType as getCacheForType} from 'react';
|
||||
import {enableHookNameParsing} from 'react-devtools-feature-flags';
|
||||
|
||||
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
|
||||
import type {Thenable, Wakeable} from 'shared/ReactTypes';
|
||||
import type {Element} from './devtools/views/Components/types';
|
||||
import type {HookNames} from 'react-devtools-shared/src/types';
|
||||
|
||||
const TIMEOUT = 3000;
|
||||
|
||||
const Pending = 0;
|
||||
const Resolved = 1;
|
||||
const Rejected = 2;
|
||||
|
||||
type PendingRecord = {|
|
||||
status: 0,
|
||||
value: Wakeable,
|
||||
|};
|
||||
|
||||
type ResolvedRecord<T> = {|
|
||||
status: 1,
|
||||
value: T,
|
||||
|};
|
||||
|
||||
type RejectedRecord = {|
|
||||
status: 2,
|
||||
value: null,
|
||||
|};
|
||||
|
||||
type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
|
||||
|
||||
function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
|
||||
if (record.status === Resolved) {
|
||||
// This is just a type refinement.
|
||||
return record;
|
||||
} else if (record.status === Rejected) {
|
||||
// This is just a type refinement.
|
||||
return record;
|
||||
} else {
|
||||
throw record.value;
|
||||
}
|
||||
}
|
||||
|
||||
type HookNamesMap = WeakMap<Element, Record<HookNames>>;
|
||||
|
||||
function createMap(): HookNamesMap {
|
||||
return new WeakMap();
|
||||
}
|
||||
|
||||
function getRecordMap(): WeakMap<Element, Record<HookNames>> {
|
||||
return getCacheForType(createMap);
|
||||
}
|
||||
|
||||
export function loadHookNames(
|
||||
element: Element,
|
||||
hooksTree: HooksTree,
|
||||
loadHookNamesFunction: (hookLog: HooksTree) => Thenable<HookNames>,
|
||||
): HookNames | null {
|
||||
if (!enableHookNameParsing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const map = getRecordMap();
|
||||
|
||||
let record = map.get(element);
|
||||
if (record) {
|
||||
// TODO Do we need to update the Map to use new the hooks list objects as keys
|
||||
// or will these be stable between inspections as a component updates?
|
||||
// It seems like they're stable.
|
||||
} else {
|
||||
const callbacks = new Set();
|
||||
const wakeable: Wakeable = {
|
||||
then(callback) {
|
||||
callbacks.add(callback);
|
||||
},
|
||||
};
|
||||
|
||||
const wake = () => {
|
||||
if (timeoutID) {
|
||||
clearTimeout(timeoutID);
|
||||
timeoutID = null;
|
||||
}
|
||||
|
||||
// This assumes they won't throw.
|
||||
callbacks.forEach(callback => callback());
|
||||
callbacks.clear();
|
||||
};
|
||||
|
||||
const newRecord: Record<HookNames> = (record = {
|
||||
status: Pending,
|
||||
value: wakeable,
|
||||
});
|
||||
|
||||
let didTimeout = false;
|
||||
|
||||
loadHookNamesFunction(hooksTree).then(
|
||||
function onSuccess(hookNames) {
|
||||
if (didTimeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hookNames) {
|
||||
const resolvedRecord = ((newRecord: any): ResolvedRecord<HookNames>);
|
||||
resolvedRecord.status = Resolved;
|
||||
resolvedRecord.value = hookNames;
|
||||
} else {
|
||||
const notFoundRecord = ((newRecord: any): RejectedRecord);
|
||||
notFoundRecord.status = Rejected;
|
||||
notFoundRecord.value = null;
|
||||
}
|
||||
|
||||
wake();
|
||||
},
|
||||
function onError(error) {
|
||||
if (didTimeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thrownRecord = ((newRecord: any): RejectedRecord);
|
||||
thrownRecord.status = Rejected;
|
||||
thrownRecord.value = null;
|
||||
|
||||
wake();
|
||||
},
|
||||
);
|
||||
|
||||
// Eventually timeout and stop trying to load names.
|
||||
let timeoutID = setTimeout(() => {
|
||||
timeoutID = null;
|
||||
|
||||
didTimeout = true;
|
||||
|
||||
const timedoutRecord = ((newRecord: any): RejectedRecord);
|
||||
timedoutRecord.status = Rejected;
|
||||
timedoutRecord.value = null;
|
||||
|
||||
wake();
|
||||
}, TIMEOUT);
|
||||
|
||||
map.set(element, record);
|
||||
}
|
||||
|
||||
const response = readRecord(record).value;
|
||||
return response;
|
||||
}
|
|
@ -7,6 +7,8 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import type {HooksNode} from 'react-debug-tools/src/ReactDebugHooks';
|
||||
|
||||
export type Wall = {|
|
||||
// `listen` returns the "unlisten" function.
|
||||
listen: (fn: Function) => Function,
|
||||
|
@ -76,3 +78,12 @@ export type ComponentFilter =
|
|||
| BooleanComponentFilter
|
||||
| ElementTypeComponentFilter
|
||||
| RegExpComponentFilter;
|
||||
|
||||
export type HookName = string | null;
|
||||
export type HookNames = Map<HooksNode, HookName>;
|
||||
|
||||
export type LRUCache<K, V> = {|
|
||||
get: (key: K) => V,
|
||||
has: (key: K) => boolean,
|
||||
set: (key: K, value: V) => void,
|
||||
|};
|
||||
|
|
|
@ -47,13 +47,17 @@ import {
|
|||
} from 'react-devtools-shared/src/types';
|
||||
import {localStorageGetItem, localStorageSetItem} from './storage';
|
||||
import {meta} from './hydration';
|
||||
|
||||
import type {ComponentFilter, ElementType} from './types';
|
||||
import type {LRUCache} from 'react-devtools-shared/src/types';
|
||||
|
||||
const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
|
||||
|
||||
// On large trees, encoding takes significant time.
|
||||
// Try to reuse the already encoded strings.
|
||||
const encodedStringCache = new LRU({max: 1000});
|
||||
const encodedStringCache: LRUCache<string, Array<number>> = new LRU({
|
||||
max: 1000,
|
||||
});
|
||||
|
||||
export function alphaSortKeys(
|
||||
a: string | number | Symbol,
|
||||
|
|
|
@ -31,6 +31,11 @@ const config = {
|
|||
app: './src/app/index.js',
|
||||
devtools: './src/devtools.js',
|
||||
},
|
||||
node: {
|
||||
// source-maps package has a dependency on 'fs'
|
||||
// but this build won't trigger that code path
|
||||
fs: 'empty',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
react: resolve(builtModulesDir, 'react'),
|
||||
|
|
|
@ -55,12 +55,20 @@ module.exports = Object.assign({}, baseConfig, {
|
|||
// Don't run bundle tests on -test.internal.* files
|
||||
testPathIgnorePatterns: ['/node_modules/', '-test.internal.js$'],
|
||||
// Exclude the build output from transforms
|
||||
transformIgnorePatterns: ['/node_modules/', '<rootDir>/build2/'],
|
||||
testRegex: 'packages/react-devtools-shared/src/__tests__/[^]+.test.js$',
|
||||
transformIgnorePatterns: [
|
||||
'/node_modules/',
|
||||
'<rootDir>/build2/',
|
||||
'/__compiled__/',
|
||||
],
|
||||
testRegex:
|
||||
'packages/react-devtools-(extensions|shared)/src/__tests__/[^]+.test.js$',
|
||||
snapshotSerializers: [
|
||||
require.resolve(
|
||||
'../../packages/react-devtools-shared/src/__tests__/dehydratedValueSerializer.js'
|
||||
),
|
||||
require.resolve(
|
||||
'../../packages/react-devtools-shared/src/__tests__/hookSerializer.js'
|
||||
),
|
||||
require.resolve(
|
||||
'../../packages/react-devtools-shared/src/__tests__/inspectedElementSerializer.js'
|
||||
),
|
||||
|
|
|
@ -52,6 +52,7 @@ module.exports = Object.assign({}, baseConfig, {
|
|||
moduleNameMapper,
|
||||
modulePathIgnorePatterns: [
|
||||
...baseConfig.modulePathIgnorePatterns,
|
||||
'packages/react-devtools-extensions',
|
||||
'packages/react-devtools-shared',
|
||||
],
|
||||
// Don't run bundle tests on -test.internal.* files
|
||||
|
|
|
@ -5,6 +5,7 @@ const baseConfig = require('./config.base');
|
|||
module.exports = Object.assign({}, baseConfig, {
|
||||
modulePathIgnorePatterns: [
|
||||
...baseConfig.modulePathIgnorePatterns,
|
||||
'packages/react-devtools-extensions',
|
||||
'packages/react-devtools-shared',
|
||||
'ReactIncrementalPerf',
|
||||
'ReactIncrementalUpdatesMinimalism',
|
||||
|
|
|
@ -5,6 +5,7 @@ const baseConfig = require('./config.base');
|
|||
module.exports = Object.assign({}, baseConfig, {
|
||||
modulePathIgnorePatterns: [
|
||||
...baseConfig.modulePathIgnorePatterns,
|
||||
'packages/react-devtools-extensions',
|
||||
'packages/react-devtools-shared',
|
||||
],
|
||||
setupFiles: [
|
||||
|
|
|
@ -5,6 +5,7 @@ const baseConfig = require('./config.base');
|
|||
module.exports = Object.assign({}, baseConfig, {
|
||||
modulePathIgnorePatterns: [
|
||||
...baseConfig.modulePathIgnorePatterns,
|
||||
'packages/react-devtools-extensions',
|
||||
'packages/react-devtools-shared',
|
||||
],
|
||||
setupFiles: [
|
||||
|
|
|
@ -25,7 +25,14 @@ let didWarn = false;
|
|||
let didError = false;
|
||||
|
||||
const files = glob
|
||||
.sync('**/*.js', {ignore: ['**/node_modules/**', '**/cjs/**']})
|
||||
.sync('**/*.js', {
|
||||
ignore: [
|
||||
'**/node_modules/**',
|
||||
'**/cjs/**',
|
||||
'**/__compiled__/**',
|
||||
'packages/react-devtools-extensions/src/ErrorTesterCompiled.js',
|
||||
],
|
||||
})
|
||||
.filter(f => !onlyChanged || changedFiles.has(f));
|
||||
|
||||
if (!files.length) {
|
||||
|
|
289
yarn.lock
289
yarn.lock
|
@ -124,6 +124,15 @@
|
|||
jsesc "^2.5.1"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/generator@^7.12.5":
|
||||
version "7.12.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.5.tgz#a2c50de5c8b6d708ab95be5e6053936c1884a4de"
|
||||
integrity sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==
|
||||
dependencies:
|
||||
"@babel/types" "^7.12.5"
|
||||
jsesc "^2.5.1"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/generator@^7.8.3":
|
||||
version "7.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03"
|
||||
|
@ -481,7 +490,7 @@
|
|||
dependencies:
|
||||
"@babel/types" "^7.8.3"
|
||||
|
||||
"@babel/helper-validator-identifier@^7.14.0":
|
||||
"@babel/helper-validator-identifier@^7.10.4", "@babel/helper-validator-identifier@^7.14.0":
|
||||
version "7.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288"
|
||||
integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==
|
||||
|
@ -547,6 +556,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.3.tgz#9b530eecb071fd0c93519df25c5ff9f14759f298"
|
||||
integrity sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ==
|
||||
|
||||
"@babel/parser@^7.12.5":
|
||||
version "7.12.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.5.tgz#b4af32ddd473c0bfa643bd7ff0728b8e71b81ea0"
|
||||
integrity sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==
|
||||
|
||||
"@babel/parser@^7.8.6", "@babel/parser@^7.9.0":
|
||||
version "7.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8"
|
||||
|
@ -1507,6 +1521,21 @@
|
|||
globals "^11.1.0"
|
||||
lodash "^4.17.19"
|
||||
|
||||
"@babel/traverse@^7.12.5":
|
||||
version "7.12.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.5.tgz#78a0c68c8e8a35e4cacfd31db8bb303d5606f095"
|
||||
integrity sha512-xa15FbQnias7z9a62LwYAA5SZZPkHIXpd42C6uW68o8uTuua96FHZy1y61Va5P/i83FAAcMpW8+A/QayntzuqA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.10.4"
|
||||
"@babel/generator" "^7.12.5"
|
||||
"@babel/helper-function-name" "^7.10.4"
|
||||
"@babel/helper-split-export-declaration" "^7.11.0"
|
||||
"@babel/parser" "^7.12.5"
|
||||
"@babel/types" "^7.12.5"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
lodash "^4.17.19"
|
||||
|
||||
"@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0":
|
||||
version "7.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2"
|
||||
|
@ -1539,6 +1568,15 @@
|
|||
"@babel/helper-validator-identifier" "^7.14.0"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.12.5":
|
||||
version "7.12.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.6.tgz#ae0e55ef1cce1fbc881cd26f8234eb3e657edc96"
|
||||
integrity sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.10.4"
|
||||
lodash "^4.17.19"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5":
|
||||
version "7.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444"
|
||||
|
@ -3353,6 +3391,41 @@ babel-eslint@^9.0.0:
|
|||
eslint-scope "3.7.1"
|
||||
eslint-visitor-keys "^1.0.0"
|
||||
|
||||
babel-helper-evaluate-path@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.5.0.tgz#a62fa9c4e64ff7ea5cea9353174ef023a900a67c"
|
||||
integrity sha512-mUh0UhS607bGh5wUMAQfOpt2JX2ThXMtppHRdRU1kL7ZLRWIXxoV2UIV1r2cAeeNeU1M5SB5/RSUgUxrK8yOkA==
|
||||
|
||||
babel-helper-flip-expressions@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.4.3.tgz#3696736a128ac18bc25254b5f40a22ceb3c1d3fd"
|
||||
integrity sha1-NpZzahKKwYvCUlS19AoizrPB0/0=
|
||||
|
||||
babel-helper-is-nodes-equiv@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz#34e9b300b1479ddd98ec77ea0bbe9342dfe39684"
|
||||
integrity sha1-NOmzALFHnd2Y7HfqC76TQt/jloQ=
|
||||
|
||||
babel-helper-is-void-0@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-helper-is-void-0/-/babel-helper-is-void-0-0.4.3.tgz#7d9c01b4561e7b95dbda0f6eee48f5b60e67313e"
|
||||
integrity sha1-fZwBtFYee5Xb2g9u7kj1tg5nMT4=
|
||||
|
||||
babel-helper-mark-eval-scopes@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.4.3.tgz#d244a3bef9844872603ffb46e22ce8acdf551562"
|
||||
integrity sha1-0kSjvvmESHJgP/tG4izorN9VFWI=
|
||||
|
||||
babel-helper-remove-or-void@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.4.3.tgz#a4f03b40077a0ffe88e45d07010dee241ff5ae60"
|
||||
integrity sha1-pPA7QAd6D/6I5F0HAQ3uJB/1rmA=
|
||||
|
||||
babel-helper-to-multiple-sequence-expressions@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz#a3f924e3561882d42fcf48907aa98f7979a4588d"
|
||||
integrity sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==
|
||||
|
||||
babel-jest@^26.6.3:
|
||||
version "26.6.3"
|
||||
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:
|
|||
"@types/babel__core" "^7.0.0"
|
||||
"@types/babel__traverse" "^7.0.6"
|
||||
|
||||
babel-plugin-minify-builtins@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.5.0.tgz#31eb82ed1a0d0efdc31312f93b6e4741ce82c36b"
|
||||
integrity sha512-wpqbN7Ov5hsNwGdzuzvFcjgRlzbIeVv1gMIlICbPj0xkexnfoIDe7q+AZHMkQmAE/F9R5jkrB6TLfTegImlXag==
|
||||
|
||||
babel-plugin-minify-constant-folding@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-minify-constant-folding/-/babel-plugin-minify-constant-folding-0.5.0.tgz#f84bc8dbf6a561e5e350ff95ae216b0ad5515b6e"
|
||||
integrity sha512-Vj97CTn/lE9hR1D+jKUeHfNy+m1baNiJ1wJvoGyOBUx7F7kJqDZxr9nCHjO/Ad+irbR3HzR6jABpSSA29QsrXQ==
|
||||
dependencies:
|
||||
babel-helper-evaluate-path "^0.5.0"
|
||||
|
||||
babel-plugin-minify-dead-code-elimination@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.1.tgz#1a0c68e44be30de4976ca69ffc535e08be13683f"
|
||||
integrity sha512-x8OJOZIrRmQBcSqxBcLbMIK8uPmTvNWPXH2bh5MDCW1latEqYiRMuUkPImKcfpo59pTUB2FT7HfcgtG8ZlR5Qg==
|
||||
dependencies:
|
||||
babel-helper-evaluate-path "^0.5.0"
|
||||
babel-helper-mark-eval-scopes "^0.4.3"
|
||||
babel-helper-remove-or-void "^0.4.3"
|
||||
lodash "^4.17.11"
|
||||
|
||||
babel-plugin-minify-flip-comparisons@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.4.3.tgz#00ca870cb8f13b45c038b3c1ebc0f227293c965a"
|
||||
integrity sha1-AMqHDLjxO0XAOLPB68DyJyk8llo=
|
||||
dependencies:
|
||||
babel-helper-is-void-0 "^0.4.3"
|
||||
|
||||
babel-plugin-minify-guarded-expressions@^0.4.4:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-minify-guarded-expressions/-/babel-plugin-minify-guarded-expressions-0.4.4.tgz#818960f64cc08aee9d6c75bec6da974c4d621135"
|
||||
integrity sha512-RMv0tM72YuPPfLT9QLr3ix9nwUIq+sHT6z8Iu3sLbqldzC1Dls8DPCywzUIzkTx9Zh1hWX4q/m9BPoPed9GOfA==
|
||||
dependencies:
|
||||
babel-helper-evaluate-path "^0.5.0"
|
||||
babel-helper-flip-expressions "^0.4.3"
|
||||
|
||||
babel-plugin-minify-infinity@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.4.3.tgz#dfb876a1b08a06576384ef3f92e653ba607b39ca"
|
||||
integrity sha1-37h2obCKBldjhO8/kuZTumB7Oco=
|
||||
|
||||
babel-plugin-minify-mangle-names@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.0.tgz#bcddb507c91d2c99e138bd6b17a19c3c271e3fd3"
|
||||
integrity sha512-3jdNv6hCAw6fsX1p2wBGPfWuK69sfOjfd3zjUXkbq8McbohWy23tpXfy5RnToYWggvqzuMOwlId1PhyHOfgnGw==
|
||||
dependencies:
|
||||
babel-helper-mark-eval-scopes "^0.4.3"
|
||||
|
||||
babel-plugin-minify-numeric-literals@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.4.3.tgz#8e4fd561c79f7801286ff60e8c5fd9deee93c0bc"
|
||||
integrity sha1-jk/VYcefeAEob/YOjF/Z3u6TwLw=
|
||||
|
||||
babel-plugin-minify-replace@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-minify-replace/-/babel-plugin-minify-replace-0.5.0.tgz#d3e2c9946c9096c070efc96761ce288ec5c3f71c"
|
||||
integrity sha512-aXZiaqWDNUbyNNNpWs/8NyST+oU7QTpK7J9zFEFSA0eOmtUNMU3fczlTTTlnCxHmq/jYNFEmkkSG3DDBtW3Y4Q==
|
||||
|
||||
babel-plugin-minify-simplify@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-minify-simplify/-/babel-plugin-minify-simplify-0.5.1.tgz#f21613c8b95af3450a2ca71502fdbd91793c8d6a"
|
||||
integrity sha512-OSYDSnoCxP2cYDMk9gxNAed6uJDiDz65zgL6h8d3tm8qXIagWGMLWhqysT6DY3Vs7Fgq7YUDcjOomhVUb+xX6A==
|
||||
dependencies:
|
||||
babel-helper-evaluate-path "^0.5.0"
|
||||
babel-helper-flip-expressions "^0.4.3"
|
||||
babel-helper-is-nodes-equiv "^0.0.1"
|
||||
babel-helper-to-multiple-sequence-expressions "^0.5.0"
|
||||
|
||||
babel-plugin-minify-type-constructors@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.4.3.tgz#1bc6f15b87f7ab1085d42b330b717657a2156500"
|
||||
integrity sha1-G8bxW4f3qxCF1CszC3F2V6IVZQA=
|
||||
dependencies:
|
||||
babel-helper-is-void-0 "^0.4.3"
|
||||
|
||||
babel-plugin-syntax-trailing-function-commas@^6.5.0:
|
||||
version "6.22.0"
|
||||
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:
|
|||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz#aa213c1435e2bffeb6fca842287ef534ad05d5cf"
|
||||
integrity sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==
|
||||
|
||||
babel-plugin-transform-inline-consecutive-adds@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz#323d47a3ea63a83a7ac3c811ae8e6941faf2b0d1"
|
||||
integrity sha1-Mj1Ho+pjqDp6w8gRro5pQfrysNE=
|
||||
|
||||
babel-plugin-transform-member-expression-literals@^6.9.4:
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz#37039c9a0c3313a39495faac2ff3a6b5b9d038bf"
|
||||
integrity sha1-NwOcmgwzE6OUlfqsL/OmtbnQOL8=
|
||||
|
||||
babel-plugin-transform-merge-sibling-variables@^6.9.4:
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.4.tgz#85b422fc3377b449c9d1cde44087203532401dae"
|
||||
integrity sha1-hbQi/DN3tEnJ0c3kQIcgNTJAHa4=
|
||||
|
||||
babel-plugin-transform-minify-booleans@^6.9.4:
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz#acbb3e56a3555dd23928e4b582d285162dd2b198"
|
||||
integrity sha1-rLs+VqNVXdI5KOS1gtKFFi3SsZg=
|
||||
|
||||
babel-plugin-transform-property-literals@^6.9.4:
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz#98c1d21e255736573f93ece54459f6ce24985d39"
|
||||
integrity sha1-mMHSHiVXNlc/k+zlRFn2ziSYXTk=
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
babel-plugin-transform-regexp-constructors@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.4.3.tgz#58b7775b63afcf33328fae9a5f88fbd4fb0b4965"
|
||||
integrity sha1-WLd3W2OvzzMyj66aX4j71PsLSWU=
|
||||
|
||||
babel-plugin-transform-remove-console@^6.9.4:
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz#b980360c067384e24b357a588d807d3c83527780"
|
||||
integrity sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A=
|
||||
|
||||
babel-plugin-transform-remove-debugger@^6.9.4:
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz#42b727631c97978e1eb2d199a7aec84a18339ef2"
|
||||
integrity sha1-QrcnYxyXl44estGZp67IShgznvI=
|
||||
|
||||
babel-plugin-transform-remove-undefined@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-undefined/-/babel-plugin-transform-remove-undefined-0.5.0.tgz#80208b31225766c630c97fa2d288952056ea22dd"
|
||||
integrity sha512-+M7fJYFaEE/M9CXa0/IRkDbiV3wRELzA1kKQFCJ4ifhrzLKn/9VCCgj9OFmYWwBd8IB48YdgPkHYtbYq+4vtHQ==
|
||||
dependencies:
|
||||
babel-helper-evaluate-path "^0.5.0"
|
||||
|
||||
babel-plugin-transform-simplify-comparison-operators@^6.9.4:
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz#f62afe096cab0e1f68a2d753fdf283888471ceb9"
|
||||
integrity sha1-9ir+CWyrDh9ootdT/fKDiIRxzrk=
|
||||
|
||||
babel-plugin-transform-undefined-to-void@^6.9.4:
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz#be241ca81404030678b748717322b89d0c8fe280"
|
||||
integrity sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA=
|
||||
|
||||
babel-preset-current-node-syntax@^1.0.0:
|
||||
version "1.0.1"
|
||||
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:
|
|||
babel-plugin-jest-hoist "^26.6.2"
|
||||
babel-preset-current-node-syntax "^1.0.0"
|
||||
|
||||
babel-preset-minify@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-minify/-/babel-preset-minify-0.5.1.tgz#25f5d0bce36ec818be80338d0e594106e21eaa9f"
|
||||
integrity sha512-1IajDumYOAPYImkHbrKeiN5AKKP9iOmRoO2IPbIuVp0j2iuCcj0n7P260z38siKMZZ+85d3mJZdtW8IgOv+Tzg==
|
||||
dependencies:
|
||||
babel-plugin-minify-builtins "^0.5.0"
|
||||
babel-plugin-minify-constant-folding "^0.5.0"
|
||||
babel-plugin-minify-dead-code-elimination "^0.5.1"
|
||||
babel-plugin-minify-flip-comparisons "^0.4.3"
|
||||
babel-plugin-minify-guarded-expressions "^0.4.4"
|
||||
babel-plugin-minify-infinity "^0.4.3"
|
||||
babel-plugin-minify-mangle-names "^0.5.0"
|
||||
babel-plugin-minify-numeric-literals "^0.4.3"
|
||||
babel-plugin-minify-replace "^0.5.0"
|
||||
babel-plugin-minify-simplify "^0.5.1"
|
||||
babel-plugin-minify-type-constructors "^0.4.3"
|
||||
babel-plugin-transform-inline-consecutive-adds "^0.4.3"
|
||||
babel-plugin-transform-member-expression-literals "^6.9.4"
|
||||
babel-plugin-transform-merge-sibling-variables "^6.9.4"
|
||||
babel-plugin-transform-minify-booleans "^6.9.4"
|
||||
babel-plugin-transform-property-literals "^6.9.4"
|
||||
babel-plugin-transform-regexp-constructors "^0.4.3"
|
||||
babel-plugin-transform-remove-console "^6.9.4"
|
||||
babel-plugin-transform-remove-debugger "^6.9.4"
|
||||
babel-plugin-transform-remove-undefined "^0.5.0"
|
||||
babel-plugin-transform-simplify-comparison-operators "^6.9.4"
|
||||
babel-plugin-transform-undefined-to-void "^6.9.4"
|
||||
lodash "^4.17.11"
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
|
@ -4754,6 +4991,13 @@ cross-env@^3.1.4:
|
|||
cross-spawn "^5.1.0"
|
||||
is-windows "^1.0.0"
|
||||
|
||||
cross-fetch@^3.0.4:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
|
||||
integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==
|
||||
dependencies:
|
||||
node-fetch "2.6.1"
|
||||
|
||||
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||
|
@ -6780,6 +7024,15 @@ fs-exists-sync@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
|
||||
integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=
|
||||
|
||||
fs-extra@^4.0.2, fs-extra@~4.0.2:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
|
||||
integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-extra@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
||||
|
@ -6789,15 +7042,6 @@ fs-extra@^8.1.0:
|
|||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-extra@~4.0.2:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
|
||||
integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-minipass@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
|
||||
|
@ -8512,6 +8756,14 @@ jest-environment-node@^26.6.2:
|
|||
jest-mock "^26.6.2"
|
||||
jest-util "^26.6.2"
|
||||
|
||||
jest-fetch-mock@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b"
|
||||
integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==
|
||||
dependencies:
|
||||
cross-fetch "^3.0.4"
|
||||
promise-polyfill "^8.1.3"
|
||||
|
||||
jest-get-type@^26.3.0:
|
||||
version "26.3.0"
|
||||
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:
|
|||
resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c"
|
||||
integrity sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=
|
||||
|
||||
node-fetch@2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
|
@ -11267,6 +11524,11 @@ promise-polyfill@^6.0.1:
|
|||
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-6.1.0.tgz#dfa96943ea9c121fca4de9b5868cb39d3472e057"
|
||||
integrity sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=
|
||||
|
||||
promise-polyfill@^8.1.3:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0"
|
||||
integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==
|
||||
|
||||
promise-retry@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d"
|
||||
|
@ -12778,6 +13040,13 @@ source-map@^0.7.3:
|
|||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
||||
source-map@^0.8.0-beta.0:
|
||||
version "0.8.0-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11"
|
||||
integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==
|
||||
dependencies:
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
sourcemap-codec@^1.4.1, sourcemap-codec@^1.4.4:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
|
|
Loading…
Reference in New Issue