import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {
Family,
RefreshUpdate,
ScheduleRefresh,
ScheduleRoot,
SetRefreshHandler,
} from 'react-reconciler/src/ReactFiberHotReloading';
import type {ReactNodeList} from 'shared/ReactTypes';
import {REACT_MEMO_TYPE, REACT_FORWARD_REF_TYPE} from 'shared/ReactSymbols';
type Signature = {
ownKey: string,
forceReset: boolean,
fullKey: string | null,
getCustomHooks: () => Array<Function>,
};
type RendererHelpers = {
scheduleRefresh: ScheduleRefresh,
scheduleRoot: ScheduleRoot,
setRefreshHandler: SetRefreshHandler,
};
if (!__DEV__) {
throw new Error(
'React Refresh runtime should not be included in the production bundle.',
);
}
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
const allFamiliesByID: Map<string, Family> = new Map();
const allFamiliesByType: WeakMap<any, Family> | Map<any, Family> =
new PossiblyWeakMap();
const allSignaturesByType: WeakMap<any, Signature> | Map<any, Signature> =
new PossiblyWeakMap();
const updatedFamiliesByType: WeakMap<any, Family> | Map<any, Family> =
new PossiblyWeakMap();
let pendingUpdates: Array<[Family, any]> = [];
const helpersByRendererID: Map<number, RendererHelpers> = new Map();
const helpersByRoot: Map<FiberRoot, RendererHelpers> = new Map();
const mountedRoots: Set<FiberRoot> = new Set();
const failedRoots: Set<FiberRoot> = new Set();
const rootElements: WeakMap<any, ReactNodeList> | null =
typeof WeakMap === 'function' ? new WeakMap() : null;
let isPerformingRefresh = false;
function computeFullKey(signature: Signature): string {
if (signature.fullKey !== null) {
return signature.fullKey;
}
let fullKey: string = signature.ownKey;
let hooks;
try {
hooks = signature.getCustomHooks();
} catch (err) {
signature.forceReset = true;
signature.fullKey = fullKey;
return fullKey;
}
for (let i = 0; i < hooks.length; i++) {
const hook = hooks[i];
if (typeof hook !== 'function') {
signature.forceReset = true;
signature.fullKey = fullKey;
return fullKey;
}
const nestedHookSignature = allSignaturesByType.get(hook);
if (nestedHookSignature === undefined) {
continue;
}
const nestedHookKey = computeFullKey(nestedHookSignature);
if (nestedHookSignature.forceReset) {
signature.forceReset = true;
}
fullKey += '\n---\n' + nestedHookKey;
}
signature.fullKey = fullKey;
return fullKey;
}
function haveEqualSignatures(prevType: any, nextType: any) {
const prevSignature = allSignaturesByType.get(prevType);
const nextSignature = allSignaturesByType.get(nextType);
if (prevSignature === undefined && nextSignature === undefined) {
return true;
}
if (prevSignature === undefined || nextSignature === undefined) {
return false;
}
if (computeFullKey(prevSignature) !== computeFullKey(nextSignature)) {
return false;
}
if (nextSignature.forceReset) {
return false;
}
return true;
}
function isReactClass(type: any) {
return type.prototype && type.prototype.isReactComponent;
}
function canPreserveStateBetween(prevType: any, nextType: any) {
if (isReactClass(prevType) || isReactClass(nextType)) {
return false;
}
if (haveEqualSignatures(prevType, nextType)) {
return true;
}
return false;
}
function resolveFamily(type: any) {
return updatedFamiliesByType.get(type);
}
function cloneMap<K, V>(map: Map<K, V>): Map<K, V> {
const clone = new Map<K, V>();
map.forEach((value, key) => {
clone.set(key, value);
});
return clone;
}
function cloneSet<T>(set: Set<T>): Set<T> {
const clone = new Set<T>();
set.forEach(value => {
clone.add(value);
});
return clone;
}
function getProperty(object: any, property: string) {
try {
return object[property];
} catch (err) {
return undefined;
}
}
export function performReactRefresh(): RefreshUpdate | null {
if (!__DEV__) {
throw new Error(
'Unexpected call to React Refresh in a production environment.',
);
}
if (pendingUpdates.length === 0) {
return null;
}
if (isPerformingRefresh) {
return null;
}
isPerformingRefresh = true;
try {
const staleFamilies = new Set<Family>();
const updatedFamilies = new Set<Family>();
const updates = pendingUpdates;
pendingUpdates = [];
updates.forEach(([family, nextType]) => {
const prevType = family.current;
updatedFamiliesByType.set(prevType, family);
updatedFamiliesByType.set(nextType, family);
family.current = nextType;
if (canPreserveStateBetween(prevType, nextType)) {
updatedFamilies.add(family);
} else {
staleFamilies.add(family);
}
});
const update: RefreshUpdate = {
updatedFamilies,
staleFamilies,
};
helpersByRendererID.forEach(helpers => {
helpers.setRefreshHandler(resolveFamily);
});
let didError = false;
let firstError = null;
const failedRootsSnapshot = cloneSet(failedRoots);
const mountedRootsSnapshot = cloneSet(mountedRoots);
const helpersByRootSnapshot = cloneMap(helpersByRoot);
failedRootsSnapshot.forEach(root => {
const helpers = helpersByRootSnapshot.get(root);
if (helpers === undefined) {
throw new Error(
'Could not find helpers for a root. This is a bug in React Refresh.',
);
}
if (!failedRoots.has(root)) {
}
if (rootElements === null) {
return;
}
if (!rootElements.has(root)) {
return;
}
const element = rootElements.get(root);
try {
helpers.scheduleRoot(root, element);
} catch (err) {
if (!didError) {
didError = true;
firstError = err;
}
}
});
mountedRootsSnapshot.forEach(root => {
const helpers = helpersByRootSnapshot.get(root);
if (helpers === undefined) {
throw new Error(
'Could not find helpers for a root. This is a bug in React Refresh.',
);
}
if (!mountedRoots.has(root)) {
}
try {
helpers.scheduleRefresh(root, update);
} catch (err) {
if (!didError) {
didError = true;
firstError = err;
}
}
});
if (didError) {
throw firstError;
}
return update;
} finally {
isPerformingRefresh = false;
}
}
export function register(type: any, id: string): void {
if (__DEV__) {
if (type === null) {
return;
}
if (typeof type !== 'function' && typeof type !== 'object') {
return;
}
if (allFamiliesByType.has(type)) {
return;
}
let family = allFamiliesByID.get(id);
if (family === undefined) {
family = {current: type};
allFamiliesByID.set(id, family);
} else {
pendingUpdates.push([family, type]);
}
allFamiliesByType.set(type, family);
if (typeof type === 'object' && type !== null) {
switch (getProperty(type, '$$typeof')) {
case REACT_FORWARD_REF_TYPE:
register(type.render, id + '$render');
break;
case REACT_MEMO_TYPE:
register(type.type, id + '$type');
break;
}
}
} else {
throw new Error(
'Unexpected call to React Refresh in a production environment.',
);
}
}
export function setSignature(
type: any,
key: string,
forceReset?: boolean = false,
getCustomHooks?: () => Array<Function>,
): void {
if (__DEV__) {
if (!allSignaturesByType.has(type)) {
allSignaturesByType.set(type, {
forceReset,
ownKey: key,
fullKey: null,
getCustomHooks: getCustomHooks || (() => []),
});
}
if (typeof type === 'object' && type !== null) {
switch (getProperty(type, '$$typeof')) {
case REACT_FORWARD_REF_TYPE:
setSignature(type.render, key, forceReset, getCustomHooks);
break;
case REACT_MEMO_TYPE:
setSignature(type.type, key, forceReset, getCustomHooks);
break;
}
}
} else {
throw new Error(
'Unexpected call to React Refresh in a production environment.',
);
}
}
export function collectCustomHooksForSignature(type: any) {
if (__DEV__) {
const signature = allSignaturesByType.get(type);
if (signature !== undefined) {
computeFullKey(signature);
}
} else {
throw new Error(
'Unexpected call to React Refresh in a production environment.',
);
}
}
export function getFamilyByID(id: string): Family | void {
if (__DEV__) {
return allFamiliesByID.get(id);
} else {
throw new Error(
'Unexpected call to React Refresh in a production environment.',
);
}
}
export function getFamilyByType(type: any): Family | void {
if (__DEV__) {
return allFamiliesByType.get(type);
} else {
throw new Error(
'Unexpected call to React Refresh in a production environment.',
);
}
}
export function injectIntoGlobalHook(globalObject: any): void {
if (__DEV__) {
let hook = globalObject.__REACT_DEVTOOLS_GLOBAL_HOOK__;
if (hook === undefined) {
let nextID = 0;
globalObject.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook = {
renderers: new Map(),
supportsFiber: true,
inject: injected => nextID++,
onScheduleFiberRoot: (
id: number,
root: FiberRoot,
children: ReactNodeList,
) => {},
onCommitFiberRoot: (
id: number,
root: FiberRoot,
maybePriorityLevel: mixed,
didError: boolean,
) => {},
onCommitFiberUnmount() {},
};
}
if (hook.isDisabled) {
console['warn'](
'Something has shimmed the React DevTools global hook (__REACT_DEVTOOLS_GLOBAL_HOOK__). ' +
'Fast Refresh is not compatible with this shim and will be disabled.',
);
return;
}
const oldInject = hook.inject;
hook.inject = function (this: mixed, injected) {
const id = oldInject.apply(this, arguments);
if (
typeof injected.scheduleRefresh === 'function' &&
typeof injected.setRefreshHandler === 'function'
) {
helpersByRendererID.set(id, ((injected: any): RendererHelpers));
}
return id;
};
hook.renderers.forEach((injected, id) => {
if (
typeof injected.scheduleRefresh === 'function' &&
typeof injected.setRefreshHandler === 'function'
) {
helpersByRendererID.set(id, ((injected: any): RendererHelpers));
}
});
const oldOnCommitFiberRoot = hook.onCommitFiberRoot;
const oldOnScheduleFiberRoot = hook.onScheduleFiberRoot || (() => {});
hook.onScheduleFiberRoot = function (
this: mixed,
id: number,
root: FiberRoot,
children: ReactNodeList,
) {
if (!isPerformingRefresh) {
failedRoots.delete(root);
if (rootElements !== null) {
rootElements.set(root, children);
}
}
return oldOnScheduleFiberRoot.apply(this, arguments);
};
hook.onCommitFiberRoot = function (
this: mixed,
id: number,
root: FiberRoot,
maybePriorityLevel: mixed,
didError: boolean,
) {
const helpers = helpersByRendererID.get(id);
if (helpers !== undefined) {
helpersByRoot.set(root, helpers);
const current = root.current;
const alternate = current.alternate;
if (alternate !== null) {
const wasMounted =
alternate.memoizedState != null &&
alternate.memoizedState.element != null &&
mountedRoots.has(root);
const isMounted =
current.memoizedState != null &&
current.memoizedState.element != null;
if (!wasMounted && isMounted) {
mountedRoots.add(root);
failedRoots.delete(root);
} else if (wasMounted && isMounted) {
} else if (wasMounted && !isMounted) {
mountedRoots.delete(root);
if (didError) {
failedRoots.add(root);
} else {
helpersByRoot.delete(root);
}
} else if (!wasMounted && !isMounted) {
if (didError) {
failedRoots.add(root);
}
}
} else {
mountedRoots.add(root);
}
}
return oldOnCommitFiberRoot.apply(this, arguments);
};
} else {
throw new Error(
'Unexpected call to React Refresh in a production environment.',
);
}
}
export function hasUnrecoverableErrors(): boolean {
return false;
}
export function _getMountedRootCount(): number {
if (__DEV__) {
return mountedRoots.size;
} else {
throw new Error(
'Unexpected call to React Refresh in a production environment.',
);
}
}
export function createSignatureFunctionForTransform(): <T>(
type: T,
key: string,
forceReset?: boolean,
getCustomHooks?: () => Array<Function>,
) => T | void {
if (__DEV__) {
let savedType: mixed;
let hasCustomHooks: boolean;
let didCollectHooks = false;
return function <T>(
type: T,
key: string,
forceReset?: boolean,
getCustomHooks?: () => Array<Function>,
): T | void {
if (typeof key === 'string') {
if (!savedType) {
savedType = type;
hasCustomHooks = typeof getCustomHooks === 'function';
}
if (
type != null &&
(typeof type === 'function' || typeof type === 'object')
) {
setSignature(type, key, forceReset, getCustomHooks);
}
return type;
} else {
if (!didCollectHooks && hasCustomHooks) {
didCollectHooks = true;
collectCustomHooksForSignature(savedType);
}
}
};
} else {
throw new Error(
'Unexpected call to React Refresh in a production environment.',
);
}
}
export function isLikelyComponentType(type: any): boolean {
if (__DEV__) {
switch (typeof type) {
case 'function': {
if (type.prototype != null) {
if (type.prototype.isReactComponent) {
return true;
}
const ownNames = Object.getOwnPropertyNames(type.prototype);
if (ownNames.length > 1 || ownNames[0] !== 'constructor') {
return false;
}
if (type.prototype.__proto__ !== Object.prototype) {
return false;
}
}
const name = type.name || type.displayName;
return typeof name === 'string' && /^[A-Z]/.test(name);
}
case 'object': {
if (type != null) {
switch (getProperty(type, '$$typeof')) {
case REACT_FORWARD_REF_TYPE:
case REACT_MEMO_TYPE:
return true;
default:
return false;
}
}
return false;
}
default: {
return false;
}
}
} else {
throw new Error(
'Unexpected call to React Refresh in a production environment.',
);
}
}