diff --git a/.gitignore b/.gitignore index b0b314aa..653b66ef 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,6 @@ lib/ # jest coverage/ + +# Claude +.claude/ diff --git a/README.md b/README.md index dfe7d3d3..48761afd 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,23 @@ cd ios && bundler install && bundler exec pod install The library includes native code so you will need to re-build the native app. +### Worklets Bundle Mode + +If you're using [Worklets Bundle Mode](https://docs.swmansion.com/react-native-worklets/docs/bundleMode) you will have to +configure your [import forwarding](https://docs.swmansion.com/react-native-worklets/docs/worklets-babel-plugin/plugin-options#importforwarding) as follows in your `babel.config.js` file: + +```js +importForwarding: { + moduleNames: [ + // your existing module names here... + // the order of the modules does not matter + 'expensify-common', + 'html-entities', + 'react-native-live-markdown', + ], +}, +``` + > [!NOTE] > The library does not support Expo Go, you will need to setup Expo Dev Client (see [here](https://docs.expo.dev/workflow/prebuild/)). diff --git a/example/babel.config.js b/example/babel.config.js index 058e8577..f2936de3 100644 --- a/example/babel.config.js +++ b/example/babel.config.js @@ -1,6 +1,29 @@ const path = require('path'); const pak = require('../package.json'); +const monorepoRootPath = path.resolve(__dirname, '..'); + +/** @type {import('react-native-worklets/plugin').PluginOptions} */ +const workletsPluginOptions = { + bundleMode: process.env.BUNDLE_MODE === '1', + strictGlobal: true, + importForwarding: { + moduleNames: [ + 'expensify-common', + 'html-entities', + 'react-native-live-markdown', + ], + /** + * The following relative paths look like this due to monorepo structure. + * In a consumer app this wouldn't be necessary. + */ + relativePaths: [ + path.join(monorepoRootPath, 'src'), + path.join(monorepoRootPath, 'lib'), + ], + }, +}; + module.exports = { presets: ['module:@react-native/babel-preset'], plugins: [ @@ -13,6 +36,6 @@ module.exports = { }, }, ], - 'react-native-worklets/plugin', + ['react-native-worklets/plugin', workletsPluginOptions], ], }; diff --git a/example/metro.config.js b/example/metro.config.js index 7fd57bd1..d5fd5eae 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -1,4 +1,5 @@ const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); +const {bundleModeMetroConfig} = require('react-native-worklets/bundleMode'); const path = require('path'); const root = path.resolve(__dirname, '..'); @@ -22,4 +23,8 @@ const config = { }, }; -module.exports = mergeConfig(getDefaultConfig(__dirname), config); +module.exports = mergeConfig( + getDefaultConfig(__dirname), + bundleModeMetroConfig, + config, +); diff --git a/example/package.json b/example/package.json index 33d7ac2c..f24cf152 100644 --- a/example/package.json +++ b/example/package.json @@ -6,7 +6,8 @@ "android": "react-native run-android", "ios": "react-native run-ios", "lint": "eslint .", - "start": "react-native start --client-logs" + "start": "react-native start --client-logs", + "bundle-mode-start": "BUNDLE_MODE=1 react-native start --reset-cache --client-logs" }, "dependencies": { "expensify-common": "2.0.148", diff --git a/example/src/PlatformInfo.tsx b/example/src/PlatformInfo.tsx index 978ca1bb..5591cc9a 100644 --- a/example/src/PlatformInfo.tsx +++ b/example/src/PlatformInfo.tsx @@ -1,4 +1,5 @@ import {Platform, Text, View, StyleSheet} from 'react-native'; +import {isBundleModeEnabled} from 'react-native-worklets'; import React from 'react'; function isWeb() { @@ -62,6 +63,9 @@ export function PlatformInfo() { Bridgeless: {isBridgeless() ? 'yes' : 'no'} RN version: {getReactNativeVersion()} RN runtime: {getRuntime()} + + Worklets Bundle Mode: {isBundleModeEnabled() ? 'yes' : 'no'} + )} diff --git a/patches/metro+0.84.4.patch b/patches/metro+0.84.4.patch new file mode 100644 index 00000000..7e1075d6 --- /dev/null +++ b/patches/metro+0.84.4.patch @@ -0,0 +1,31 @@ +diff --git a/node_modules/metro/src/node-haste/DependencyGraph.js b/node_modules/metro/src/node-haste/DependencyGraph.js +index 8222d8c..e36770d 100644 +--- a/node_modules/metro/src/node-haste/DependencyGraph.js ++++ b/node_modules/metro/src/node-haste/DependencyGraph.js +@@ -32,6 +32,11 @@ function getOrCreateMap(map, field) { + } + return subMap; + } ++ ++const workletsDirPath = _path.default.join( ++ "react-native-worklets", ++ ".worklets", ++); + class DependencyGraph extends _events.default { + #packageCache; + #dependencyPlugin; +@@ -197,6 +202,14 @@ class DependencyGraph extends _events.default { + return (0, _nullthrows.default)(this._fileSystem).getAllFiles(); + } + async getOrComputeSha1(mixedPath) { ++ if (mixedPath.includes(workletsDirPath)) { ++ const createHash = require("crypto").createHash; ++ return { ++ sha1: createHash("sha1") ++ .update(performance.now().toString()) ++ .digest("hex"), ++ }; ++ } + const result = await this._fileSystem.getOrComputeSha1(mixedPath); + if (!result || !result.sha1) { + throw new Error(`Failed to get the SHA-1 for: ${mixedPath}. diff --git a/patches/react-native++metro-runtime+0.84.4.patch b/patches/react-native++metro-runtime+0.84.4.patch new file mode 100644 index 00000000..903b0105 --- /dev/null +++ b/patches/react-native++metro-runtime+0.84.4.patch @@ -0,0 +1,14 @@ +diff --git a/node_modules/react-native/node_modules/metro-runtime/src/modules/HMRClient.js b/node_modules/react-native/node_modules/metro-runtime/src/modules/HMRClient.js +index 534ac87..5773985 100644 +--- a/node_modules/react-native/node_modules/metro-runtime/src/modules/HMRClient.js ++++ b/node_modules/react-native/node_modules/metro-runtime/src/modules/HMRClient.js +@@ -3,6 +3,9 @@ + const EventEmitter = require("./vendor/eventemitter3"); + const HEARTBEAT_INTERVAL_MS = 20_000; + const inject = ({ module: [id, code], sourceURL }) => { ++ if (global.__workletsModuleProxy?.propagateModuleUpdate) { ++ global.__workletsModuleProxy.propagateModuleUpdate(code, sourceURL); ++ } + if (global.globalEvalWithSourceUrl) { + global.globalEvalWithSourceUrl(code, sourceURL); + } else {