11
Thenable,
12
FulfilledThenable,
13
RejectedThenable,
14
+ ReactDebugInfo,
15
+ ReactIOInfo,
16
+ ReactAsyncInfo,
17
} from 'shared/ReactTypes';
18
+
19
import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
20
21
export type ServerConsumerModuleMap = string; // Module root path
122
}
123
return moduleExports[metadata.name];
124
}
125
+
126
+// We cache ReactIOInfo across requests so that inner refreshes can dedupe with outer.
127
+const moduleIOInfoCache: Map<string, ReactIOInfo> = __DEV__
128
+ ? new Map()
129
+ : (null: any);
130
+
131
+export function getModuleDebugInfo<T>(
132
+ metadata: ClientReference<T>,
133
+): null | ReactDebugInfo {
134
+ if (!__DEV__) {
135
+ return null;
136
+ }
137
+ const filename = metadata.specifier;
138
+ let ioInfo = moduleIOInfoCache.get(filename);
139
+ if (ioInfo === undefined) {
140
+ let href;
141
+ try {
142
+ // $FlowFixMe
143
+ href = new URL(filename, document.baseURI).href;
144
+ } catch (_) {
145
+ href = filename;
146
+ }
147
+ let start = -1;
148
+ let end = -1;
149
+ let byteSize = 0;
150
+ // $FlowFixMe[method-unbinding]
151
+ if (typeof performance.getEntriesByType === 'function') {
152
+ // We may be able to collect the start and end time of this resource from Performance Observer.
153
+ const resourceEntries = performance.getEntriesByType('resource');
154
+ for (let i = 0; i < resourceEntries.length; i++) {
155
+ const resourceEntry = resourceEntries[i];
156
+ if (resourceEntry.name === href) {
157
+ start = resourceEntry.startTime;
158
+ end = start + resourceEntry.duration;
159
+ // $FlowFixMe[prop-missing]
160
+ byteSize = (resourceEntry.transferSize: any) || 0;
161
+ }
162
+ }
163
+ }
164
+ const value = Promise.resolve(href);
165
+ // $FlowFixMe
166
+ value.status = 'fulfilled';
167
+ // Is there some more useful representation for the chunk?
168
+ // $FlowFixMe
169
+ value.value = href;
170
+ // Create a fake stack frame that points to the beginning of the chunk. This is
171
+ // probably not source mapped so will link to the compiled source rather than
172
+ // any individual file that goes into the chunks.
173
+ const fakeStack = new Error('react-stack-top-frame');
174
+ if (fakeStack.stack.startsWith('Error: react-stack-top-frame')) {
175
+ // Looks like V8
176
+ fakeStack.stack =
177
+ 'Error: react-stack-top-frame\n' +
178
+ // Add two frames since we always trim one off the top.
179
+ ' at Client Component Bundle (' +
180
+ href +
181
+ ':1:1)\n' +
182
+ ' at Client Component Bundle (' +
183
+ href +
184
+ ':1:1)';
185
+ } else {
186
+ // Looks like Firefox or Safari.
187
+ // Add two frames since we always trim one off the top.
188
+ fakeStack.stack =
189
+ 'Client Component Bundle@' +
190
+ href +
191
+ ':1:1\n' +
192
+ 'Client Component Bundle@' +
193
+ href +
194
+ ':1:1';
195
+ }
196
+ ioInfo = ({
197
+ name: 'script',
198
+ start: start,
199
+ end: end,
200
+ value: value,
201
+ debugStack: fakeStack,
202
+ }: ReactIOInfo);
203
+ if (byteSize > 0) {
204
+ // $FlowFixMe[cannot-write]
205
+ ioInfo.byteSize = byteSize;
206
+ }
207
+ moduleIOInfoCache.set(filename, ioInfo);
208
+ }
209
+ // We could dedupe the async info too but conceptually each request is its own await.
210
+ const asyncInfo: ReactAsyncInfo = {
211
+ awaited: ioInfo,
212
+ };
213
+ return [asyncInfo];
214
+}