react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
188 lines (174 loc) • 7.08 kB
JavaScript
import { registerReanimatedError, reportFatalErrorOnJS } from "./errors.js";
import { DEFAULT_LOGGER_CONFIG, logToLogBoxAndConsole, registerLoggerConfig, replaceLoggerImplementation } from "./logger/index.js";
import { mockedRequestAnimationFrame } from "./mockedRequestAnimationFrame.js";
import { isChromeDebugger, isJest, isWeb, shouldBeUseWeb } from "./PlatformChecker.js";
import { callMicrotasks, executeOnUIRuntimeSync, runOnJS, runOnUIImmediately, setupMicrotasks } from "./threads.js";
const IS_JEST = isJest();
const SHOULD_BE_USE_WEB = shouldBeUseWeb();
const IS_CHROME_DEBUGGER = isChromeDebugger();
// Override the logFunction implementation with the one that adds logs
// with better stack traces to the LogBox (need to override it after `runOnJS`
// is defined).
function overrideLogFunctionImplementation() {
'worklet';
replaceLoggerImplementation(data => {
'worklet';
runOnJS(logToLogBoxAndConsole)(data);
});
}
// Register logger config and replace the log function implementation in
// the React runtime global scope
registerLoggerConfig(DEFAULT_LOGGER_CONFIG);
overrideLogFunctionImplementation();
// this is for web implementation
if (SHOULD_BE_USE_WEB) {
global._WORKLET = false;
global._log = console.log;
global._getAnimationTimestamp = () => performance.now();
} else {
// Register ReanimatedError and logger config in the UI runtime global scope.
// (we are using `executeOnUIRuntimeSync` here to make sure that the changes
// are applied before any async operations are executed on the UI runtime)
executeOnUIRuntimeSync(registerReanimatedError)();
executeOnUIRuntimeSync(registerLoggerConfig)(DEFAULT_LOGGER_CONFIG);
executeOnUIRuntimeSync(overrideLogFunctionImplementation)();
}
// callGuard is only used with debug builds
export function callGuardDEV(fn, ...args) {
'worklet';
try {
return fn(...args);
} catch (e) {
if (global.__ErrorUtils) {
global.__ErrorUtils.reportFatalError(e);
} else {
throw e;
}
}
}
export function setupCallGuard() {
'worklet';
global.__callGuardDEV = callGuardDEV;
global.__ErrorUtils = {
reportFatalError: error => {
runOnJS(reportFatalErrorOnJS)({
message: error.message,
stack: error.stack
});
}
};
}
/**
* Currently there seems to be a bug in the JSI layer which causes a crash when
* we try to copy some of the console methods, i.e. `clear` or `dirxml`.
*
* The crash happens only in React Native 0.75. It's not reproducible in neither
* 0.76 nor 0.74. It also happens only in the configuration of a debug app and
* production bundle.
*
* I haven't yet discovered what exactly causes the crash. It's tied to the
* console methods sometimes being `HostFunction`s. Therefore, as a workaround
* we don't copy the methods as they are in the original console object, we copy
* JavaScript wrappers instead.
*/
function createMemorySafeCapturableConsole() {
const consoleCopy = Object.fromEntries(Object.entries(console).map(([methodName, method]) => {
const methodWrapper = function methodWrapper(...args) {
return method(...args);
};
if (method.name) {
/**
* Set the original method name as the wrapper name if available.
*
* It might be unnecessary but if we want to fully mimic the console
* object we should take into the account the fact some code might rely
* on the method name.
*/
Object.defineProperty(methodWrapper, 'name', {
value: method.name,
writable: false
});
}
return [methodName, methodWrapper];
}));
return consoleCopy;
}
// We really have to create a copy of console here. Function runOnJS we use on elements inside
// this object makes it not configurable
const capturableConsole = createMemorySafeCapturableConsole();
export function setupConsole() {
'worklet';
if (!IS_CHROME_DEBUGGER) {
// @ts-ignore TypeScript doesn't like that there are missing methods in console object, but we don't provide all the methods for the UI runtime console version
global.console = {
/* eslint-disable @typescript-eslint/unbound-method */
assert: runOnJS(capturableConsole.assert),
debug: runOnJS(capturableConsole.debug),
log: runOnJS(capturableConsole.log),
warn: runOnJS(capturableConsole.warn),
error: runOnJS(capturableConsole.error),
info: runOnJS(capturableConsole.info)
/* eslint-enable @typescript-eslint/unbound-method */
};
}
}
function setupRequestAnimationFrame() {
'worklet';
// Jest mocks requestAnimationFrame API and it does not like if that mock gets overridden
// so we avoid doing requestAnimationFrame batching in Jest environment.
const nativeRequestAnimationFrame = global.requestAnimationFrame;
let animationFrameCallbacks = [];
let flushRequested = false;
global.__flushAnimationFrame = frameTimestamp => {
const currentCallbacks = animationFrameCallbacks;
animationFrameCallbacks = [];
currentCallbacks.forEach(f => f(frameTimestamp));
callMicrotasks();
};
global.requestAnimationFrame = callback => {
animationFrameCallbacks.push(callback);
if (!flushRequested) {
flushRequested = true;
nativeRequestAnimationFrame(timestamp => {
flushRequested = false;
global.__frameTimestamp = timestamp;
global.__flushAnimationFrame(timestamp);
global.__frameTimestamp = undefined;
});
}
// Reanimated currently does not support cancelling callbacks requested with
// requestAnimationFrame. We return -1 as identifier which isn't in line
// with the spec but it should give users better clue in case they actually
// attempt to store the value returned from rAF and use it for cancelling.
return -1;
};
}
export function initializeUIRuntime(ReanimatedModule) {
if (isWeb()) {
return;
}
if (!ReanimatedModule) {
// eslint-disable-next-line reanimated/use-reanimated-error
throw new Error('[Reanimated] Reanimated is trying to initialize the UI runtime without a valid ReanimatedModule');
}
if (IS_JEST) {
// requestAnimationFrame react-native jest's setup is incorrect as it polyfills
// the method directly using setTimeout, therefore the callback doesn't get the
// expected timestamp as the only argument: https://github.com/facebook/react-native/blob/main/packages/react-native/jest/setup.js#L28
// We override this setup here to make sure that callbacks get the proper timestamps
// when executed. For non-jest environments we define requestAnimationFrame in setupRequestAnimationFrame
// @ts-ignore TypeScript uses Node definition for rAF, setTimeout, etc which returns a Timeout object rather than a number
globalThis.requestAnimationFrame = mockedRequestAnimationFrame;
}
runOnUIImmediately(() => {
'worklet';
setupCallGuard();
setupConsole();
if (!SHOULD_BE_USE_WEB) {
setupMicrotasks();
setupRequestAnimationFrame();
}
})();
}
//# sourceMappingURL=initializers.js.map
;