From 28a574ea8f5d0efa95f861abca26a67af47dd0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 27 Oct 2022 02:43:17 -0400 Subject: [PATCH] Try assigning fetch to globalThis if global assignment fails (#25571) In case it's a more modern yet rigid environment. --- .eslintrc.js | 1 + packages/react/src/ReactFetch.js | 164 ++++++++++---------- scripts/flow/environment.js | 2 + scripts/rollup/validate/eslintrc.cjs2015.js | 1 + scripts/rollup/validate/eslintrc.esm.js | 1 + scripts/rollup/validate/eslintrc.fb.js | 1 + scripts/rollup/validate/eslintrc.rn.js | 1 + scripts/rollup/validate/eslintrc.umd.js | 2 +- 8 files changed, 93 insertions(+), 80 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 9445bacebe835..c0b751ad853aa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -277,5 +277,6 @@ module.exports = { trustedTypes: 'readonly', IS_REACT_ACT_ENVIRONMENT: 'readonly', AsyncLocalStorage: 'readonly', + globalThis: 'readonly', }, }; diff --git a/packages/react/src/ReactFetch.js b/packages/react/src/ReactFetch.js index 7c39cefdf3f92..befdcef4de850 100644 --- a/packages/react/src/ReactFetch.js +++ b/packages/react/src/ReactFetch.js @@ -42,92 +42,98 @@ function generateCacheKey(request: Request): string { if (enableCache && enableFetchInstrumentation) { if (typeof fetch === 'function') { const originalFetch = fetch; - try { - // eslint-disable-next-line no-native-reassign - fetch = function fetch( - resource: URL | RequestInfo, - options?: RequestOptions, + const cachedFetch = function fetch( + resource: URL | RequestInfo, + options?: RequestOptions, + ) { + const dispatcher = ReactCurrentCache.current; + if (!dispatcher) { + // We're outside a cached scope. + return originalFetch(resource, options); + } + if ( + options && + options.signal && + options.signal !== dispatcher.getCacheSignal() ) { - const dispatcher = ReactCurrentCache.current; - if (!dispatcher) { - // We're outside a cached scope. - return originalFetch(resource, options); - } + // If we're passed a signal that is not ours, then we assume that + // someone else controls the lifetime of this object and opts out of + // caching. It's effectively the opt-out mechanism. + // Ideally we should be able to check this on the Request but + // it always gets initialized with its own signal so we don't + // know if it's supposed to override - unless we also override the + // Request constructor. + return originalFetch(resource, options); + } + // Normalize the Request + let url: string; + let cacheKey: string; + if (typeof resource === 'string' && !options) { + // Fast path. + cacheKey = simpleCacheKey; + url = resource; + } else { + // Normalize the request. + const request = new Request(resource, options); if ( - options && - options.signal && - options.signal !== dispatcher.getCacheSignal() + (request.method !== 'GET' && request.method !== 'HEAD') || + // $FlowFixMe: keepalive is real + request.keepalive ) { - // If we're passed a signal that is not ours, then we assume that - // someone else controls the lifetime of this object and opts out of - // caching. It's effectively the opt-out mechanism. - // Ideally we should be able to check this on the Request but - // it always gets initialized with its own signal so we don't - // know if it's supposed to override - unless we also override the - // Request constructor. + // We currently don't dedupe requests that might have side-effects. Those + // have to be explicitly cached. We assume that the request doesn't have a + // body if it's GET or HEAD. + // keepalive gets treated the same as if you passed a custom cache signal. return originalFetch(resource, options); } - // Normalize the Request - let url: string; - let cacheKey: string; - if (typeof resource === 'string' && !options) { - // Fast path. - cacheKey = simpleCacheKey; - url = resource; - } else { - // Normalize the request. - const request = new Request(resource, options); - if ( - (request.method !== 'GET' && request.method !== 'HEAD') || - // $FlowFixMe: keepalive is real - request.keepalive - ) { - // We currently don't dedupe requests that might have side-effects. Those - // have to be explicitly cached. We assume that the request doesn't have a - // body if it's GET or HEAD. - // keepalive gets treated the same as if you passed a custom cache signal. - return originalFetch(resource, options); + cacheKey = generateCacheKey(request); + url = request.url; + } + const cache = dispatcher.getCacheForType(createFetchCache); + const cacheEntries = cache.get(url); + let match; + if (cacheEntries === undefined) { + // We pass the original arguments here in case normalizing the Request + // doesn't include all the options in this environment. + match = originalFetch(resource, options); + cache.set(url, [cacheKey, match]); + } else { + // We use an array as the inner data structure since it's lighter and + // we typically only expect to see one or two entries here. + for (let i = 0, l = cacheEntries.length; i < l; i += 2) { + const key = cacheEntries[i]; + const value = cacheEntries[i + 1]; + if (key === cacheKey) { + match = value; + // I would've preferred a labelled break but lint says no. + return match.then(response => response.clone()); } - cacheKey = generateCacheKey(request); - url = request.url; } - const cache = dispatcher.getCacheForType(createFetchCache); - const cacheEntries = cache.get(url); - let match; - if (cacheEntries === undefined) { - // We pass the original arguments here in case normalizing the Request - // doesn't include all the options in this environment. - match = originalFetch(resource, options); - cache.set(url, [cacheKey, match]); - } else { - // We use an array as the inner data structure since it's lighter and - // we typically only expect to see one or two entries here. - for (let i = 0, l = cacheEntries.length; i < l; i += 2) { - const key = cacheEntries[i]; - const value = cacheEntries[i + 1]; - if (key === cacheKey) { - match = value; - // I would've preferred a labelled break but lint says no. - return match.then(response => response.clone()); - } - } - match = originalFetch(resource, options); - cacheEntries.push(cacheKey, match); - } - // We clone the response so that each time you call this you get a new read - // of the body so that it can be read multiple times. - return match.then(response => response.clone()); - }; - // We don't expect to see any extra properties on fetch but if there are any, - // copy them over. Useful for extended fetch environments or mocks. - Object.assign(fetch, originalFetch); - } catch (error) { - // Log even in production just to make sure this is seen if only prod is frozen. - // eslint-disable-next-line react-internal/no-production-logging - console.warn( - 'React was unable to patch the fetch() function in this environment. ' + - 'Suspensey APIs might not work correctly as a result.', - ); + match = originalFetch(resource, options); + cacheEntries.push(cacheKey, match); + } + // We clone the response so that each time you call this you get a new read + // of the body so that it can be read multiple times. + return match.then(response => response.clone()); + }; + // We don't expect to see any extra properties on fetch but if there are any, + // copy them over. Useful for extended fetch environments or mocks. + Object.assign(cachedFetch, originalFetch); + try { + // eslint-disable-next-line no-native-reassign + fetch = cachedFetch; + } catch (error1) { + try { + // In case assigning it globally fails, try globalThis instead just in case it exists. + globalThis.fetch = cachedFetch; + } catch (error2) { + // Log even in production just to make sure this is seen if only prod is frozen. + // eslint-disable-next-line react-internal/no-production-logging + console.warn( + 'React was unable to patch the fetch() function in this environment. ' + + 'Suspensey APIs might not work correctly as a result.', + ); + } } } } diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js index ffb3728c17729..fb61783e3d6d8 100644 --- a/scripts/flow/environment.js +++ b/scripts/flow/environment.js @@ -18,6 +18,8 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{ inject: ?((stuff: Object) => void) };*/ +declare var globalThis: Object; + declare var queueMicrotask: (fn: Function) => void; declare var reportError: (error: mixed) => void; diff --git a/scripts/rollup/validate/eslintrc.cjs2015.js b/scripts/rollup/validate/eslintrc.cjs2015.js index 29847ce1ec540..531d5923a37e8 100644 --- a/scripts/rollup/validate/eslintrc.cjs2015.js +++ b/scripts/rollup/validate/eslintrc.cjs2015.js @@ -16,6 +16,7 @@ module.exports = { WeakSet: 'readonly', Uint16Array: 'readonly', Reflect: 'readonly', + globalThis: 'readonly', // Vendor specific MSApp: 'readonly', __REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.esm.js b/scripts/rollup/validate/eslintrc.esm.js index 32640e55edc23..a8fd18cb304b6 100644 --- a/scripts/rollup/validate/eslintrc.esm.js +++ b/scripts/rollup/validate/eslintrc.esm.js @@ -15,6 +15,7 @@ module.exports = { WeakSet: 'readonly', Uint16Array: 'readonly', Reflect: 'readonly', + globalThis: 'readonly', // Vendor specific MSApp: 'readonly', __REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.fb.js b/scripts/rollup/validate/eslintrc.fb.js index 693e648a0b1d4..0e10648bda049 100644 --- a/scripts/rollup/validate/eslintrc.fb.js +++ b/scripts/rollup/validate/eslintrc.fb.js @@ -15,6 +15,7 @@ module.exports = { WeakSet: 'readonly', Uint16Array: 'readonly', Reflect: 'readonly', + globalThis: 'readonly', // Vendor specific MSApp: 'readonly', __REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.rn.js b/scripts/rollup/validate/eslintrc.rn.js index d78cad2a2b641..f87cac7af86c7 100644 --- a/scripts/rollup/validate/eslintrc.rn.js +++ b/scripts/rollup/validate/eslintrc.rn.js @@ -14,6 +14,7 @@ module.exports = { WeakMap: 'readonly', WeakSet: 'readonly', Reflect: 'readonly', + globalThis: 'readonly', // Vendor specific MSApp: 'readonly', __REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly', diff --git a/scripts/rollup/validate/eslintrc.umd.js b/scripts/rollup/validate/eslintrc.umd.js index 06c147c3638f5..c9e912aec88dd 100644 --- a/scripts/rollup/validate/eslintrc.umd.js +++ b/scripts/rollup/validate/eslintrc.umd.js @@ -14,6 +14,7 @@ module.exports = { WeakSet: 'readonly', Uint16Array: 'readonly', Reflect: 'readonly', + globalThis: 'readonly', // Vendor specific MSApp: 'readonly', __REACT_DEVTOOLS_GLOBAL_HOOK__: 'readonly', @@ -24,7 +25,6 @@ module.exports = { module: 'readonly', define: 'readonly', require: 'readonly', - globalThis: 'readonly', global: 'readonly', // Internet Explorer setImmediate: 'readonly',