@samitouri / QOS-React-2 / commits / 24c2e27256

DevTools Suspense cache cleanup (#22275)

Brian Vaughn committed Sep 9, 2021 at 09:28 UTC 24c2e27256b8f2051bde626598f2fba34efd109b
5 files changed +120 -87
packages/react-devtools-shared/src/hookNamesCache.js
+42 -35
@@ -110,44 +110,51 @@ export function loadHookNames(
110
111 let didTimeout = false;
112
113 - loadHookNamesFunction(hooksTree, fetchFileWithCaching).then(
114 - function onSuccess(hookNames) {
115 - if (didTimeout) {
116 - return;
117 - }
118 -
119 - if (__DEBUG__) {
120 - console.log('[hookNamesCache] onSuccess() hookNames:', hookNames);
121 - }
122 -
123 - if (hookNames) {
124 - const resolvedRecord = ((newRecord: any): ResolvedRecord<HookNames>);
125 - resolvedRecord.status = Resolved;
126 - resolvedRecord.value = hookNames;
127 - } else {
128 - const notFoundRecord = ((newRecord: any): RejectedRecord);
129 - notFoundRecord.status = Rejected;
130 - notFoundRecord.value = null;
131 - }
132 -
133 - wake();
134 - },
135 - function onError(error) {
136 - if (didTimeout) {
137 - return;
138 - }
113 + const onSuccess = hookNames => {
114 + if (didTimeout) {
115 + return;
116 + }
117 +
118 + if (__DEBUG__) {
119 + console.log('[hookNamesCache] onSuccess() hookNames:', hookNames);
120 + }
121
140 - if (__DEBUG__) {
141 - console.log('[hookNamesCache] onError() error:', error);
142 - }
122 + if (hookNames) {
123 + const resolvedRecord = ((newRecord: any): ResolvedRecord<HookNames>);
124 + resolvedRecord.status = Resolved;
125 + resolvedRecord.value = hookNames;
126 + } else {
127 + const notFoundRecord = ((newRecord: any): RejectedRecord);
128 + notFoundRecord.status = Rejected;
129 + notFoundRecord.value = null;
130 + }
131
144 - const thrownRecord = ((newRecord: any): RejectedRecord);
145 - thrownRecord.status = Rejected;
146 - thrownRecord.value = null;
132 + wake();
133 + };
134
148 - wake();
149 - },
150 - );
135 + const onError = error => {
136 + if (didTimeout) {
137 + return;
138 + }
139 +
140 + if (__DEBUG__) {
141 + console.log('[hookNamesCache] onError()');
142 + }
143 +
144 + console.error(error);
145 +
146 + const thrownRecord = ((newRecord: any): RejectedRecord);
147 + thrownRecord.status = Rejected;
148 + thrownRecord.value = null;
149 +
150 + wake();
151 + };
152 +
153 + const thenable = loadHookNamesFunction(hooksTree, fetchFileWithCaching);
154 + thenable.then(onSuccess, onError);
155 + if (typeof (thenable: any).catch === 'function') {
156 + (thenable: any).catch(onError);
157 + }
158
159 // Eventually timeout and stop trying to load names.
160 let timeoutID = setTimeout(function onTimeout() {
packages/react-devtools-shared/src/hooks/__tests__/__source__/ContainingStringSourceMappingURL.js
+5
@@ -11,12 +11,17 @@ import React, {useState} from 'react';
11
12 // ?sourceMappingURL=([^\s'"]+)/gm
13
14 +const abc = 'abc';
15 +const string =
16 + 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + abc;
17 +
18 export function Component() {
19 const [count, setCount] = useState(0);
20
21 return (
22 <div>
23 <p>You clicked {count} times</p>
24 + <div>string: {string}</div>
25 <button onClick={() => setCount(count + 1)}>Click me</button>
26 </div>
27 );
packages/react-devtools-shared/src/hooks/__tests__/parseHookNames-test.js
+1 -1
@@ -136,7 +136,7 @@ describe('parseHookNames', () => {
136 .Component;
137
138 fetchMock.mockIf(/.+$/, request => {
139 - // Since th custom hook contains only unnamed hooks, the source code should not be loaded.
139 + // Since the custom hook contains only unnamed hooks, the source code should not be loaded.
140 if (request.url.endsWith('useCustom.js')) {
141 throw Error(`Unexpected file request for "${request.url}"`);
142 }
packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js
+49 -34
@@ -148,6 +148,14 @@ function extractAndLoadSourceMapJSON(
148 // Deduplicate fetches, since there can be multiple location keys per source map.
149 const dedupedFetchPromises = new Map();
150
151 + if (__DEBUG__) {
152 + console.log(
153 + 'extractAndLoadSourceMapJSON() load',
154 + locationKeyToHookSourceAndMetadata.size,
155 + 'source maps',
156 + );
157 + }
158 +
159 const setterPromises = [];
160 locationKeyToHookSourceAndMetadata.forEach(hookSourceAndMetadata => {
161 const sourceMapRegex = / ?sourceMappingURL=([^\s'"]+)/gm;
@@ -177,45 +185,52 @@ function extractAndLoadSourceMapJSON(
185 const sourceMappingURL = sourceMappingURLMatch[1];
186 const hasInlineSourceMap = sourceMappingURL.indexOf('base64,') >= 0;
187 if (hasInlineSourceMap) {
180 - // TODO (named hooks) deduplicate parsing in this branch (similar to fetching in the other branch)
181 - // since there can be multiple location keys per source map.
182 -
183 - // Web apps like Code Sandbox embed multiple inline source maps.
184 - // In this case, we need to loop through and find the right one.
185 - // We may also need to trim any part of this string that isn't based64 encoded data.
186 - const trimmed = ((sourceMappingURL.match(
187 - /base64,([a-zA-Z0-9+\/=]+)/,
188 - ): any): Array<string>)[1];
189 - const decoded = withSyncPerformanceMark('decodeBase64String()', () =>
190 - decodeBase64String(trimmed),
191 - );
192 -
193 - const sourceMapJSON = withSyncPerformanceMark(
194 - 'JSON.parse(decoded)',
195 - () => JSON.parse(decoded),
196 - );
188 + try {
189 + // TODO (named hooks) deduplicate parsing in this branch (similar to fetching in the other branch)
190 + // since there can be multiple location keys per source map.
191 +
192 + // Web apps like Code Sandbox embed multiple inline source maps.
193 + // In this case, we need to loop through and find the right one.
194 + // We may also need to trim any part of this string that isn't based64 encoded data.
195 + const trimmed = ((sourceMappingURL.match(
196 + /base64,([a-zA-Z0-9+\/=]+)/,
197 + ): any): Array<string>)[1];
198 + const decoded = withSyncPerformanceMark(
199 + 'decodeBase64String()',
200 + () => decodeBase64String(trimmed),
201 + );
202
198 - if (__DEBUG__) {
199 - console.groupCollapsed(
200 - 'extractAndLoadSourceMapJSON() Inline source map',
203 + const sourceMapJSON = withSyncPerformanceMark(
204 + 'JSON.parse(decoded)',
205 + () => JSON.parse(decoded),
206 );
202 - console.log(sourceMapJSON);
203 - console.groupEnd();
204 - }
207
206 - // Hook source might be a URL like "https://4syus.csb.app/src/App.js"
207 - // Parsed source map might be a partial path like "src/App.js"
208 - if (sourceMapIncludesSource(sourceMapJSON, runtimeSourceURL)) {
209 - hookSourceAndMetadata.sourceMapJSON = sourceMapJSON;
208 + if (__DEBUG__) {
209 + console.groupCollapsed(
210 + 'extractAndLoadSourceMapJSON() Inline source map',
211 + );
212 + console.log(sourceMapJSON);
213 + console.groupEnd();
214 + }
215 +
216 + // Hook source might be a URL like "https://4syus.csb.app/src/App.js"
217 + // Parsed source map might be a partial path like "src/App.js"
218 + if (sourceMapIncludesSource(sourceMapJSON, runtimeSourceURL)) {
219 + hookSourceAndMetadata.sourceMapJSON = sourceMapJSON;
220
211 - // OPTIMIZATION If we've located a source map for this source,
212 - // we'll use it to retrieve the original source (to extract hook names).
213 - // We only fall back to parsing the full source code is when there's no source map.
214 - // The source is (potentially) very large,
215 - // So we can avoid the overhead of serializing it unnecessarily.
216 - hookSourceAndMetadata.runtimeSourceCode = null;
221 + // OPTIMIZATION If we've located a source map for this source,
222 + // we'll use it to retrieve the original source (to extract hook names).
223 + // We only fall back to parsing the full source code is when there's no source map.
224 + // The source is (potentially) very large,
225 + // So we can avoid the overhead of serializing it unnecessarily.
226 + hookSourceAndMetadata.runtimeSourceCode = null;
227
218 - break;
228 + break;
229 + }
230 + } catch (error) {
231 + // We've likely encountered a string in the source code that looks like a source map but isn't.
232 + // Maybe the source code contains a "sourceMappingURL" comment or soething similar.
233 + // In either case, let's skip this and keep looking.
234 }
235 } else {
236 externalSourceMapURLs.push(sourceMappingURL);
packages/react-devtools-shared/src/inspectedElementCache.js
+23 -17
@@ -116,28 +116,34 @@ export function inspectElement(
116 return null;
117 }
118
119 - inspectElementMutableSource({
119 + const onSuccess = ([inspectedElement: InspectedElementFrontend]) => {
120 + const resolvedRecord = ((newRecord: any): ResolvedRecord<InspectedElementFrontend>);
121 + resolvedRecord.status = Resolved;
122 + resolvedRecord.value = inspectedElement;
123 + wake();
124 + };
125 +
126 + const onError = error => {
127 + if (newRecord.status === Pending) {
128 + const rejectedRecord = ((newRecord: any): RejectedRecord);
129 + rejectedRecord.status = Rejected;
130 + rejectedRecord.value = `Could not inspect element with id "${element.id}". Error thrown:\n${error.message}`;
131 + wake();
132 + }
133 + };
134 +
135 + const thenable = inspectElementMutableSource({
136 bridge,
137 element,
138 path,
139 rendererID: ((rendererID: any): number),
124 - }).then(
125 - ([inspectedElement: InspectedElementFrontend]) => {
126 - const resolvedRecord = ((newRecord: any): ResolvedRecord<InspectedElementFrontend>);
127 - resolvedRecord.status = Resolved;
128 - resolvedRecord.value = inspectedElement;
129 - wake();
130 - },
140 + });
141 + thenable.then(onSuccess, onError);
142 +
143 + if (typeof (thenable: any).catch === 'function') {
144 + (thenable: any).catch();
145 + }
146
132 - error => {
133 - if (newRecord.status === Pending) {
134 - const rejectedRecord = ((newRecord: any): RejectedRecord);
135 - rejectedRecord.status = Rejected;
136 - rejectedRecord.value = `Could not inspect element with id "${element.id}". Error thrown:\n${error.message}`;
137 - wake();
138 - }
139 - },
140 - );
147 map.set(element, record);
148 }
149