@@ -12,6 +12,7 @@ import type {
12
12
ReactDebugInfo,
13
13
ReactComponentInfo,
14
14
ReactAsyncInfo,
15
+ ReactStackTrace,
15
16
} from 'shared/ReactTypes';
16
17
import type {LazyComponent} from 'react/src/ReactLazy';
17
18
@@ -624,7 +625,7 @@ function createElement(
624
625
key: mixed,
625
626
props: mixed,
626
627
owner: null | ReactComponentInfo, // DEV-only
627
- stack: null | string , // DEV-only
628
+ stack: null | ReactStackTrace , // DEV-only
628
629
validated: number, // DEV-only
629
630
):
630
631
| React$Element<any>
@@ -1738,6 +1739,27 @@ function stopStream(
1738
1739
controller.close(row === '' ? '"$undefined"' : row);
1739
1740
}
1740
1741
1742
+ function formatV8Stack(
1743
+ errorName: string,
1744
+ errorMessage: string,
1745
+ stack: null | ReactStackTrace,
1746
+ ): string {
1747
+ let v8StyleStack = errorName + ': ' + errorMessage;
1748
+ if (stack) {
1749
+ for (let i = 0; i < stack.length; i++) {
1750
+ const frame = stack[i];
1751
+ const [name, filename, line, col] = frame;
1752
+ if (!name) {
1753
+ v8StyleStack += '\n at ' + filename + ':' + line + ':' + col;
1754
+ } else {
1755
+ v8StyleStack +=
1756
+ '\n at ' + name + ' (' + filename + ':' + line + ':' + col + ')';
1757
+ }
1758
+ }
1759
+ }
1760
+ return v8StyleStack;
1761
+ }
1762
+
1741
1763
type ErrorWithDigest = Error & {digest?: string};
1742
1764
function resolveErrorProd(
1743
1765
response: Response,
@@ -1773,7 +1795,7 @@ function resolveErrorDev(
1773
1795
id: number,
1774
1796
digest: string,
1775
1797
message: string,
1776
- stack: string ,
1798
+ stack: ReactStackTrace ,
1777
1799
env: string,
1778
1800
): void {
1779
1801
if (!__DEV__) {
@@ -1793,7 +1815,8 @@ function resolveErrorDev(
1793
1815
message ||
1794
1816
'An error occurred in the Server Components render but no message was provided',
1795
1817
);
1796
- error.stack = stack;
1818
+ // For backwards compat we use the V8 formatting when the flag is off.
1819
+ error.stack = formatV8Stack(error.name, error.message, stack);
1797
1820
} else {
1798
1821
const callStack = buildFakeCallStack(
1799
1822
response,
@@ -1853,7 +1876,7 @@ function resolvePostponeDev(
1853
1876
response: Response,
1854
1877
id: number,
1855
1878
reason: string,
1856
- stack: string ,
1879
+ stack: ReactStackTrace ,
1857
1880
): void {
1858
1881
if (!__DEV__) {
1859
1882
// These errors should never make it into a build so we don't need to encode them in codes.json
@@ -1862,11 +1885,34 @@ function resolvePostponeDev(
1862
1885
'resolvePostponeDev should never be called in production mode. Use resolvePostponeProd instead. This is a bug in React.',
1863
1886
);
1864
1887
}
1865
- // eslint-disable-next-line react-internal/prod-error-codes
1866
- const error = new Error(reason || '');
1867
- const postponeInstance: Postpone = (error: any);
1868
- postponeInstance.$$typeof = REACT_POSTPONE_TYPE;
1869
- postponeInstance.stack = stack;
1888
+ let postponeInstance: Postpone;
1889
+ if (!enableOwnerStacks) {
1890
+ // Executing Error within a native stack isn't really limited to owner stacks
1891
+ // but we gate it behind the same flag for now while iterating.
1892
+ // eslint-disable-next-line react-internal/prod-error-codes
1893
+ postponeInstance = (Error(reason || ''): any);
1894
+ postponeInstance.$$typeof = REACT_POSTPONE_TYPE;
1895
+ // For backwards compat we use the V8 formatting when the flag is off.
1896
+ postponeInstance.stack = formatV8Stack(
1897
+ postponeInstance.name,
1898
+ postponeInstance.message,
1899
+ stack,
1900
+ );
1901
+ } else {
1902
+ const callStack = buildFakeCallStack(
1903
+ response,
1904
+ stack,
1905
+ // $FlowFixMe[incompatible-use]
1906
+ Error.bind(null, reason || ''),
1907
+ );
1908
+ const rootTask = response._debugRootTask;
1909
+ if (rootTask != null) {
1910
+ postponeInstance = rootTask.run(callStack);
1911
+ } else {
1912
+ postponeInstance = callStack();
1913
+ }
1914
+ postponeInstance.$$typeof = REACT_POSTPONE_TYPE;
1915
+ }
1870
1916
const chunks = response._chunks;
1871
1917
const chunk = chunks.get(id);
1872
1918
if (!chunk) {
@@ -1973,40 +2019,25 @@ function createFakeFunction<T>(
1973
2019
return fn;
1974
2020
}
1975
2021
1976
- // This matches either of these V8 formats.
1977
- // at name (filename:0:0)
1978
- // at filename:0:0
1979
- // at async filename:0:0
1980
- const frameRegExp =
1981
- /^ {3} at (?:(.+) \(([^\)]+):(\d+):(\d+)\)|(?:async )?([^\)]+):(\d+):(\d+))$/;
1982
-
1983
2022
function buildFakeCallStack<T>(
1984
2023
response: Response,
1985
- stack: string ,
2024
+ stack: ReactStackTrace ,
1986
2025
innerCall: () => T,
1987
2026
): () => T {
1988
- const frames = stack.split('\n');
1989
2027
let callStack = innerCall;
1990
- for (let i = 0; i < frames.length; i++) {
1991
- const frame = frames[i];
1992
- let fn = fakeFunctionCache.get(frame);
2028
+ for (let i = 0; i < stack.length; i++) {
2029
+ const frame = stack[i];
2030
+ const frameKey = frame.join('-');
2031
+ let fn = fakeFunctionCache.get(frameKey);
1993
2032
if (fn === undefined) {
1994
- const parsed = frameRegExp.exec(frame);
1995
- if (!parsed) {
1996
- // We assume the server returns a V8 compatible stack trace.
1997
- continue;
1998
- }
1999
- const name = parsed[1] || '';
2000
- const filename = parsed[2] || parsed[5] || '';
2001
- const line = +(parsed[3] || parsed[6]);
2002
- const col = +(parsed[4] || parsed[7]);
2033
+ const [name, filename, line, col] = frame;
2003
2034
const sourceMap = response._debugFindSourceMapURL
2004
2035
? response._debugFindSourceMapURL(filename)
2005
2036
: null;
2006
2037
fn = createFakeFunction(name, filename, sourceMap, line, col);
2007
2038
// TODO: This cache should technically live on the response since the _debugFindSourceMapURL
2008
2039
// function is an input and can vary by response.
2009
- fakeFunctionCache.set(frame , fn);
2040
+ fakeFunctionCache.set(frameKey , fn);
2010
2041
}
2011
2042
callStack = fn.bind(null, callStack);
2012
2043
}
@@ -2026,7 +2057,7 @@ function initializeFakeTask(
2026
2057
return cachedEntry;
2027
2058
}
2028
2059
2029
- if (typeof debugInfo.stack ! == 'string' ) {
2060
+ if (debugInfo.stack == null ) {
2030
2061
// If this is an error, we should've really already initialized the task.
2031
2062
// If it's null, we can't initialize a task.
2032
2063
return null;
@@ -2064,7 +2095,7 @@ function initializeFakeTask(
2064
2095
const createFakeJSXCallStack = {
2065
2096
'react-stack-bottom-frame': function (
2066
2097
response: Response,
2067
- stack: string ,
2098
+ stack: ReactStackTrace ,
2068
2099
): Error {
2069
2100
const callStackForError = buildFakeCallStack(
2070
2101
response,
@@ -2077,7 +2108,7 @@ const createFakeJSXCallStack = {
2077
2108
2078
2109
const createFakeJSXCallStackInDEV: (
2079
2110
response: Response,
2080
- stack: string ,
2111
+ stack: ReactStackTrace ,
2081
2112
) => Error = __DEV__
2082
2113
? // We use this technique to trick minifiers to preserve the function name.
2083
2114
(createFakeJSXCallStack['react-stack-bottom-frame'].bind(
@@ -2100,7 +2131,7 @@ function initializeFakeStack(
2100
2131
if (cachedEntry !== undefined) {
2101
2132
return;
2102
2133
}
2103
- if (typeof debugInfo.stack === 'string' ) {
2134
+ if (debugInfo.stack != null ) {
2104
2135
// $FlowFixMe[cannot-write]
2105
2136
// $FlowFixMe[prop-missing]
2106
2137
debugInfo.debugStack = createFakeJSXCallStackInDEV(
@@ -2154,8 +2185,13 @@ function resolveConsoleEntry(
2154
2185
return;
2155
2186
}
2156
2187
2157
- const payload: [string, string, null | ReactComponentInfo, string, mixed] =
2158
- parseModel(response, value);
2188
+ const payload: [
2189
+ string,
2190
+ ReactStackTrace,
2191
+ null | ReactComponentInfo,
2192
+ string,
2193
+ mixed,
2194
+ ] = parseModel(response, value);
2159
2195
const methodName = payload[0];
2160
2196
const stackTrace = payload[1];
2161
2197
const owner = payload[2];
0 commit comments