From 39512a38385429da2549bc90485aa5253222987e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBelawski?= Date: Tue, 21 Jul 2026 10:18:50 +0200 Subject: [PATCH 1/3] feat: make react-native-live-markdown work in both modes --- .gitignore | 3 ++ example/babel.config.js | 25 ++++++++++++- example/metro.config.js | 7 +++- example/package.json | 3 +- example/src/PlatformInfo.tsx | 4 +++ patches/metro+0.84.4.patch | 31 ++++++++++++++++ .../react-native++metro-runtime+0.84.4.patch | 36 +++++++++++++++++++ 7 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 patches/metro+0.84.4.patch create mode 100644 patches/react-native++metro-runtime+0.84.4.patch diff --git a/.gitignore b/.gitignore index b0b314aae..653b66efc 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,6 @@ lib/ # jest coverage/ + +# Claude +.claude/ diff --git a/example/babel.config.js b/example/babel.config.js index 058e85775..f2936de31 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 7fd57bd18..d5fd5eae9 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 33d7ac2cb..f24cf1522 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 978ca1bb7..5591cc9ab 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 000000000..7e1075d61 --- /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 000000000..60c099450 --- /dev/null +++ b/patches/react-native++metro-runtime+0.84.4.patch @@ -0,0 +1,36 @@ +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 { +diff --git a/node_modules/react-native/node_modules/metro-runtime/src/polyfills/require.js b/node_modules/react-native/node_modules/metro-runtime/src/polyfills/require.js +index c46fb7c..2ba29ab 100644 +--- a/node_modules/react-native/node_modules/metro-runtime/src/polyfills/require.js ++++ b/node_modules/react-native/node_modules/metro-runtime/src/polyfills/require.js +@@ -334,14 +334,14 @@ function unknownModuleError(id) { + } + return Error(message); + } ++metroRequire.getModules = () => { ++ return modules; ++}; + if (__DEV__) { + metroRequire.Systrace = { + beginEvent: () => {}, + endEvent: () => {}, + }; +- metroRequire.getModules = () => { +- return modules; +- }; + var createHotReloadingObject = function () { + const hot = { + _acceptCallback: null, From f5608b61b59e13eeb344cf60579d6f96a27d4e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBelawski?= Date: Tue, 21 Jul 2026 10:25:18 +0200 Subject: [PATCH 2/3] feat: update docs --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index dfe7d3d32..48761afde 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/)). From 95002e77b04c689966c3fd1ae869c94fb6dc5e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBelawski?= Date: Tue, 21 Jul 2026 10:28:59 +0200 Subject: [PATCH 3/3] chore: shorter patch --- .../react-native++metro-runtime+0.84.4.patch | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/patches/react-native++metro-runtime+0.84.4.patch b/patches/react-native++metro-runtime+0.84.4.patch index 60c099450..903b01055 100644 --- a/patches/react-native++metro-runtime+0.84.4.patch +++ b/patches/react-native++metro-runtime+0.84.4.patch @@ -12,25 +12,3 @@ index 534ac87..5773985 100644 if (global.globalEvalWithSourceUrl) { global.globalEvalWithSourceUrl(code, sourceURL); } else { -diff --git a/node_modules/react-native/node_modules/metro-runtime/src/polyfills/require.js b/node_modules/react-native/node_modules/metro-runtime/src/polyfills/require.js -index c46fb7c..2ba29ab 100644 ---- a/node_modules/react-native/node_modules/metro-runtime/src/polyfills/require.js -+++ b/node_modules/react-native/node_modules/metro-runtime/src/polyfills/require.js -@@ -334,14 +334,14 @@ function unknownModuleError(id) { - } - return Error(message); - } -+metroRequire.getModules = () => { -+ return modules; -+}; - if (__DEV__) { - metroRequire.Systrace = { - beginEvent: () => {}, - endEvent: () => {}, - }; -- metroRequire.getModules = () => { -- return modules; -- }; - var createHotReloadingObject = function () { - const hot = { - _acceptCallback: null,