diff --git a/apps/common-app/src/apps/reanimated/App.tsx b/apps/common-app/src/apps/reanimated/App.tsx
index 2ef8af9cb099..086f53d91057 100644
--- a/apps/common-app/src/apps/reanimated/App.tsx
+++ b/apps/common-app/src/apps/reanimated/App.tsx
@@ -5,6 +5,7 @@ import type { StackNavigationProp } from '@react-navigation/stack';
import React, { memo } from 'react';
import {
FlatList,
+ Platform,
Pressable,
ScrollView,
StyleSheet,
@@ -77,7 +78,7 @@ function HomeScreen({ navigation }: HomeScreenProps) {
setTimeout(() => setWasClicked([...wasClicked, name]), 500);
}
}}
- missingOnFabric={EXAMPLES[name].missingOnFabric}
+ disabled={EXAMPLES[name].disabledPlatforms?.includes(Platform.OS)}
wasClicked={wasClicked.includes(name)}
/>
)}
@@ -91,29 +92,22 @@ function HomeScreen({ navigation }: HomeScreenProps) {
interface ItemProps {
icon?: string;
title: string;
+ disabled?: boolean;
onPress: () => void;
- missingOnFabric?: boolean;
wasClicked?: boolean;
}
-function Item({
- icon,
- title,
- onPress,
- missingOnFabric,
- wasClicked,
-}: ItemProps) {
- const isDisabled = missingOnFabric;
+function Item({ icon, title, onPress, disabled, wasClicked }: ItemProps) {
const Button = IS_MACOS ? Pressable : RectButton;
return (
diff --git a/apps/common-app/src/apps/reanimated/examples/index.ts b/apps/common-app/src/apps/reanimated/examples/index.ts
index afaa5adccad3..885d4f5dcbf4 100644
--- a/apps/common-app/src/apps/reanimated/examples/index.ts
+++ b/apps/common-app/src/apps/reanimated/examples/index.ts
@@ -135,14 +135,28 @@ import WorkletExample from './WorkletExample';
import WorkletFactoryCrash from './WorkletFactoryCrashExample';
import WorkletRuntimeExample from './WorkletRuntimeExample';
-interface Example {
+export const REAPlatform = {
+ IOS: 'ios',
+ ANDROID: 'android',
+ MACOS: 'macos',
+ WEB: 'web',
+};
+
+export interface Example {
icon?: string;
title: string;
screen: React.FC;
- missingOnFabric?: boolean;
+ disabledPlatforms?: (typeof REAPlatform)[keyof typeof REAPlatform][];
}
export const EXAMPLES: Record = {
+ // About
+ AboutExample: {
+ icon: 'âšī¸',
+ title: 'About',
+ screen: AboutExample,
+ },
+
// Empty example for test purposes
EmptyExample: {
icon: 'đģ',
@@ -163,11 +177,13 @@ export const EXAMPLES: Record = {
icon: 'âī¸',
title: 'RuntimeTestsExample',
screen: RuntimeTestsExample,
+ disabledPlatforms: [REAPlatform.WEB],
},
Synchronizable: {
icon: 'đ',
title: 'Synchronizable performance',
screen: SynchronizablePerformanceExample,
+ disabledPlatforms: [REAPlatform.WEB],
},
ReactFreeze: {
icon: 'âī¸',
@@ -183,6 +199,7 @@ export const EXAMPLES: Record = {
icon: 'đââī¸',
title: 'Worklet runtime',
screen: WorkletRuntimeExample,
+ disabledPlatforms: [REAPlatform.WEB],
},
ModifyExample: {
icon: 'đĒ',
@@ -203,6 +220,7 @@ export const EXAMPLES: Record = {
icon: 'đĨļ',
title: 'Serializable freezing',
screen: SerializableFreezingExample,
+ disabledPlatforms: [REAPlatform.WEB],
},
InvalidReadWriteExample: {
icon: 'đ',
@@ -218,6 +236,7 @@ export const EXAMPLES: Record = {
icon: 'đ',
title: 'Copy serializable performance test',
screen: CopySerializablePerformanceTest,
+ disabledPlatforms: [REAPlatform.WEB],
},
FlatListWithLayoutAnimations: {
icon: 'đģ',
@@ -225,14 +244,6 @@ export const EXAMPLES: Record = {
screen: FlatListWithLayoutAnimations,
},
- // About
-
- AboutExample: {
- icon: 'âšī¸',
- title: 'About',
- screen: AboutExample,
- },
-
// Showcase
BokehExample: {
@@ -299,6 +310,7 @@ export const EXAMPLES: Record = {
icon: 'đē',
title: 'Screen transition',
screen: ScreenTransitionExample,
+ disabledPlatforms: [REAPlatform.WEB],
},
// Basic examples
@@ -496,6 +508,11 @@ export const EXAMPLES: Record = {
icon: 'đ',
title: 'Without Babel plugin',
screen: WithoutBabelPluginExample,
+ disabledPlatforms: [
+ REAPlatform.ANDROID,
+ REAPlatform.IOS,
+ REAPlatform.MACOS,
+ ],
},
MatrixExample: {
icon: 'đ§Ž',
@@ -531,7 +548,12 @@ export const EXAMPLES: Record = {
icon: 'đ',
title: 'getViewProp',
screen: GetViewPropExample,
- missingOnFabric: true,
+ disabledPlatforms: [
+ REAPlatform.WEB,
+ REAPlatform.ANDROID,
+ REAPlatform.IOS,
+ REAPlatform.MACOS,
+ ],
},
LogExample: {
icon: 'â¨',
@@ -582,6 +604,7 @@ export const EXAMPLES: Record = {
title: 'DynamicColorIOS',
screen: DynamicColorIOSExample,
icon: 'đ',
+ disabledPlatforms: [REAPlatform.ANDROID, REAPlatform.WEB],
},
// Old examples
diff --git a/apps/fabric-example/ios/Podfile.lock b/apps/fabric-example/ios/Podfile.lock
index 9b1ff727a948..5e3fd3c31efc 100644
--- a/apps/fabric-example/ios/Podfile.lock
+++ b/apps/fabric-example/ios/Podfile.lock
@@ -3190,10 +3190,10 @@ SPEC CHECKSUMS:
RNReanimated: 3b47c33660454c6f9700b463e92daa282030866a
RNScreens: 6ced6ae8a526512a6eef6e28c2286e1fc2d378c3
RNSVG: 287504b73fa0e90a605225aa9f852a86d5461e84
- RNWorklets: 7119ae08263033c456c80d90794a312f2f88c956
+ RNWorklets: 991f94e4fa31fc20853e74d5d987426f8580cb0d
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: e80c5fabbc3e26311152fa20404cdfa14f16a11f
-PODFILE CHECKSUM: 5d8c04f461eed0f22e86610877d94f2b8b838b8b
+PODFILE CHECKSUM: db099f48c6dadedd8fc0a430129b75e561867ab9
COCOAPODS: 1.15.2
diff --git a/packages/react-native-reanimated/jest-setup.js b/packages/react-native-reanimated/jest-setup.js
index 1c4e95c9f09a..d235392ed8eb 100644
--- a/packages/react-native-reanimated/jest-setup.js
+++ b/packages/react-native-reanimated/jest-setup.js
@@ -2,6 +2,10 @@ delete global.MessageChannel;
require('react-native-worklets/jestSetup');
require('./src/jestUtils').setUpTests();
+jest.mock('react-native-worklets', () =>
+ require('react-native-worklets/src/mock')
+);
+
global.__reanimatedLoggerConfig = {
logFunction: (data) => {
switch (data.level) {
diff --git a/packages/react-native-worklets/__tests__/API.test.ts b/packages/react-native-worklets/__tests__/API.test.ts
new file mode 100644
index 000000000000..7a5d2e5240b6
--- /dev/null
+++ b/packages/react-native-worklets/__tests__/API.test.ts
@@ -0,0 +1,46 @@
+import * as Worklets from '../src/index';
+
+describe('web API', () => {
+ it('should have all exports available', () => {
+ const WorkletAPI = [
+ 'isShareableRef',
+ 'makeShareable',
+ 'makeShareableCloneOnUIRecursive',
+ 'makeShareableCloneRecursive',
+ 'shareableMappingCache',
+ 'getStaticFeatureFlag',
+ 'setDynamicFeatureFlag',
+ 'isSynchronizable',
+ 'getRuntimeKind',
+ 'RuntimeKind',
+ 'createWorkletRuntime',
+ 'runOnRuntime',
+ 'scheduleOnRuntime',
+ 'createSerializable',
+ 'isSerializableRef',
+ 'serializableMappingCache',
+ 'createSynchronizable',
+ 'callMicrotasks',
+ 'executeOnUIRuntimeSync',
+ 'runOnJS',
+ 'runOnUI',
+ 'runOnUIAsync',
+ 'runOnUISync',
+ 'scheduleOnRN',
+ 'scheduleOnUI',
+ 'unstable_eventLoopTask',
+ 'isWorkletFunction',
+ 'WorkletsModule',
+ ];
+
+ // Check if all exports are available.
+ expect(WorkletAPI.sort()).toEqual(Object.keys(Worklets).sort());
+
+ const definedWorklets = WorkletAPI.filter((api) => {
+ return Worklets[api as keyof typeof Worklets] !== undefined;
+ }).map((api) => api);
+
+ // Check if all exports are defined.
+ expect(WorkletAPI.sort()).toEqual(definedWorklets.sort());
+ });
+});
diff --git a/packages/react-native-worklets/jest.config.js b/packages/react-native-worklets/jest.config.js
index 0587b08d2690..f07dd16bf53e 100644
--- a/packages/react-native-worklets/jest.config.js
+++ b/packages/react-native-worklets/jest.config.js
@@ -4,4 +4,14 @@ module.exports = {
modulePathIgnorePatterns: ['lib'],
testEnvironment: 'node',
transformIgnorePatterns: [],
+ moduleFileExtensions: [
+ 'web.ts',
+ 'web.tsx',
+ 'web.js',
+ 'ts',
+ 'tsx',
+ 'js',
+ 'jsx',
+ 'json',
+ ],
};
diff --git a/packages/react-native-worklets/src/PlatformChecker/index.ts b/packages/react-native-worklets/src/PlatformChecker/index.ts
index f77f7bac92f5..be1d064ef2d4 100644
--- a/packages/react-native-worklets/src/PlatformChecker/index.ts
+++ b/packages/react-native-worklets/src/PlatformChecker/index.ts
@@ -1,30 +1,6 @@
'use strict';
-import { getRuntimeKind, RuntimeKind } from '../runtimeKind';
-import {
- IS_JEST as RN_IS_JEST,
- IS_WEB as RN_IS_WEB,
- IS_WINDOWS as RN_IS_WINDOWS,
- SHOULD_BE_USE_WEB as RN_SHOULD_BE_USE_WEB,
-} from './PlatformChecker';
-
-let IS_JEST = false;
-let IS_WEB = false;
-let IS_WINDOWS = false;
-let SHOULD_BE_USE_WEB = false;
-
-if (getRuntimeKind() === RuntimeKind.ReactNative) {
- IS_JEST = RN_IS_JEST;
- IS_WEB = RN_IS_WEB;
- IS_WINDOWS = RN_IS_WINDOWS;
- SHOULD_BE_USE_WEB = RN_SHOULD_BE_USE_WEB;
-}
-
-export {
- IS_JEST,
- /** @knipIgnore */
- IS_WEB,
- /** @knipIgnore */
- IS_WINDOWS,
- SHOULD_BE_USE_WEB,
-};
+export const IS_JEST = false;
+export const IS_WEB = false;
+export const IS_WINDOWS = false;
+export const SHOULD_BE_USE_WEB = false;
diff --git a/packages/react-native-worklets/src/PlatformChecker/index.web.ts b/packages/react-native-worklets/src/PlatformChecker/index.web.ts
new file mode 100644
index 000000000000..93ca38f9c264
--- /dev/null
+++ b/packages/react-native-worklets/src/PlatformChecker/index.web.ts
@@ -0,0 +1,8 @@
+'use strict';
+
+export {
+ IS_JEST,
+ IS_WEB,
+ IS_WINDOWS,
+ SHOULD_BE_USE_WEB,
+} from './PlatformChecker';
diff --git a/packages/react-native-worklets/src/WorkletsError.web.ts b/packages/react-native-worklets/src/WorkletsError.web.ts
new file mode 100644
index 000000000000..32c43f3a9438
--- /dev/null
+++ b/packages/react-native-worklets/src/WorkletsError.web.ts
@@ -0,0 +1,12 @@
+'use strict';
+
+function WorkletsErrorConstructor(message?: string) {
+ const prefix = '[Worklets]';
+
+ // eslint-disable-next-line reanimated/use-worklets-error
+ const errorInstance = new Error(message ? `${prefix} ${message}` : prefix);
+ errorInstance.name = `WorkletsError`;
+ return errorInstance;
+}
+
+export const WorkletsError = WorkletsErrorConstructor;
diff --git a/packages/react-native-worklets/src/WorkletsModule/JSWorklets.ts b/packages/react-native-worklets/src/WorkletsModule/JSWorklets.ts
deleted file mode 100644
index 68efbd62898d..000000000000
--- a/packages/react-native-worklets/src/WorkletsModule/JSWorklets.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-'use strict';
-
-import { IS_JEST } from '../PlatformChecker';
-import { mockedRequestAnimationFrame } from '../runLoop/uiRuntime/mockedRequestAnimationFrame';
-import { WorkletsError } from '../WorkletsError';
-import type { SerializableRef } from '../workletTypes';
-import type { IWorkletsModule } from './workletsModuleProxy';
-
-export function createJSWorkletsModule(): IWorkletsModule {
- return new JSWorklets();
-}
-
-// In Node.js environments (like when static rendering with Expo Router)
-// requestAnimationFrame is unavailable, so we use our mock.
-// It also has to be mocked for Jest purposes (see `initializeUIRuntime`).
-const requestAnimationFrameImpl =
- IS_JEST || !globalThis.requestAnimationFrame
- ? mockedRequestAnimationFrame
- : globalThis.requestAnimationFrame;
-
-class JSWorklets implements IWorkletsModule {
- createSerializable(): never {
- throw new WorkletsError(
- 'createSerializable should never be called in JSWorklets.'
- );
- }
-
- createSerializableString(): never {
- throw new WorkletsError(
- 'createSerializableString should never be called in JSWorklets.'
- );
- }
-
- createSerializableNumber(): never {
- throw new WorkletsError(
- 'createSerializableNumber should never be called in JSWorklets.'
- );
- }
-
- createSerializableBoolean(): never {
- throw new WorkletsError(
- 'createSerializableBoolean should never be called in JSWorklets.'
- );
- }
-
- createSerializableBigInt(): never {
- throw new WorkletsError(
- 'createSerializableBigInt should never be called in JSWorklets.'
- );
- }
-
- createSerializableUndefined(): never {
- throw new WorkletsError(
- 'createSerializableUndefined should never be called in JSWorklets.'
- );
- }
-
- createSerializableNull(): never {
- throw new WorkletsError(
- 'createSerializableNull should never be called in JSWorklets.'
- );
- }
-
- createSerializableTurboModuleLike(): never {
- throw new WorkletsError(
- 'createSerializableTurboModuleLike should never be called in JSWorklets.'
- );
- }
-
- createSerializableObject(): never {
- throw new WorkletsError(
- 'createSerializableObject should never be called in JSWorklets.'
- );
- }
-
- createSerializableMap(): never {
- throw new WorkletsError(
- 'createSerializableMap should never be called in JSWorklets.'
- );
- }
-
- createSerializableSet(): never {
- throw new WorkletsError(
- 'createSerializableSet should never be called in JSWorklets.'
- );
- }
-
- createSerializableImport(): never {
- throw new WorkletsError(
- 'createSerializableImport should never be called in JSWorklets.'
- );
- }
-
- createSerializableHostObject(): never {
- throw new WorkletsError(
- 'createSerializableHostObject should never be called in JSWorklets.'
- );
- }
-
- createSerializableArray(): never {
- throw new WorkletsError(
- 'createSerializableArray should never be called in JSWorklets.'
- );
- }
-
- createSerializableInitializer(): never {
- throw new WorkletsError(
- 'createSerializableInitializer should never be called in JSWorklets.'
- );
- }
-
- createSerializableFunction(): never {
- throw new WorkletsError(
- 'createSerializableFunction should never be called in JSWorklets.'
- );
- }
-
- createSerializableWorklet(): never {
- throw new WorkletsError(
- 'createSerializableWorklet should never be called in JSWorklets.'
- );
- }
-
- scheduleOnUI(worklet: SerializableRef) {
- // TODO: `requestAnimationFrame` should be used exclusively in Reanimated
-
- // @ts-ignore web implementation has still not been updated after the rewrite,
- // this will be addressed once the web implementation updates are ready
- requestAnimationFrameImpl(worklet);
- }
-
- executeOnUIRuntimeSync(): never {
- throw new WorkletsError(
- '`executeOnUIRuntimeSync` is not available in JSWorklets.'
- );
- }
-
- createWorkletRuntime(): never {
- throw new WorkletsError(
- 'createWorkletRuntime is not available in JSWorklets.'
- );
- }
-
- scheduleOnRuntime(): never {
- throw new WorkletsError(
- 'scheduleOnRuntime is not available in JSWorklets.'
- );
- }
-
- createSynchronizable(): never {
- throw new WorkletsError(
- 'createSynchronizable should never be called in JSWorklets.'
- );
- }
-
- synchronizableGetDirty(): never {
- throw new WorkletsError(
- 'synchronizableGetDirty should never be called in JSWorklets.'
- );
- }
-
- synchronizableGetBlocking(): never {
- throw new WorkletsError(
- 'synchronizableGetBlocking should never be called in JSWorklets.'
- );
- }
-
- synchronizableSetBlocking(): never {
- throw new WorkletsError(
- 'synchronizableSetBlocking should never be called in JSWorklets.'
- );
- }
-
- synchronizableLock(): never {
- throw new WorkletsError(
- 'synchronizableLock should never be called in JSWorklets.'
- );
- }
-
- synchronizableUnlock(): never {
- throw new WorkletsError(
- 'synchronizableUnlock should never be called in JSWorklets.'
- );
- }
-
- reportFatalErrorOnJS(): never {
- throw new WorkletsError(
- 'reportFatalErrorOnJS should never be called in JSWorklets.'
- );
- }
-
- getStaticFeatureFlag(): boolean {
- // mock implementation
- return false;
- }
-
- setDynamicFeatureFlag() {
- // noop
- }
-}
diff --git a/packages/react-native-worklets/src/WorkletsModule/index.web.ts b/packages/react-native-worklets/src/WorkletsModule/index.web.ts
new file mode 100644
index 000000000000..ef9c59d23717
--- /dev/null
+++ b/packages/react-native-worklets/src/WorkletsModule/index.web.ts
@@ -0,0 +1,3 @@
+'use strict';
+
+export const WorkletsModule = null;
diff --git a/packages/react-native-worklets/src/WorkletsModule/workletsModuleInstance.ts b/packages/react-native-worklets/src/WorkletsModule/workletsModuleInstance.ts
index ec07b6ff4d62..5db935842e96 100644
--- a/packages/react-native-worklets/src/WorkletsModule/workletsModuleInstance.ts
+++ b/packages/react-native-worklets/src/WorkletsModule/workletsModuleInstance.ts
@@ -1,9 +1,5 @@
'use strict';
-import { SHOULD_BE_USE_WEB } from '../PlatformChecker';
-import { createJSWorkletsModule } from './JSWorklets';
import { createNativeWorkletsModule } from './NativeWorklets';
-export const WorkletsModule = SHOULD_BE_USE_WEB
- ? createJSWorkletsModule()
- : createNativeWorkletsModule();
+export const WorkletsModule = createNativeWorkletsModule();
diff --git a/packages/react-native-worklets/src/WorkletsModule/workletsModuleInstance.web.ts b/packages/react-native-worklets/src/WorkletsModule/workletsModuleInstance.web.ts
deleted file mode 100644
index 93c11d4d1d1b..000000000000
--- a/packages/react-native-worklets/src/WorkletsModule/workletsModuleInstance.web.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-'use strict';
-
-import { createJSWorkletsModule } from './JSWorklets';
-
-export const WorkletsModule = createJSWorkletsModule();
diff --git a/packages/react-native-worklets/src/featureFlags/index.web.ts b/packages/react-native-worklets/src/featureFlags/index.web.ts
new file mode 100644
index 000000000000..270bc2b8a61e
--- /dev/null
+++ b/packages/react-native-worklets/src/featureFlags/index.web.ts
@@ -0,0 +1,9 @@
+'use strict';
+
+export function getStaticFeatureFlag() {
+ return false;
+}
+
+export function setDynamicFeatureFlag() {
+ // no-op
+}
diff --git a/packages/react-native-worklets/src/initializers.ts b/packages/react-native-worklets/src/initializers.ts
index fd180700c777..57c05be983fc 100644
--- a/packages/react-native-worklets/src/initializers.ts
+++ b/packages/react-native-worklets/src/initializers.ts
@@ -3,10 +3,8 @@
import { bundleValueUnpacker } from './bundleUnpacker';
import { setupCallGuard } from './callGuard';
import { registerReportFatalRemoteError } from './errors';
-import { IS_JEST, SHOULD_BE_USE_WEB } from './PlatformChecker';
import { setupSetImmediate } from './runLoop/common/setImmediatePolyfill';
import { setupSetInterval } from './runLoop/common/setIntervalPolyfill';
-import { mockedRequestAnimationFrame } from './runLoop/uiRuntime/mockedRequestAnimationFrame';
import { setupRequestAnimationFrame } from './runLoop/uiRuntime/requestAnimationFrame';
import { setupSetTimeout } from './runLoop/uiRuntime/setTimeoutPolyfill';
import { RuntimeKind } from './runtimeKind';
@@ -17,6 +15,12 @@ import { registerWorkletsError, WorkletsError } from './WorkletsError';
import { WorkletsModule } from './WorkletsModule';
import type { ValueUnpacker } from './workletTypes';
+if (globalThis.__RUNTIME_KIND === undefined) {
+ // The only runtime that doesn't have `__RUNTIME_KIND` preconfigured
+ // is the RN Runtime. We must set it as soon as possible.
+ globalThis.__RUNTIME_KIND = RuntimeKind.ReactNative;
+}
+
let capturableConsole: typeof console;
/**
@@ -85,25 +89,13 @@ export function init() {
}
initialized = true;
- if (globalThis.__RUNTIME_KIND === undefined) {
- // The only runtime that doesn't have `__RUNTIME_KIND` preconfigured
- // is the RN Runtime. We must set it as soon as possible.
- globalThis.__RUNTIME_KIND = RuntimeKind.ReactNative;
- }
-
initializeRuntime();
- if (SHOULD_BE_USE_WEB) {
- initializeRuntimeOnWeb();
- }
-
if (globalThis.__RUNTIME_KIND !== RuntimeKind.ReactNative) {
initializeWorkletRuntime();
} else {
initializeRNRuntime();
- if (!SHOULD_BE_USE_WEB) {
- installRNBindingsOnUIRuntime();
- }
+ installRNBindingsOnUIRuntime();
}
}
@@ -117,7 +109,7 @@ function initializeRuntime() {
/** A function that should be run only on React Native runtime. */
function initializeRNRuntime() {
- if (__DEV__ && !SHOULD_BE_USE_WEB) {
+ if (__DEV__) {
const testWorklet = () => {
'worklet';
};
@@ -201,22 +193,6 @@ function initializeWorkletRuntime() {
}
}
-/** A function that should be run only on RN Runtime in web implementation. */
-function initializeRuntimeOnWeb() {
- globalThis._WORKLET = false;
- globalThis._log = console.log;
- globalThis._getAnimationTimestamp = () => performance.now();
- 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;
- }
-}
-
/**
* A function that should be run on the RN Runtime to configure the UI Runtime
* with callback bindings.
diff --git a/packages/react-native-worklets/src/initializers.web.ts b/packages/react-native-worklets/src/initializers.web.ts
new file mode 100644
index 000000000000..bb9d16763c3e
--- /dev/null
+++ b/packages/react-native-worklets/src/initializers.web.ts
@@ -0,0 +1,21 @@
+'use strict';
+
+import { IS_JEST } from './PlatformChecker';
+import { mockedRequestAnimationFrame } from './runLoop/uiRuntime/mockedRequestAnimationFrame';
+import { RuntimeKind } from './runtimeKind';
+
+export function init() {
+ globalThis._WORKLET = false;
+ globalThis.__RUNTIME_KIND = RuntimeKind.ReactNative;
+ globalThis._log = console.log;
+ globalThis._getAnimationTimestamp = () => performance.now();
+ 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;
+ }
+}
diff --git a/packages/react-native-worklets/src/isSynchronizable.web.ts b/packages/react-native-worklets/src/isSynchronizable.web.ts
new file mode 100644
index 000000000000..7164292807c9
--- /dev/null
+++ b/packages/react-native-worklets/src/isSynchronizable.web.ts
@@ -0,0 +1,7 @@
+'use strict';
+
+import { WorkletsError } from './WorkletsError';
+
+export function isSynchronizable(): never {
+ throw new WorkletsError('`isSynchronizable` is not supported on web.');
+}
diff --git a/packages/react-native-worklets/src/mock.ts b/packages/react-native-worklets/src/mock.ts
new file mode 100644
index 000000000000..a7d18f7968e6
--- /dev/null
+++ b/packages/react-native-worklets/src/mock.ts
@@ -0,0 +1,107 @@
+'use strict';
+
+import { mockedRequestAnimationFrame } from './runLoop/uiRuntime/mockedRequestAnimationFrame';
+import { RuntimeKind } from './runtimeKind';
+import { isWorkletFunction } from './workletFunction';
+
+const NOOP = () => {};
+const NOOP_FACTORY = () => NOOP;
+const ID = (value: TValue) => value;
+const IMMEDIATE_CALLBACK_INVOCATION = (callback: () => TCallback) =>
+ callback();
+
+globalThis._WORKLET = false;
+globalThis.__RUNTIME_KIND = RuntimeKind.ReactNative;
+globalThis._log = console.log;
+globalThis._getAnimationTimestamp = () => performance.now();
+// 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;
+
+const WorkletAPI = {
+ isShareableRef: () => true,
+ makeShareable: ID,
+ makeShareableCloneOnUIRecursive: ID,
+ makeShareableCloneRecursive: ID,
+ shareableMappingCache: new Map(),
+ getStaticFeatureFlag: () => false,
+ setDynamicFeatureFlag: NOOP,
+ isSynchronizable: () => false,
+ getRuntimeKind: () => RuntimeKind.ReactNative,
+ RuntimeKind: RuntimeKind,
+ createWorkletRuntime: NOOP_FACTORY,
+ runOnRuntime: ID,
+ scheduleOnRuntime: IMMEDIATE_CALLBACK_INVOCATION,
+ createSerializable: ID,
+ isSerializableRef: ID,
+ serializableMappingCache: new Map(),
+ createSynchronizable: ID,
+ callMicrotasks: NOOP,
+ executeOnUIRuntimeSync: ID,
+ runOnJS(
+ fun: (...args: Args) => ReturnValue
+ ): (...args: Args) => void {
+ return (...args) =>
+ queueMicrotask(
+ args.length
+ ? () => (fun as (...args: Args) => ReturnValue)(...args)
+ : (fun as () => ReturnValue)
+ );
+ },
+ runOnUI(
+ worklet: (...args: Args) => ReturnValue
+ ): (...args: Args) => void {
+ return (...args) => {
+ // Mocking time in Jest is tricky as both requestAnimationFrame and queueMicrotask
+ // callbacks run on the same queue and can be interleaved. There is no way
+ // to flush particular queue in Jest and the only control over mocked timers
+ // is by using jest.advanceTimersByTime() method which advances all types
+ // of timers including immediate and animation callbacks. Ideally we'd like
+ // to have some way here to schedule work along with React updates, but
+ // that's not possible, and hence in Jest environment instead of using scheduling
+ // mechanism we just schedule the work ommiting the queue. This is ok for the
+ // uses that we currently have but may not be ok for future tests that we write.
+ mockedRequestAnimationFrame(() => {
+ worklet(...args);
+ });
+ };
+ },
+ runOnUIAsync(
+ worklet: (...args: Args) => ReturnValue
+ ): (...args: Args) => Promise {
+ return (...args: Args) => {
+ return new Promise((resolve) => {
+ mockedRequestAnimationFrame(() => {
+ const result = worklet(...args);
+ resolve(result);
+ });
+ });
+ };
+ },
+ runOnUISync: IMMEDIATE_CALLBACK_INVOCATION,
+ scheduleOnRN(
+ fun: (...args: Args) => ReturnValue,
+ ...args: Args
+ ): void {
+ this.runOnJS(fun)(...args);
+ },
+ scheduleOnUI(
+ worklet: (...args: Args) => ReturnValue,
+ ...args: Args
+ ): void {
+ this.runOnUI(worklet)(...args);
+ },
+ // eslint-disable-next-line camelcase
+ unstable_eventLoopTask: NOOP_FACTORY,
+ isWorkletFunction: isWorkletFunction,
+ WorkletsModule: {},
+};
+
+module.exports = {
+ __esModule: true,
+ ...WorkletAPI,
+};
diff --git a/packages/react-native-worklets/src/runtimes.ts b/packages/react-native-worklets/src/runtimes.ts
index fafc128647a5..bea572de8a5b 100644
--- a/packages/react-native-worklets/src/runtimes.ts
+++ b/packages/react-native-worklets/src/runtimes.ts
@@ -2,7 +2,6 @@
import { setupCallGuard } from './callGuard';
import { getMemorySafeCapturableConsole, setupConsole } from './initializers';
-import { SHOULD_BE_USE_WEB } from './PlatformChecker';
import { setupRunLoop } from './runLoop/workletRuntime';
import { RuntimeKind } from './runtimeKind';
import {
@@ -110,7 +109,7 @@ export function runOnRuntime(
worklet: WorkletFunction
): (...args: Args) => void {
'worklet';
- if (__DEV__ && !SHOULD_BE_USE_WEB && !isWorkletFunction(worklet)) {
+ if (__DEV__ && !isWorkletFunction(worklet)) {
throw new WorkletsError(
'The function passed to `runOnRuntime` is not a worklet.'
);
diff --git a/packages/react-native-worklets/src/runtimes.web.ts b/packages/react-native-worklets/src/runtimes.web.ts
new file mode 100644
index 000000000000..69b2078d4fc4
--- /dev/null
+++ b/packages/react-native-worklets/src/runtimes.web.ts
@@ -0,0 +1,13 @@
+'use strict';
+
+export function createWorkletRuntime(): never {
+ throw new WorkletsError('`createWorkletRuntime` is not supported on web.');
+}
+
+export function runOnRuntime(): never {
+ throw new WorkletsError('`runOnRuntime` is not supported on web.');
+}
+
+export function scheduleOnRuntime(): never {
+ throw new WorkletsError('`scheduleOnRuntime` is not supported on web.');
+}
diff --git a/packages/react-native-worklets/src/serializable.ts b/packages/react-native-worklets/src/serializable.ts
index d4a4400be1e4..e8c4ba07c9d7 100644
--- a/packages/react-native-worklets/src/serializable.ts
+++ b/packages/react-native-worklets/src/serializable.ts
@@ -2,7 +2,6 @@
import { registerWorkletStackDetails } from './errors';
import { isSynchronizable } from './isSynchronizable';
import { logger } from './logger';
-import { SHOULD_BE_USE_WEB } from './PlatformChecker';
import {
serializableMappingCache,
serializableMappingFlag,
@@ -123,42 +122,38 @@ const DETECT_CYCLIC_OBJECT_DEPTH_THRESHOLD = 30;
// We use it to check if later on the function reenters with the same object
let processedObjectAtThresholdDepth: unknown;
-function createSerializableWeb(value: T): SerializableRef {
- return value as SerializableRef;
-}
-
-function createSerializableNative(
- value: T,
+export function createSerializable(
+ value: TValue,
shouldPersistRemote = false,
depth = 0
-): SerializableRef {
+): SerializableRef {
detectCyclicObject(value, depth);
const isObject = typeof value === 'object';
const isFunction = typeof value === 'function';
if (typeof value === 'string') {
- return cloneString(value) as SerializableRef;
+ return cloneString(value) as SerializableRef;
}
if (typeof value === 'number') {
- return cloneNumber(value) as SerializableRef;
+ return cloneNumber(value) as SerializableRef;
}
if (typeof value === 'boolean') {
- return cloneBoolean(value) as SerializableRef;
+ return cloneBoolean(value) as SerializableRef;
}
if (typeof value === 'bigint') {
- return cloneBigInt(value) as SerializableRef;
+ return cloneBigInt(value) as SerializableRef;
}
if (value === undefined) {
- return cloneUndefined() as SerializableRef;
+ return cloneUndefined() as SerializableRef;
}
if (value === null) {
- return cloneNull() as SerializableRef;
+ return cloneNull() as SerializableRef;
}
if ((!isObject && !isFunction) || value === null) {
@@ -167,7 +162,7 @@ function createSerializableNative(
const cached = getFromCache(value);
if (cached !== undefined) {
- return cached as SerializableRef;
+ return cached as SerializableRef;
}
if (Array.isArray(value)) {
@@ -178,7 +173,7 @@ function createSerializableNative(
isFunction &&
(value as WorkletImport).__bundleData
) {
- return cloneImport(value as WorkletImport) as SerializableRef;
+ return cloneImport(value as WorkletImport) as SerializableRef;
}
if (isFunction && !isWorkletFunction(value)) {
return cloneRemoteFunction(value);
@@ -196,7 +191,7 @@ function createSerializableNative(
value,
shouldPersistRemote,
depth
- ) as SerializableRef;
+ ) as SerializableRef;
}
if (isPlainJSObject(value) && value.__workletContextObjectFactory) {
return cloneContextObject(value);
@@ -205,7 +200,7 @@ function createSerializableNative(
return cloneWorklet(value, shouldPersistRemote, depth);
}
if (isSynchronizable(value)) {
- return cloneSynchronizable(value) as SerializableRef;
+ return cloneSynchronizable(value) as SerializableRef;
}
if (isPlainJSObject(value) || isFunction) {
return clonePlainJSObject(value, shouldPersistRemote, depth);
@@ -234,26 +229,13 @@ function createSerializableNative(
if (globalThis._WORKLETS_BUNDLE_MODE) {
// TODO: Do it programatically.
- createSerializableNative.__bundleData = {
+ createSerializable.__bundleData = {
imported: 'createSerializable',
// @ts-expect-error resolveWeak is defined by Metro
source: require.resolveWeak('./index'),
};
}
-interface CreateSerializable {
- (value: T): SerializableRef;
- (
- value: T,
- shouldPersistRemote: boolean,
- depth: number
- ): SerializableRef;
-}
-
-export const createSerializable: CreateSerializable = SHOULD_BE_USE_WEB
- ? createSerializableWeb
- : createSerializableNative;
-
function detectCyclicObject(value: unknown, depth: number) {
if (depth >= DETECT_CYCLIC_OBJECT_DEPTH_THRESHOLD) {
// if we reach certain recursion depth we suspect that we are dealing with a cyclic object.
@@ -380,11 +362,11 @@ function cloneHostObject(value: T): SerializableRef {
return clone;
}
-function cloneWorklet(
- value: T,
+function cloneWorklet(
+ value: TValue,
shouldPersistRemote: boolean,
depth: number
-): SerializableRef {
+): SerializableRef {
if (__DEV__) {
const babelVersion = (value as WorkletFunction).__pluginVersion;
if (babelVersion !== undefined && babelVersion !== jsVersion) {
@@ -427,7 +409,7 @@ function cloneWorklet(
// TODO: Check after refactor if we can remove shouldPersistRemote parameter (imho it's redundant here since worklets are always persistent)
// retain all worklets
true
- ) as SerializableRef;
+ ) as SerializableRef;
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
@@ -439,23 +421,25 @@ function cloneWorklet(
* TurboModuleLike objects are JS objects that have a TurboModule as their
* prototype.
*/
-function cloneTurboModuleLike(
- value: T,
+function cloneTurboModuleLike(
+ value: TValue,
shouldPersistRemote: boolean,
depth: number
-): SerializableRef {
+): SerializableRef {
const proto = Object.getPrototypeOf(value);
const clonedProps = cloneObjectProperties(value, shouldPersistRemote, depth);
const clone = WorkletsModule.createSerializableTurboModuleLike(
clonedProps,
proto
- ) as SerializableRef;
+ ) as SerializableRef;
return clone;
}
-function cloneContextObject(value: T): SerializableRef {
+function cloneContextObject(
+ value: TValue
+): SerializableRef {
const workletContextObjectFactory = (value as Record)
- .__workletContextObjectFactory as () => T;
+ .__workletContextObjectFactory as () => TValue;
const handle = cloneInitializer({
__init: () => {
'worklet';
@@ -463,14 +447,14 @@ function cloneContextObject(value: T): SerializableRef {
},
});
serializableMappingCache.set(value, handle);
- return handle as SerializableRef;
+ return handle as SerializableRef;
}
-function clonePlainJSObject(
- value: T,
+function clonePlainJSObject(
+ value: TValue,
shouldPersistRemote: boolean,
depth: number
-): SerializableRef {
+): SerializableRef {
const clonedProps: Record = cloneObjectProperties(
value,
shouldPersistRemote,
@@ -480,7 +464,7 @@ function clonePlainJSObject(
clonedProps,
shouldPersistRemote,
value
- ) as SerializableRef;
+ ) as SerializableRef;
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
@@ -488,9 +472,9 @@ function clonePlainJSObject(
return clone;
}
-function cloneMap>(
- value: T
-): SerializableRef {
+function cloneMap>(
+ value: TValue
+): SerializableRef {
const clonedKeys: unknown[] = [];
const clonedValues: unknown[] = [];
for (const [key, element] of value.entries()) {
@@ -500,7 +484,7 @@ function cloneMap>(
const clone = WorkletsModule.createSerializableMap(
clonedKeys,
clonedValues
- ) as SerializableRef;
+ ) as SerializableRef;
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
@@ -508,14 +492,16 @@ function cloneMap>(
return clone;
}
-function cloneSet>(value: T): SerializableRef {
+function cloneSet>(
+ value: TValue
+): SerializableRef {
const clonedElements: unknown[] = [];
for (const element of value) {
clonedElements.push(createSerializable(element));
}
const clone = WorkletsModule.createSerializableSet(
clonedElements
- ) as SerializableRef;
+ ) as SerializableRef;
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
@@ -523,7 +509,9 @@ function cloneSet>(value: T): SerializableRef {
return clone;
}
-function cloneRegExp(value: T): SerializableRef {
+function cloneRegExp(
+ value: TValue
+): SerializableRef {
const pattern = value.source;
const flags = value.flags;
const handle = cloneInitializer({
@@ -531,13 +519,15 @@ function cloneRegExp(value: T): SerializableRef {
'worklet';
return new RegExp(pattern, flags);
},
- }) as unknown as SerializableRef;
+ }) as unknown as SerializableRef;
serializableMappingCache.set(value, handle);
return handle;
}
-function cloneError(value: T): SerializableRef {
+function cloneError(
+ value: TValue
+): SerializableRef {
const { name, message, stack } = value;
const handle = cloneInitializer({
__init: () => {
@@ -551,7 +541,7 @@ function cloneError(value: T): SerializableRef {
},
});
serializableMappingCache.set(value, handle);
- return handle as unknown as SerializableRef;
+ return handle as unknown as SerializableRef;
}
function cloneArrayBuffer(
@@ -569,9 +559,9 @@ function cloneArrayBuffer(
return clone;
}
-function cloneArrayBufferView(
- value: T
-): SerializableRef {
+function cloneArrayBufferView(
+ value: TValue
+): SerializableRef {
const buffer = value.buffer;
const typeName = value.constructor.name;
const handle = cloneInitializer({
@@ -586,7 +576,7 @@ function cloneArrayBufferView(
}
return new constructor(buffer);
},
- }) as unknown as SerializableRef;
+ }) as unknown as SerializableRef;
serializableMappingCache.set(value, handle);
return handle;
@@ -611,7 +601,9 @@ function cloneImport(
return clone as SerializableRef;
}
-function inaccessibleObject(value: T): SerializableRef {
+function inaccessibleObject(
+ value: TValue
+): SerializableRef {
// This is reached for object types that are not of plain Object.prototype.
// We don't support such objects from being transferred as serializables to
// the UI runtime and hence we replace them with "inaccessible object"
@@ -620,7 +612,7 @@ function inaccessibleObject(value: T): SerializableRef {
// as attributes of objects being captured by worklets but should never
// be used on the UI runtime regardless. If they are being accessed, the user
// will get an appropriate error message.
- const clone = createSerializable(INACCESSIBLE_OBJECT as T);
+ const clone = createSerializable(INACCESSIBLE_OBJECT as TValue);
serializableMappingCache.set(value, clone);
return clone;
}
@@ -638,13 +630,13 @@ function getWorkletCode(value: WorkletFunction) {
return code;
}
-type RemoteFunction = {
- __remoteFunction: FlatSerializableRef;
+type RemoteFunction = {
+ __remoteFunction: FlatSerializableRef;
};
-function isRemoteFunction(value: {
+function isRemoteFunction(value: {
__remoteFunction?: unknown;
-}): value is RemoteFunction {
+}): value is RemoteFunction {
'worklet';
return !!value.__remoteFunction;
}
@@ -663,7 +655,7 @@ function isRemoteFunction(value: {
* the UI thread. If the user really wants some objects to be mutable they
* should use shared values instead.
*/
-function freezeObjectInDev(value: T) {
+function freezeObjectInDev(value: TValue) {
if (!__DEV__) {
return;
}
@@ -688,17 +680,12 @@ function freezeObjectInDev(value: T) {
Object.preventExtensions(value);
}
-function makeShareableCloneOnUIRecursiveLEGACY(
- value: T
-): FlatSerializableRef {
+function makeShareableCloneOnUIRecursiveLEGACY(
+ value: TValue
+): FlatSerializableRef {
'worklet';
- if (SHOULD_BE_USE_WEB) {
- // @ts-ignore web is an interesting place where we don't run a secondary VM on the UI thread
- // see more details in the comment where USE_STUB_IMPLEMENTATION is defined.
- return value;
- }
// eslint-disable-next-line @typescript-eslint/no-shadow
- function cloneRecursive(value: T): FlatSerializableRef {
+ function cloneRecursive(value: TValue): FlatSerializableRef {
if (
(typeof value === 'object' && value !== null) ||
typeof value === 'function'
@@ -708,9 +695,9 @@ function makeShareableCloneOnUIRecursiveLEGACY(
// inside SerializableJSRef.
return global._createSerializableHostObject(
value
- ) as FlatSerializableRef;
+ ) as FlatSerializableRef;
}
- if (isRemoteFunction(value)) {
+ if (isRemoteFunction(value)) {
// RemoteFunctions are created by us therefore they are
// a Serializable out of the box and there is no need to
// call `_createSerializableClone`.
@@ -719,21 +706,21 @@ function makeShareableCloneOnUIRecursiveLEGACY(
if (Array.isArray(value)) {
return global._createSerializableArray(
value.map(cloneRecursive)
- ) as FlatSerializableRef;
+ ) as FlatSerializableRef;
}
if ((value as Record).__synchronizableRef) {
return global._createSerializableSynchronizable(
value
- ) as FlatSerializableRef;
+ ) as FlatSerializableRef;
}
- const toAdapt: Record> = {};
+ const toAdapt: Record> = {};
for (const [key, element] of Object.entries(value)) {
toAdapt[key] = cloneRecursive(element);
}
return global._createSerializable(
toAdapt,
value
- ) as FlatSerializableRef;
+ ) as FlatSerializableRef;
}
if (typeof value === 'string') {
@@ -772,11 +759,14 @@ export const makeShareableCloneOnUIRecursive = (
: makeShareableCloneOnUIRecursiveLEGACY
) as typeof makeShareableCloneOnUIRecursiveLEGACY;
-function makeShareableJS(value: T): T {
- return value;
-}
-
-function makeShareableNative(value: T): T {
+/**
+ * This function creates a value on UI with persistent state - changes to it on
+ * the UI thread will be seen by all worklets. Use it when you want to create a
+ * value that is read and written only on the UI thread.
+ *
+ * @deprecated This function is no longer supported.
+ */
+export function makeShareable(value: TValue): TValue {
if (serializableMappingCache.get(value)) {
return value;
}
@@ -789,13 +779,3 @@ function makeShareableNative(value: T): T {
serializableMappingCache.set(value, handle);
return value;
}
-
-/**
- * This function creates a value on UI with persistent state - changes to it on
- * the UI thread will be seen by all worklets. Use it when you want to create a
- * value that is read and written only on the UI thread.
- */
-/** @deprecated This function is no longer supported. */
-export const makeShareable = SHOULD_BE_USE_WEB
- ? makeShareableJS
- : makeShareableNative;
diff --git a/packages/react-native-worklets/src/serializable.web.ts b/packages/react-native-worklets/src/serializable.web.ts
new file mode 100644
index 000000000000..bb767d877e48
--- /dev/null
+++ b/packages/react-native-worklets/src/serializable.web.ts
@@ -0,0 +1,17 @@
+'use strict';
+
+export function isSerializableRef(): boolean {
+ return true;
+}
+
+export function createSerializable(value: TValue): TValue {
+ return value;
+}
+
+export function makeShareableCloneOnUIRecursive(value: TValue): TValue {
+ return value;
+}
+
+export function makeShareable(value: TValue): TValue {
+ return value;
+}
diff --git a/packages/react-native-worklets/src/serializableMappingCache.ts b/packages/react-native-worklets/src/serializableMappingCache.ts
index 77e65aa0d051..7cc4e1c8d477 100644
--- a/packages/react-native-worklets/src/serializableMappingCache.ts
+++ b/packages/react-native-worklets/src/serializableMappingCache.ts
@@ -1,5 +1,5 @@
'use strict';
-import { SHOULD_BE_USE_WEB } from './PlatformChecker';
+
import type { SerializableRef } from './workletTypes';
/**
@@ -21,22 +21,11 @@ During cloning we use `Object.entries` to iterate over the keys which throws an
For convenience we moved this cache to a separate file so it doesn't scare us with red squiggles.
*/
-const cache = SHOULD_BE_USE_WEB
- ? null
- : new WeakMap