main
js 129 lines 3.87 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and 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 {
11 ReactDebugInfo,
12 ReactIOInfo,
13 ReactAsyncInfo,
14 } from 'shared/ReactTypes';
15
16 const chunkMap: Map<string, string> = new Map();
17
18 /**
19 * We patch the chunk filename function in webpack to insert our own resolution
20 * of chunks that come from Flight and may not be known to the webpack runtime
21 */
22 const webpackGetChunkFilename = __webpack_require__.u;
23 __webpack_require__.u = function (chunkId: string) {
24 const flightChunk = chunkMap.get(chunkId);
25 if (flightChunk !== undefined) {
26 return flightChunk;
27 }
28 return webpackGetChunkFilename(chunkId);
29 };
30
31 export function loadChunk(chunkId: string, filename: string): Promise<mixed> {
32 chunkMap.set(chunkId, filename);
33 return __webpack_chunk_load__(chunkId);
34 }
35
36 // We cache ReactIOInfo across requests so that inner refreshes can dedupe with outer.
37 const chunkIOInfoCache: Map<string, ReactIOInfo> = __DEV__
38 ? new Map()
39 : (null as any);
40
41 export function addChunkDebugInfo(
42 target: ReactDebugInfo,
43 chunkId: string,
44 filename: string,
45 ): void {
46 if (!__DEV__) {
47 return;
48 }
49 let ioInfo = chunkIOInfoCache.get(chunkId);
50 if (ioInfo === undefined) {
51 const scriptFilename = __webpack_get_script_filename__(chunkId);
52 let href;
53 try {
54 // $FlowFixMe[incompatible-type]
55 href = new URL(scriptFilename, document.baseURI).href;
56 } catch (_) {
57 href = scriptFilename;
58 }
59 let start = -1;
60 let end = -1;
61 let byteSize = 0;
62 // $FlowFixMe[method-unbinding]
63 if (typeof performance.getEntriesByType === 'function') {
64 // We may be able to collect the start and end time of this resource from Performance Observer.
65 const resourceEntries = performance.getEntriesByType('resource');
66 for (let i = 0; i < resourceEntries.length; i++) {
67 const resourceEntry = resourceEntries[i];
68 if (resourceEntry.name === href) {
69 start = resourceEntry.startTime;
70 end = start + resourceEntry.duration;
71 // $FlowFixMe[prop-missing]
72 byteSize = (resourceEntry.transferSize as any) || 0;
73 }
74 }
75 }
76 const value = Promise.resolve(href);
77 // $FlowFixMe[prop-missing]
78 value.status = 'fulfilled';
79 // $FlowFixMe[prop-missing]
80 value.value = {
81 chunkId: chunkId,
82 href: href,
83 // Is there some more useful representation for the chunk?
84 };
85 // Create a fake stack frame that points to the beginning of the chunk. This is
86 // probably not source mapped so will link to the compiled source rather than
87 // any individual file that goes into the chunks.
88 const fakeStack = new Error('react-stack-top-frame');
89 if (fakeStack.stack.startsWith('Error: react-stack-top-frame')) {
90 // Looks like V8
91 fakeStack.stack =
92 'Error: react-stack-top-frame\n' +
93 // Add two frames since we always trim one off the top.
94 ' at Client Component Bundle (' +
95 href +
96 ':1:1)\n' +
97 ' at Client Component Bundle (' +
98 href +
99 ':1:1)';
100 } else {
101 // Looks like Firefox or Safari.
102 // Add two frames since we always trim one off the top.
103 fakeStack.stack =
104 'Client Component Bundle@' +
105 href +
106 ':1:1\n' +
107 'Client Component Bundle@' +
108 href +
109 ':1:1';
110 }
111 ioInfo = {
112 name: 'script',
113 start: start,
114 end: end,
115 value: value,
116 debugStack: fakeStack,
117 } as ReactIOInfo;
118 if (byteSize > 0) {
119 // $FlowFixMe[cannot-write]
120 ioInfo.byteSize = byteSize;
121 }
122 chunkIOInfoCache.set(chunkId, ioInfo);
123 }
124 // We could dedupe the async info too but conceptually each request is its own await.
125 const asyncInfo: ReactAsyncInfo = {
126 awaited: ioInfo,
127 };
128 target.push(asyncInfo);
129 }