diff --git a/packages/react-devtools-core/webpack.backend.js b/packages/react-devtools-core/webpack.backend.js
index ecd066af867a6..1461545d29d57 100644
--- a/packages/react-devtools-core/webpack.backend.js
+++ b/packages/react-devtools-core/webpack.backend.js
@@ -43,6 +43,8 @@ module.exports = {
plugins: [
new DefinePlugin({
__DEV__: true,
+ __PROFILE__: false,
+ __EXPERIMENTAL__: true,
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
}),
diff --git a/packages/react-devtools-core/webpack.standalone.js b/packages/react-devtools-core/webpack.standalone.js
index a1882f20cf8ac..61c1d962372b5 100644
--- a/packages/react-devtools-core/webpack.standalone.js
+++ b/packages/react-devtools-core/webpack.standalone.js
@@ -48,6 +48,8 @@ module.exports = {
plugins: [
new DefinePlugin({
__DEV__: false,
+ __PROFILE__: false,
+ __EXPERIMENTAL__: true,
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
'process.env.NODE_ENV': `"${NODE_ENV}"`,
diff --git a/packages/react-devtools-extensions/webpack.backend.js b/packages/react-devtools-extensions/webpack.backend.js
index d7de389f8ce08..1f649dbb6db58 100644
--- a/packages/react-devtools-extensions/webpack.backend.js
+++ b/packages/react-devtools-extensions/webpack.backend.js
@@ -38,6 +38,8 @@ module.exports = {
plugins: [
new DefinePlugin({
__DEV__: true,
+ __PROFILE__: false,
+ __EXPERIMENTAL__: true,
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
}),
diff --git a/packages/react-devtools-extensions/webpack.config.js b/packages/react-devtools-extensions/webpack.config.js
index e6aa905e6c0fa..b22ea605699e7 100644
--- a/packages/react-devtools-extensions/webpack.config.js
+++ b/packages/react-devtools-extensions/webpack.config.js
@@ -43,6 +43,8 @@ module.exports = {
plugins: [
new DefinePlugin({
__DEV__: false,
+ __PROFILE__: false,
+ __EXPERIMENTAL__: true,
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
'process.env.NODE_ENV': `"${NODE_ENV}"`,
diff --git a/packages/react-devtools-inline/webpack.config.js b/packages/react-devtools-inline/webpack.config.js
index 7e24e54a0558f..3f0677f80b793 100644
--- a/packages/react-devtools-inline/webpack.config.js
+++ b/packages/react-devtools-inline/webpack.config.js
@@ -39,6 +39,8 @@ module.exports = {
plugins: [
new DefinePlugin({
__DEV__,
+ __PROFILE__: false,
+ __EXPERIMENTAL__: true,
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
'process.env.NODE_ENV': `"${NODE_ENV}"`,
diff --git a/packages/react-devtools-shared/src/__tests__/console-test.js b/packages/react-devtools-shared/src/__tests__/console-test.js
index 6d400164a8356..74e9fed9ff8ef 100644
--- a/packages/react-devtools-shared/src/__tests__/console-test.js
+++ b/packages/react-devtools-shared/src/__tests__/console-test.js
@@ -114,7 +114,7 @@ describe('console', () => {
});
it('should not append multiple stacks', () => {
- const Child = () => {
+ const Child = ({children}) => {
fakeConsole.warn('warn\n in Child (at fake.js:123)');
fakeConsole.error('error', '\n in Child (at fake.js:123)');
return null;
@@ -135,12 +135,12 @@ describe('console', () => {
it('should append component stacks to errors and warnings logged during render', () => {
const Intermediate = ({children}) => children;
- const Parent = () => (
+ const Parent = ({children}) => (
);
- const Child = () => {
+ const Child = ({children}) => {
fakeConsole.error('error');
fakeConsole.log('log');
fakeConsole.warn('warn');
@@ -156,24 +156,24 @@ describe('console', () => {
expect(mockWarn.mock.calls[0]).toHaveLength(2);
expect(mockWarn.mock.calls[0][0]).toBe('warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError).toHaveBeenCalledTimes(1);
expect(mockError.mock.calls[0]).toHaveLength(2);
expect(mockError.mock.calls[0][0]).toBe('error');
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
});
it('should append component stacks to errors and warnings logged from effects', () => {
const Intermediate = ({children}) => children;
- const Parent = () => (
+ const Parent = ({children}) => (
);
- const Child = () => {
+ const Child = ({children}) => {
React.useLayoutEffect(() => {
fakeConsole.error('active error');
fakeConsole.log('active log');
@@ -198,29 +198,29 @@ describe('console', () => {
expect(mockWarn.mock.calls[0]).toHaveLength(2);
expect(mockWarn.mock.calls[0][0]).toBe('active warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockWarn.mock.calls[1]).toHaveLength(2);
expect(mockWarn.mock.calls[1][0]).toBe('passive warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError).toHaveBeenCalledTimes(2);
expect(mockError.mock.calls[0]).toHaveLength(2);
expect(mockError.mock.calls[0][0]).toBe('active error');
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError.mock.calls[1]).toHaveLength(2);
expect(mockError.mock.calls[1][0]).toBe('passive error');
expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
});
it('should append component stacks to errors and warnings logged from commit hooks', () => {
const Intermediate = ({children}) => children;
- const Parent = () => (
+ const Parent = ({children}) => (
@@ -254,29 +254,29 @@ describe('console', () => {
expect(mockWarn.mock.calls[0]).toHaveLength(2);
expect(mockWarn.mock.calls[0][0]).toBe('didMount warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockWarn.mock.calls[1]).toHaveLength(2);
expect(mockWarn.mock.calls[1][0]).toBe('didUpdate warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError).toHaveBeenCalledTimes(2);
expect(mockError.mock.calls[0]).toHaveLength(2);
expect(mockError.mock.calls[0][0]).toBe('didMount error');
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError.mock.calls[1]).toHaveLength(2);
expect(mockError.mock.calls[1][0]).toBe('didUpdate error');
expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
});
it('should append component stacks to errors and warnings logged from gDSFP', () => {
const Intermediate = ({children}) => children;
- const Parent = () => (
+ const Parent = ({children}) => (
@@ -303,18 +303,18 @@ describe('console', () => {
expect(mockWarn.mock.calls[0]).toHaveLength(2);
expect(mockWarn.mock.calls[0][0]).toBe('warn');
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
expect(mockError).toHaveBeenCalledTimes(1);
expect(mockError.mock.calls[0]).toHaveLength(2);
expect(mockError.mock.calls[0][0]).toBe('error');
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
- '\n in Child (at **)\n in Parent (at **)',
+ '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
);
});
it('should append stacks after being uninstalled and reinstalled', () => {
- const Child = () => {
+ const Child = ({children}) => {
fakeConsole.warn('warn');
fakeConsole.error('error');
return null;
diff --git a/packages/react-devtools-shared/src/backend/DevToolsComponentStackFrame.js b/packages/react-devtools-shared/src/backend/DevToolsComponentStackFrame.js
new file mode 100644
index 0000000000000..f4aab13ece0c5
--- /dev/null
+++ b/packages/react-devtools-shared/src/backend/DevToolsComponentStackFrame.js
@@ -0,0 +1,298 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+// This is a DevTools fork of ReactComponentStackFrame.
+// This fork enables DevTools to use the same "native" component stack format,
+// while still maintaining support for multiple renderer versions
+// (which use different values for ReactTypeOfWork).
+
+import type {Source} from 'shared/ReactElementType';
+import type {LazyComponent} from 'react/src/ReactLazy';
+import type {CurrentDispatcherRef} from './types';
+
+import {
+ BLOCK_NUMBER,
+ BLOCK_SYMBOL_STRING,
+ FORWARD_REF_NUMBER,
+ FORWARD_REF_SYMBOL_STRING,
+ LAZY_NUMBER,
+ LAZY_SYMBOL_STRING,
+ MEMO_NUMBER,
+ MEMO_SYMBOL_STRING,
+ SUSPENSE_NUMBER,
+ SUSPENSE_SYMBOL_STRING,
+ SUSPENSE_LIST_NUMBER,
+ SUSPENSE_LIST_SYMBOL_STRING,
+} from './ReactSymbols';
+
+// These methods are safe to import from shared;
+// there is no React-specific logic here.
+import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
+
+let prefix;
+export function describeBuiltInComponentFrame(
+ name: string,
+ source: void | null | Source,
+ ownerFn: void | null | Function,
+): string {
+ if (prefix === undefined) {
+ // Extract the VM specific prefix used by each line.
+ try {
+ throw Error();
+ } catch (x) {
+ const match = x.stack.trim().match(/\n( *(at )?)/);
+ prefix = (match && match[1]) || '';
+ }
+ }
+ // We use the prefix to ensure our stacks line up with native stack frames.
+ return '\n' + prefix + name;
+}
+
+let reentry = false;
+let componentFrameCache;
+if (__DEV__) {
+ const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
+ componentFrameCache = new PossiblyWeakMap();
+}
+
+export function describeNativeComponentFrame(
+ fn: Function,
+ construct: boolean,
+ currentDispatcherRef: CurrentDispatcherRef,
+): string {
+ // If something asked for a stack inside a fake render, it should get ignored.
+ if (!fn || reentry) {
+ return '';
+ }
+
+ if (__DEV__) {
+ const frame = componentFrameCache.get(fn);
+ if (frame !== undefined) {
+ return frame;
+ }
+ }
+
+ let control;
+
+ reentry = true;
+ let previousDispatcher;
+ if (__DEV__) {
+ previousDispatcher = currentDispatcherRef.current;
+ // Set the dispatcher in DEV because this might be call in the render function
+ // for warnings.
+ currentDispatcherRef.current = null;
+ disableLogs();
+ }
+ try {
+ // This should throw.
+ if (construct) {
+ // Something should be setting the props in the constructor.
+ const Fake = function() {
+ throw Error();
+ };
+ // $FlowFixMe
+ Object.defineProperty(Fake.prototype, 'props', {
+ set: function() {
+ // We use a throwing setter instead of frozen or non-writable props
+ // because that won't throw in a non-strict mode function.
+ throw Error();
+ },
+ });
+ if (typeof Reflect === 'object' && Reflect.construct) {
+ // We construct a different control for this case to include any extra
+ // frames added by the construct call.
+ try {
+ Reflect.construct(Fake, []);
+ } catch (x) {
+ control = x;
+ }
+ Reflect.construct(fn, [], Fake);
+ } else {
+ try {
+ Fake.call();
+ } catch (x) {
+ control = x;
+ }
+ fn.call(Fake.prototype);
+ }
+ } else {
+ try {
+ throw Error();
+ } catch (x) {
+ control = x;
+ }
+ fn();
+ }
+ } catch (sample) {
+ // This is inlined manually because closure doesn't do it for us.
+ if (sample && control && typeof sample.stack === 'string') {
+ // This extracts the first frame from the sample that isn't also in the control.
+ // Skipping one frame that we assume is the frame that calls the two.
+ const sampleLines = sample.stack.split('\n');
+ const controlLines = control.stack.split('\n');
+ let s = sampleLines.length - 1;
+ let c = controlLines.length - 1;
+ while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
+ // We expect at least one stack frame to be shared.
+ // Typically this will be the root most one. However, stack frames may be
+ // cut off due to maximum stack limits. In this case, one maybe cut off
+ // earlier than the other. We assume that the sample is longer or the same
+ // and there for cut off earlier. So we should find the root most frame in
+ // the sample somewhere in the control.
+ c--;
+ }
+ for (; s >= 1 && c >= 0; s--, c--) {
+ // Next we find the first one that isn't the same which should be the
+ // frame that called our sample function and the control.
+ if (sampleLines[s] !== controlLines[c]) {
+ // In V8, the first line is describing the message but other VMs don't.
+ // If we're about to return the first line, and the control is also on the same
+ // line, that's a pretty good indicator that our sample threw at same line as
+ // the control. I.e. before we entered the sample frame. So we ignore this result.
+ // This can happen if you passed a class to function component, or non-function.
+ if (s !== 1 || c !== 1) {
+ do {
+ s--;
+ c--;
+ // We may still have similar intermediate frames from the construct call.
+ // The next one that isn't the same should be our match though.
+ if (c < 0 || sampleLines[s] !== controlLines[c]) {
+ // V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
+ const frame = '\n' + sampleLines[s].replace(' at new ', ' at ');
+ if (__DEV__) {
+ if (typeof fn === 'function') {
+ componentFrameCache.set(fn, frame);
+ }
+ }
+ // Return the line we found.
+ return frame;
+ }
+ } while (s >= 1 && c >= 0);
+ }
+ break;
+ }
+ }
+ }
+ } finally {
+ reentry = false;
+ if (__DEV__) {
+ currentDispatcherRef.current = previousDispatcher;
+ reenableLogs();
+ }
+ }
+ // Fallback to just using the name if we couldn't make it throw.
+ const name = fn ? fn.displayName || fn.name : '';
+ const syntheticFrame = name ? describeBuiltInComponentFrame(name) : '';
+ if (__DEV__) {
+ if (typeof fn === 'function') {
+ componentFrameCache.set(fn, syntheticFrame);
+ }
+ }
+ return syntheticFrame;
+}
+
+export function describeClassComponentFrame(
+ ctor: Function,
+ source: void | null | Source,
+ ownerFn: void | null | Function,
+ currentDispatcherRef: CurrentDispatcherRef,
+): string {
+ return describeNativeComponentFrame(ctor, true, currentDispatcherRef);
+}
+
+export function describeFunctionComponentFrame(
+ fn: Function,
+ source: void | null | Source,
+ ownerFn: void | null | Function,
+ currentDispatcherRef: CurrentDispatcherRef,
+): string {
+ return describeNativeComponentFrame(fn, false, currentDispatcherRef);
+}
+
+function shouldConstruct(Component: Function) {
+ const prototype = Component.prototype;
+ return !!(prototype && prototype.isReactComponent);
+}
+
+export function describeUnknownElementTypeFrameInDEV(
+ type: any,
+ source: void | null | Source,
+ ownerFn: void | null | Function,
+ currentDispatcherRef: CurrentDispatcherRef,
+): string {
+ if (!__DEV__) {
+ return '';
+ }
+ if (type == null) {
+ return '';
+ }
+ if (typeof type === 'function') {
+ return describeNativeComponentFrame(
+ type,
+ shouldConstruct(type),
+ currentDispatcherRef,
+ );
+ }
+ if (typeof type === 'string') {
+ return describeBuiltInComponentFrame(type, source, ownerFn);
+ }
+ switch (type) {
+ case SUSPENSE_NUMBER:
+ case SUSPENSE_SYMBOL_STRING:
+ return describeBuiltInComponentFrame('Suspense', source, ownerFn);
+ case SUSPENSE_LIST_NUMBER:
+ case SUSPENSE_LIST_SYMBOL_STRING:
+ return describeBuiltInComponentFrame('SuspenseList', source, ownerFn);
+ }
+ if (typeof type === 'object') {
+ switch (type.$$typeof) {
+ case FORWARD_REF_NUMBER:
+ case FORWARD_REF_SYMBOL_STRING:
+ return describeFunctionComponentFrame(
+ type.render,
+ source,
+ ownerFn,
+ currentDispatcherRef,
+ );
+ case MEMO_NUMBER:
+ case MEMO_SYMBOL_STRING:
+ // Memo may contain any component type so we recursively resolve it.
+ return describeUnknownElementTypeFrameInDEV(
+ type.type,
+ source,
+ ownerFn,
+ currentDispatcherRef,
+ );
+ case BLOCK_NUMBER:
+ case BLOCK_SYMBOL_STRING:
+ return describeFunctionComponentFrame(
+ type._render,
+ source,
+ ownerFn,
+ currentDispatcherRef,
+ );
+ case LAZY_NUMBER:
+ case LAZY_SYMBOL_STRING: {
+ const lazyComponent: LazyComponent = (type: any);
+ const payload = lazyComponent._payload;
+ const init = lazyComponent._init;
+ try {
+ // Lazy may contain any component type so we recursively resolve it.
+ return describeUnknownElementTypeFrameInDEV(
+ init(payload),
+ source,
+ ownerFn,
+ currentDispatcherRef,
+ );
+ } catch (x) {}
+ }
+ }
+ }
+ return '';
+}
diff --git a/packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js b/packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js
new file mode 100644
index 0000000000000..ecb2ac6d3be58
--- /dev/null
+++ b/packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js
@@ -0,0 +1,108 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+// This is a DevTools fork of ReactFiberComponentStack.
+// This fork enables DevTools to use the same "native" component stack format,
+// while still maintaining support for multiple renderer versions
+// (which use different values for ReactTypeOfWork).
+
+import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
+import type {CurrentDispatcherRef, WorkTagMap} from './types';
+
+import {
+ describeBuiltInComponentFrame,
+ describeFunctionComponentFrame,
+ describeClassComponentFrame,
+} from './DevToolsComponentStackFrame';
+
+function describeFiber(
+ workTagMap: WorkTagMap,
+ workInProgress: Fiber,
+ currentDispatcherRef: CurrentDispatcherRef,
+): string {
+ const {
+ HostComponent,
+ LazyComponent,
+ SuspenseComponent,
+ SuspenseListComponent,
+ FunctionComponent,
+ IndeterminateComponent,
+ SimpleMemoComponent,
+ ForwardRef,
+ Block,
+ ClassComponent,
+ } = workTagMap;
+
+ const owner: null | Function = __DEV__
+ ? workInProgress._debugOwner
+ ? workInProgress._debugOwner.type
+ : null
+ : null;
+ const source = __DEV__ ? workInProgress._debugSource : null;
+ switch (workInProgress.tag) {
+ case HostComponent:
+ return describeBuiltInComponentFrame(workInProgress.type, source, owner);
+ case LazyComponent:
+ return describeBuiltInComponentFrame('Lazy', source, owner);
+ case SuspenseComponent:
+ return describeBuiltInComponentFrame('Suspense', source, owner);
+ case SuspenseListComponent:
+ return describeBuiltInComponentFrame('SuspenseList', source, owner);
+ case FunctionComponent:
+ case IndeterminateComponent:
+ case SimpleMemoComponent:
+ return describeFunctionComponentFrame(
+ workInProgress.type,
+ source,
+ owner,
+ currentDispatcherRef,
+ );
+ case ForwardRef:
+ return describeFunctionComponentFrame(
+ workInProgress.type.render,
+ source,
+ owner,
+ currentDispatcherRef,
+ );
+ case Block:
+ return describeFunctionComponentFrame(
+ workInProgress.type._render,
+ source,
+ owner,
+ currentDispatcherRef,
+ );
+ case ClassComponent:
+ return describeClassComponentFrame(
+ workInProgress.type,
+ source,
+ owner,
+ currentDispatcherRef,
+ );
+ default:
+ return '';
+ }
+}
+
+export function getStackByFiberInDevAndProd(
+ workTagMap: WorkTagMap,
+ workInProgress: Fiber,
+ currentDispatcherRef: CurrentDispatcherRef,
+): string {
+ try {
+ let info = '';
+ let node = workInProgress;
+ do {
+ info += describeFiber(workTagMap, node, currentDispatcherRef);
+ node = node.return;
+ } while (node);
+ return info;
+ } catch (x) {
+ return '\nError generating stack: ' + x.message + '\n' + x.stack;
+ }
+}
diff --git a/packages/react-devtools-shared/src/backend/ReactSymbols.js b/packages/react-devtools-shared/src/backend/ReactSymbols.js
new file mode 100644
index 0000000000000..677c0a9d66582
--- /dev/null
+++ b/packages/react-devtools-shared/src/backend/ReactSymbols.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+// This list should be kept updated to reflect additions to 'shared/ReactSymbols'.
+// DevTools can't import symbols from 'shared/ReactSymbols' directly for two reasons:
+// 1. DevTools requires symbols which may have been deleted in more recent versions (e.g. concurrent mode)
+// 2. DevTools must support both Symbol and numeric forms of each symbol;
+// Since e.g. standalone DevTools runs in a separate process, it can't rely on its own ES capabilities.
+
+export const BLOCK_NUMBER = 0xead9;
+export const BLOCK_SYMBOL_STRING = 'Symbol(react.block)';
+
+export const CONCURRENT_MODE_NUMBER = 0xeacf;
+export const CONCURRENT_MODE_SYMBOL_STRING = 'Symbol(react.concurrent_mode)';
+
+export const CONTEXT_NUMBER = 0xeace;
+export const CONTEXT_SYMBOL_STRING = 'Symbol(react.context)';
+
+export const DEPRECATED_ASYNC_MODE_SYMBOL_STRING = 'Symbol(react.async_mode)';
+
+export const ELEMENT_NUMBER = 0xeac7;
+export const ELEMENT_SYMBOL_STRING = 'Symbol(react.element)';
+
+export const DEBUG_TRACING_MODE_NUMBER = 0xeae1;
+export const DEBUG_TRACING_MODE_SYMBOL_STRING =
+ 'Symbol(react.debug_trace_mode)';
+
+export const FORWARD_REF_NUMBER = 0xead0;
+export const FORWARD_REF_SYMBOL_STRING = 'Symbol(react.forward_ref)';
+
+export const FRAGMENT_NUMBER = 0xeacb;
+export const FRAGMENT_SYMBOL_STRING = 'Symbol(react.fragment)';
+
+export const FUNDAMENTAL_NUMBER = 0xead5;
+export const FUNDAMENTAL_SYMBOL_STRING = 'Symbol(react.fundamental)';
+
+export const LAZY_NUMBER = 0xead4;
+export const LAZY_SYMBOL_STRING = 'Symbol(react.lazy)';
+
+export const MEMO_NUMBER = 0xead3;
+export const MEMO_SYMBOL_STRING = 'Symbol(react.memo)';
+
+export const OPAQUE_ID_NUMBER = 0xeae0;
+export const OPAQUE_ID_SYMBOL_STRING = 'Symbol(react.opaque.id)';
+
+export const PORTAL_NUMBER = 0xeaca;
+export const PORTAL_SYMBOL_STRING = 'Symbol(react.portal)';
+
+export const PROFILER_NUMBER = 0xead2;
+export const PROFILER_SYMBOL_STRING = 'Symbol(react.profiler)';
+
+export const PROVIDER_NUMBER = 0xeacd;
+export const PROVIDER_SYMBOL_STRING = 'Symbol(react.provider)';
+
+export const RESPONDER_NUMBER = 0xead6;
+export const RESPONDER_SYMBOL_STRING = 'Symbol(react.responder)';
+
+export const SCOPE_NUMBER = 0xead7;
+export const SCOPE_SYMBOL_STRING = 'Symbol(react.scope)';
+
+export const SERVER_BLOCK_NUMBER = 0xeada;
+export const SERVER_BLOCK_SYMBOL_STRING = 'Symbol(react.server.block)';
+
+export const STRICT_MODE_NUMBER = 0xeacc;
+export const STRICT_MODE_SYMBOL_STRING = 'Symbol(react.strict_mode)';
+
+export const SUSPENSE_NUMBER = 0xead1;
+export const SUSPENSE_SYMBOL_STRING = 'Symbol(react.suspense)';
+
+export const SUSPENSE_LIST_NUMBER = 0xead8;
+export const SUSPENSE_LIST_SYMBOL_STRING = 'Symbol(react.suspense_list)';
diff --git a/packages/react-devtools-shared/src/backend/console.js b/packages/react-devtools-shared/src/backend/console.js
index b42cf2f9d3839..80a69701158ca 100644
--- a/packages/react-devtools-shared/src/backend/console.js
+++ b/packages/react-devtools-shared/src/backend/console.js
@@ -7,21 +7,27 @@
* @flow
*/
-import {getInternalReactConstants} from './renderer';
-import describeComponentFrame from './describeComponentFrame';
-
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
-import type {ReactRenderer} from './types';
+import type {CurrentDispatcherRef, ReactRenderer, WorkTagMap} from './types';
+
+import {getInternalReactConstants} from './renderer';
+import {getStackByFiberInDevAndProd} from './DevToolsFiberComponentStack';
const APPEND_STACK_TO_METHODS = ['error', 'trace', 'warn'];
-const FRAME_REGEX = /\n {4}in /;
+// React's custom built component stack strings match "\s{4}in"
+// Chrome's prefix matches "\s{4}at"
+const PREFIX_REGEX = /\s{4}(in|at)\s{1}/;
+// Firefox and Safari have no prefix ("")
+// but we can fallback to looking for location info (e.g. "foo.js:12:345")
+const ROW_COLUMN_NUMBER_REGEX = /:\d+:\d+(\n|$)/;
const injectedRenderers: Map<
ReactRenderer,
{|
+ currentDispatcherRef: CurrentDispatcherRef,
getCurrentFiber: () => Fiber | null,
- getDisplayNameForFiber: (fiber: Fiber) => string | null,
+ workTagMap: WorkTagMap,
|},
> = new Map();
@@ -49,19 +55,27 @@ export function dangerous_setTargetConsoleForTesting(
// These internals will be used if the console is patched.
// Injecting them separately allows the console to easily be patched or un-patched later (at runtime).
export function registerRenderer(renderer: ReactRenderer): void {
- const {getCurrentFiber, findFiberByHostInstance, version} = renderer;
+ const {
+ currentDispatcherRef,
+ getCurrentFiber,
+ findFiberByHostInstance,
+ version,
+ } = renderer;
// Ignore React v15 and older because they don't expose a component stack anyway.
if (typeof findFiberByHostInstance !== 'function') {
return;
}
- if (typeof getCurrentFiber === 'function') {
- const {getDisplayNameForFiber} = getInternalReactConstants(version);
+ // currentDispatcherRef gets injected for v16.8+ to support hooks inspection.
+ // getCurrentFiber gets injected for v16.9+.
+ if (currentDispatcherRef != null && typeof getCurrentFiber === 'function') {
+ const {ReactTypeOfWork} = getInternalReactConstants(version);
injectedRenderers.set(renderer, {
+ currentDispatcherRef,
getCurrentFiber,
- getDisplayNameForFiber,
+ workTagMap: ReactTypeOfWork,
});
}
}
@@ -94,36 +108,31 @@ export function patch(): void {
try {
// If we are ever called with a string that already has a component stack, e.g. a React error/warning,
// don't append a second stack.
+ const lastArg = args.length > 0 ? args[args.length - 1] : null;
const alreadyHasComponentStack =
- args.length > 0 && FRAME_REGEX.exec(args[args.length - 1]);
+ lastArg !== null &&
+ (PREFIX_REGEX.test(lastArg) ||
+ ROW_COLUMN_NUMBER_REGEX.test(lastArg));
if (!alreadyHasComponentStack) {
// If there's a component stack for at least one of the injected renderers, append it.
// We don't handle the edge case of stacks for more than one (e.g. interleaved renderers?)
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const {
+ currentDispatcherRef,
getCurrentFiber,
- getDisplayNameForFiber,
+ workTagMap,
} of injectedRenderers.values()) {
- let current: ?Fiber = getCurrentFiber();
- let ownerStack: string = '';
- while (current != null) {
- const name = getDisplayNameForFiber(current);
- const owner = current._debugOwner;
- const ownerName =
- owner != null ? getDisplayNameForFiber(owner) : null;
-
- ownerStack += describeComponentFrame(
- name,
- current._debugSource,
- ownerName,
+ const current: ?Fiber = getCurrentFiber();
+ if (current != null) {
+ const componentStack = getStackByFiberInDevAndProd(
+ workTagMap,
+ current,
+ currentDispatcherRef,
);
-
- current = owner;
- }
-
- if (ownerStack !== '') {
- args.push(ownerStack);
+ if (componentStack !== '') {
+ args.push(componentStack);
+ }
break;
}
}
diff --git a/packages/react-devtools-shared/src/backend/describeComponentFrame.js b/packages/react-devtools-shared/src/backend/describeComponentFrame.js
deleted file mode 100644
index 1be9d86494211..0000000000000
--- a/packages/react-devtools-shared/src/backend/describeComponentFrame.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-// This file was forked from the React GitHub repo:
-// https://raw.githubusercontent.com/facebook/react/master/packages/shared/describeComponentFrame.js
-//
-// It has been modified slightly to add a zero width space as commented below.
-
-const BEFORE_SLASH_RE = /^(.*)[\\/]/;
-
-export default function describeComponentFrame(
- name: null | string,
- source: any,
- ownerName: null | string,
-) {
- let sourceInfo = '';
- if (source) {
- const path = source.fileName;
- let fileName = path.replace(BEFORE_SLASH_RE, '');
- if (__DEV__) {
- // In DEV, include code for a common special case:
- // prefer "folder/index.js" instead of just "index.js".
- if (/^index\./.test(fileName)) {
- const match = path.match(BEFORE_SLASH_RE);
- if (match) {
- const pathBeforeSlash = match[1];
- if (pathBeforeSlash) {
- const folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, '');
- // Note the below string contains a zero width space after the "/" character.
- // This is to prevent browsers like Chrome from formatting the file name as a link.
- // (Since this is a source link, it would not work to open the source file anyway.)
- fileName = folderName + '/' + fileName;
- }
- }
- }
- }
- sourceInfo = ' (at ' + fileName + ':' + source.lineNumber + ')';
- } else if (ownerName) {
- sourceInfo = ' (created by ' + ownerName + ')';
- }
- return '\n in ' + (name || 'Unknown') + sourceInfo;
-}
diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js
index dbe7270527386..70bbac1949f73 100644
--- a/packages/react-devtools-shared/src/backend/renderer.js
+++ b/packages/react-devtools-shared/src/backend/renderer.js
@@ -49,6 +49,25 @@ import {
patch as patchConsole,
registerRenderer as registerRendererWithConsole,
} from './console';
+import {
+ CONCURRENT_MODE_NUMBER,
+ CONCURRENT_MODE_SYMBOL_STRING,
+ DEPRECATED_ASYNC_MODE_SYMBOL_STRING,
+ PROVIDER_NUMBER,
+ PROVIDER_SYMBOL_STRING,
+ CONTEXT_NUMBER,
+ CONTEXT_SYMBOL_STRING,
+ STRICT_MODE_NUMBER,
+ STRICT_MODE_SYMBOL_STRING,
+ PROFILER_NUMBER,
+ PROFILER_SYMBOL_STRING,
+ SCOPE_NUMBER,
+ SCOPE_SYMBOL_STRING,
+ FORWARD_REF_NUMBER,
+ FORWARD_REF_SYMBOL_STRING,
+ MEMO_NUMBER,
+ MEMO_SYMBOL_STRING,
+} from './ReactSymbols';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {
@@ -66,6 +85,7 @@ import type {
ProfilingDataForRootBackend,
ReactRenderer,
RendererInterface,
+ WorkTagMap,
} from './types';
import type {Interaction} from 'react-devtools-shared/src/devtools/views/Profiler/types';
import type {
@@ -76,26 +96,6 @@ import type {
type getDisplayNameForFiberType = (fiber: Fiber) => string | null;
type getTypeSymbolType = (type: any) => Symbol | number;
-type ReactSymbolsType = {|
- CONCURRENT_MODE_NUMBER: number,
- CONCURRENT_MODE_SYMBOL_STRING: string,
- DEPRECATED_ASYNC_MODE_SYMBOL_STRING: string,
- CONTEXT_CONSUMER_NUMBER: number,
- CONTEXT_CONSUMER_SYMBOL_STRING: string,
- CONTEXT_PROVIDER_NUMBER: number,
- CONTEXT_PROVIDER_SYMBOL_STRING: string,
- FORWARD_REF_NUMBER: number,
- FORWARD_REF_SYMBOL_STRING: string,
- MEMO_NUMBER: number,
- MEMO_SYMBOL_STRING: string,
- PROFILER_NUMBER: number,
- PROFILER_SYMBOL_STRING: string,
- STRICT_MODE_NUMBER: number,
- STRICT_MODE_SYMBOL_STRING: string,
- SCOPE_NUMBER: number,
- SCOPE_SYMBOL_STRING: string,
-|};
-
type ReactPriorityLevelsType = {|
ImmediatePriority: number,
UserBlockingPriority: number,
@@ -105,32 +105,6 @@ type ReactPriorityLevelsType = {|
NoPriority: number,
|};
-type ReactTypeOfWorkType = {|
- ClassComponent: number,
- ContextConsumer: number,
- ContextProvider: number,
- CoroutineComponent: number,
- CoroutineHandlerPhase: number,
- DehydratedSuspenseComponent: number,
- ForwardRef: number,
- Fragment: number,
- FunctionComponent: number,
- HostComponent: number,
- HostPortal: number,
- HostRoot: number,
- HostText: number,
- IncompleteClassComponent: number,
- IndeterminateComponent: number,
- LazyComponent: number,
- MemoComponent: number,
- Mode: number,
- Profiler: number,
- SimpleMemoComponent: number,
- SuspenseComponent: number,
- SuspenseListComponent: number,
- YieldComponent: number,
-|};
-
type ReactTypeOfSideEffectType = {|
NoEffect: number,
PerformedWork: number,
@@ -149,30 +123,9 @@ export function getInternalReactConstants(
getDisplayNameForFiber: getDisplayNameForFiberType,
getTypeSymbol: getTypeSymbolType,
ReactPriorityLevels: ReactPriorityLevelsType,
- ReactSymbols: ReactSymbolsType,
ReactTypeOfSideEffect: ReactTypeOfSideEffectType,
- ReactTypeOfWork: ReactTypeOfWorkType,
+ ReactTypeOfWork: WorkTagMap,
|} {
- const ReactSymbols: ReactSymbolsType = {
- CONCURRENT_MODE_NUMBER: 0xeacf,
- CONCURRENT_MODE_SYMBOL_STRING: 'Symbol(react.concurrent_mode)',
- DEPRECATED_ASYNC_MODE_SYMBOL_STRING: 'Symbol(react.async_mode)',
- CONTEXT_CONSUMER_NUMBER: 0xeace,
- CONTEXT_CONSUMER_SYMBOL_STRING: 'Symbol(react.context)',
- CONTEXT_PROVIDER_NUMBER: 0xeacd,
- CONTEXT_PROVIDER_SYMBOL_STRING: 'Symbol(react.provider)',
- FORWARD_REF_NUMBER: 0xead0,
- FORWARD_REF_SYMBOL_STRING: 'Symbol(react.forward_ref)',
- MEMO_NUMBER: 0xead3,
- MEMO_SYMBOL_STRING: 'Symbol(react.memo)',
- PROFILER_NUMBER: 0xead2,
- PROFILER_SYMBOL_STRING: 'Symbol(react.profiler)',
- STRICT_MODE_NUMBER: 0xeacc,
- STRICT_MODE_SYMBOL_STRING: 'Symbol(react.strict_mode)',
- SCOPE_NUMBER: 0xead7,
- SCOPE_SYMBOL_STRING: 'Symbol(react.scope)',
- };
-
const ReactTypeOfSideEffect: ReactTypeOfSideEffectType = {
NoEffect: 0b00,
PerformedWork: 0b01,
@@ -195,13 +148,14 @@ export function getInternalReactConstants(
NoPriority: 90,
};
- let ReactTypeOfWork: ReactTypeOfWorkType = ((null: any): ReactTypeOfWorkType);
+ let ReactTypeOfWork: WorkTagMap = ((null: any): WorkTagMap);
// **********************************************************
// The section below is copied from files in React repo.
// Keep it in sync, and add version guards if it changes.
if (gte(version, '16.6.0-beta.0')) {
ReactTypeOfWork = {
+ Block: 22,
ClassComponent: 1,
ContextConsumer: 9,
ContextProvider: 10,
@@ -228,6 +182,7 @@ export function getInternalReactConstants(
};
} else if (gte(version, '16.4.3-alpha')) {
ReactTypeOfWork = {
+ Block: -1, // Doesn't exist yet
ClassComponent: 2,
ContextConsumer: 11,
ContextProvider: 12,
@@ -254,6 +209,7 @@ export function getInternalReactConstants(
};
} else {
ReactTypeOfWork = {
+ Block: -1, // Doesn't exist yet
ClassComponent: 2,
ContextConsumer: 12,
ContextProvider: 13,
@@ -310,26 +266,6 @@ export function getInternalReactConstants(
SuspenseListComponent,
} = ReactTypeOfWork;
- const {
- CONCURRENT_MODE_NUMBER,
- CONCURRENT_MODE_SYMBOL_STRING,
- DEPRECATED_ASYNC_MODE_SYMBOL_STRING,
- CONTEXT_PROVIDER_NUMBER,
- CONTEXT_PROVIDER_SYMBOL_STRING,
- CONTEXT_CONSUMER_NUMBER,
- CONTEXT_CONSUMER_SYMBOL_STRING,
- STRICT_MODE_NUMBER,
- STRICT_MODE_SYMBOL_STRING,
- PROFILER_NUMBER,
- PROFILER_SYMBOL_STRING,
- SCOPE_NUMBER,
- SCOPE_SYMBOL_STRING,
- FORWARD_REF_NUMBER,
- FORWARD_REF_SYMBOL_STRING,
- MEMO_NUMBER,
- MEMO_SYMBOL_STRING,
- } = ReactSymbols;
-
function resolveFiberType(type: any) {
const typeSymbol = getTypeSymbol(type);
switch (typeSymbol) {
@@ -392,15 +328,15 @@ export function getInternalReactConstants(
case CONCURRENT_MODE_SYMBOL_STRING:
case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
return null;
- case CONTEXT_PROVIDER_NUMBER:
- case CONTEXT_PROVIDER_SYMBOL_STRING:
+ case PROVIDER_NUMBER:
+ case PROVIDER_SYMBOL_STRING:
// 16.3.0 exposed the context object as "context"
// PR #12501 changed it to "_context" for 16.3.1+
// NOTE Keep in sync with inspectElementRaw()
resolvedContext = fiber.type._context || fiber.type.context;
return `${resolvedContext.displayName || 'Context'}.Provider`;
- case CONTEXT_CONSUMER_NUMBER:
- case CONTEXT_CONSUMER_SYMBOL_STRING:
+ case CONTEXT_NUMBER:
+ case CONTEXT_SYMBOL_STRING:
// 16.3-16.5 read from "type" because the Consumer is the actual context object.
// 16.6+ should read from "type._context" because Consumer can be different (in DEV).
// NOTE Keep in sync with inspectElementRaw()
@@ -431,7 +367,6 @@ export function getInternalReactConstants(
getTypeSymbol,
ReactPriorityLevels,
ReactTypeOfWork,
- ReactSymbols,
ReactTypeOfSideEffect,
};
}
@@ -447,7 +382,6 @@ export function attach(
getTypeSymbol,
ReactPriorityLevels,
ReactTypeOfWork,
- ReactSymbols,
ReactTypeOfSideEffect,
} = getInternalReactConstants(renderer.version);
const {NoEffect, PerformedWork, Placement} = ReactTypeOfSideEffect;
@@ -477,19 +411,6 @@ export function attach(
IdlePriority,
NoPriority,
} = ReactPriorityLevels;
- const {
- CONCURRENT_MODE_NUMBER,
- CONCURRENT_MODE_SYMBOL_STRING,
- DEPRECATED_ASYNC_MODE_SYMBOL_STRING,
- CONTEXT_CONSUMER_NUMBER,
- CONTEXT_CONSUMER_SYMBOL_STRING,
- CONTEXT_PROVIDER_NUMBER,
- CONTEXT_PROVIDER_SYMBOL_STRING,
- PROFILER_NUMBER,
- PROFILER_SYMBOL_STRING,
- STRICT_MODE_NUMBER,
- STRICT_MODE_SYMBOL_STRING,
- } = ReactSymbols;
const {
overrideHookState,
@@ -731,11 +652,11 @@ export function attach(
case CONCURRENT_MODE_SYMBOL_STRING:
case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
return ElementTypeOtherOrUnknown;
- case CONTEXT_PROVIDER_NUMBER:
- case CONTEXT_PROVIDER_SYMBOL_STRING:
+ case PROVIDER_NUMBER:
+ case PROVIDER_SYMBOL_STRING:
return ElementTypeContext;
- case CONTEXT_CONSUMER_NUMBER:
- case CONTEXT_CONSUMER_SYMBOL_STRING:
+ case CONTEXT_NUMBER:
+ case CONTEXT_SYMBOL_STRING:
return ElementTypeContext;
case STRICT_MODE_NUMBER:
case STRICT_MODE_SYMBOL_STRING:
@@ -2262,8 +2183,8 @@ export function attach(
}
}
} else if (
- typeSymbol === CONTEXT_CONSUMER_NUMBER ||
- typeSymbol === CONTEXT_CONSUMER_SYMBOL_STRING
+ typeSymbol === CONTEXT_NUMBER ||
+ typeSymbol === CONTEXT_SYMBOL_STRING
) {
// 16.3-16.5 read from "type" because the Consumer is the actual context object.
// 16.6+ should read from "type._context" because Consumer can be different (in DEV).
@@ -2279,8 +2200,8 @@ export function attach(
const currentType = current.type;
const currentTypeSymbol = getTypeSymbol(currentType);
if (
- currentTypeSymbol === CONTEXT_PROVIDER_NUMBER ||
- currentTypeSymbol === CONTEXT_PROVIDER_SYMBOL_STRING
+ currentTypeSymbol === PROVIDER_NUMBER ||
+ currentTypeSymbol === PROVIDER_SYMBOL_STRING
) {
// 16.3.0 exposed the context object as "context"
// PR #12501 changed it to "_context" for 16.3.1+
diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js
index 9b3c0ea335ae3..81521bd577c4a 100644
--- a/packages/react-devtools-shared/src/backend/types.js
+++ b/packages/react-devtools-shared/src/backend/types.js
@@ -25,6 +25,33 @@ export type WorkTag = number;
export type SideEffectTag = number;
export type ExpirationTime = number;
+export type WorkTagMap = {|
+ Block: WorkTag,
+ ClassComponent: WorkTag,
+ ContextConsumer: WorkTag,
+ ContextProvider: WorkTag,
+ CoroutineComponent: WorkTag,
+ CoroutineHandlerPhase: WorkTag,
+ DehydratedSuspenseComponent: WorkTag,
+ ForwardRef: WorkTag,
+ Fragment: WorkTag,
+ FunctionComponent: WorkTag,
+ HostComponent: WorkTag,
+ HostPortal: WorkTag,
+ HostRoot: WorkTag,
+ HostText: WorkTag,
+ IncompleteClassComponent: WorkTag,
+ IndeterminateComponent: WorkTag,
+ LazyComponent: WorkTag,
+ MemoComponent: WorkTag,
+ Mode: WorkTag,
+ Profiler: WorkTag,
+ SimpleMemoComponent: WorkTag,
+ SuspenseComponent: WorkTag,
+ SuspenseListComponent: WorkTag,
+ YieldComponent: WorkTag,
+|};
+
// TODO: If it's useful for the frontend to know which types of data an Element has
// (e.g. props, state, context, hooks) then we could add a bitmask field for this
// to keep the number of attributes small.
@@ -38,6 +65,7 @@ export type NativeType = Object;
export type RendererID = number;
type Dispatcher = any;
+export type CurrentDispatcherRef = {|current: null | Dispatcher|};
export type GetDisplayNameForFiberID = (
id: number,
@@ -77,7 +105,7 @@ export type ReactRenderer = {
scheduleUpdate?: ?(fiber: Object) => void,
setSuspenseHandler?: ?(shouldSuspend: (fiber: Object) => boolean) => void,
// Only injected by React v16.8+ in order to support hooks inspection.
- currentDispatcherRef?: {|current: null | Dispatcher|},
+ currentDispatcherRef?: CurrentDispatcherRef,
// Only injected by React v16.9+ in DEV mode.
// Enables DevTools to append owners-only component stack to error messages.
getCurrentFiber?: () => Fiber | null,
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js
index 5424d35039cb3..ae0ef9493a6c9 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js
@@ -463,7 +463,7 @@ function InspectedElementView({
);
}
-// This function is based on packages/shared/describeComponentFrame.js
+// This function is based on describeComponentFrame() in packages/shared/ReactComponentStackFrame
function formatSourceForDisplay(fileName: string, lineNumber: string) {
const BEFORE_SLASH_RE = /^(.*)[\\\/]/;
diff --git a/packages/react-devtools-shell/webpack.config.js b/packages/react-devtools-shell/webpack.config.js
index 824cd55b522cb..095c12cbd4709 100644
--- a/packages/react-devtools-shell/webpack.config.js
+++ b/packages/react-devtools-shell/webpack.config.js
@@ -42,6 +42,8 @@ const config = {
plugins: [
new DefinePlugin({
__DEV__,
+ __PROFILE__: false,
+ __EXPERIMENTAL__: true,
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
}),
diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js
index a4ecac2fa6608..672ed8d8aa27d 100644
--- a/packages/shared/ReactSymbols.js
+++ b/packages/shared/ReactSymbols.js
@@ -7,6 +7,10 @@
* @flow
*/
+// ATTENTION
+// When adding new symbols to this file,
+// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols'
+
// The Symbol used to tag the ReactElement-like types. If there is no native Symbol
// nor polyfill, then a plain number is used for performance.
export let REACT_ELEMENT_TYPE = 0xeac7;
diff --git a/scripts/jest/config.build-devtools.js b/scripts/jest/config.build-devtools.js
index 9c8501486ab49..494cc4a8e721a 100644
--- a/scripts/jest/config.build-devtools.js
+++ b/scripts/jest/config.build-devtools.js
@@ -26,13 +26,6 @@ const packages = readdirSync(packagesRoot).filter(dir => {
// Create a module map to point React packages to the build output
const moduleNameMapper = {};
-// Allow bundle tests to read (but not write!) default feature flags.
-// This lets us determine whether we're running in different modes
-// without making relevant tests internal-only.
-moduleNameMapper[
- '^shared/ReactFeatureFlags'
-] = `/packages/shared/forks/ReactFeatureFlags.readonly`;
-
// Map packages to bundles
packages.forEach(name => {
// Root entry point
@@ -43,6 +36,11 @@ packages.forEach(name => {
] = `/build/node_modules/${name}/$1`;
});
+// Allow tests to import shared code (e.g. feature flags, getStackByFiberInDevAndProd)
+moduleNameMapper['^shared/([^/]+)$'] = '/packages/shared/$1';
+moduleNameMapper['^react-reconciler/([^/]+)$'] =
+ '/packages/react-reconciler/$1';
+
module.exports = Object.assign({}, baseConfig, {
// Redirect imports to the compiled bundles
moduleNameMapper,