@samitouri / QOS-React / commits / ec7d9a7249

Resolve the .default export of a React.lazy as the canonical value (#34906)

For debug purposes this is the value that the `React.lazy` resolves to. It also lets us look at that value for descriptions like its name.

Sebastian Markbåge committed Oct 19, 2025 at 11:56 UTC ec7d9a7249e84e841fbe1e4c22e1be2c0c15dae4
1 file changed +27 -4
packages/react/src/ReactLazy.js
+27 -4
@@ -20,6 +20,8 @@ import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
20
21 import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';
22
23 +import noop from 'shared/noop';
24 +
25 const Uninitialized = -1;
26 const Pending = 0;
27 const Resolved = 1;
@@ -67,12 +69,20 @@ export type LazyComponent<T, P> = {
69
70 function lazyInitializer<T>(payload: Payload<T>): T {
71 if (payload._status === Uninitialized) {
72 + let resolveDebugValue: (void | T) => void = (null: any);
73 + let rejectDebugValue: mixed => void = (null: any);
74 if (__DEV__ && enableAsyncDebugInfo) {
75 const ioInfo = payload._ioInfo;
76 if (ioInfo != null) {
77 // Mark when we first kicked off the lazy request.
78 // $FlowFixMe[cannot-write]
79 ioInfo.start = ioInfo.end = performance.now();
80 + // Stash a Promise for introspection of the value later.
81 + // $FlowFixMe[cannot-write]
82 + ioInfo.value = new Promise((resolve, reject) => {
83 + resolveDebugValue = resolve;
84 + rejectDebugValue = reject;
85 + });
86 }
87 }
88 const ctor = payload._result;
@@ -92,12 +102,20 @@ function lazyInitializer<T>(payload: Payload<T>): T {
102 const resolved: ResolvedPayload<T> = (payload: any);
103 resolved._status = Resolved;
104 resolved._result = moduleObject;
95 - if (__DEV__) {
105 + if (__DEV__ && enableAsyncDebugInfo) {
106 const ioInfo = payload._ioInfo;
107 if (ioInfo != null) {
108 // Mark the end time of when we resolved.
109 // $FlowFixMe[cannot-write]
110 ioInfo.end = performance.now();
111 + // Surface the default export as the resolved "value" for debug purposes.
112 + const debugValue =
113 + moduleObject == null ? undefined : moduleObject.default;
114 + resolveDebugValue(debugValue);
115 + // $FlowFixMe
116 + ioInfo.value.status = 'fulfilled';
117 + // $FlowFixMe
118 + ioInfo.value.value = debugValue;
119 }
120 // Make the thenable introspectable
121 if (thenable.status === undefined) {
@@ -124,6 +142,14 @@ function lazyInitializer<T>(payload: Payload<T>): T {
142 // Mark the end time of when we rejected.
143 // $FlowFixMe[cannot-write]
144 ioInfo.end = performance.now();
145 + // Hide unhandled rejections.
146 + // $FlowFixMe
147 + ioInfo.value.then(noop, noop);
148 + rejectDebugValue(error);
149 + // $FlowFixMe
150 + ioInfo.value.status = 'rejected';
151 + // $FlowFixMe
152 + ioInfo.value.reason = error;
153 }
154 // Make the thenable introspectable
155 if (thenable.status === undefined) {
@@ -139,9 +165,6 @@ function lazyInitializer<T>(payload: Payload<T>): T {
165 if (__DEV__ && enableAsyncDebugInfo) {
166 const ioInfo = payload._ioInfo;
167 if (ioInfo != null) {
142 - // Stash the thenable for introspection of the value later.
143 - // $FlowFixMe[cannot-write]
144 - ioInfo.value = thenable;
168 const displayName = thenable.displayName;
169 if (typeof displayName === 'string') {
170 // $FlowFixMe[cannot-write]