mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Merged changes from 4.0.0 -> 4.0.5 from DevTools fork
This commit is contained in:
commit
4da836af71
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
|
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
|
||||||
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
|
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
|
||||||
|
<script src="https://unpkg.com/immutable@4.0.0-rc.12/dist/immutable.js"></script>
|
||||||
|
|
||||||
<!-- Don't use this in production: -->
|
<!-- Don't use this in production: -->
|
||||||
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
|
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
|
||||||
|
|
@ -255,6 +256,33 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const set = new Set(['abc', 123]);
|
||||||
|
const map = new Map([['name', 'Brian'], ['food', 'sushi']]);
|
||||||
|
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
||||||
|
const mapOfMaps = new Map([['first', map], ['second', map]]);
|
||||||
|
const typedArray = Int8Array.from([100, -100, 0]);
|
||||||
|
const immutable = Immutable.fromJS({
|
||||||
|
a: [{ hello: 'there' }, 'fixed', true],
|
||||||
|
b: 123,
|
||||||
|
c: {
|
||||||
|
'1': 'xyz',
|
||||||
|
xyz: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function UnserializableProps() {
|
||||||
|
return (
|
||||||
|
<ChildComponent
|
||||||
|
map={map}
|
||||||
|
set={set}
|
||||||
|
mapOfMaps={mapOfMaps}
|
||||||
|
setOfSets={setOfSets}
|
||||||
|
typedArray={typedArray}
|
||||||
|
immutable={immutable}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ChildComponent(props: any) {
|
function ChildComponent(props: any) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -264,6 +292,7 @@
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<SimpleValues />
|
<SimpleValues />
|
||||||
<ObjectProps />
|
<ObjectProps />
|
||||||
|
<UnserializableProps />
|
||||||
<CustomObject />
|
<CustomObject />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "react-devtools-core",
|
"name": "react-devtools-core",
|
||||||
"version": "4.0.0-alpha.9",
|
"version": "4.0.5",
|
||||||
"description": "Use react-devtools outside of the browser",
|
"description": "Use react-devtools outside of the browser",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/backend.js",
|
"main": "./dist/backend.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "https://github.com/bvaughn/react-devtools-experimental.git",
|
"type": "git",
|
||||||
"type": "git"
|
"url": "https://github.com/facebook/react.git",
|
||||||
|
"directory": "packages/react-devtools-core"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|
|
||||||
11
packages/react-devtools-core/src/standalone.js
vendored
11
packages/react-devtools-core/src/standalone.js
vendored
|
|
@ -14,7 +14,8 @@ import {
|
||||||
getAppendComponentStack,
|
getAppendComponentStack,
|
||||||
} from 'react-devtools-shared/src/utils';
|
} from 'react-devtools-shared/src/utils';
|
||||||
import {Server} from 'ws';
|
import {Server} from 'ws';
|
||||||
import {existsSync, readFileSync} from 'fs';
|
import {join} from 'path';
|
||||||
|
import {readFileSync} from 'fs';
|
||||||
import {installHook} from 'react-devtools-shared/src/hook';
|
import {installHook} from 'react-devtools-shared/src/hook';
|
||||||
import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
|
import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
|
||||||
import {doesFilePathExist, launchEditor} from './editor';
|
import {doesFilePathExist, launchEditor} from './editor';
|
||||||
|
|
@ -259,14 +260,8 @@ function startServer(port?: number = 8097) {
|
||||||
});
|
});
|
||||||
|
|
||||||
httpServer.on('request', (request, response) => {
|
httpServer.on('request', (request, response) => {
|
||||||
// NPM installs should read from node_modules,
|
|
||||||
// But local dev mode needs to use a relative path.
|
|
||||||
const basePath = existsSync('./node_modules/react-devtools-core')
|
|
||||||
? 'node_modules/react-devtools-core'
|
|
||||||
: '../react-devtools-core';
|
|
||||||
|
|
||||||
// Serve a file that immediately sets up the connection.
|
// Serve a file that immediately sets up the connection.
|
||||||
const backendFile = readFileSync(`${basePath}/dist/backend.js`);
|
const backendFile = readFileSync(join(__dirname, 'backend.js'));
|
||||||
|
|
||||||
// The renderer interface doesn't read saved component filters directly,
|
// The renderer interface doesn't read saved component filters directly,
|
||||||
// because they are generally stored in localStorage within the context of the extension.
|
// because they are generally stored in localStorage within the context of the extension.
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,12 @@ module.exports = {
|
||||||
scheduler: resolve(builtModulesDir, 'scheduler'),
|
scheduler: resolve(builtModulesDir, 'scheduler'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
node: {
|
||||||
|
// Don't replace __dirname!
|
||||||
|
// This would break the standalone DevTools ability to load the backend.
|
||||||
|
// see https://github.com/facebook/react-devtools/issues/1269
|
||||||
|
__dirname: false,
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new DefinePlugin({
|
new DefinePlugin({
|
||||||
__DEV__: false,
|
__DEV__: false,
|
||||||
|
|
|
||||||
7
packages/react-devtools-extensions/build.js
vendored
7
packages/react-devtools-extensions/build.js
vendored
|
|
@ -61,14 +61,13 @@ const build = async (tempPath, manifestPath) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const commit = getGitCommit();
|
const commit = getGitCommit();
|
||||||
const versionDateString = `${commit} (${new Date().toLocaleDateString()})`;
|
const dateString = new Date().toLocaleDateString();
|
||||||
|
|
||||||
const manifest = JSON.parse(readFileSync(copiedManifestPath).toString());
|
const manifest = JSON.parse(readFileSync(copiedManifestPath).toString());
|
||||||
|
const versionDateString = `${manifest.version} (${dateString})`;
|
||||||
if (manifest.version_name) {
|
if (manifest.version_name) {
|
||||||
manifest.version_name = versionDateString;
|
manifest.version_name = versionDateString;
|
||||||
} else {
|
|
||||||
manifest.description += `\n\nCreated from revision ${versionDateString}`;
|
|
||||||
}
|
}
|
||||||
|
manifest.description += `\n\nCreated from revision ${commit} on ${dateString}.`;
|
||||||
|
|
||||||
writeFileSync(copiedManifestPath, JSON.stringify(manifest, null, 2));
|
writeFileSync(copiedManifestPath, JSON.stringify(manifest, null, 2));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "React Developer Tools",
|
"name": "React Developer Tools",
|
||||||
"description": "Adds React debugging tools to the Chrome Developer Tools.",
|
"description": "Adds React debugging tools to the Chrome Developer Tools.",
|
||||||
"version": "4.0.0",
|
"version": "4.0.5",
|
||||||
"version_name": "4.0.0",
|
"version_name": "4.0.5",
|
||||||
|
|
||||||
"minimum_chrome_version": "49",
|
"minimum_chrome_version": "49",
|
||||||
|
|
||||||
|
|
@ -40,15 +40,7 @@
|
||||||
"persistent": false
|
"persistent": false
|
||||||
},
|
},
|
||||||
|
|
||||||
"permissions": [
|
"permissions": ["file:///*", "http://*/*", "https://*/*"],
|
||||||
"<all_urls>",
|
|
||||||
"background",
|
|
||||||
"tabs",
|
|
||||||
"webNavigation",
|
|
||||||
"file:///*",
|
|
||||||
"http://*/*",
|
|
||||||
"https://*/*"
|
|
||||||
],
|
|
||||||
|
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,11 @@
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
Created on <strong>%date%</strong> from
|
Created on <strong>%date%</strong> from
|
||||||
<a href="http://github.com/bvaughn/react-devtools-experimental/commit/%commit%"><code>%commit%</code></a>
|
<a href="http://github.com/facebook/react/commit/%commit%"><code>%commit%</code></a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This is a preview build of an <a href="https://github.com/facebook/react/tree/master/packages/react-devtools-extensions">unreleased DevTools extension</a>.
|
This is a preview build of the <a href="https://github.com/facebook/react">React DevTools extension</a>.
|
||||||
It has no developer support.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Installation instructions</h2>
|
<h2>Installation instructions</h2>
|
||||||
|
|
@ -37,10 +36,5 @@
|
||||||
Please report bugs as <a href="https://github.com/facebook/react/issues/new?labels=Component:%20Developer%20Tools">GitHub issues</a>.
|
Please report bugs as <a href="https://github.com/facebook/react/issues/new?labels=Component:%20Developer%20Tools">GitHub issues</a>.
|
||||||
Please include all of the info required to reproduce the bug (e.g. links, code, instructions).
|
Please include all of the info required to reproduce the bug (e.g. links, code, instructions).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Feature requests</h2>
|
|
||||||
<p>
|
|
||||||
Feature requests are not being accepted at this time.
|
|
||||||
</p>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "React Developer Tools",
|
"name": "React Developer Tools",
|
||||||
"description": "Adds React debugging tools to the Firefox Developer Tools.",
|
"description": "Adds React debugging tools to the Firefox Developer Tools.",
|
||||||
"version": "4.0.0",
|
"version": "4.0.5",
|
||||||
|
|
||||||
"applications": {
|
"applications": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
|
|
@ -44,15 +44,7 @@
|
||||||
"scripts": ["build/background.js"]
|
"scripts": ["build/background.js"]
|
||||||
},
|
},
|
||||||
|
|
||||||
"permissions": [
|
"permissions": ["file:///*", "http://*/*", "https://*/*"],
|
||||||
"<all_urls>",
|
|
||||||
"activeTab",
|
|
||||||
"tabs",
|
|
||||||
"webNavigation",
|
|
||||||
"file:///*",
|
|
||||||
"http://*/*",
|
|
||||||
"https://*/*"
|
|
||||||
],
|
|
||||||
|
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
19
packages/react-devtools-extensions/src/main.js
vendored
19
packages/react-devtools-extensions/src/main.js
vendored
|
|
@ -120,6 +120,10 @@ function createPanelIfReactLoaded() {
|
||||||
localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY);
|
localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (store !== null) {
|
||||||
|
profilingData = store.profilerStore.profilingData;
|
||||||
|
}
|
||||||
|
|
||||||
store = new Store(bridge, {
|
store = new Store(bridge, {
|
||||||
isProfiling,
|
isProfiling,
|
||||||
supportsReloadAndProfile: getBrowserName() === 'Chrome',
|
supportsReloadAndProfile: getBrowserName() === 'Chrome',
|
||||||
|
|
@ -281,21 +285,6 @@ function createPanelIfReactLoaded() {
|
||||||
|
|
||||||
chrome.devtools.network.onNavigated.removeListener(checkPageForReact);
|
chrome.devtools.network.onNavigated.removeListener(checkPageForReact);
|
||||||
|
|
||||||
// Shutdown bridge before a new page is loaded.
|
|
||||||
chrome.webNavigation.onBeforeNavigate.addListener(
|
|
||||||
function onBeforeNavigate(details) {
|
|
||||||
// Ignore navigation events from other tabs (or from within frames).
|
|
||||||
if (details.tabId !== tabId || details.frameId !== 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// `bridge.shutdown()` will remove all listeners we added, so we don't have to.
|
|
||||||
bridge.shutdown();
|
|
||||||
|
|
||||||
profilingData = store.profilerStore.profilingData;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Re-initialize DevTools panel when a new page is loaded.
|
// Re-initialize DevTools panel when a new page is loaded.
|
||||||
chrome.devtools.network.onNavigated.addListener(function onNavigated() {
|
chrome.devtools.network.onNavigated.addListener(function onNavigated() {
|
||||||
// Re-initialize saved filters on navigation,
|
// Re-initialize saved filters on navigation,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "react-devtools-inline",
|
"name": "react-devtools-inline",
|
||||||
"version": "4.0.0-alpha.9",
|
"version": "4.0.5",
|
||||||
"description": "Embed react-devtools within a website",
|
"description": "Embed react-devtools within a website",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/backend.js",
|
"main": "./dist/backend.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "https://github.com/bvaughn/react-devtools-experimental.git",
|
"type": "git",
|
||||||
"type": "git"
|
"url": "https://github.com/facebook/react.git",
|
||||||
|
"directory": "packages/react-devtools-inline"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,41 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`InspectedElementContext should dehydrate complex nested values when requested: 1: Initially inspect element 1`] = `
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"owners": null,
|
||||||
|
"context": null,
|
||||||
|
"hooks": null,
|
||||||
|
"props": {
|
||||||
|
"set_of_sets": {
|
||||||
|
"0": {},
|
||||||
|
"1": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"state": null
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`InspectedElementContext should dehydrate complex nested values when requested: 2: Inspect props.set_of_sets.0 1`] = `
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"owners": null,
|
||||||
|
"context": null,
|
||||||
|
"hooks": null,
|
||||||
|
"props": {
|
||||||
|
"set_of_sets": {
|
||||||
|
"0": {
|
||||||
|
"0": 1,
|
||||||
|
"1": 2,
|
||||||
|
"2": 3
|
||||||
|
},
|
||||||
|
"1": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"state": null
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`InspectedElementContext should include updates for nested values that were previously hydrated: 1: Initially inspect element 1`] = `
|
exports[`InspectedElementContext should include updates for nested values that were previously hydrated: 1: Initially inspect element 1`] = `
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
|
|
@ -91,6 +127,28 @@ exports[`InspectedElementContext should include updates for nested values that w
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`InspectedElementContext should inspect hooks for components that only use context: 1: Inspected element 2 1`] = `
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"owners": null,
|
||||||
|
"context": null,
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"id": null,
|
||||||
|
"isStateEditable": false,
|
||||||
|
"name": "Context",
|
||||||
|
"value": true,
|
||||||
|
"subHooks": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"props": {
|
||||||
|
"a": 1,
|
||||||
|
"b": "abc"
|
||||||
|
},
|
||||||
|
"state": null
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`InspectedElementContext should inspect the currently selected element: 1: Inspected element 2 1`] = `
|
exports[`InspectedElementContext should inspect the currently selected element: 1: Inspected element 2 1`] = `
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
|
|
@ -427,13 +485,38 @@ exports[`InspectedElementContext should support complex data types: 1: Inspected
|
||||||
"context": null,
|
"context": null,
|
||||||
"hooks": null,
|
"hooks": null,
|
||||||
"props": {
|
"props": {
|
||||||
"html_element": {},
|
|
||||||
"fn": {},
|
|
||||||
"symbol": {},
|
|
||||||
"react_element": {},
|
|
||||||
"array_buffer": {},
|
"array_buffer": {},
|
||||||
"typed_array": {},
|
"date": {},
|
||||||
"date": {}
|
"fn": {},
|
||||||
|
"html_element": {},
|
||||||
|
"immutable": {
|
||||||
|
"0": {},
|
||||||
|
"1": {},
|
||||||
|
"2": {}
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
"0": {},
|
||||||
|
"1": {}
|
||||||
|
},
|
||||||
|
"map_of_maps": {
|
||||||
|
"0": {},
|
||||||
|
"1": {}
|
||||||
|
},
|
||||||
|
"react_element": {},
|
||||||
|
"set": {
|
||||||
|
"0": "abc",
|
||||||
|
"1": 123
|
||||||
|
},
|
||||||
|
"set_of_sets": {
|
||||||
|
"0": {},
|
||||||
|
"1": {}
|
||||||
|
},
|
||||||
|
"symbol": {},
|
||||||
|
"typed_array": {
|
||||||
|
"0": 100,
|
||||||
|
"1": -100,
|
||||||
|
"2": 0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"state": null
|
"state": null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -385,23 +385,42 @@ describe('InspectedElementContext', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support complex data types', async done => {
|
it('should support complex data types', async done => {
|
||||||
|
const Immutable = require('immutable');
|
||||||
|
|
||||||
const Example = () => null;
|
const Example = () => null;
|
||||||
|
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
const exmapleFunction = () => {};
|
const exampleFunction = () => {};
|
||||||
const typedArray = new Uint8Array(3);
|
const setShallow = new Set(['abc', 123]);
|
||||||
|
const mapShallow = new Map([['name', 'Brian'], ['food', 'sushi']]);
|
||||||
|
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
||||||
|
const mapOfMaps = new Map([['first', mapShallow], ['second', mapShallow]]);
|
||||||
|
const typedArray = Int8Array.from([100, -100, 0]);
|
||||||
|
const immutableMap = Immutable.fromJS({
|
||||||
|
a: [{hello: 'there'}, 'fixed', true],
|
||||||
|
b: 123,
|
||||||
|
c: {
|
||||||
|
'1': 'xyz',
|
||||||
|
xyz: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
await utils.actAsync(() =>
|
await utils.actAsync(() =>
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Example
|
<Example
|
||||||
html_element={div}
|
|
||||||
fn={exmapleFunction}
|
|
||||||
symbol={Symbol('symbol')}
|
|
||||||
react_element={<span />}
|
|
||||||
array_buffer={typedArray.buffer}
|
array_buffer={typedArray.buffer}
|
||||||
typed_array={typedArray}
|
|
||||||
date={new Date()}
|
date={new Date()}
|
||||||
|
fn={exampleFunction}
|
||||||
|
html_element={div}
|
||||||
|
immutable={immutableMap}
|
||||||
|
map={mapShallow}
|
||||||
|
map_of_maps={mapOfMaps}
|
||||||
|
react_element={<span />}
|
||||||
|
set={setShallow}
|
||||||
|
set_of_sets={setOfSets}
|
||||||
|
symbol={Symbol('symbol')}
|
||||||
|
typed_array={typedArray}
|
||||||
/>,
|
/>,
|
||||||
container,
|
container,
|
||||||
),
|
),
|
||||||
|
|
@ -435,37 +454,77 @@ describe('InspectedElementContext', () => {
|
||||||
expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
|
expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
html_element,
|
|
||||||
fn,
|
|
||||||
symbol,
|
|
||||||
react_element,
|
|
||||||
array_buffer,
|
array_buffer,
|
||||||
typed_array,
|
|
||||||
date,
|
date,
|
||||||
|
fn,
|
||||||
|
html_element,
|
||||||
|
immutable,
|
||||||
|
map,
|
||||||
|
map_of_maps,
|
||||||
|
react_element,
|
||||||
|
set,
|
||||||
|
set_of_sets,
|
||||||
|
symbol,
|
||||||
|
typed_array,
|
||||||
} = (inspectedElement: any).props;
|
} = (inspectedElement: any).props;
|
||||||
expect(html_element[meta.inspectable]).toBe(false);
|
|
||||||
expect(html_element[meta.name]).toBe('DIV');
|
|
||||||
expect(html_element[meta.type]).toBe('html_element');
|
|
||||||
expect(fn[meta.inspectable]).toBe(false);
|
|
||||||
expect(fn[meta.name]).toBe('exmapleFunction');
|
|
||||||
expect(fn[meta.type]).toBe('function');
|
|
||||||
expect(symbol[meta.inspectable]).toBe(false);
|
|
||||||
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
|
||||||
expect(symbol[meta.type]).toBe('symbol');
|
|
||||||
expect(react_element[meta.inspectable]).toBe(false);
|
|
||||||
expect(react_element[meta.name]).toBe('span');
|
|
||||||
expect(react_element[meta.type]).toBe('react_element');
|
|
||||||
expect(array_buffer[meta.size]).toBe(3);
|
expect(array_buffer[meta.size]).toBe(3);
|
||||||
expect(array_buffer[meta.inspectable]).toBe(false);
|
expect(array_buffer[meta.inspectable]).toBe(false);
|
||||||
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
|
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
|
||||||
expect(array_buffer[meta.type]).toBe('array_buffer');
|
expect(array_buffer[meta.type]).toBe('array_buffer');
|
||||||
expect(typed_array[meta.size]).toBe(3);
|
|
||||||
expect(typed_array[meta.inspectable]).toBe(false);
|
|
||||||
expect(typed_array[meta.name]).toBe('Uint8Array');
|
|
||||||
expect(typed_array[meta.type]).toBe('typed_array');
|
|
||||||
expect(date[meta.inspectable]).toBe(false);
|
expect(date[meta.inspectable]).toBe(false);
|
||||||
expect(date[meta.type]).toBe('date');
|
expect(date[meta.type]).toBe('date');
|
||||||
|
|
||||||
|
expect(fn[meta.inspectable]).toBe(false);
|
||||||
|
expect(fn[meta.name]).toBe('exampleFunction');
|
||||||
|
expect(fn[meta.type]).toBe('function');
|
||||||
|
|
||||||
|
expect(html_element[meta.inspectable]).toBe(false);
|
||||||
|
expect(html_element[meta.name]).toBe('DIV');
|
||||||
|
expect(html_element[meta.type]).toBe('html_element');
|
||||||
|
|
||||||
|
expect(immutable[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(immutable[meta.name]).toBe('Map');
|
||||||
|
expect(immutable[meta.type]).toBe('iterator');
|
||||||
|
|
||||||
|
expect(map[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(map[meta.name]).toBe('Map');
|
||||||
|
expect(map[meta.type]).toBe('iterator');
|
||||||
|
expect(map[0][meta.type]).toBe('array');
|
||||||
|
|
||||||
|
expect(map_of_maps[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(map_of_maps[meta.name]).toBe('Map');
|
||||||
|
expect(map_of_maps[meta.type]).toBe('iterator');
|
||||||
|
expect(map_of_maps[0][meta.type]).toBe('array');
|
||||||
|
|
||||||
|
expect(react_element[meta.inspectable]).toBe(false);
|
||||||
|
expect(react_element[meta.name]).toBe('span');
|
||||||
|
expect(react_element[meta.type]).toBe('react_element');
|
||||||
|
|
||||||
|
expect(set[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(set[meta.name]).toBe('Set');
|
||||||
|
expect(set[meta.type]).toBe('iterator');
|
||||||
|
expect(set[0]).toBe('abc');
|
||||||
|
expect(set[1]).toBe(123);
|
||||||
|
|
||||||
|
expect(set_of_sets[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(set_of_sets[meta.name]).toBe('Set');
|
||||||
|
expect(set_of_sets[meta.type]).toBe('iterator');
|
||||||
|
expect(set_of_sets['0'][meta.inspectable]).toBe(true);
|
||||||
|
|
||||||
|
expect(symbol[meta.inspectable]).toBe(false);
|
||||||
|
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
||||||
|
expect(symbol[meta.type]).toBe('symbol');
|
||||||
|
|
||||||
|
expect(typed_array[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(typed_array[meta.size]).toBe(3);
|
||||||
|
expect(typed_array[meta.name]).toBe('Int8Array');
|
||||||
|
expect(typed_array[meta.type]).toBe('typed_array');
|
||||||
|
expect(typed_array[0]).toBe(100);
|
||||||
|
expect(typed_array[1]).toBe(-100);
|
||||||
|
expect(typed_array[2]).toBe(0);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -655,6 +714,61 @@ describe('InspectedElementContext', () => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should dehydrate complex nested values when requested', async done => {
|
||||||
|
const Example = () => null;
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
await utils.actAsync(() =>
|
||||||
|
ReactDOM.render(
|
||||||
|
<Example
|
||||||
|
set_of_sets={new Set([new Set([1, 2, 3]), new Set(['a', 'b', 'c'])])}
|
||||||
|
/>,
|
||||||
|
container,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
||||||
|
|
||||||
|
let getInspectedElementPath: GetInspectedElementPath = ((null: any): GetInspectedElementPath);
|
||||||
|
let inspectedElement = null;
|
||||||
|
|
||||||
|
function Suspender({target}) {
|
||||||
|
const context = React.useContext(InspectedElementContext);
|
||||||
|
getInspectedElementPath = context.getInspectedElementPath;
|
||||||
|
inspectedElement = context.getInspectedElement(target);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.actAsync(
|
||||||
|
() =>
|
||||||
|
TestRenderer.create(
|
||||||
|
<Contexts
|
||||||
|
defaultSelectedElementID={id}
|
||||||
|
defaultSelectedElementIndex={0}>
|
||||||
|
<React.Suspense fallback={null}>
|
||||||
|
<Suspender target={id} />
|
||||||
|
</React.Suspense>
|
||||||
|
</Contexts>,
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(getInspectedElementPath).not.toBeNull();
|
||||||
|
expect(inspectedElement).not.toBeNull();
|
||||||
|
expect(inspectedElement).toMatchSnapshot('1: Initially inspect element');
|
||||||
|
|
||||||
|
inspectedElement = null;
|
||||||
|
TestUtils.act(() => {
|
||||||
|
TestRenderer.act(() => {
|
||||||
|
getInspectedElementPath(id, ['props', 'set_of_sets', 0]);
|
||||||
|
jest.runOnlyPendingTimers();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(inspectedElement).not.toBeNull();
|
||||||
|
expect(inspectedElement).toMatchSnapshot('2: Inspect props.set_of_sets.0');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
it('should include updates for nested values that were previously hydrated', async done => {
|
it('should include updates for nested values that were previously hydrated', async done => {
|
||||||
const Example = () => null;
|
const Example = () => null;
|
||||||
|
|
||||||
|
|
@ -846,4 +960,46 @@ describe('InspectedElementContext', () => {
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should inspect hooks for components that only use context', async done => {
|
||||||
|
const Context = React.createContext(true);
|
||||||
|
const Example = () => {
|
||||||
|
const value = React.useContext(Context);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
await utils.actAsync(() =>
|
||||||
|
ReactDOM.render(<Example a={1} b="abc" />, container),
|
||||||
|
);
|
||||||
|
|
||||||
|
const id = ((store.getElementIDAtIndex(0): any): number);
|
||||||
|
|
||||||
|
let didFinish = false;
|
||||||
|
|
||||||
|
function Suspender({target}) {
|
||||||
|
const {getInspectedElement} = React.useContext(InspectedElementContext);
|
||||||
|
const inspectedElement = getInspectedElement(id);
|
||||||
|
expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
|
||||||
|
didFinish = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.actAsync(
|
||||||
|
() =>
|
||||||
|
TestRenderer.create(
|
||||||
|
<Contexts
|
||||||
|
defaultSelectedElementID={id}
|
||||||
|
defaultSelectedElementIndex={0}>
|
||||||
|
<React.Suspense fallback={null}>
|
||||||
|
<Suspender target={id} />
|
||||||
|
</React.Suspense>
|
||||||
|
</Contexts>,
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(didFinish).toBe(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -126,13 +126,38 @@ Object {
|
||||||
"context": {},
|
"context": {},
|
||||||
"hooks": null,
|
"hooks": null,
|
||||||
"props": {
|
"props": {
|
||||||
"html_element": {},
|
|
||||||
"fn": {},
|
|
||||||
"symbol": {},
|
|
||||||
"react_element": {},
|
|
||||||
"array_buffer": {},
|
"array_buffer": {},
|
||||||
"typed_array": {},
|
"date": {},
|
||||||
"date": {}
|
"fn": {},
|
||||||
|
"html_element": {},
|
||||||
|
"immutable": {
|
||||||
|
"0": {},
|
||||||
|
"1": {},
|
||||||
|
"2": {}
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
"0": {},
|
||||||
|
"1": {}
|
||||||
|
},
|
||||||
|
"map_of_maps": {
|
||||||
|
"0": {},
|
||||||
|
"1": {}
|
||||||
|
},
|
||||||
|
"react_element": {},
|
||||||
|
"set": {
|
||||||
|
"0": "abc",
|
||||||
|
"1": 123
|
||||||
|
},
|
||||||
|
"set_of_sets": {
|
||||||
|
"0": {},
|
||||||
|
"1": {}
|
||||||
|
},
|
||||||
|
"symbol": {},
|
||||||
|
"typed_array": {
|
||||||
|
"0": 100,
|
||||||
|
"1": -100,
|
||||||
|
"2": 0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"state": null
|
"state": null
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,11 @@ describe('InspectedElementContext', () => {
|
||||||
dehydratedData: DehydratedData | null,
|
dehydratedData: DehydratedData | null,
|
||||||
): Object | null {
|
): Object | null {
|
||||||
if (dehydratedData !== null) {
|
if (dehydratedData !== null) {
|
||||||
return hydrate(dehydratedData.data, dehydratedData.cleaned);
|
return hydrate(
|
||||||
|
dehydratedData.data,
|
||||||
|
dehydratedData.cleaned,
|
||||||
|
dehydratedData.unserializable,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -132,22 +136,41 @@ describe('InspectedElementContext', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support complex data types', async done => {
|
it('should support complex data types', async done => {
|
||||||
|
const Immutable = require('immutable');
|
||||||
|
|
||||||
const Example = () => null;
|
const Example = () => null;
|
||||||
|
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
const exmapleFunction = () => {};
|
const exampleFunction = () => {};
|
||||||
const typedArray = new Uint8Array(3);
|
const setShallow = new Set(['abc', 123]);
|
||||||
|
const mapShallow = new Map([['name', 'Brian'], ['food', 'sushi']]);
|
||||||
|
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
||||||
|
const mapOfMaps = new Map([['first', mapShallow], ['second', mapShallow]]);
|
||||||
|
const typedArray = Int8Array.from([100, -100, 0]);
|
||||||
|
const immutableMap = Immutable.fromJS({
|
||||||
|
a: [{hello: 'there'}, 'fixed', true],
|
||||||
|
b: 123,
|
||||||
|
c: {
|
||||||
|
'1': 'xyz',
|
||||||
|
xyz: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
act(() =>
|
act(() =>
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Example
|
<Example
|
||||||
html_element={div}
|
|
||||||
fn={exmapleFunction}
|
|
||||||
symbol={Symbol('symbol')}
|
|
||||||
react_element={<span />}
|
|
||||||
array_buffer={typedArray.buffer}
|
array_buffer={typedArray.buffer}
|
||||||
typed_array={typedArray}
|
|
||||||
date={new Date()}
|
date={new Date()}
|
||||||
|
fn={exampleFunction}
|
||||||
|
html_element={div}
|
||||||
|
immutable={immutableMap}
|
||||||
|
map={mapShallow}
|
||||||
|
map_of_maps={mapOfMaps}
|
||||||
|
react_element={<span />}
|
||||||
|
set={setShallow}
|
||||||
|
set_of_sets={setOfSets}
|
||||||
|
symbol={Symbol('symbol')}
|
||||||
|
typed_array={typedArray}
|
||||||
/>,
|
/>,
|
||||||
document.createElement('div'),
|
document.createElement('div'),
|
||||||
),
|
),
|
||||||
|
|
@ -159,37 +182,77 @@ describe('InspectedElementContext', () => {
|
||||||
expect(inspectedElement).toMatchSnapshot('1: Initial inspection');
|
expect(inspectedElement).toMatchSnapshot('1: Initial inspection');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
html_element,
|
|
||||||
fn,
|
|
||||||
symbol,
|
|
||||||
react_element,
|
|
||||||
array_buffer,
|
array_buffer,
|
||||||
typed_array,
|
|
||||||
date,
|
date,
|
||||||
|
fn,
|
||||||
|
html_element,
|
||||||
|
immutable,
|
||||||
|
map,
|
||||||
|
map_of_maps,
|
||||||
|
react_element,
|
||||||
|
set,
|
||||||
|
set_of_sets,
|
||||||
|
symbol,
|
||||||
|
typed_array,
|
||||||
} = inspectedElement.value.props;
|
} = inspectedElement.value.props;
|
||||||
expect(html_element[meta.inspectable]).toBe(false);
|
|
||||||
expect(html_element[meta.name]).toBe('DIV');
|
|
||||||
expect(html_element[meta.type]).toBe('html_element');
|
|
||||||
expect(fn[meta.inspectable]).toBe(false);
|
|
||||||
expect(fn[meta.name]).toBe('exmapleFunction');
|
|
||||||
expect(fn[meta.type]).toBe('function');
|
|
||||||
expect(symbol[meta.inspectable]).toBe(false);
|
|
||||||
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
|
||||||
expect(symbol[meta.type]).toBe('symbol');
|
|
||||||
expect(react_element[meta.inspectable]).toBe(false);
|
|
||||||
expect(react_element[meta.name]).toBe('span');
|
|
||||||
expect(react_element[meta.type]).toBe('react_element');
|
|
||||||
expect(array_buffer[meta.size]).toBe(3);
|
expect(array_buffer[meta.size]).toBe(3);
|
||||||
expect(array_buffer[meta.inspectable]).toBe(false);
|
expect(array_buffer[meta.inspectable]).toBe(false);
|
||||||
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
|
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
|
||||||
expect(array_buffer[meta.type]).toBe('array_buffer');
|
expect(array_buffer[meta.type]).toBe('array_buffer');
|
||||||
expect(typed_array[meta.size]).toBe(3);
|
|
||||||
expect(typed_array[meta.inspectable]).toBe(false);
|
|
||||||
expect(typed_array[meta.name]).toBe('Uint8Array');
|
|
||||||
expect(typed_array[meta.type]).toBe('typed_array');
|
|
||||||
expect(date[meta.inspectable]).toBe(false);
|
expect(date[meta.inspectable]).toBe(false);
|
||||||
expect(date[meta.type]).toBe('date');
|
expect(date[meta.type]).toBe('date');
|
||||||
|
|
||||||
|
expect(fn[meta.inspectable]).toBe(false);
|
||||||
|
expect(fn[meta.name]).toBe('exampleFunction');
|
||||||
|
expect(fn[meta.type]).toBe('function');
|
||||||
|
|
||||||
|
expect(html_element[meta.inspectable]).toBe(false);
|
||||||
|
expect(html_element[meta.name]).toBe('DIV');
|
||||||
|
expect(html_element[meta.type]).toBe('html_element');
|
||||||
|
|
||||||
|
expect(immutable[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(immutable[meta.name]).toBe('Map');
|
||||||
|
expect(immutable[meta.type]).toBe('iterator');
|
||||||
|
|
||||||
|
expect(map[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(map[meta.name]).toBe('Map');
|
||||||
|
expect(map[meta.type]).toBe('iterator');
|
||||||
|
expect(map[0][meta.type]).toBe('array');
|
||||||
|
|
||||||
|
expect(map_of_maps[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(map_of_maps[meta.name]).toBe('Map');
|
||||||
|
expect(map_of_maps[meta.type]).toBe('iterator');
|
||||||
|
expect(map_of_maps[0][meta.type]).toBe('array');
|
||||||
|
|
||||||
|
expect(react_element[meta.inspectable]).toBe(false);
|
||||||
|
expect(react_element[meta.name]).toBe('span');
|
||||||
|
expect(react_element[meta.type]).toBe('react_element');
|
||||||
|
|
||||||
|
expect(set[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(set[meta.name]).toBe('Set');
|
||||||
|
expect(set[meta.type]).toBe('iterator');
|
||||||
|
expect(set[0]).toBe('abc');
|
||||||
|
expect(set[1]).toBe(123);
|
||||||
|
|
||||||
|
expect(set_of_sets[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(set_of_sets[meta.name]).toBe('Set');
|
||||||
|
expect(set_of_sets[meta.type]).toBe('iterator');
|
||||||
|
expect(set_of_sets['0'][meta.inspectable]).toBe(true);
|
||||||
|
|
||||||
|
expect(symbol[meta.inspectable]).toBe(false);
|
||||||
|
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
||||||
|
expect(symbol[meta.type]).toBe('symbol');
|
||||||
|
|
||||||
|
expect(typed_array[meta.inspectable]).toBeUndefined(); // Complex type
|
||||||
|
expect(typed_array[meta.size]).toBe(3);
|
||||||
|
expect(typed_array[meta.name]).toBe('Int8Array');
|
||||||
|
expect(typed_array[meta.type]).toBe('typed_array');
|
||||||
|
expect(typed_array[0]).toBe(100);
|
||||||
|
expect(typed_array[1]).toBe(-100);
|
||||||
|
expect(typed_array[2]).toBe(0);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ describe('ProfilerContext', () => {
|
||||||
expect(context.didRecordCommits).toBe(false);
|
expect(context.didRecordCommits).toBe(false);
|
||||||
expect(context.isProcessingData).toBe(false);
|
expect(context.isProcessingData).toBe(false);
|
||||||
expect(context.isProfiling).toBe(false);
|
expect(context.isProfiling).toBe(false);
|
||||||
expect(context.profilingData).not.toBe(null);
|
expect(context.profilingData).toBe(null);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -882,10 +882,10 @@ export function attach(
|
||||||
throw new Error('setInHook not supported by this renderer');
|
throw new Error('setInHook not supported by this renderer');
|
||||||
};
|
};
|
||||||
const startProfiling = () => {
|
const startProfiling = () => {
|
||||||
throw new Error('startProfiling not supported by this renderer');
|
// Do not throw, since this would break a multi-root scenario where v15 and v16 were both present.
|
||||||
};
|
};
|
||||||
const stopProfiling = () => {
|
const stopProfiling = () => {
|
||||||
throw new Error('stopProfiling not supported by this renderer');
|
// Do not throw, since this would break a multi-root scenario where v15 and v16 were both present.
|
||||||
};
|
};
|
||||||
|
|
||||||
function getBestMatchForTrackedPath(): PathMatch | null {
|
function getBestMatchForTrackedPath(): PathMatch | null {
|
||||||
|
|
|
||||||
|
|
@ -1621,7 +1621,9 @@ export function attach(
|
||||||
currentRootID = getFiberID(getPrimaryFiber(root.current));
|
currentRootID = getFiberID(getPrimaryFiber(root.current));
|
||||||
setRootPseudoKey(currentRootID, root.current);
|
setRootPseudoKey(currentRootID, root.current);
|
||||||
|
|
||||||
if (isProfiling) {
|
// Checking root.memoizedInteractions handles multi-renderer edge-case-
|
||||||
|
// where some v16 renderers support profiling and others don't.
|
||||||
|
if (isProfiling && root.memoizedInteractions != null) {
|
||||||
// If profiling is active, store commit time and duration, and the current interactions.
|
// If profiling is active, store commit time and duration, and the current interactions.
|
||||||
// The frontend may request this information after profiling has stopped.
|
// The frontend may request this information after profiling has stopped.
|
||||||
currentCommitProfilingMetadata = {
|
currentCommitProfilingMetadata = {
|
||||||
|
|
@ -1665,7 +1667,11 @@ export function attach(
|
||||||
mightBeOnTrackedPath = true;
|
mightBeOnTrackedPath = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isProfiling) {
|
// Checking root.memoizedInteractions handles multi-renderer edge-case-
|
||||||
|
// where some v16 renderers support profiling and others don't.
|
||||||
|
const isProfilingSupported = root.memoizedInteractions != null;
|
||||||
|
|
||||||
|
if (isProfiling && isProfilingSupported) {
|
||||||
// If profiling is active, store commit time and duration, and the current interactions.
|
// If profiling is active, store commit time and duration, and the current interactions.
|
||||||
// The frontend may request this information after profiling has stopped.
|
// The frontend may request this information after profiling has stopped.
|
||||||
currentCommitProfilingMetadata = {
|
currentCommitProfilingMetadata = {
|
||||||
|
|
@ -1709,7 +1715,7 @@ export function attach(
|
||||||
mountFiberRecursively(current, null);
|
mountFiberRecursively(current, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isProfiling) {
|
if (isProfiling && isProfilingSupported) {
|
||||||
const commitProfilingMetadata = ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).get(
|
const commitProfilingMetadata = ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).get(
|
||||||
currentRootID,
|
currentRootID,
|
||||||
);
|
);
|
||||||
|
|
@ -2083,6 +2089,7 @@ export function attach(
|
||||||
const {
|
const {
|
||||||
_debugOwner,
|
_debugOwner,
|
||||||
_debugSource,
|
_debugSource,
|
||||||
|
dependencies,
|
||||||
stateNode,
|
stateNode,
|
||||||
memoizedProps,
|
memoizedProps,
|
||||||
memoizedState,
|
memoizedState,
|
||||||
|
|
@ -2094,7 +2101,7 @@ export function attach(
|
||||||
(tag === FunctionComponent ||
|
(tag === FunctionComponent ||
|
||||||
tag === SimpleMemoComponent ||
|
tag === SimpleMemoComponent ||
|
||||||
tag === ForwardRef) &&
|
tag === ForwardRef) &&
|
||||||
!!memoizedState;
|
(!!memoizedState || !!dependencies);
|
||||||
|
|
||||||
const typeSymbol = getTypeSymbol(type);
|
const typeSymbol = getTypeSymbol(type);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export type Source = {|
|
||||||
fileName: string,
|
fileName: string,
|
||||||
lineNumber: number,
|
lineNumber: number,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
export type HookType =
|
export type HookType =
|
||||||
| 'useState'
|
| 'useState'
|
||||||
| 'useReducer'
|
| 'useReducer'
|
||||||
|
|
@ -41,6 +42,9 @@ export type Fiber = {|
|
||||||
|
|
||||||
key: null | string,
|
key: null | string,
|
||||||
|
|
||||||
|
// Dependencies (contexts, events) for this fiber, if it has any
|
||||||
|
dependencies: mixed | null,
|
||||||
|
|
||||||
elementType: any,
|
elementType: any,
|
||||||
|
|
||||||
type: any,
|
type: any,
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,20 @@ export function cleanForBridge(
|
||||||
path?: Array<string | number> = [],
|
path?: Array<string | number> = [],
|
||||||
): DehydratedData | null {
|
): DehydratedData | null {
|
||||||
if (data !== null) {
|
if (data !== null) {
|
||||||
const cleaned = [];
|
const cleanedPaths = [];
|
||||||
|
const unserializablePaths = [];
|
||||||
|
const cleanedData = dehydrate(
|
||||||
|
data,
|
||||||
|
cleanedPaths,
|
||||||
|
unserializablePaths,
|
||||||
|
path,
|
||||||
|
isPathWhitelisted,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: dehydrate(data, cleaned, path, isPathWhitelisted),
|
data: cleanedData,
|
||||||
cleaned,
|
cleaned: cleanedPaths,
|
||||||
|
unserializable: unserializablePaths,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export const LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY =
|
||||||
export const PROFILER_EXPORT_VERSION = 4;
|
export const PROFILER_EXPORT_VERSION = 4;
|
||||||
|
|
||||||
export const CHANGE_LOG_URL =
|
export const CHANGE_LOG_URL =
|
||||||
'https://github.com/bvaughn/react-devtools-experimental/blob/master/CHANGELOG.md';
|
'https://github.com/facebook/react/blob/master/packages/react-devtools/CHANGELOG.md';
|
||||||
|
|
||||||
// HACK
|
// HACK
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,9 @@ export default class ProfilerStore extends EventEmitter<{|
|
||||||
// When profiling is in progress, operations are stored so that we can later reconstruct past commit trees.
|
// When profiling is in progress, operations are stored so that we can later reconstruct past commit trees.
|
||||||
_isProfiling: boolean = false;
|
_isProfiling: boolean = false;
|
||||||
|
|
||||||
|
// Tracks whether a specific renderer logged any profiling data during the most recent session.
|
||||||
|
_rendererIDsThatReportedProfilingData: Set<number> = new Set();
|
||||||
|
|
||||||
// After profiling, data is requested from each attached renderer using this queue.
|
// After profiling, data is requested from each attached renderer using this queue.
|
||||||
// So long as this queue is not empty, the store is retrieving and processing profiling data from the backend.
|
// So long as this queue is not empty, the store is retrieving and processing profiling data from the backend.
|
||||||
_rendererQueue: Set<number> = new Set();
|
_rendererQueue: Set<number> = new Set();
|
||||||
|
|
@ -233,6 +236,8 @@ export default class ProfilerStore extends EventEmitter<{|
|
||||||
if (!this._initialSnapshotsByRootID.has(rootID)) {
|
if (!this._initialSnapshotsByRootID.has(rootID)) {
|
||||||
this._initialSnapshotsByRootID.set(rootID, new Map());
|
this._initialSnapshotsByRootID.set(rootID, new Map());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._rendererIDsThatReportedProfilingData.add(rendererID);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -280,6 +285,7 @@ export default class ProfilerStore extends EventEmitter<{|
|
||||||
this._initialRendererIDs.clear();
|
this._initialRendererIDs.clear();
|
||||||
this._initialSnapshotsByRootID.clear();
|
this._initialSnapshotsByRootID.clear();
|
||||||
this._inProgressOperationsByRootID.clear();
|
this._inProgressOperationsByRootID.clear();
|
||||||
|
this._rendererIDsThatReportedProfilingData.clear();
|
||||||
this._rendererQueue.clear();
|
this._rendererQueue.clear();
|
||||||
|
|
||||||
// Record all renderer IDs initially too (in case of unmount)
|
// Record all renderer IDs initially too (in case of unmount)
|
||||||
|
|
@ -316,7 +322,10 @@ export default class ProfilerStore extends EventEmitter<{|
|
||||||
this._dataBackends.splice(0);
|
this._dataBackends.splice(0);
|
||||||
this._rendererQueue.clear();
|
this._rendererQueue.clear();
|
||||||
|
|
||||||
this._initialRendererIDs.forEach(rendererID => {
|
// Only request data from renderers that actually logged it.
|
||||||
|
// This avoids unnecessary bridge requests and also avoids edge case mixed renderer bugs.
|
||||||
|
// (e.g. when v15 and v16 are both present)
|
||||||
|
this._rendererIDsThatReportedProfilingData.forEach(rendererID => {
|
||||||
if (!this._rendererQueue.has(rendererID)) {
|
if (!this._rendererQueue.has(rendererID)) {
|
||||||
this._rendererQueue.add(rendererID);
|
this._rendererQueue.add(rendererID);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,7 @@ function HookView({canEditHooks, hook, id, inspectPath, path}: HookViewProps) {
|
||||||
) : (
|
) : (
|
||||||
<KeyValue
|
<KeyValue
|
||||||
depth={1}
|
depth={1}
|
||||||
|
alphaSort={false}
|
||||||
inspectPath={inspectPath}
|
inspectPath={inspectPath}
|
||||||
name="subHooks"
|
name="subHooks"
|
||||||
path={path.concat(['subHooks'])}
|
path={path.concat(['subHooks'])}
|
||||||
|
|
@ -179,6 +180,7 @@ function HookView({canEditHooks, hook, id, inspectPath, path}: HookViewProps) {
|
||||||
<div className={styles.Children} hidden={!isOpen}>
|
<div className={styles.Children} hidden={!isOpen}>
|
||||||
<KeyValue
|
<KeyValue
|
||||||
depth={1}
|
depth={1}
|
||||||
|
alphaSort={false}
|
||||||
inspectPath={inspectPath}
|
inspectPath={inspectPath}
|
||||||
name="DebugValue"
|
name="DebugValue"
|
||||||
path={path.concat(['value'])}
|
path={path.concat(['value'])}
|
||||||
|
|
@ -237,6 +239,7 @@ function HookView({canEditHooks, hook, id, inspectPath, path}: HookViewProps) {
|
||||||
<div className={styles.Hook}>
|
<div className={styles.Hook}>
|
||||||
<KeyValue
|
<KeyValue
|
||||||
depth={1}
|
depth={1}
|
||||||
|
alphaSort={false}
|
||||||
inspectPath={inspectPath}
|
inspectPath={inspectPath}
|
||||||
name={name}
|
name={name}
|
||||||
overrideValueFn={overrideValueFn}
|
overrideValueFn={overrideValueFn}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import type {
|
||||||
InspectedElementPayload,
|
InspectedElementPayload,
|
||||||
} from 'react-devtools-shared/src/backend/types';
|
} from 'react-devtools-shared/src/backend/types';
|
||||||
import type {
|
import type {
|
||||||
|
DehydratedData,
|
||||||
Element,
|
Element,
|
||||||
InspectedElement as InspectedElementFrontend,
|
InspectedElement as InspectedElementFrontend,
|
||||||
} from 'react-devtools-shared/src/devtools/views/Components/types';
|
} from 'react-devtools-shared/src/devtools/views/Components/types';
|
||||||
|
|
@ -290,11 +291,11 @@ function InspectedElementContextController({children}: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function hydrateHelper(
|
function hydrateHelper(
|
||||||
dehydratedData: any | null,
|
dehydratedData: DehydratedData | null,
|
||||||
path?: Array<string | number>,
|
path?: Array<string | number>,
|
||||||
): Object | null {
|
): Object | null {
|
||||||
if (dehydratedData !== null) {
|
if (dehydratedData !== null) {
|
||||||
let {cleaned, data} = dehydratedData;
|
let {cleaned, data, unserializable} = dehydratedData;
|
||||||
|
|
||||||
if (path) {
|
if (path) {
|
||||||
const {length} = path;
|
const {length} = path;
|
||||||
|
|
@ -302,10 +303,13 @@ function hydrateHelper(
|
||||||
// Hydration helper requires full paths, but inspection dehydrates with relative paths.
|
// Hydration helper requires full paths, but inspection dehydrates with relative paths.
|
||||||
// In that event it's important that we adjust the "cleaned" paths to match.
|
// In that event it's important that we adjust the "cleaned" paths to match.
|
||||||
cleaned = cleaned.map(cleanedPath => cleanedPath.slice(length));
|
cleaned = cleaned.map(cleanedPath => cleanedPath.slice(length));
|
||||||
|
unserializable = unserializable.map(unserializablePath =>
|
||||||
|
unserializablePath.slice(length),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hydrate(data, cleaned);
|
return hydrate(data, cleaned, unserializable);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import React, {useCallback} from 'react';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import ButtonIcon from '../ButtonIcon';
|
import ButtonIcon from '../ButtonIcon';
|
||||||
import KeyValue from './KeyValue';
|
import KeyValue from './KeyValue';
|
||||||
import {serializeDataForCopy} from '../utils';
|
import {alphaSortEntries, serializeDataForCopy} from '../utils';
|
||||||
import styles from './InspectedElementTree.css';
|
import styles from './InspectedElementTree.css';
|
||||||
|
|
||||||
import type {InspectPath} from './SelectedElement';
|
import type {InspectPath} from './SelectedElement';
|
||||||
|
|
@ -27,7 +27,12 @@ export default function InspectedElementTree({
|
||||||
overrideValueFn,
|
overrideValueFn,
|
||||||
showWhenEmpty = false,
|
showWhenEmpty = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const isEmpty = data === null || Object.keys(data).length === 0;
|
const entries = data != null ? Object.entries(data) : null;
|
||||||
|
if (entries !== null) {
|
||||||
|
entries.sort(alphaSortEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEmpty = entries === null || entries.length === 0;
|
||||||
|
|
||||||
const handleCopy = useCallback(
|
const handleCopy = useCallback(
|
||||||
() => copy(serializeDataForCopy(((data: any): Object))),
|
() => copy(serializeDataForCopy(((data: any): Object))),
|
||||||
|
|
@ -49,15 +54,16 @@ export default function InspectedElementTree({
|
||||||
</div>
|
</div>
|
||||||
{isEmpty && <div className={styles.Empty}>None</div>}
|
{isEmpty && <div className={styles.Empty}>None</div>}
|
||||||
{!isEmpty &&
|
{!isEmpty &&
|
||||||
Object.keys((data: any)).map(name => (
|
(entries: any).map(([name, value]) => (
|
||||||
<KeyValue
|
<KeyValue
|
||||||
key={name}
|
key={name}
|
||||||
|
alphaSort={true}
|
||||||
depth={1}
|
depth={1}
|
||||||
inspectPath={inspectPath}
|
inspectPath={inspectPath}
|
||||||
name={name}
|
name={name}
|
||||||
overrideValueFn={overrideValueFn}
|
overrideValueFn={overrideValueFn}
|
||||||
path={[name]}
|
path={[name]}
|
||||||
value={(data: any)[name]}
|
value={value}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -35,3 +35,7 @@
|
||||||
flex: 0 0 1rem;
|
flex: 0 0 1rem;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Empty {
|
||||||
|
color: var(--color-dimmer);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import React, {useEffect, useRef, useState} from 'react';
|
||||||
import type {Element} from 'react';
|
import type {Element} from 'react';
|
||||||
import EditableValue from './EditableValue';
|
import EditableValue from './EditableValue';
|
||||||
import ExpandCollapseToggle from './ExpandCollapseToggle';
|
import ExpandCollapseToggle from './ExpandCollapseToggle';
|
||||||
import {getMetaValueLabel} from '../utils';
|
import {alphaSortEntries, getMetaValueLabel} from '../utils';
|
||||||
import {meta} from '../../../hydration';
|
import {meta} from '../../../hydration';
|
||||||
import styles from './KeyValue.css';
|
import styles from './KeyValue.css';
|
||||||
|
|
||||||
|
|
@ -13,9 +13,11 @@ import type {InspectPath} from './SelectedElement';
|
||||||
type OverrideValueFn = (path: Array<string | number>, value: any) => void;
|
type OverrideValueFn = (path: Array<string | number>, value: any) => void;
|
||||||
|
|
||||||
type KeyValueProps = {|
|
type KeyValueProps = {|
|
||||||
|
alphaSort: boolean,
|
||||||
depth: number,
|
depth: number,
|
||||||
hidden?: boolean,
|
hidden?: boolean,
|
||||||
inspectPath?: InspectPath,
|
inspectPath?: InspectPath,
|
||||||
|
isReadOnly?: boolean,
|
||||||
name: string,
|
name: string,
|
||||||
overrideValueFn?: ?OverrideValueFn,
|
overrideValueFn?: ?OverrideValueFn,
|
||||||
path: Array<any>,
|
path: Array<any>,
|
||||||
|
|
@ -23,8 +25,10 @@ type KeyValueProps = {|
|
||||||
|};
|
|};
|
||||||
|
|
||||||
export default function KeyValue({
|
export default function KeyValue({
|
||||||
|
alphaSort,
|
||||||
depth,
|
depth,
|
||||||
inspectPath,
|
inspectPath,
|
||||||
|
isReadOnly,
|
||||||
hidden,
|
hidden,
|
||||||
name,
|
name,
|
||||||
overrideValueFn,
|
overrideValueFn,
|
||||||
|
|
@ -81,17 +85,18 @@ export default function KeyValue({
|
||||||
displayValue = 'undefined';
|
displayValue = 'undefined';
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameClassName =
|
const isEditable = typeof overrideValueFn === 'function' && !isReadOnly;
|
||||||
typeof overrideValueFn === 'function' ? styles.EditableName : styles.Name;
|
|
||||||
|
|
||||||
children = (
|
children = (
|
||||||
<div key="root" className={styles.Item} hidden={hidden} style={style}>
|
<div key="root" className={styles.Item} hidden={hidden} style={style}>
|
||||||
<div className={styles.ExpandCollapseToggleSpacer} />
|
<div className={styles.ExpandCollapseToggleSpacer} />
|
||||||
<span className={nameClassName}>{name}</span>
|
<span className={isEditable ? styles.EditableName : styles.Name}>
|
||||||
{typeof overrideValueFn === 'function' ? (
|
{name}
|
||||||
|
</span>
|
||||||
|
{isEditable ? (
|
||||||
<EditableValue
|
<EditableValue
|
||||||
dataType={dataType}
|
dataType={dataType}
|
||||||
overrideValueFn={overrideValueFn}
|
overrideValueFn={((overrideValueFn: any): OverrideValueFn)}
|
||||||
path={path}
|
path={path}
|
||||||
value={value}
|
value={value}
|
||||||
/>
|
/>
|
||||||
|
|
@ -100,7 +105,10 @@ export default function KeyValue({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (value.hasOwnProperty(meta.type)) {
|
} else if (
|
||||||
|
value.hasOwnProperty(meta.type) &&
|
||||||
|
!value.hasOwnProperty(meta.unserializable)
|
||||||
|
) {
|
||||||
children = (
|
children = (
|
||||||
<div key="root" className={styles.Item} hidden={hidden} style={style}>
|
<div key="root" className={styles.Item} hidden={hidden} style={style}>
|
||||||
{isInspectable ? (
|
{isInspectable ? (
|
||||||
|
|
@ -123,8 +131,10 @@ export default function KeyValue({
|
||||||
children = value.map((innerValue, index) => (
|
children = value.map((innerValue, index) => (
|
||||||
<KeyValue
|
<KeyValue
|
||||||
key={index}
|
key={index}
|
||||||
|
alphaSort={alphaSort}
|
||||||
depth={depth + 1}
|
depth={depth + 1}
|
||||||
inspectPath={inspectPath}
|
inspectPath={inspectPath}
|
||||||
|
isReadOnly={isReadOnly}
|
||||||
hidden={hidden || !isOpen}
|
hidden={hidden || !isOpen}
|
||||||
name={index}
|
name={index}
|
||||||
overrideValueFn={overrideValueFn}
|
overrideValueFn={overrideValueFn}
|
||||||
|
|
@ -148,26 +158,41 @@ export default function KeyValue({
|
||||||
onClick={hasChildren ? toggleIsOpen : undefined}>
|
onClick={hasChildren ? toggleIsOpen : undefined}>
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
<span>Array</span>
|
<span>
|
||||||
|
Array{' '}
|
||||||
|
{hasChildren ? '' : <span className={styles.Empty}>(empty)</span>}
|
||||||
|
</span>
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const hasChildren = Object.entries(value).length > 0;
|
// TRICKY
|
||||||
|
// It's important to use Object.entries() rather than Object.keys()
|
||||||
|
// because of the hidden meta Symbols used for hydration and unserializable values.
|
||||||
|
const entries = Object.entries(value);
|
||||||
|
if (alphaSort) {
|
||||||
|
entries.sort(alphaSortEntries);
|
||||||
|
}
|
||||||
|
|
||||||
children = Object.entries(value).map<Element<any>>(
|
const hasChildren = entries.length > 0;
|
||||||
([innerName, innerValue]) => (
|
const displayName = value.hasOwnProperty(meta.unserializable)
|
||||||
<KeyValue
|
? getMetaValueLabel(value)
|
||||||
key={innerName}
|
: 'Object';
|
||||||
depth={depth + 1}
|
|
||||||
inspectPath={inspectPath}
|
let areChildrenReadOnly = isReadOnly || !!value[meta.readonly];
|
||||||
hidden={hidden || !isOpen}
|
children = entries.map<Element<any>>(([key, keyValue]) => (
|
||||||
name={innerName}
|
<KeyValue
|
||||||
overrideValueFn={overrideValueFn}
|
key={key}
|
||||||
path={path.concat(innerName)}
|
alphaSort={alphaSort}
|
||||||
value={innerValue}
|
depth={depth + 1}
|
||||||
/>
|
inspectPath={inspectPath}
|
||||||
),
|
isReadOnly={areChildrenReadOnly}
|
||||||
);
|
hidden={hidden || !isOpen}
|
||||||
|
name={key}
|
||||||
|
overrideValueFn={overrideValueFn}
|
||||||
|
path={path.concat(key)}
|
||||||
|
value={keyValue}
|
||||||
|
/>
|
||||||
|
));
|
||||||
children.unshift(
|
children.unshift(
|
||||||
<div
|
<div
|
||||||
key={`${depth}-root`}
|
key={`${depth}-root`}
|
||||||
|
|
@ -184,7 +209,10 @@ export default function KeyValue({
|
||||||
onClick={hasChildren ? toggleIsOpen : undefined}>
|
onClick={hasChildren ? toggleIsOpen : undefined}>
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
<span>Object</span>
|
<span>
|
||||||
|
{`${displayName || ''} `}
|
||||||
|
{hasChildren ? '' : <span className={styles.Empty}>(empty)</span>}
|
||||||
|
</span>
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ function Row({
|
||||||
const validateAndSetLocalValue = newValue => {
|
const validateAndSetLocalValue = newValue => {
|
||||||
let isValid = false;
|
let isValid = false;
|
||||||
try {
|
try {
|
||||||
JSON.parse(newValue);
|
JSON.parse(sanitizeForParse(value));
|
||||||
isValid = true;
|
isValid = true;
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
|
||||||
|
|
@ -197,7 +197,7 @@ function Row({
|
||||||
|
|
||||||
const submitValueChange = () => {
|
const submitValueChange = () => {
|
||||||
if (isValueValid) {
|
if (isValueValid) {
|
||||||
const parsedLocalValue = JSON.parse(localValue);
|
const parsedLocalValue = JSON.parse(sanitizeForParse(localValue));
|
||||||
if (value !== parsedLocalValue) {
|
if (value !== parsedLocalValue) {
|
||||||
changeValue(attribute, parsedLocalValue);
|
changeValue(attribute, parsedLocalValue);
|
||||||
}
|
}
|
||||||
|
|
@ -281,3 +281,16 @@ function Field({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We use JSON.parse to parse string values
|
||||||
|
// e.g. 'foo' is not valid JSON but it is a valid string
|
||||||
|
// so this method replaces e.g. 'foo' with "foo"
|
||||||
|
function sanitizeForParse(value: any) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
if (value.charAt(0) === "'" && value.charAt(value.length - 1) === "'") {
|
||||||
|
return '"' + value.substr(1, value.length - 2) + '"';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,4 +89,5 @@ export type DehydratedData = {|
|
||||||
| Dehydrated
|
| Dehydrated
|
||||||
| Array<Dehydrated>
|
| Array<Dehydrated>
|
||||||
| {[key: string]: string | Dehydrated},
|
| {[key: string]: string | Dehydrated},
|
||||||
|
unserializable: Array<Array<string | number>>,
|
||||||
|};
|
|};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
import '@reach/menu-button/styles.css';
|
import '@reach/menu-button/styles.css';
|
||||||
import '@reach/tooltip/styles.css';
|
import '@reach/tooltip/styles.css';
|
||||||
|
|
||||||
import React, {useMemo, useState} from 'react';
|
import React, {useEffect, useMemo, useState} from 'react';
|
||||||
import Store from '../store';
|
import Store from '../store';
|
||||||
import {BridgeContext, StoreContext} from './context';
|
import {BridgeContext, StoreContext} from './context';
|
||||||
import Components from './Components/Components';
|
import Components from './Components/Components';
|
||||||
|
|
@ -103,6 +103,19 @@ export default function DevTools({
|
||||||
[canViewElementSourceFunction, viewElementSourceFunction],
|
[canViewElementSourceFunction, viewElementSourceFunction],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
return () => {
|
||||||
|
try {
|
||||||
|
bridge.shutdown();
|
||||||
|
} catch (error) {
|
||||||
|
// Attempting to use a disconnected port.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[bridge],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BridgeContext.Provider value={bridge}>
|
<BridgeContext.Provider value={bridge}>
|
||||||
<StoreContext.Provider value={store}>
|
<StoreContext.Provider value={store}>
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,11 @@ export default class ErrorBoundary extends Component<Props, State> {
|
||||||
const title = `Error: "${errorMessage || ''}"`;
|
const title = `Error: "${errorMessage || ''}"`;
|
||||||
const label = 'Component: Developer Tools';
|
const label = 'Component: Developer Tools';
|
||||||
|
|
||||||
let body = '<!-- please provide repro information here -->\n';
|
let body = 'Describe what you were doing when the bug occurred:';
|
||||||
body += '\n---------------------------------------------';
|
body += '\n1. ';
|
||||||
|
body += '\n2. ';
|
||||||
|
body += '\n3. ';
|
||||||
|
body += '\n\n---------------------------------------------';
|
||||||
body += '\nPlease do not remove the text below this line';
|
body += '\nPlease do not remove the text below this line';
|
||||||
body += '\n---------------------------------------------';
|
body += '\n---------------------------------------------';
|
||||||
body += `\n\nDevTools version: ${process.env.DEVTOOLS_VERSION || ''}`;
|
body += `\n\nDevTools version: ${process.env.DEVTOOLS_VERSION || ''}`;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ function Profiler(_: {||}) {
|
||||||
didRecordCommits,
|
didRecordCommits,
|
||||||
isProcessingData,
|
isProcessingData,
|
||||||
isProfiling,
|
isProfiling,
|
||||||
|
selectedCommitIndex,
|
||||||
selectedFiberID,
|
selectedFiberID,
|
||||||
selectedTabID,
|
selectedTabID,
|
||||||
selectTab,
|
selectTab,
|
||||||
|
|
@ -67,10 +68,18 @@ function Profiler(_: {||}) {
|
||||||
break;
|
break;
|
||||||
case 'flame-chart':
|
case 'flame-chart':
|
||||||
case 'ranked-chart':
|
case 'ranked-chart':
|
||||||
if (selectedFiberID !== null) {
|
// TRICKY
|
||||||
sidebar = <SidebarSelectedFiberInfo />;
|
// Handle edge case where no commit is selected because of a min-duration filter update.
|
||||||
} else {
|
// In that case, the selected commit index would be null.
|
||||||
sidebar = <SidebarCommitInfo />;
|
// We could still show a sidebar for the previously selected fiber,
|
||||||
|
// but it would be an odd user experience.
|
||||||
|
// TODO (ProfilerContext) This check should not be necessary.
|
||||||
|
if (selectedCommitIndex !== null) {
|
||||||
|
if (selectedFiberID !== null) {
|
||||||
|
sidebar = <SidebarSelectedFiberInfo />;
|
||||||
|
} else {
|
||||||
|
sidebar = <SidebarCommitInfo />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -126,28 +126,32 @@ function ProfilerContextController({children}: Props) {
|
||||||
const [rootID, setRootID] = useState<number | null>(null);
|
const [rootID, setRootID] = useState<number | null>(null);
|
||||||
|
|
||||||
if (prevProfilingData !== profilingData) {
|
if (prevProfilingData !== profilingData) {
|
||||||
setPrevProfilingData(profilingData);
|
batchedUpdates(() => {
|
||||||
|
setPrevProfilingData(profilingData);
|
||||||
|
|
||||||
const dataForRoots =
|
const dataForRoots =
|
||||||
profilingData !== null ? profilingData.dataForRoots : null;
|
profilingData !== null ? profilingData.dataForRoots : null;
|
||||||
if (dataForRoots != null) {
|
if (dataForRoots != null) {
|
||||||
const firstRootID = dataForRoots.keys().next().value || null;
|
const firstRootID = dataForRoots.keys().next().value || null;
|
||||||
|
|
||||||
if (rootID === null || !dataForRoots.has(rootID)) {
|
if (rootID === null || !dataForRoots.has(rootID)) {
|
||||||
let selectedElementRootID = null;
|
let selectedElementRootID = null;
|
||||||
if (selectedElementID !== null) {
|
if (selectedElementID !== null) {
|
||||||
selectedElementRootID = store.getRootIDForElement(selectedElementID);
|
selectedElementRootID = store.getRootIDForElement(
|
||||||
}
|
selectedElementID,
|
||||||
if (
|
);
|
||||||
selectedElementRootID !== null &&
|
}
|
||||||
dataForRoots.has(selectedElementRootID)
|
if (
|
||||||
) {
|
selectedElementRootID !== null &&
|
||||||
setRootID(selectedElementRootID);
|
dataForRoots.has(selectedElementRootID)
|
||||||
} else {
|
) {
|
||||||
setRootID(firstRootID);
|
setRootID(selectedElementRootID);
|
||||||
|
} else {
|
||||||
|
setRootID(firstRootID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const startProfiling = useCallback(
|
const startProfiling = useCallback(
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ export default function SidebarSelectedFiberInfo(_: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type WhatChangedProps = {|
|
type WhatChangedProps = {|
|
||||||
commitIndex: number,
|
commitIndex: number | null,
|
||||||
fiberID: number,
|
fiberID: number,
|
||||||
profilerStore: ProfilerStore,
|
profilerStore: ProfilerStore,
|
||||||
rootID: number,
|
rootID: number,
|
||||||
|
|
@ -100,6 +100,14 @@ function WhatChanged({
|
||||||
profilerStore,
|
profilerStore,
|
||||||
rootID,
|
rootID,
|
||||||
}: WhatChangedProps) {
|
}: WhatChangedProps) {
|
||||||
|
// TRICKY
|
||||||
|
// Handle edge case where no commit is selected because of a min-duration filter update.
|
||||||
|
// If the commit index is null, suspending for data below would throw an error.
|
||||||
|
// TODO (ProfilerContext) This check should not be necessary.
|
||||||
|
if (commitIndex === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const {changeDescriptions} = profilerStore.getCommitData(
|
const {changeDescriptions} = profilerStore.getCommitData(
|
||||||
((rootID: any): number),
|
((rootID: any): number),
|
||||||
commitIndex,
|
commitIndex,
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ export default function SnapshotSelector(_: Props) {
|
||||||
[filteredCommitIndices, selectedCommitIndex],
|
[filteredCommitIndices, selectedCommitIndex],
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO (profiling) This should be managed by the context controller (reducer).
|
// TODO (ProfilerContext) This should be managed by the context controller (reducer).
|
||||||
// It doesn't currently know about the filtered commits though (since it doesn't suspend).
|
// It doesn't currently know about the filtered commits though (since it doesn't suspend).
|
||||||
// Maybe this component should pass filteredCommitIndices up?
|
// Maybe this component should pass filteredCommitIndices up?
|
||||||
if (selectedFilteredCommitIndex === null) {
|
if (selectedFilteredCommitIndex === null) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,21 @@ import {meta} from '../../hydration';
|
||||||
|
|
||||||
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
|
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
|
||||||
|
|
||||||
|
export function alphaSortEntries(
|
||||||
|
entryA: [string, mixed],
|
||||||
|
entryB: [string, mixed],
|
||||||
|
): number {
|
||||||
|
const a = entryA[0];
|
||||||
|
const b = entryB[0];
|
||||||
|
if ('' + +a === a) {
|
||||||
|
if ('' + +b !== b) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return +a < +b ? -1 : 1;
|
||||||
|
}
|
||||||
|
return a < b ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
export function createRegExp(string: string): RegExp {
|
export function createRegExp(string: string): RegExp {
|
||||||
// Allow /regex/ syntax with optional last /
|
// Allow /regex/ syntax with optional last /
|
||||||
if (string[0] === '/') {
|
if (string[0] === '/') {
|
||||||
|
|
|
||||||
161
packages/react-devtools-shared/src/hydration.js
vendored
161
packages/react-devtools-shared/src/hydration.js
vendored
|
|
@ -18,6 +18,8 @@ import {
|
||||||
} from 'react-is';
|
} from 'react-is';
|
||||||
import {getDisplayName, getInObject, setInObject} from './utils';
|
import {getDisplayName, getInObject, setInObject} from './utils';
|
||||||
|
|
||||||
|
import type {DehydratedData} from 'react-devtools-shared/src/devtools/views/Components/types';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
inspectable: Symbol('inspectable'),
|
inspectable: Symbol('inspectable'),
|
||||||
inspected: Symbol('inspected'),
|
inspected: Symbol('inspected'),
|
||||||
|
|
@ -25,6 +27,7 @@ export const meta = {
|
||||||
readonly: Symbol('readonly'),
|
readonly: Symbol('readonly'),
|
||||||
size: Symbol('size'),
|
size: Symbol('size'),
|
||||||
type: Symbol('type'),
|
type: Symbol('type'),
|
||||||
|
unserializable: Symbol('unserializable'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Dehydrated = {|
|
export type Dehydrated = {|
|
||||||
|
|
@ -35,6 +38,18 @@ export type Dehydrated = {|
|
||||||
type: string,
|
type: string,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
|
// Typed arrays and other complex iteratable objects (e.g. Map, Set, ImmutableJS) need special handling.
|
||||||
|
// These objects can't be serialized without losing type information,
|
||||||
|
// so a "Unserializable" type wrapper is used (with meta-data keys) to send nested values-
|
||||||
|
// while preserving the original type and name.
|
||||||
|
type Unserializable = {
|
||||||
|
name: string | null,
|
||||||
|
readonly?: boolean,
|
||||||
|
size?: number,
|
||||||
|
type: string,
|
||||||
|
unserializable: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
// This threshold determines the depth at which the bridge "dehydrates" nested data.
|
// This threshold determines the depth at which the bridge "dehydrates" nested data.
|
||||||
// Dehydration means that we don't serialize the data for e.g. postMessage or stringify,
|
// Dehydration means that we don't serialize the data for e.g. postMessage or stringify,
|
||||||
// unless the frontend explicitly requests it (e.g. a user clicks to expand a props object).
|
// unless the frontend explicitly requests it (e.g. a user clicks to expand a props object).
|
||||||
|
|
@ -173,16 +188,19 @@ function createDehydrated(
|
||||||
export function dehydrate(
|
export function dehydrate(
|
||||||
data: Object,
|
data: Object,
|
||||||
cleaned: Array<Array<string | number>>,
|
cleaned: Array<Array<string | number>>,
|
||||||
|
unserializable: Array<Array<string | number>>,
|
||||||
path: Array<string | number>,
|
path: Array<string | number>,
|
||||||
isPathWhitelisted: (path: Array<string | number>) => boolean,
|
isPathWhitelisted: (path: Array<string | number>) => boolean,
|
||||||
level?: number = 0,
|
level?: number = 0,
|
||||||
):
|
):
|
||||||
| string
|
| string
|
||||||
| Dehydrated
|
| Dehydrated
|
||||||
| Array<Dehydrated>
|
| Unserializable
|
||||||
| {[key: string]: string | Dehydrated} {
|
| {[key: string]: string | Dehydrated | Unserializable} {
|
||||||
const type = getDataType(data);
|
const type = getDataType(data);
|
||||||
|
|
||||||
|
let isPathWhitelistedCheck;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'html_element':
|
case 'html_element':
|
||||||
cleaned.push(path);
|
cleaned.push(path);
|
||||||
|
|
@ -233,23 +251,56 @@ export function dehydrate(
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'array':
|
case 'array':
|
||||||
const arrayPathCheck = isPathWhitelisted(path);
|
isPathWhitelistedCheck = isPathWhitelisted(path);
|
||||||
if (level >= LEVEL_THRESHOLD && !arrayPathCheck) {
|
if (level >= LEVEL_THRESHOLD && !isPathWhitelistedCheck) {
|
||||||
return createDehydrated(type, true, data, cleaned, path);
|
return createDehydrated(type, true, data, cleaned, path);
|
||||||
}
|
}
|
||||||
return data.map((item, i) =>
|
return data.map((item, i) =>
|
||||||
dehydrate(
|
dehydrate(
|
||||||
item,
|
item,
|
||||||
cleaned,
|
cleaned,
|
||||||
|
unserializable,
|
||||||
path.concat([i]),
|
path.concat([i]),
|
||||||
isPathWhitelisted,
|
isPathWhitelisted,
|
||||||
arrayPathCheck ? 1 : level + 1,
|
isPathWhitelistedCheck ? 1 : level + 1,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'typed_array':
|
case 'typed_array':
|
||||||
case 'iterator':
|
case 'iterator':
|
||||||
return createDehydrated(type, false, data, cleaned, path);
|
isPathWhitelistedCheck = isPathWhitelisted(path);
|
||||||
|
if (level >= LEVEL_THRESHOLD && !isPathWhitelistedCheck) {
|
||||||
|
return createDehydrated(type, true, data, cleaned, path);
|
||||||
|
} else {
|
||||||
|
const unserializableValue: Unserializable = {
|
||||||
|
unserializable: true,
|
||||||
|
type: type,
|
||||||
|
readonly: true,
|
||||||
|
size: type === 'typed_array' ? data.length : undefined,
|
||||||
|
name:
|
||||||
|
!data.constructor || data.constructor.name === 'Object'
|
||||||
|
? ''
|
||||||
|
: data.constructor.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof data[Symbol.iterator]) {
|
||||||
|
[...data].forEach(
|
||||||
|
(item, i) =>
|
||||||
|
(unserializableValue[i] = dehydrate(
|
||||||
|
item,
|
||||||
|
cleaned,
|
||||||
|
unserializable,
|
||||||
|
path.concat([i]),
|
||||||
|
isPathWhitelisted,
|
||||||
|
isPathWhitelistedCheck ? 1 : level + 1,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unserializable.push(path);
|
||||||
|
|
||||||
|
return unserializableValue;
|
||||||
|
}
|
||||||
|
|
||||||
case 'date':
|
case 'date':
|
||||||
cleaned.push(path);
|
cleaned.push(path);
|
||||||
|
|
@ -260,8 +311,8 @@ export function dehydrate(
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'object':
|
case 'object':
|
||||||
const objectPathCheck = isPathWhitelisted(path);
|
isPathWhitelistedCheck = isPathWhitelisted(path);
|
||||||
if (level >= LEVEL_THRESHOLD && !objectPathCheck) {
|
if (level >= LEVEL_THRESHOLD && !isPathWhitelistedCheck) {
|
||||||
return createDehydrated(type, true, data, cleaned, path);
|
return createDehydrated(type, true, data, cleaned, path);
|
||||||
} else {
|
} else {
|
||||||
const object = {};
|
const object = {};
|
||||||
|
|
@ -269,9 +320,10 @@ export function dehydrate(
|
||||||
object[name] = dehydrate(
|
object[name] = dehydrate(
|
||||||
data[name],
|
data[name],
|
||||||
cleaned,
|
cleaned,
|
||||||
|
unserializable,
|
||||||
path.concat([name]),
|
path.concat([name]),
|
||||||
isPathWhitelisted,
|
isPathWhitelisted,
|
||||||
objectPathCheck ? 1 : level + 1,
|
isPathWhitelistedCheck ? 1 : level + 1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return object;
|
return object;
|
||||||
|
|
@ -294,24 +346,43 @@ export function dehydrate(
|
||||||
|
|
||||||
export function fillInPath(
|
export function fillInPath(
|
||||||
object: Object,
|
object: Object,
|
||||||
|
data: DehydratedData,
|
||||||
path: Array<string | number>,
|
path: Array<string | number>,
|
||||||
value: any,
|
value: any,
|
||||||
) {
|
) {
|
||||||
const target = getInObject(object, path);
|
const target = getInObject(object, path);
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
delete target[meta.inspectable];
|
if (!target[meta.unserializable]) {
|
||||||
delete target[meta.inspected];
|
delete target[meta.inspectable];
|
||||||
delete target[meta.name];
|
delete target[meta.inspected];
|
||||||
delete target[meta.readonly];
|
delete target[meta.name];
|
||||||
delete target[meta.size];
|
delete target[meta.readonly];
|
||||||
delete target[meta.type];
|
delete target[meta.size];
|
||||||
|
delete target[meta.type];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value !== null && data.unserializable.length > 0) {
|
||||||
|
const unserializablePath = data.unserializable[0];
|
||||||
|
let isMatch = unserializablePath.length === path.length;
|
||||||
|
for (let i = 0; i < path.length; i++) {
|
||||||
|
if (path[i] !== unserializablePath[i]) {
|
||||||
|
isMatch = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isMatch) {
|
||||||
|
upgradeUnserializable(value, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setInObject(object, path, value);
|
setInObject(object, path, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hydrate(
|
export function hydrate(
|
||||||
object: Object,
|
object: Object,
|
||||||
cleaned: Array<Array<string | number>>,
|
cleaned: Array<Array<string | number>>,
|
||||||
|
unserializable: Array<Array<string | number>>,
|
||||||
): Object {
|
): Object {
|
||||||
cleaned.forEach((path: Array<string | number>) => {
|
cleaned.forEach((path: Array<string | number>) => {
|
||||||
const length = path.length;
|
const length = path.length;
|
||||||
|
|
@ -342,9 +413,69 @@ export function hydrate(
|
||||||
parent[last] = replaced;
|
parent[last] = replaced;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
unserializable.forEach((path: Array<string | number>) => {
|
||||||
|
const length = path.length;
|
||||||
|
const last = path[length - 1];
|
||||||
|
const parent = getInObject(object, path.slice(0, length - 1));
|
||||||
|
if (!parent || !parent.hasOwnProperty(last)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = parent[last];
|
||||||
|
|
||||||
|
const replacement = {
|
||||||
|
...node,
|
||||||
|
};
|
||||||
|
|
||||||
|
upgradeUnserializable(replacement, node);
|
||||||
|
|
||||||
|
parent[last] = replacement;
|
||||||
|
});
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function upgradeUnserializable(destination: Object, source: Object) {
|
||||||
|
Object.defineProperties(destination, {
|
||||||
|
[meta.inspected]: {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
value: !!source.inspected,
|
||||||
|
},
|
||||||
|
[meta.name]: {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
value: source.name,
|
||||||
|
},
|
||||||
|
[meta.size]: {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
value: source.size,
|
||||||
|
},
|
||||||
|
[meta.readonly]: {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
value: !!source.readonly,
|
||||||
|
},
|
||||||
|
[meta.type]: {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
value: source.type,
|
||||||
|
},
|
||||||
|
[meta.unserializable]: {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: false,
|
||||||
|
value: !!source.unserializable,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
delete destination.inspected;
|
||||||
|
delete destination.name;
|
||||||
|
delete destination.size;
|
||||||
|
delete destination.readonly;
|
||||||
|
delete destination.type;
|
||||||
|
delete destination.unserializable;
|
||||||
|
}
|
||||||
|
|
||||||
export function getDisplayNameForReactElement(
|
export function getDisplayNameForReactElement(
|
||||||
element: React$Element<any>,
|
element: React$Element<any>,
|
||||||
): string | null {
|
): string | null {
|
||||||
|
|
|
||||||
16
packages/react-devtools-shared/src/utils.js
vendored
16
packages/react-devtools-shared/src/utils.js
vendored
|
|
@ -266,13 +266,17 @@ export function shallowDiffers(prev: Object, next: Object): boolean {
|
||||||
|
|
||||||
export function getInObject(object: Object, path: Array<string | number>): any {
|
export function getInObject(object: Object, path: Array<string | number>): any {
|
||||||
return path.reduce((reduced: Object, attr: string | number): any => {
|
return path.reduce((reduced: Object, attr: string | number): any => {
|
||||||
if (typeof reduced === 'object' && reduced !== null) {
|
if (reduced) {
|
||||||
return reduced[attr];
|
if (hasOwnProperty.call(reduced, attr)) {
|
||||||
} else if (Array.isArray(reduced)) {
|
return reduced[attr];
|
||||||
return reduced[attr];
|
}
|
||||||
} else {
|
if (typeof reduced[Symbol.iterator] === 'function') {
|
||||||
return null;
|
// Convert iterable to array and return array[index]
|
||||||
|
return [...reduced][attr];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}, object);
|
}, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
"start": "cross-env NODE_ENV=development cross-env TARGET=local webpack-dev-server --open"
|
"start": "cross-env NODE_ENV=development cross-env TARGET=local webpack-dev-server --open"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"immutable": "^4.0.0-rc.12",
|
||||||
"react-native-web": "^0.11.5"
|
"react-native-web": "^0.11.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, {Fragment} from 'react';
|
import React, {Fragment} from 'react';
|
||||||
|
import UnserializableProps from './UnserializableProps';
|
||||||
import Contexts from './Contexts';
|
import Contexts from './Contexts';
|
||||||
import CustomHooks from './CustomHooks';
|
import CustomHooks from './CustomHooks';
|
||||||
import CustomObject from './CustomObject';
|
import CustomObject from './CustomObject';
|
||||||
|
|
@ -14,6 +15,7 @@ export default function InspectableElements() {
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<h1>Inspectable elements</h1>
|
<h1>Inspectable elements</h1>
|
||||||
<SimpleValues />
|
<SimpleValues />
|
||||||
|
<UnserializableProps />
|
||||||
<NestedProps />
|
<NestedProps />
|
||||||
<Contexts />
|
<Contexts />
|
||||||
<CustomHooks />
|
<CustomHooks />
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ export default function ObjectProps() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
emptyArray={[]}
|
||||||
|
emptyObject={{}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
packages/react-devtools-shell/src/app/InspectableElements/UnserializableProps.js
vendored
Normal file
35
packages/react-devtools-shell/src/app/InspectableElements/UnserializableProps.js
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Immutable from 'immutable';
|
||||||
|
|
||||||
|
const set = new Set(['abc', 123]);
|
||||||
|
const map = new Map([['name', 'Brian'], ['food', 'sushi']]);
|
||||||
|
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
||||||
|
const mapOfMaps = new Map([['first', map], ['second', map]]);
|
||||||
|
const typedArray = Int8Array.from([100, -100, 0]);
|
||||||
|
const immutable = Immutable.fromJS({
|
||||||
|
a: [{hello: 'there'}, 'fixed', true],
|
||||||
|
b: 123,
|
||||||
|
c: {
|
||||||
|
'1': 'xyz',
|
||||||
|
xyz: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function UnserializableProps() {
|
||||||
|
return (
|
||||||
|
<ChildComponent
|
||||||
|
map={map}
|
||||||
|
set={set}
|
||||||
|
mapOfMaps={mapOfMaps}
|
||||||
|
setOfSets={setOfSets}
|
||||||
|
typedArray={typedArray}
|
||||||
|
immutable={immutable}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChildComponent(props: any) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
@ -62,7 +62,9 @@ export default function List(props: Props) {
|
||||||
|
|
||||||
const toggleItem = useCallback(
|
const toggleItem = useCallback(
|
||||||
itemToToggle => {
|
itemToToggle => {
|
||||||
const index = items.indexOf(itemToToggle);
|
// Dont use indexOf()
|
||||||
|
// because editing props in DevTools creates a new Object.
|
||||||
|
const index = items.findIndex(item => item.id === itemToToggle.id);
|
||||||
|
|
||||||
setItems(
|
setItems(
|
||||||
items
|
items
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "react-devtools",
|
"name": "react-devtools",
|
||||||
"version": "4.0.0-alpha.9",
|
"version": "4.0.5",
|
||||||
"description": "Use react-devtools outside of the browser",
|
"description": "Use react-devtools outside of the browser",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "https://github.com/bvaughn/react-devtools-experimental.git",
|
"type": "git",
|
||||||
"type": "git"
|
"url": "https://github.com/facebook/react.git",
|
||||||
|
"directory": "packages/react-devtools"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"react-devtools": "./bin.js"
|
"react-devtools": "./bin.js"
|
||||||
|
|
@ -26,7 +27,7 @@
|
||||||
"electron": "^5.0.0",
|
"electron": "^5.0.0",
|
||||||
"ip": "^1.1.4",
|
"ip": "^1.1.4",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"react-devtools-core": "4.0.0-alpha.9",
|
"react-devtools-core": "4.0.5",
|
||||||
"update-notifier": "^2.1.0"
|
"update-notifier": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6435,6 +6435,11 @@ immediate@~3.0.5:
|
||||||
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||||
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
|
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
|
||||||
|
|
||||||
|
immutable@^4.0.0-rc.12:
|
||||||
|
version "4.0.0-rc.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0-rc.12.tgz#ca59a7e4c19ae8d9bf74a97bdf0f6e2f2a5d0217"
|
||||||
|
integrity sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A==
|
||||||
|
|
||||||
import-fresh@^3.0.0:
|
import-fresh@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.1.0.tgz#6d33fa1dcef6df930fae003446f33415af905118"
|
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.1.0.tgz#6d33fa1dcef6df930fae003446f33415af905118"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user