@samitouri / QOS-React / commits / fd1256a561

Add Debug Tools Package for Introspection of Hooks (#14085)

* Add debug tools package * Add basic implementation * Implement inspection of the current state of hooks using the fiber tree * Support useContext hooks inspection by backtracking from the Fiber I'm not sure this is safe because the return fibers may not be current but close enough and it's fast. We use this to set up the current values of the providers. * rm copypasta * Use lastIndexOf Just in case. I don't know of any scenario where this can happen. * Support ForwardRef * Add test for memo and custom hooks * Support defaultProps resolution

Sebastian Markbåge committed Nov 5, 2018 at 10:02 UTC fd1256a5618d335d5eb563858e0841869b97b1ad
14 files changed +1265 -150
package.json
+1
@@ -44,6 +44,7 @@
44 "create-react-class": "^15.6.3",
45 "cross-env": "^5.1.1",
46 "danger": "^3.0.4",
47 + "error-stack-parser": "^2.0.2",
48 "eslint": "^4.1.0",
49 "eslint-config-fbjs": "^1.1.1",
50 "eslint-plugin-babel": "^3.3.0",
packages/react-debug-tools/README.md new
+7
@@ -0,0 +1,7 @@
1 +# react-debug-tools
2 +
3 +This is an experimental package for debugging React renderers.
4 +
5 +**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**
6 +
7 +**Use it at your own risk.**
packages/react-debug-tools/index.js new
+13
@@ -0,0 +1,13 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +'use strict';
9 +
10 +const ReactDebugTools = require('./src/ReactDebugTools');
11 +
12 +// This is hacky but makes it work with both Rollup and Jest.
13 +module.exports = ReactDebugTools.default || ReactDebugTools;
packages/react-debug-tools/npm/index.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-debug-tools.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-debug-tools.development.js');
7 +}
packages/react-debug-tools/package.json new
+28
@@ -0,0 +1,28 @@
1 +{
2 + "name": "react-debug-tools",
3 + "description": "React package for debugging React trees.",
4 + "version": "0.16.0",
5 + "keywords": [
6 + "react"
7 + ],
8 + "homepage": "https://reactjs.org/",
9 + "bugs": "https://github.com/facebook/react/issues",
10 + "license": "MIT",
11 + "files": [
12 + "LICENSE",
13 + "README.md",
14 + "index.js",
15 + "cjs/"
16 + ],
17 + "main": "index.js",
18 + "repository": "facebook/react",
19 + "engines": {
20 + "node": ">=0.10.0"
21 + },
22 + "peerDependencies": {
23 + "react": "^16.0.0"
24 + },
25 + "dependencies": {
26 + "error-stack-parser": "^2.0.2"
27 + }
28 +}
packages/react-debug-tools/src/ReactDebugHooks.js new
+530
@@ -0,0 +1,530 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
11 +import type {Fiber} from 'react-reconciler/src/ReactFiber';
12 +import type {Hook} from 'react-reconciler/src/ReactFiberHooks';
13 +
14 +import ErrorStackParser from 'error-stack-parser';
15 +import ReactSharedInternals from 'shared/ReactSharedInternals';
16 +import {
17 + FunctionComponent,
18 + SimpleMemoComponent,
19 + ContextProvider,
20 + ForwardRef,
21 +} from 'shared/ReactWorkTags';
22 +
23 +const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
24 +
25 +// Used to track hooks called during a render
26 +
27 +type HookLogEntry = {
28 + primitive: string,
29 + stackError: Error,
30 + value: mixed,
31 +};
32 +
33 +let hookLog: Array<HookLogEntry> = [];
34 +
35 +// Primitives
36 +
37 +type BasicStateAction<S> = (S => S) | S;
38 +
39 +type Dispatch<A> = A => void;
40 +
41 +let primitiveStackCache: null | Map<string, Array<any>> = null;
42 +
43 +function getPrimitiveStackCache(): Map<string, Array<any>> {
44 + // This initializes a cache of all primitive hooks so that the top
45 + // most stack frames added by calling the primitive hook can be removed.
46 + if (primitiveStackCache === null) {
47 + let cache = new Map();
48 + let readHookLog;
49 + try {
50 + // Use all hooks here to add them to the hook log.
51 + Dispatcher.useContext(({_currentValue: null}: any));
52 + Dispatcher.useState(null);
53 + Dispatcher.useReducer((s, a) => s, null);
54 + Dispatcher.useRef(null);
55 + Dispatcher.useMutationEffect(() => {});
56 + Dispatcher.useLayoutEffect(() => {});
57 + Dispatcher.useEffect(() => {});
58 + Dispatcher.useImperativeMethods(undefined, () => null);
59 + Dispatcher.useCallback(() => {});
60 + Dispatcher.useMemo(() => null);
61 + } finally {
62 + readHookLog = hookLog;
63 + hookLog = [];
64 + }
65 + for (let i = 0; i < readHookLog.length; i++) {
66 + let hook = readHookLog[i];
67 + cache.set(hook.primitive, ErrorStackParser.parse(hook.stackError));
68 + }
69 + primitiveStackCache = cache;
70 + }
71 + return primitiveStackCache;
72 +}
73 +
74 +let currentHook: null | Hook = null;
75 +function nextHook(): null | Hook {
76 + let hook = currentHook;
77 + if (hook !== null) {
78 + currentHook = hook.next;
79 + }
80 + return hook;
81 +}
82 +
83 +function readContext<T>(
84 + context: ReactContext<T>,
85 + observedBits: void | number | boolean,
86 +): T {
87 + // For now we don't expose readContext usage in the hooks debugging info.
88 + return context._currentValue;
89 +}
90 +
91 +function useContext<T>(
92 + context: ReactContext<T>,
93 + observedBits: void | number | boolean,
94 +): T {
95 + hookLog.push({
96 + primitive: 'Context',
97 + stackError: new Error(),
98 + value: context._currentValue,
99 + });
100 + return context._currentValue;
101 +}
102 +
103 +function useState<S>(
104 + initialState: (() => S) | S,
105 +): [S, Dispatch<BasicStateAction<S>>] {
106 + let hook = nextHook();
107 + let state: S =
108 + hook !== null
109 + ? hook.memoizedState
110 + : typeof initialState === 'function'
111 + ? initialState()
112 + : initialState;
113 + hookLog.push({primitive: 'State', stackError: new Error(), value: state});
114 + return [state, (action: BasicStateAction<S>) => {}];
115 +}
116 +
117 +function useReducer<S, A>(
118 + reducer: (S, A) => S,
119 + initialState: S,
120 + initialAction: A | void | null,
121 +): [S, Dispatch<A>] {
122 + let hook = nextHook();
123 + let state = hook !== null ? hook.memoizedState : initialState;
124 + hookLog.push({
125 + primitive: 'Reducer',
126 + stackError: new Error(),
127 + value: state,
128 + });
129 + return [state, (action: A) => {}];
130 +}
131 +
132 +function useRef<T>(initialValue: T): {current: T} {
133 + let hook = nextHook();
134 + let ref = hook !== null ? hook.memoizedState : {current: initialValue};
135 + hookLog.push({
136 + primitive: 'Ref',
137 + stackError: new Error(),
138 + value: ref.current,
139 + });
140 + return ref;
141 +}
142 +
143 +function useMutationEffect(
144 + create: () => mixed,
145 + inputs: Array<mixed> | void | null,
146 +): void {
147 + nextHook();
148 + hookLog.push({
149 + primitive: 'MutationEffect',
150 + stackError: new Error(),
151 + value: create,
152 + });
153 +}
154 +
155 +function useLayoutEffect(
156 + create: () => mixed,
157 + inputs: Array<mixed> | void | null,
158 +): void {
159 + nextHook();
160 + hookLog.push({
161 + primitive: 'LayoutEffect',
162 + stackError: new Error(),
163 + value: create,
164 + });
165 +}
166 +
167 +function useEffect(
168 + create: () => mixed,
169 + inputs: Array<mixed> | void | null,
170 +): void {
171 + nextHook();
172 + hookLog.push({primitive: 'Effect', stackError: new Error(), value: create});
173 +}
174 +
175 +function useImperativeMethods<T>(
176 + ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
177 + create: () => T,
178 + inputs: Array<mixed> | void | null,
179 +): void {
180 + nextHook();
181 + // We don't actually store the instance anywhere if there is no ref callback
182 + // and if there is a ref callback it might not store it but if it does we
183 + // have no way of knowing where. So let's only enable introspection of the
184 + // ref itself if it is using the object form.
185 + let instance = undefined;
186 + if (ref !== null && typeof ref === 'object') {
187 + instance = ref.current;
188 + }
189 + hookLog.push({
190 + primitive: 'ImperativeMethods',
191 + stackError: new Error(),
192 + value: instance,
193 + });
194 +}
195 +
196 +function useCallback<T>(callback: T, inputs: Array<mixed> | void | null): T {
197 + let hook = nextHook();
198 + hookLog.push({
199 + primitive: 'Callback',
200 + stackError: new Error(),
201 + value: hook !== null ? hook.memoizedState[0] : callback,
202 + });
203 + return callback;
204 +}
205 +
206 +function useMemo<T>(
207 + nextCreate: () => T,
208 + inputs: Array<mixed> | void | null,
209 +): T {
210 + let hook = nextHook();
211 + let value = hook !== null ? hook.memoizedState[0] : nextCreate();
212 + hookLog.push({primitive: 'Memo', stackError: new Error(), value});
213 + return value;
214 +}
215 +
216 +const Dispatcher = {
217 + readContext,
218 + useCallback,
219 + useContext,
220 + useEffect,
221 + useImperativeMethods,
222 + useLayoutEffect,
223 + useMemo,
224 + useMutationEffect,
225 + useReducer,
226 + useRef,
227 + useState,
228 +};
229 +
230 +// Inspect
231 +
232 +type HooksNode = {
233 + name: string,
234 + value: mixed,
235 + subHooks: Array<HooksNode>,
236 +};
237 +type HooksTree = Array<HooksNode>;
238 +
239 +// Don't assume
240 +//
241 +// We can't assume that stack frames are nth steps away from anything.
242 +// E.g. we can't assume that the root call shares all frames with the stack
243 +// of a hook call. A simple way to demonstrate this is wrapping `new Error()`
244 +// in a wrapper constructor like a polyfill. That'll add an extra frame.
245 +// Similar things can happen with the call to the dispatcher. The top frame
246 +// may not be the primitive. Likewise the primitive can have fewer stack frames
247 +// such as when a call to useState got inlined to use dispatcher.useState.
248 +//
249 +// We also can't assume that the last frame of the root call is the same
250 +// frame as the last frame of the hook call because long stack traces can be
251 +// truncated to a stack trace limit.
252 +
253 +let mostLikelyAncestorIndex = 0;
254 +
255 +function findSharedIndex(hookStack, rootStack, rootIndex) {
256 + let source = rootStack[rootIndex].source;
257 + hookSearch: for (let i = 0; i < hookStack.length; i++) {
258 + if (hookStack[i].source === source) {
259 + // This looks like a match. Validate that the rest of both stack match up.
260 + for (
261 + let a = rootIndex + 1, b = i + 1;
262 + a < rootStack.length && b < hookStack.length;
263 + a++, b++
264 + ) {
265 + if (hookStack[b].source !== rootStack[a].source) {
266 + // If not, give up and try a different match.
267 + continue hookSearch;
268 + }
269 + }
270 + return i;
271 + }
272 + }
273 + return -1;
274 +}
275 +
276 +function findCommonAncestorIndex(rootStack, hookStack) {
277 + let rootIndex = findSharedIndex(
278 + hookStack,
279 + rootStack,
280 + mostLikelyAncestorIndex,
281 + );
282 + if (rootIndex !== -1) {
283 + return rootIndex;
284 + }
285 + // If the most likely one wasn't a hit, try any other frame to see if it is shared.
286 + // If that takes more than 5 frames, something probably went wrong.
287 + for (let i = 0; i < rootStack.length && i < 5; i++) {
288 + rootIndex = findSharedIndex(hookStack, rootStack, i);
289 + if (rootIndex !== -1) {
290 + mostLikelyAncestorIndex = i;
291 + return rootIndex;
292 + }
293 + }
294 + return -1;
295 +}
296 +
297 +function isReactWrapper(functionName, primitiveName) {
298 + if (!functionName) {
299 + return false;
300 + }
301 + let expectedPrimitiveName = 'use' + primitiveName;
302 + if (functionName.length < expectedPrimitiveName.length) {
303 + return false;
304 + }
305 + return (
306 + functionName.lastIndexOf(expectedPrimitiveName) ===
307 + functionName.length - expectedPrimitiveName.length
308 + );
309 +}
310 +
311 +function findPrimitiveIndex(hookStack, hook) {
312 + let stackCache = getPrimitiveStackCache();
313 + let primitiveStack = stackCache.get(hook.primitive);
314 + if (primitiveStack === undefined) {
315 + return -1;
316 + }
317 + for (let i = 0; i < primitiveStack.length && i < hookStack.length; i++) {
318 + if (primitiveStack[i].source !== hookStack[i].source) {
319 + // If the next two frames are functions called `useX` then we assume that they're part of the
320 + // wrappers that the React packager or other packages adds around the dispatcher.
321 + if (
322 + i < hookStack.length - 1 &&
323 + isReactWrapper(hookStack[i].functionName, hook.primitive)
324 + ) {
325 + i++;
326 + }
327 + if (
328 + i < hookStack.length - 1 &&
329 + isReactWrapper(hookStack[i].functionName, hook.primitive)
330 + ) {
331 + i++;
332 + }
333 + return i;
334 + }
335 + }
336 + return -1;
337 +}
338 +
339 +function parseTrimmedStack(rootStack, hook) {
340 + // Get the stack trace between the primitive hook function and
341 + // the root function call. I.e. the stack frames of custom hooks.
342 + let hookStack = ErrorStackParser.parse(hook.stackError);
343 + let rootIndex = findCommonAncestorIndex(rootStack, hookStack);
344 + let primitiveIndex = findPrimitiveIndex(hookStack, hook);
345 + if (
346 + rootIndex === -1 ||
347 + primitiveIndex === -1 ||
348 + rootIndex - primitiveIndex < 2
349 + ) {
350 + // Something went wrong. Give up.
351 + return null;
352 + }
353 + return hookStack.slice(primitiveIndex, rootIndex - 1);
354 +}
355 +
356 +function parseCustomHookName(functionName: void | string): string {
357 + if (!functionName) {
358 + return '';
359 + }
360 + let startIndex = functionName.lastIndexOf('.');
361 + if (startIndex === -1) {
362 + startIndex = 0;
363 + }
364 + if (functionName.substr(startIndex, 3) === 'use') {
365 + startIndex += 3;
366 + }
367 + return functionName.substr(startIndex);
368 +}
369 +
370 +function buildTree(rootStack, readHookLog): HooksTree {
371 + let rootChildren = [];
372 + let prevStack = null;
373 + let levelChildren = rootChildren;
374 + let stackOfChildren = [];
375 + for (let i = 0; i < readHookLog.length; i++) {
376 + let hook = readHookLog[i];
377 + let stack = parseTrimmedStack(rootStack, hook);
378 + if (stack !== null) {
379 + // Note: The indices 0 <= n < length-1 will contain the names.
380 + // The indices 1 <= n < length will contain the source locations.
381 + // That's why we get the name from n - 1 and don't check the source
382 + // of index 0.
383 + let commonSteps = 0;
384 + if (prevStack !== null) {
385 + // Compare the current level's stack to the new stack.
386 + while (commonSteps < stack.length && commonSteps < prevStack.length) {
387 + let stackSource = stack[stack.length - commonSteps - 1].source;
388 + let prevSource = prevStack[prevStack.length - commonSteps - 1].source;
389 + if (stackSource !== prevSource) {
390 + break;
391 + }
392 + commonSteps++;
393 + }
394 + // Pop back the stack as many steps as were not common.
395 + for (let j = prevStack.length - 1; j > commonSteps; j--) {
396 + levelChildren = stackOfChildren.pop();
397 + }
398 + }
399 + // The remaining part of the new stack are custom hooks. Push them
400 + // to the tree.
401 + for (let j = stack.length - commonSteps - 1; j >= 1; j--) {
402 + let children = [];
403 + levelChildren.push({
404 + name: parseCustomHookName(stack[j - 1].functionName),
405 + value: undefined, // TODO: Support custom inspectable values.
406 + subHooks: children,
407 + });
408 + stackOfChildren.push(levelChildren);
409 + levelChildren = children;
410 + }
411 + prevStack = stack;
412 + }
413 + levelChildren.push({
414 + name: hook.primitive,
415 + value: hook.value,
416 + subHooks: [],
417 + });
418 + }
419 + return rootChildren;
420 +}
421 +
422 +export function inspectHooks<Props>(
423 + renderFunction: Props => React$Node,
424 + props: Props,
425 +): HooksTree {
426 + let previousDispatcher = ReactCurrentOwner.currentDispatcher;
427 + let readHookLog;
428 + ReactCurrentOwner.currentDispatcher = Dispatcher;
429 + let ancestorStackError;
430 + try {
431 + ancestorStackError = new Error();
432 + renderFunction(props);
433 + } finally {
434 + readHookLog = hookLog;
435 + hookLog = [];
436 + ReactCurrentOwner.currentDispatcher = previousDispatcher;
437 + }
438 + let rootStack = ErrorStackParser.parse(ancestorStackError);
439 + return buildTree(rootStack, readHookLog);
440 +}
441 +
442 +function setupContexts(contextMap: Map<ReactContext<any>, any>, fiber: Fiber) {
443 + let current = fiber;
444 + while (current) {
445 + if (current.tag === ContextProvider) {
446 + const providerType: ReactProviderType<any> = current.type;
447 + const context: ReactContext<any> = providerType._context;
448 + if (!contextMap.has(context)) {
449 + // Store the current value that we're going to restore later.
450 + contextMap.set(context, context._currentValue);
451 + // Set the inner most provider value on the context.
452 + context._currentValue = current.memoizedProps.value;
453 + }
454 + }
455 + current = current.return;
456 + }
457 +}
458 +
459 +function restoreContexts(contextMap: Map<ReactContext<any>, any>) {
460 + contextMap.forEach((value, context) => (context._currentValue = value));
461 +}
462 +
463 +function inspectHooksOfForwardRef<Props, Ref>(
464 + renderFunction: (Props, Ref) => React$Node,
465 + props: Props,
466 + ref: Ref,
467 +): HooksTree {
468 + let previousDispatcher = ReactCurrentOwner.currentDispatcher;
469 + let readHookLog;
470 + ReactCurrentOwner.currentDispatcher = Dispatcher;
471 + let ancestorStackError;
472 + try {
473 + ancestorStackError = new Error();
474 + renderFunction(props, ref);
475 + } finally {
476 + readHookLog = hookLog;
477 + hookLog = [];
478 + ReactCurrentOwner.currentDispatcher = previousDispatcher;
479 + }
480 + let rootStack = ErrorStackParser.parse(ancestorStackError);
481 + return buildTree(rootStack, readHookLog);
482 +}
483 +
484 +function resolveDefaultProps(Component, baseProps) {
485 + if (Component && Component.defaultProps) {
486 + // Resolve default props. Taken from ReactElement
487 + const props = Object.assign({}, baseProps);
488 + const defaultProps = Component.defaultProps;
489 + for (let propName in defaultProps) {
490 + if (props[propName] === undefined) {
491 + props[propName] = defaultProps[propName];
492 + }
493 + }
494 + return props;
495 + }
496 + return baseProps;
497 +}
498 +
499 +export function inspectHooksOfFiber(fiber: Fiber) {
500 + if (
501 + fiber.tag !== FunctionComponent &&
502 + fiber.tag !== SimpleMemoComponent &&
503 + fiber.tag !== ForwardRef
504 + ) {
505 + throw new Error(
506 + 'Unknown Fiber. Needs to be a function component to inspect hooks.',
507 + );
508 + }
509 + // Warm up the cache so that it doesn't consume the currentHook.
510 + getPrimitiveStackCache();
511 + let type = fiber.type;
512 + let props = fiber.memoizedProps;
513 + if (type !== fiber.elementType) {
514 + props = resolveDefaultProps(type, props);
515 + }
516 + // Set up the current hook so that we can step through and read the
517 + // current state from them.
518 + currentHook = (fiber.memoizedState: Hook);
519 + let contextMap = new Map();
520 + try {
521 + setupContexts(contextMap, fiber);
522 + if (fiber.tag === ForwardRef) {
523 + return inspectHooksOfForwardRef(type.render, props, fiber.ref);
524 + }
525 + return inspectHooks(type, props);
526 + } finally {
527 + currentHook = null;
528 + restoreContexts(contextMap);
529 + }
530 +}
packages/react-debug-tools/src/ReactDebugTools.js new
+12
@@ -0,0 +1,12 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import {inspectHooks, inspectHooksOfFiber} from './ReactDebugHooks';
11 +
12 +export {inspectHooks, inspectHooksOfFiber};
packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.internal.js new
+219
@@ -0,0 +1,219 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + * @jest-environment node
9 + */
10 +
11 +'use strict';
12 +
13 +let React;
14 +let ReactDebugTools;
15 +
16 +describe('ReactHooksInspection', () => {
17 + beforeEach(() => {
18 + jest.resetModules();
19 + let ReactFeatureFlags = require('shared/ReactFeatureFlags');
20 + // TODO: Switch this test to non-internal once the flag is on by default.
21 + ReactFeatureFlags.enableHooks = true;
22 + React = require('react');
23 + ReactDebugTools = require('react-debug-tools');
24 + });
25 +
26 + it('should inspect a simple useState hook', () => {
27 + function Foo(props) {
28 + let [state] = React.useState('hello world');
29 + return <div>{state}</div>;
30 + }
31 + let tree = ReactDebugTools.inspectHooks(Foo, {});
32 + expect(tree).toEqual([
33 + {
34 + name: 'State',
35 + value: 'hello world',
36 + subHooks: [],
37 + },
38 + ]);
39 + });
40 +
41 + it('should inspect a simple custom hook', () => {
42 + function useCustom(value) {
43 + let [state] = React.useState(value);
44 + return state;
45 + }
46 + function Foo(props) {
47 + let value = useCustom('hello world');
48 + return <div>{value}</div>;
49 + }
50 + let tree = ReactDebugTools.inspectHooks(Foo, {});
51 + expect(tree).toEqual([
52 + {
53 + name: 'Custom',
54 + value: undefined,
55 + subHooks: [
56 + {
57 + name: 'State',
58 + value: 'hello world',
59 + subHooks: [],
60 + },
61 + ],
62 + },
63 + ]);
64 + });
65 +
66 + it('should inspect a tree of multiple hooks', () => {
67 + function effect() {}
68 + function useCustom(value) {
69 + let [state] = React.useState(value);
70 + React.useEffect(effect);
71 + return state;
72 + }
73 + function Foo(props) {
74 + let value1 = useCustom('hello');
75 + let value2 = useCustom('world');
76 + return (
77 + <div>
78 + {value1} {value2}
79 + </div>
80 + );
81 + }
82 + let tree = ReactDebugTools.inspectHooks(Foo, {});
83 + expect(tree).toEqual([
84 + {
85 + name: 'Custom',
86 + value: undefined,
87 + subHooks: [
88 + {
89 + name: 'State',
90 + subHooks: [],
91 + value: 'hello',
92 + },
93 + {
94 + name: 'Effect',
95 + subHooks: [],
96 + value: effect,
97 + },
98 + ],
99 + },
100 + {
101 + name: 'Custom',
102 + value: undefined,
103 + subHooks: [
104 + {
105 + name: 'State',
106 + value: 'world',
107 + subHooks: [],
108 + },
109 + {
110 + name: 'Effect',
111 + value: effect,
112 + subHooks: [],
113 + },
114 + ],
115 + },
116 + ]);
117 + });
118 +
119 + it('should inspect a tree of multiple levels of hooks', () => {
120 + function effect() {}
121 + function useCustom(value) {
122 + let [state] = React.useReducer((s, a) => s, value);
123 + React.useEffect(effect);
124 + return state;
125 + }
126 + function useBar(value) {
127 + let result = useCustom(value);
128 + React.useLayoutEffect(effect);
129 + return result;
130 + }
131 + function useBaz(value) {
132 + React.useMutationEffect(effect);
133 + let result = useCustom(value);
134 + return result;
135 + }
136 + function Foo(props) {
137 + let value1 = useBar('hello');
138 + let value2 = useBaz('world');
139 + return (
140 + <div>
141 + {value1} {value2}
142 + </div>
143 + );
144 + }
145 + let tree = ReactDebugTools.inspectHooks(Foo, {});
146 + expect(tree).toEqual([
147 + {
148 + name: 'Bar',
149 + value: undefined,
150 + subHooks: [
151 + {
152 + name: 'Custom',
153 + value: undefined,
154 + subHooks: [
155 + {
156 + name: 'Reducer',
157 + value: 'hello',
158 + subHooks: [],
159 + },
160 + {
161 + name: 'Effect',
162 + value: effect,
163 + subHooks: [],
164 + },
165 + ],
166 + },
167 + {
168 + name: 'LayoutEffect',
169 + value: effect,
170 + subHooks: [],
171 + },
172 + ],
173 + },
174 + {
175 + name: 'Baz',
176 + value: undefined,
177 + subHooks: [
178 + {
179 + name: 'MutationEffect',
180 + value: effect,
181 + subHooks: [],
182 + },
183 + {
184 + name: 'Custom',
185 + subHooks: [
186 + {
187 + name: 'Reducer',
188 + subHooks: [],
189 + value: 'world',
190 + },
191 + {
192 + name: 'Effect',
193 + subHooks: [],
194 + value: effect,
195 + },
196 + ],
197 + value: undefined,
198 + },
199 + ],
200 + },
201 + ]);
202 + });
203 +
204 + it('should inspect the default value using the useContext hook', () => {
205 + let MyContext = React.createContext('default');
206 + function Foo(props) {
207 + let value = React.useContext(MyContext);
208 + return <div>{value}</div>;
209 + }
210 + let tree = ReactDebugTools.inspectHooks(Foo, {});
211 + expect(tree).toEqual([
212 + {
213 + name: 'Context',
214 + value: 'default',
215 + subHooks: [],
216 + },
217 + ]);
218 + });
219 +});
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.internal.js new
+247
@@ -0,0 +1,247 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + * @jest-environment node
9 + */
10 +
11 +'use strict';
12 +
13 +let React;
14 +let ReactTestRenderer;
15 +let ReactDebugTools;
16 +
17 +describe('ReactHooksInspectionIntergration', () => {
18 + beforeEach(() => {
19 + jest.resetModules();
20 + let ReactFeatureFlags = require('shared/ReactFeatureFlags');
21 + // TODO: Switch this test to non-internal once the flag is on by default.
22 + ReactFeatureFlags.enableHooks = true;
23 + React = require('react');
24 + ReactTestRenderer = require('react-test-renderer');
25 + ReactDebugTools = require('react-debug-tools');
26 + });
27 +
28 + it('should inspect the current state of useState hooks', () => {
29 + let useState = React.useState;
30 + function Foo(props) {
31 + let [state1, setState1] = useState('hello');
32 + let [state2, setState2] = useState('world');
33 + return (
34 + <div onMouseDown={setState1} onMouseUp={setState2}>
35 + {state1} {state2}
36 + </div>
37 + );
38 + }
39 + let renderer = ReactTestRenderer.create(<Foo prop="prop" />);
40 +
41 + let childFiber = renderer.root.findByType(Foo)._currentFiber();
42 + let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
43 + expect(tree).toEqual([
44 + {name: 'State', value: 'hello', subHooks: []},
45 + {name: 'State', value: 'world', subHooks: []},
46 + ]);
47 +
48 + let {
49 + onMouseDown: setStateA,
50 + onMouseUp: setStateB,
51 + } = renderer.root.findByType('div').props;
52 +
53 + setStateA('Hi');
54 +
55 + childFiber = renderer.root.findByType(Foo)._currentFiber();
56 + tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
57 +
58 + expect(tree).toEqual([
59 + {name: 'State', value: 'Hi', subHooks: []},
60 + {name: 'State', value: 'world', subHooks: []},
61 + ]);
62 +
63 + setStateB('world!');
64 +
65 + childFiber = renderer.root.findByType(Foo)._currentFiber();
66 + tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
67 +
68 + expect(tree).toEqual([
69 + {name: 'State', value: 'Hi', subHooks: []},
70 + {name: 'State', value: 'world!', subHooks: []},
71 + ]);
72 + });
73 +
74 + it('should inspect the current state of all stateful hooks', () => {
75 + let outsideRef = React.createRef();
76 + function effect() {}
77 + function Foo(props) {
78 + let [state1, setState] = React.useState('a');
79 + let [state2, dispatch] = React.useReducer((s, a) => a.value, 'b');
80 + let ref = React.useRef('c');
81 +
82 + React.useMutationEffect(effect);
83 + React.useLayoutEffect(effect);
84 + React.useEffect(effect);
85 +
86 + React.useImperativeMethods(
87 + outsideRef,
88 + () => {
89 + // Return a function so that jest treats them as non-equal.
90 + return function Instance() {};
91 + },
92 + [],
93 + );
94 +
95 + React.useMemo(() => state1 + state2, [state1]);
96 +
97 + function update() {
98 + setState('A');
99 + dispatch({value: 'B'});
100 + ref.current = 'C';
101 + }
102 + let memoizedUpdate = React.useCallback(update, []);
103 + return (
104 + <div onClick={memoizedUpdate}>
105 + {state1} {state2}
106 + </div>
107 + );
108 + }
109 + let renderer = ReactTestRenderer.create(<Foo prop="prop" />);
110 +
111 + let childFiber = renderer.root.findByType(Foo)._currentFiber();
112 +
113 + let {onClick: updateStates} = renderer.root.findByType('div').props;
114 +
115 + let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
116 + expect(tree).toEqual([
117 + {name: 'State', value: 'a', subHooks: []},
118 + {name: 'Reducer', value: 'b', subHooks: []},
119 + {name: 'Ref', value: 'c', subHooks: []},
120 + {name: 'MutationEffect', value: effect, subHooks: []},
121 + {name: 'LayoutEffect', value: effect, subHooks: []},
122 + {name: 'Effect', value: effect, subHooks: []},
123 + {name: 'ImperativeMethods', value: outsideRef.current, subHooks: []},
124 + {name: 'Memo', value: 'ab', subHooks: []},
125 + {name: 'Callback', value: updateStates, subHooks: []},
126 + ]);
127 +
128 + updateStates();
129 +
130 + childFiber = renderer.root.findByType(Foo)._currentFiber();
131 + tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
132 +
133 + expect(tree).toEqual([
134 + {name: 'State', value: 'A', subHooks: []},
135 + {name: 'Reducer', value: 'B', subHooks: []},
136 + {name: 'Ref', value: 'C', subHooks: []},
137 + {name: 'MutationEffect', value: effect, subHooks: []},
138 + {name: 'LayoutEffect', value: effect, subHooks: []},
139 + {name: 'Effect', value: effect, subHooks: []},
140 + {name: 'ImperativeMethods', value: outsideRef.current, subHooks: []},
141 + {name: 'Memo', value: 'Ab', subHooks: []},
142 + {name: 'Callback', value: updateStates, subHooks: []},
143 + ]);
144 + });
145 +
146 + it('should inspect the value of the current provider in useContext', () => {
147 + let MyContext = React.createContext('default');
148 + function Foo(props) {
149 + let value = React.useContext(MyContext);
150 + return <div>{value}</div>;
151 + }
152 + let renderer = ReactTestRenderer.create(
153 + <MyContext.Provider value="contextual">
154 + <Foo prop="prop" />
155 + </MyContext.Provider>,
156 + );
157 + let childFiber = renderer.root.findByType(Foo)._currentFiber();
158 + let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
159 + expect(tree).toEqual([
160 + {
161 + name: 'Context',
162 + value: 'contextual',
163 + subHooks: [],
164 + },
165 + ]);
166 + });
167 +
168 + it('should inspect forwardRef', () => {
169 + let obj = function() {};
170 + let Foo = React.forwardRef(function(props, ref) {
171 + React.useImperativeMethods(ref, () => obj);
172 + return <div />;
173 + });
174 + let ref = React.createRef();
175 + let renderer = ReactTestRenderer.create(<Foo ref={ref} />);
176 +
177 + let childFiber = renderer.root.findByType(Foo)._currentFiber();
178 + let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
179 + expect(tree).toEqual([
180 + {name: 'ImperativeMethods', value: obj, subHooks: []},
181 + ]);
182 + });
183 +
184 + it('should inspect memo', () => {
185 + function InnerFoo(props) {
186 + let [value] = React.useState('hello');
187 + return <div>{value}</div>;
188 + }
189 + let Foo = React.memo(InnerFoo);
190 + let renderer = ReactTestRenderer.create(<Foo />);
191 + // TODO: Test renderer findByType is broken for memo. Have to search for the inner.
192 + let childFiber = renderer.root.findByType(InnerFoo)._currentFiber();
193 + let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
194 + expect(tree).toEqual([{name: 'State', value: 'hello', subHooks: []}]);
195 + });
196 +
197 + it('should inspect custom hooks', () => {
198 + function useCustom() {
199 + let [value] = React.useState('hello');
200 + return value;
201 + }
202 + function Foo(props) {
203 + let value = useCustom();
204 + return <div>{value}</div>;
205 + }
206 + let renderer = ReactTestRenderer.create(<Foo />);
207 + let childFiber = renderer.root.findByType(Foo)._currentFiber();
208 + let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
209 + expect(tree).toEqual([
210 + {
211 + name: 'Custom',
212 + value: undefined,
213 + subHooks: [{name: 'State', value: 'hello', subHooks: []}],
214 + },
215 + ]);
216 + });
217 +
218 + it('should support defaultProps and lazy', async () => {
219 + let Suspense = React.Suspense;
220 +
221 + function Foo(props) {
222 + let [value] = React.useState(props.defaultValue.substr(0, 3));
223 + return <div>{value}</div>;
224 + }
225 + Foo.defaultProps = {
226 + defaultValue: 'default',
227 + };
228 +
229 + async function fakeImport(result) {
230 + return {default: result};
231 + }
232 +
233 + let LazyFoo = React.lazy(() => fakeImport(Foo));
234 +
235 + let renderer = ReactTestRenderer.create(
236 + <Suspense fallback="Loading...">
237 + <LazyFoo />
238 + </Suspense>,
239 + );
240 +
241 + await LazyFoo;
242 +
243 + let childFiber = renderer.root._currentFiber();
244 + let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
245 + expect(tree).toEqual([{name: 'State', value: 'def', subHooks: []}]);
246 + });
247 +});
packages/react-reconciler/src/ReactFiberHooks.js
+1 -1
@@ -49,7 +49,7 @@ type UpdateQueue<A> = {
49 dispatch: any,
50 };
51
52 -type Hook = {
52 +export type Hook = {
53 memoizedState: any,
54
55 baseState: any,
packages/react-test-renderer/src/ReactTestRenderer.js
+1
@@ -211,6 +211,7 @@ const validWrapperTypes = new Set([
211 HostComponent,
212 ForwardRef,
213 MemoComponent,
214 + SimpleMemoComponent,
215 // Normally skipped, but used when there's more than one root child.
216 HostRoot,
217 ]);
scripts/rollup/bundles.js
+10
@@ -363,6 +363,16 @@ const bundles = [
363 externals: [],
364 },
365
366 + /******* React Debug Tools *******/
367 + {
368 + label: 'react-debug-tools',
369 + bundleTypes: [NODE_DEV, NODE_PROD],
370 + moduleType: ISOMORPHIC,
371 + entry: 'react-debug-tools',
372 + global: 'ReactDebugTools',
373 + externals: [],
374 + },
375 +
376 /******* React Cache (experimental) *******/
377 {
378 label: 'react-cache',
scripts/rollup/results.json
+177 -149
@@ -4,29 +4,29 @@
4 "filename": "react.development.js",
5 "bundleType": "UMD_DEV",
6 "packageName": "react",
7 - "size": 95936,
8 - "gzip": 25258
7 + "size": 98406,
8 + "gzip": 25783
9 },
10 {
11 "filename": "react.production.min.js",
12 "bundleType": "UMD_PROD",
13 "packageName": "react",
14 - "size": 11933,
15 - "gzip": 4716
14 + "size": 11771,
15 + "gzip": 4678
16 },
17 {
18 "filename": "react.development.js",
19 "bundleType": "NODE_DEV",
20 "packageName": "react",
21 - "size": 57749,
22 - "gzip": 15548
21 + "size": 61674,
22 + "gzip": 16606
23 },
24 {
25 "filename": "react.production.min.js",
26 "bundleType": "NODE_PROD",
27 "packageName": "react",
28 - "size": 6215,
29 - "gzip": 2648
28 + "size": 6223,
29 + "gzip": 2655
30 },
31 {
32 "filename": "React-dev.js",
@@ -46,29 +46,29 @@
46 "filename": "react-dom.development.js",
47 "bundleType": "UMD_DEV",
48 "packageName": "react-dom",
49 - "size": 697227,
50 - "gzip": 161844
49 + "size": 720847,
50 + "gzip": 166955
51 },
52 {
53 "filename": "react-dom.production.min.js",
54 "bundleType": "UMD_PROD",
55 "packageName": "react-dom",
56 - "size": 100367,
57 - "gzip": 32791
56 + "size": 105329,
57 + "gzip": 34551
58 },
59 {
60 "filename": "react-dom.development.js",
61 "bundleType": "NODE_DEV",
62 "packageName": "react-dom",
63 - "size": 692458,
64 - "gzip": 160422
63 + "size": 716045,
64 + "gzip": 165551
65 },
66 {
67 "filename": "react-dom.production.min.js",
68 "bundleType": "NODE_PROD",
69 "packageName": "react-dom",
70 - "size": 100389,
71 - "gzip": 32291
70 + "size": 105421,
71 + "gzip": 34088
72 },
73 {
74 "filename": "ReactDOM-dev.js",
@@ -88,8 +88,8 @@
88 "filename": "react-dom-test-utils.development.js",
89 "bundleType": "UMD_DEV",
90 "packageName": "react-dom",
91 - "size": 46915,
92 - "gzip": 12731
91 + "size": 46926,
92 + "gzip": 12739
93 },
94 {
95 "filename": "react-dom-test-utils.production.min.js",
@@ -102,8 +102,8 @@
102 "filename": "react-dom-test-utils.development.js",
103 "bundleType": "NODE_DEV",
104 "packageName": "react-dom",
105 - "size": 46629,
106 - "gzip": 12668
105 + "size": 46640,
106 + "gzip": 12676
107 },
108 {
109 "filename": "react-dom-test-utils.production.min.js",
@@ -165,29 +165,29 @@
165 "filename": "react-dom-server.browser.development.js",
166 "bundleType": "UMD_DEV",
167 "packageName": "react-dom",
168 - "size": 110808,
169 - "gzip": 29290
168 + "size": 120946,
169 + "gzip": 31984
170 },
171 {
172 "filename": "react-dom-server.browser.production.min.js",
173 "bundleType": "UMD_PROD",
174 "packageName": "react-dom",
175 - "size": 16261,
176 - "gzip": 6183
175 + "size": 18147,
176 + "gzip": 6947
177 },
178 {
179 "filename": "react-dom-server.browser.development.js",
180 "bundleType": "NODE_DEV",
181 "packageName": "react-dom",
182 - "size": 106846,
183 - "gzip": 28277
182 + "size": 116984,
183 + "gzip": 31008
184 },
185 {
186 "filename": "react-dom-server.browser.production.min.js",
187 "bundleType": "NODE_PROD",
188 "packageName": "react-dom",
189 - "size": 16156,
190 - "gzip": 6171
189 + "size": 18045,
190 + "gzip": 6934
191 },
192 {
193 "filename": "ReactDOMServer-dev.js",
@@ -207,43 +207,43 @@
207 "filename": "react-dom-server.node.development.js",
208 "bundleType": "NODE_DEV",
209 "packageName": "react-dom",
210 - "size": 108814,
211 - "gzip": 28843
210 + "size": 118952,
211 + "gzip": 31557
212 },
213 {
214 "filename": "react-dom-server.node.production.min.js",
215 "bundleType": "NODE_PROD",
216 "packageName": "react-dom",
217 - "size": 16981,
218 - "gzip": 6481
217 + "size": 18870,
218 + "gzip": 7243
219 },
220 {
221 "filename": "react-art.development.js",
222 "bundleType": "UMD_DEV",
223 "packageName": "react-art",
224 - "size": 479568,
225 - "gzip": 106369
224 + "size": 502853,
225 + "gzip": 111403
226 },
227 {
228 "filename": "react-art.production.min.js",
229 "bundleType": "UMD_PROD",
230 "packageName": "react-art",
231 - "size": 92149,
232 - "gzip": 28279
231 + "size": 96973,
232 + "gzip": 30018
233 },
234 {
235 "filename": "react-art.development.js",
236 "bundleType": "NODE_DEV",
237 "packageName": "react-art",
238 - "size": 409729,
239 - "gzip": 88882
238 + "size": 433008,
239 + "gzip": 93974
240 },
241 {
242 "filename": "react-art.production.min.js",
243 "bundleType": "NODE_PROD",
244 "packageName": "react-art",
245 - "size": 56279,
246 - "gzip": 17346
245 + "size": 61174,
246 + "gzip": 19077
247 },
248 {
249 "filename": "ReactART-dev.js",
@@ -291,29 +291,29 @@
291 "filename": "react-test-renderer.development.js",
292 "bundleType": "UMD_DEV",
293 "packageName": "react-test-renderer",
294 - "size": 422767,
295 - "gzip": 91691
294 + "size": 445955,
295 + "gzip": 96705
296 },
297 {
298 "filename": "react-test-renderer.production.min.js",
299 "bundleType": "UMD_PROD",
300 "packageName": "react-test-renderer",
301 - "size": 57493,
302 - "gzip": 17706
301 + "size": 62392,
302 + "gzip": 19461
303 },
304 {
305 "filename": "react-test-renderer.development.js",
306 "bundleType": "NODE_DEV",
307 "packageName": "react-test-renderer",
308 - "size": 418235,
309 - "gzip": 90567
308 + "size": 441056,
309 + "gzip": 95534
310 },
311 {
312 "filename": "react-test-renderer.production.min.js",
313 "bundleType": "NODE_PROD",
314 "packageName": "react-test-renderer",
315 - "size": 57208,
316 - "gzip": 17513
315 + "size": 62067,
316 + "gzip": 19233
317 },
318 {
319 "filename": "ReactTestRenderer-dev.js",
@@ -326,21 +326,21 @@
326 "filename": "react-test-renderer-shallow.development.js",
327 "bundleType": "UMD_DEV",
328 "packageName": "react-test-renderer",
329 - "size": 27270,
330 - "gzip": 7324
329 + "size": 27383,
330 + "gzip": 7340
331 },
332 {
333 "filename": "react-test-renderer-shallow.production.min.js",
334 "bundleType": "UMD_PROD",
335 "packageName": "react-test-renderer",
336 - "size": 7394,
337 - "gzip": 2412
336 + "size": 7442,
337 + "gzip": 2425
338 },
339 {
340 "filename": "react-test-renderer-shallow.development.js",
341 "bundleType": "NODE_DEV",
342 "packageName": "react-test-renderer",
343 - "size": 21638,
343 + "size": 21639,
344 "gzip": 5871
345 },
346 {
@@ -361,50 +361,50 @@
361 "filename": "react-noop-renderer.development.js",
362 "bundleType": "NODE_DEV",
363 "packageName": "react-noop-renderer",
364 - "size": 28548,
365 - "gzip": 6219
364 + "size": 28829,
365 + "gzip": 6286
366 },
367 {
368 "filename": "react-noop-renderer.production.min.js",
369 "bundleType": "NODE_PROD",
370 "packageName": "react-noop-renderer",
371 - "size": 10802,
372 - "gzip": 3587
371 + "size": 10889,
372 + "gzip": 3632
373 },
374 {
375 "filename": "react-reconciler.development.js",
376 "bundleType": "NODE_DEV",
377 "packageName": "react-reconciler",
378 - "size": 407389,
379 - "gzip": 87287
378 + "size": 430802,
379 + "gzip": 92370
380 },
381 {
382 "filename": "react-reconciler.production.min.js",
383 "bundleType": "NODE_PROD",
384 "packageName": "react-reconciler",
385 - "size": 57356,
386 - "gzip": 17175
385 + "size": 62418,
386 + "gzip": 18868
387 },
388 {
389 "filename": "react-reconciler-persistent.development.js",
390 "bundleType": "NODE_DEV",
391 "packageName": "react-reconciler",
392 - "size": 405959,
393 - "gzip": 86717
392 + "size": 429212,
393 + "gzip": 91734
394 },
395 {
396 "filename": "react-reconciler-persistent.production.min.js",
397 "bundleType": "NODE_PROD",
398 "packageName": "react-reconciler",
399 - "size": 57367,
400 - "gzip": 17180
399 + "size": 62429,
400 + "gzip": 18874
401 },
402 {
403 "filename": "react-reconciler-reflection.development.js",
404 "bundleType": "NODE_DEV",
405 "packageName": "react-reconciler",
406 - "size": 16735,
407 - "gzip": 5071
406 + "size": 16747,
407 + "gzip": 5081
408 },
409 {
410 "filename": "react-reconciler-reflection.production.min.js",
@@ -431,29 +431,29 @@
431 "filename": "react-is.development.js",
432 "bundleType": "UMD_DEV",
433 "packageName": "react-is",
434 - "size": 7539,
435 - "gzip": 2364
434 + "size": 7691,
435 + "gzip": 2393
436 },
437 {
438 "filename": "react-is.production.min.js",
439 "bundleType": "UMD_PROD",
440 "packageName": "react-is",
441 - "size": 2113,
442 - "gzip": 836
441 + "size": 2171,
442 + "gzip": 854
443 },
444 {
445 "filename": "react-is.development.js",
446 "bundleType": "NODE_DEV",
447 "packageName": "react-is",
448 - "size": 7350,
449 - "gzip": 2313
448 + "size": 7502,
449 + "gzip": 2344
450 },
451 {
452 "filename": "react-is.production.min.js",
453 "bundleType": "NODE_PROD",
454 "packageName": "react-is",
455 - "size": 2074,
456 - "gzip": 775
455 + "size": 2132,
456 + "gzip": 793
457 },
458 {
459 "filename": "ReactIs-dev.js",
@@ -501,36 +501,36 @@
501 "filename": "React-dev.js",
502 "bundleType": "FB_WWW_DEV",
503 "packageName": "react",
504 - "size": 54009,
505 - "gzip": 14673
504 + "size": 58044,
505 + "gzip": 15664
506 },
507 {
508 "filename": "React-prod.js",
509 "bundleType": "FB_WWW_PROD",
510 "packageName": "react",
511 - "size": 14256,
511 + "size": 14026,
512 "gzip": 3899
513 },
514 {
515 "filename": "ReactDOM-dev.js",
516 "bundleType": "FB_WWW_DEV",
517 "packageName": "react-dom",
518 - "size": 711278,
519 - "gzip": 161142
518 + "size": 734797,
519 + "gzip": 166398
520 },
521 {
522 "filename": "ReactDOM-prod.js",
523 "bundleType": "FB_WWW_PROD",
524 "packageName": "react-dom",
525 - "size": 303046,
526 - "gzip": 55626
525 + "size": 318299,
526 + "gzip": 58930
527 },
528 {
529 "filename": "ReactTestUtils-dev.js",
530 "bundleType": "FB_WWW_DEV",
531 "packageName": "react-dom",
532 - "size": 42309,
533 - "gzip": 11422
532 + "size": 42319,
533 + "gzip": 11429
534 },
535 {
536 "filename": "ReactDOMUnstableNativeDependencies-dev.js",
@@ -550,113 +550,113 @@
550 "filename": "ReactDOMServer-dev.js",
551 "bundleType": "FB_WWW_DEV",
552 "packageName": "react-dom",
553 - "size": 106413,
554 - "gzip": 27558
553 + "size": 116990,
554 + "gzip": 30470
555 },
556 {
557 "filename": "ReactDOMServer-prod.js",
558 "bundleType": "FB_WWW_PROD",
559 "packageName": "react-dom",
560 - "size": 35825,
561 - "gzip": 8548
560 + "size": 41749,
561 + "gzip": 9884
562 },
563 {
564 "filename": "ReactART-dev.js",
565 "bundleType": "FB_WWW_DEV",
566 "packageName": "react-art",
567 - "size": 414596,
568 - "gzip": 87310
567 + "size": 437769,
568 + "gzip": 92475
569 },
570 {
571 "filename": "ReactART-prod.js",
572 "bundleType": "FB_WWW_PROD",
573 "packageName": "react-art",
574 - "size": 173284,
575 - "gzip": 29264
574 + "size": 188325,
575 + "gzip": 32413
576 },
577 {
578 "filename": "ReactNativeRenderer-dev.js",
579 "bundleType": "RN_FB_DEV",
580 "packageName": "react-native-renderer",
581 - "size": 544253,
582 - "gzip": 118996
581 + "size": 567081,
582 + "gzip": 124053
583 },
584 {
585 "filename": "ReactNativeRenderer-prod.js",
586 "bundleType": "RN_FB_PROD",
587 "packageName": "react-native-renderer",
588 - "size": 229349,
589 - "gzip": 39944
588 + "size": 244223,
589 + "gzip": 43036
590 },
591 {
592 "filename": "ReactNativeRenderer-dev.js",
593 "bundleType": "RN_OSS_DEV",
594 "packageName": "react-native-renderer",
595 - "size": 543957,
596 - "gzip": 118920
595 + "size": 566746,
596 + "gzip": 123945
597 },
598 {
599 "filename": "ReactNativeRenderer-prod.js",
600 "bundleType": "RN_OSS_PROD",
601 "packageName": "react-native-renderer",
602 - "size": 229363,
603 - "gzip": 39944
602 + "size": 244237,
603 + "gzip": 43035
604 },
605 {
606 "filename": "ReactFabric-dev.js",
607 "bundleType": "RN_FB_DEV",
608 "packageName": "react-native-renderer",
609 - "size": 534243,
610 - "gzip": 116534
609 + "size": 557032,
610 + "gzip": 121529
611 },
612 {
613 "filename": "ReactFabric-prod.js",
614 "bundleType": "RN_FB_PROD",
615 "packageName": "react-native-renderer",
616 - "size": 223762,
617 - "gzip": 38608
616 + "size": 238983,
617 + "gzip": 41707
618 },
619 {
620 "filename": "ReactFabric-dev.js",
621 "bundleType": "RN_OSS_DEV",
622 "packageName": "react-native-renderer",
623 - "size": 534278,
624 - "gzip": 116548
623 + "size": 557067,
624 + "gzip": 121541
625 },
626 {
627 "filename": "ReactFabric-prod.js",
628 "bundleType": "RN_OSS_PROD",
629 "packageName": "react-native-renderer",
630 - "size": 223798,
631 - "gzip": 38624
630 + "size": 239019,
631 + "gzip": 41721
632 },
633 {
634 "filename": "ReactTestRenderer-dev.js",
635 "bundleType": "FB_WWW_DEV",
636 "packageName": "react-test-renderer",
637 - "size": 423481,
638 - "gzip": 89361
637 + "size": 446010,
638 + "gzip": 94386
639 },
640 {
641 "filename": "ReactShallowRenderer-dev.js",
642 "bundleType": "FB_WWW_DEV",
643 "packageName": "react-test-renderer",
644 - "size": 18563,
645 - "gzip": 4857
644 + "size": 18564,
645 + "gzip": 4859
646 },
647 {
648 "filename": "ReactIs-dev.js",
649 "bundleType": "FB_WWW_DEV",
650 "packageName": "react-is",
651 - "size": 5721,
652 - "gzip": 1588
651 + "size": 5873,
652 + "gzip": 1621
653 },
654 {
655 "filename": "ReactIs-prod.js",
656 "bundleType": "FB_WWW_PROD",
657 "packageName": "react-is",
658 - "size": 4227,
659 - "gzip": 1105
658 + "size": 4382,
659 + "gzip": 1135
660 },
661 {
662 "filename": "scheduler.development.js",
@@ -676,15 +676,15 @@
676 "filename": "scheduler.development.js",
677 "bundleType": "NODE_DEV",
678 "packageName": "scheduler",
679 - "size": 22710,
680 - "gzip": 6136
679 + "size": 22073,
680 + "gzip": 5976
681 },
682 {
683 "filename": "scheduler.production.min.js",
684 "bundleType": "NODE_PROD",
685 "packageName": "scheduler",
686 - "size": 4907,
687 - "gzip": 1905
686 + "size": 4755,
687 + "gzip": 1865
688 },
689 {
690 "filename": "SimpleCacheProvider-dev.js",
@@ -704,50 +704,50 @@
704 "filename": "react-noop-renderer-persistent.development.js",
705 "bundleType": "NODE_DEV",
706 "packageName": "react-noop-renderer",
707 - "size": 28667,
708 - "gzip": 6232
707 + "size": 28948,
708 + "gzip": 6299
709 },
710 {
711 "filename": "react-noop-renderer-persistent.production.min.js",
712 "bundleType": "NODE_PROD",
713 "packageName": "react-noop-renderer",
714 - "size": 10824,
715 - "gzip": 3593
714 + "size": 10911,
715 + "gzip": 3639
716 },
717 {
718 "filename": "react-dom.profiling.min.js",
719 "bundleType": "NODE_PROFILING",
720 "packageName": "react-dom",
721 - "size": 102854,
722 - "gzip": 32671
721 + "size": 107853,
722 + "gzip": 34402
723 },
724 {
725 "filename": "ReactNativeRenderer-profiling.js",
726 "bundleType": "RN_OSS_PROFILING",
727 "packageName": "react-native-renderer",
728 - "size": 235178,
729 - "gzip": 41307
728 + "size": 250067,
729 + "gzip": 44259
730 },
731 {
732 "filename": "ReactFabric-profiling.js",
733 "bundleType": "RN_OSS_PROFILING",
734 "packageName": "react-native-renderer",
735 - "size": 228734,
736 - "gzip": 39942
735 + "size": 243670,
736 + "gzip": 43000
737 },
738 {
739 "filename": "Scheduler-dev.js",
740 "bundleType": "FB_WWW_DEV",
741 "packageName": "scheduler",
742 - "size": 22987,
743 - "gzip": 6180
742 + "size": 22314,
743 + "gzip": 6027
744 },
745 {
746 "filename": "Scheduler-prod.js",
747 "bundleType": "FB_WWW_PROD",
748 "packageName": "scheduler",
749 - "size": 13870,
750 - "gzip": 2990
749 + "size": 13375,
750 + "gzip": 2927
751 },
752 {
753 "filename": "react.profiling.min.js",
@@ -760,50 +760,50 @@
760 "filename": "React-profiling.js",
761 "bundleType": "FB_WWW_PROFILING",
762 "packageName": "react",
763 - "size": 14256,
763 + "size": 14026,
764 "gzip": 3899
765 },
766 {
767 "filename": "ReactDOM-profiling.js",
768 "bundleType": "FB_WWW_PROFILING",
769 "packageName": "react-dom",
770 - "size": 307635,
771 - "gzip": 56727
770 + "size": 322659,
771 + "gzip": 59833
772 },
773 {
774 "filename": "ReactNativeRenderer-profiling.js",
775 "bundleType": "RN_FB_PROFILING",
776 "packageName": "react-native-renderer",
777 - "size": 235159,
778 - "gzip": 41309
777 + "size": 250048,
778 + "gzip": 44263
779 },
780 {
781 "filename": "ReactFabric-profiling.js",
782 "bundleType": "RN_FB_PROFILING",
783 "packageName": "react-native-renderer",
784 - "size": 228693,
785 - "gzip": 39926
784 + "size": 243629,
785 + "gzip": 42984
786 },
787 {
788 "filename": "react.profiling.min.js",
789 "bundleType": "UMD_PROFILING",
790 "packageName": "react",
791 - "size": 14140,
792 - "gzip": 5250
791 + "size": 13977,
792 + "gzip": 5211
793 },
794 {
795 "filename": "react-dom.profiling.min.js",
796 "bundleType": "UMD_PROFILING",
797 "packageName": "react-dom",
798 - "size": 102786,
799 - "gzip": 33199
798 + "size": 107720,
799 + "gzip": 34916
800 },
801 {
802 "filename": "scheduler-tracing.development.js",
803 "bundleType": "NODE_DEV",
804 "packageName": "scheduler",
805 - "size": 10319,
806 - "gzip": 2326
805 + "size": 10480,
806 + "gzip": 2403
807 },
808 {
809 "filename": "scheduler-tracing.production.min.js",
@@ -823,8 +823,8 @@
823 "filename": "SchedulerTracing-dev.js",
824 "bundleType": "FB_WWW_DEV",
825 "packageName": "scheduler",
826 - "size": 10105,
827 - "gzip": 2117
826 + "size": 10467,
827 + "gzip": 2295
828 },
829 {
830 "filename": "SchedulerTracing-prod.js",
@@ -909,6 +909,34 @@
909 "packageName": "jest-react",
910 "size": 5201,
911 "gzip": 1623
912 + },
913 + {
914 + "filename": "react-debug-tools.development.js",
915 + "bundleType": "NODE_DEV",
916 + "packageName": "react-debug-tools",
917 + "size": 14080,
918 + "gzip": 4317
919 + },
920 + {
921 + "filename": "react-debug-tools.production.min.js",
922 + "bundleType": "NODE_PROD",
923 + "packageName": "react-debug-tools",
924 + "size": 4112,
925 + "gzip": 1643
926 + },
927 + {
928 + "filename": "eslint-plugin-react-hooks.development.js",
929 + "bundleType": "NODE_DEV",
930 + "packageName": "eslint-plugin-react-hooks",
931 + "size": 25596,
932 + "gzip": 5886
933 + },
934 + {
935 + "filename": "eslint-plugin-react-hooks.production.min.js",
936 + "bundleType": "NODE_PROD",
937 + "packageName": "eslint-plugin-react-hooks",
938 + "size": 4943,
939 + "gzip": 1815
940 }
941 ]
942 }
\ No newline at end of file
yarn.lock
+12
@@ -1584,6 +1584,13 @@ error-ex@^1.2.0:
1584 dependencies:
1585 is-arrayish "^0.2.1"
1586
1587 +error-stack-parser@^2.0.2:
1588 + version "2.0.2"
1589 + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.2.tgz#4ae8dbaa2bf90a8b450707b9149dcabca135520d"
1590 + integrity sha512-E1fPutRDdIj/hohG0UpT5mayXNCxXP9d+snxFsPU9X0XgccOumKraa3juDMwTUyi7+Bu5+mCGagjg4IYeNbOdw==
1591 + dependencies:
1592 + stackframe "^1.0.4"
1593 +
1594 es-abstract@^1.5.1:
1595 version "1.12.0"
1596 resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
@@ -4638,6 +4645,11 @@ stack-utils@^1.0.1:
4645 version "1.0.1"
4646 resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
4647
4648 +stackframe@^1.0.4:
4649 + version "1.0.4"
4650 + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.0.4.tgz#357b24a992f9427cba6b545d96a14ed2cbca187b"
4651 + integrity sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw==
4652 +
4653 static-extend@^0.1.1:
4654 version "0.1.2"
4655 resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"