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 {