@depup/react-native-worklets 0.11.3-depup.0 → 0.12.1-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/Common/cpp/worklets/AnimationFrameQueue/AnimationFrameBatchinator.cpp +6 -3
  2. package/Common/cpp/worklets/Compat/StableApi.cpp +4 -0
  3. package/Common/cpp/worklets/Compat/StableApi.h +3 -1
  4. package/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp +26 -43
  5. package/Common/cpp/worklets/RunLoop/AsyncQueueImpl.cpp +32 -0
  6. package/Common/cpp/worklets/RunLoop/EventLoop.cpp +16 -7
  7. package/Common/cpp/worklets/RunLoop/EventLoop.h +4 -2
  8. package/Common/cpp/worklets/SharedItems/MemoryManager.cpp +2 -2
  9. package/Common/cpp/worklets/SharedItems/Serializable.h +14 -16
  10. package/Common/cpp/worklets/Tools/UIScheduler.cpp +10 -0
  11. package/Common/cpp/worklets/Tools/UIScheduler.h +3 -0
  12. package/Common/cpp/worklets/Tools/WorkletsJSIUtils.h +23 -0
  13. package/Common/cpp/worklets/WorkletRuntime/RuntimeManager.cpp +7 -4
  14. package/Common/cpp/worklets/WorkletRuntime/RuntimeManager.h +2 -1
  15. package/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp +60 -88
  16. package/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h +335 -122
  17. package/README.md +2 -2
  18. package/android/build.gradle.kts +20 -6
  19. package/android/src/main/cpp/worklets/android/AndroidUIScheduler.cpp +10 -8
  20. package/android/src/main/cpp/worklets/android/JScriptBufferWrapper.cpp +2 -11
  21. package/android/src/main/cpp/worklets/android/JScriptBufferWrapper.h +1 -4
  22. package/android/src/main/java/com/swmansion/worklets/AndroidUIScheduler.kt +3 -0
  23. package/android/src/main/java/com/swmansion/worklets/ScriptBufferWrapper.kt +87 -43
  24. package/android/src/networking/com/swmansion/worklets/WorkletsModule.kt +25 -9
  25. package/android/src/no-networking/com/swmansion/worklets/WorkletsModule.kt +25 -9
  26. package/apple/worklets/apple/IOSUIScheduler.h +3 -0
  27. package/apple/worklets/apple/IOSUIScheduler.mm +6 -1
  28. package/apple/worklets/apple/ScriptLoader.h +11 -0
  29. package/apple/worklets/apple/ScriptLoader.mm +124 -0
  30. package/apple/worklets/apple/WorkletsModule.h +1 -16
  31. package/apple/worklets/apple/WorkletsModule.mm +4 -16
  32. package/changes.json +1 -1
  33. package/compatibility.json +4 -1
  34. package/lib/module/WorkletsModule/NativeWorklets.native.js +2 -2
  35. package/lib/module/WorkletsModule/NativeWorklets.native.js.map +1 -1
  36. package/lib/module/debug/jsVersion.js +1 -1
  37. package/lib/module/memory/serializable.native.js +59 -21
  38. package/lib/module/memory/serializable.native.js.map +1 -1
  39. package/lib/module/mock.js +58 -17
  40. package/lib/module/mock.js.map +1 -1
  41. package/lib/module/runLoop/uiRuntime/requestAnimationFrame.js +1 -2
  42. package/lib/module/runLoop/uiRuntime/requestAnimationFrame.js.map +1 -1
  43. package/lib/module/runLoop/workletRuntime/taskQueue.js +0 -1
  44. package/lib/module/runLoop/workletRuntime/taskQueue.js.map +1 -1
  45. package/lib/module/runtimes.native.js +7 -8
  46. package/lib/module/runtimes.native.js.map +1 -1
  47. package/lib/module/threads.js +35 -24
  48. package/lib/module/threads.js.map +1 -1
  49. package/lib/typescript/WorkletsModule/NativeWorklets.native.d.ts.map +1 -1
  50. package/lib/typescript/WorkletsModule/workletsModuleProxy.d.ts +1 -1
  51. package/lib/typescript/WorkletsModule/workletsModuleProxy.d.ts.map +1 -1
  52. package/lib/typescript/debug/jsVersion.d.ts +1 -1
  53. package/lib/typescript/memory/serializable.native.d.ts.map +1 -1
  54. package/lib/typescript/runLoop/uiRuntime/requestAnimationFrame.d.ts.map +1 -1
  55. package/lib/typescript/runLoop/workletRuntime/taskQueue.d.ts.map +1 -1
  56. package/lib/typescript/runtimes.native.d.ts.map +1 -1
  57. package/lib/typescript/specs/NativeWorkletsModule.d.ts +1 -1
  58. package/lib/typescript/threads.d.ts.map +1 -1
  59. package/lib/typescript/types.d.ts +38 -8
  60. package/lib/typescript/types.d.ts.map +1 -1
  61. package/package.json +14 -15
  62. package/plugin/index.js +1625 -1572
  63. package/src/WorkletsModule/NativeWorklets.native.ts +4 -2
  64. package/src/WorkletsModule/workletsModuleProxy.ts +2 -1
  65. package/src/debug/jsVersion.ts +1 -1
  66. package/src/memory/serializable.native.ts +103 -36
  67. package/src/mock.ts +85 -23
  68. package/src/runLoop/uiRuntime/requestAnimationFrame.ts +1 -2
  69. package/src/runLoop/workletRuntime/taskQueue.ts +0 -1
  70. package/src/runtimes.native.ts +12 -8
  71. package/src/threads.ts +38 -26
  72. package/src/types.ts +42 -8
  73. package/Common/cpp/worklets/Registries/EventHandlerRegistry.cpp +0 -95
  74. package/Common/cpp/worklets/Registries/EventHandlerRegistry.h +0 -44
  75. package/Common/cpp/worklets/Tools/WorkletEventHandler.cpp +0 -34
  76. package/Common/cpp/worklets/Tools/WorkletEventHandler.h +0 -44
  77. package/jest/pluginTestUtils.ts +0 -16
@@ -25,21 +25,13 @@ jni::local_ref<JScriptBufferWrapper::jhybriddata> JScriptBufferWrapper::initHybr
25
25
 
26
26
  jni::local_ref<JScriptBufferWrapper::jhybriddata> JScriptBufferWrapper::initHybridFromFile(
27
27
  jni::alias_ref<jhybridobject> jThis, // NOLINT //(performance-unnecessary-value-param)
28
- const std::string &fileName) {
28
+ const std::string &fileName,
29
+ const std::string &sourceURL) {
29
30
  std::shared_ptr<const ScriptBuffer> script;
30
31
  RecoverableError::runRethrowingAsRecoverable<std::system_error>([&fileName, &script]() {
31
32
  auto bigString = JSBigFileString::fromPath(fileName);
32
33
  script = std::make_shared<ScriptBuffer>(std::move(bigString));
33
34
  });
34
- return makeCxxInstance(std::move(script), fileName);
35
- }
36
-
37
- jni::local_ref<JScriptBufferWrapper::jhybriddata> JScriptBufferWrapper::initHybridFromString(
38
- jni::alias_ref<jhybridobject> jThis, // NOLINT //(performance-unnecessary-value-param)
39
- const std::string &scriptStr,
40
- const std::string &sourceURL) {
41
- auto bigString = std::make_shared<JSBigStdString>(scriptStr);
42
- auto script = std::make_shared<ScriptBuffer>(std::move(bigString));
43
35
  return makeCxxInstance(std::move(script), sourceURL);
44
36
  }
45
37
 
@@ -60,7 +52,6 @@ void JScriptBufferWrapper::registerNatives() {
60
52
  registerHybrid({
61
53
  makeNativeMethod("initHybridFromAssets", JScriptBufferWrapper::initHybridFromAssets),
62
54
  makeNativeMethod("initHybridFromFile", JScriptBufferWrapper::initHybridFromFile),
63
- makeNativeMethod("initHybridFromString", JScriptBufferWrapper::initHybridFromString),
64
55
  });
65
56
  }
66
57
  } // namespace worklets
@@ -31,11 +31,8 @@ class JScriptBufferWrapper : public jni::HybridClass<JScriptBufferWrapper> {
31
31
  jni::alias_ref<jhybridobject> jThis,
32
32
  jni::alias_ref<JAssetManager::javaobject> assetManager,
33
33
  const std::string &assetURL);
34
- static jni::local_ref<JScriptBufferWrapper::jhybriddata> initHybridFromFile(
35
- jni::alias_ref<jhybridobject> jThis,
36
- const std::string &fileName);
37
34
  static jni::local_ref<JScriptBufferWrapper::jhybriddata>
38
- initHybridFromString(jni::alias_ref<jhybridobject> jThis, const std::string &script, const std::string &sourceURL);
35
+ initHybridFromFile(jni::alias_ref<jhybridobject> jThis, const std::string &fileName, const std::string &sourceURL);
39
36
 
40
37
  friend HybridBase;
41
38
 
@@ -31,6 +31,9 @@ class AndroidUIScheduler(
31
31
 
32
32
  private external fun initHybrid(): HybridData
33
33
 
34
+ @DoNotStrip
35
+ private fun isOnUIThread(): Boolean = UiThreadUtil.isOnUiThread()
36
+
34
37
  external fun triggerUI()
35
38
 
36
39
  external fun invalidate()
@@ -1,13 +1,16 @@
1
1
  package com.swmansion.worklets
2
2
 
3
3
  import android.annotation.SuppressLint
4
+ import android.content.Context
4
5
  import android.content.res.AssetManager
5
- import android.os.Build
6
6
  import com.facebook.jni.HybridData
7
7
  import com.facebook.proguard.annotations.DoNotStrip
8
8
  import com.facebook.proguard.annotations.DoNotStripAny
9
+ import com.facebook.react.ReactApplication
10
+ import org.json.JSONException
11
+ import org.json.JSONObject
12
+ import java.io.File
9
13
  import java.io.IOException
10
- import java.io.InputStream
11
14
  import java.net.HttpURLConnection
12
15
  import java.net.URL
13
16
  import java.nio.charset.StandardCharsets
@@ -17,13 +20,18 @@ import java.nio.charset.StandardCharsets
17
20
  @SuppressLint("MissingNativeLoadLibrary")
18
21
  @DoNotStripAny
19
22
  class ScriptBufferWrapper(
20
- uri: String,
21
- assetManager: AssetManager,
23
+ uri: String?,
24
+ context: Context,
22
25
  ) {
23
26
  @field:DoNotStrip
24
27
  private val mHybridData: HybridData
25
28
 
26
29
  init {
30
+ checkNotNull(uri) {
31
+ "[Worklets] No script URL provided. Make sure the packager is running or you have " +
32
+ "embedded a JS bundle in your application bundle."
33
+ }
34
+
27
35
  val filePrefix = "file://"
28
36
  val absPathPrefix = "/"
29
37
  val assetsPrefix = "assets://"
@@ -32,23 +40,20 @@ class ScriptBufferWrapper(
32
40
  when {
33
41
  uri.startsWith(filePrefix) -> {
34
42
  val fileName = uri.substring(filePrefix.length)
35
- initHybridFromFile(fileName)
43
+ initHybridFromFile(fileName, fileName)
36
44
  }
37
45
  uri.startsWith(assetsPrefix) -> {
38
46
  val assetURL = uri.substring(assetsPrefix.length)
39
- initHybridFromAssets(assetManager, assetURL)
47
+ initHybridFromAssets(context.assets, assetURL)
40
48
  }
41
49
  uri.startsWith(absPathPrefix) -> {
42
- initHybridFromFile(uri)
50
+ initHybridFromFile(uri, uri)
43
51
  }
44
52
  else -> {
45
- val scriptContent =
46
- try {
47
- downloadScript(uri)
48
- } catch (e: IOException) {
49
- throw RuntimeException(e)
50
- }
51
- initHybridFromString(scriptContent, uri)
53
+ val bundleFile =
54
+ reactNativeDownloadedBundleFile(context)
55
+ ?: File(context.cacheDir, DEV_BUNDLE_FILE_NAME).also { downloadScriptToFile(uri, it) }
56
+ initHybridFromFile(bundleFile.absolutePath, uri)
52
57
  }
53
58
  }
54
59
  }
@@ -58,45 +63,84 @@ class ScriptBufferWrapper(
58
63
  assetURL: String,
59
64
  ): HybridData
60
65
 
61
- private external fun initHybridFromFile(fileName: String): HybridData
62
-
63
- private external fun initHybridFromString(
64
- script: String,
65
- url: String,
66
+ private external fun initHybridFromFile(
67
+ fileName: String,
68
+ sourceURL: String,
66
69
  ): HybridData
67
70
 
68
71
  companion object {
69
- private fun downloadScript(url: String): String {
70
- val scriptUrl = URL(url)
71
- val connection = scriptUrl.openConnection() as HttpURLConnection
72
- try {
73
- val content: ByteArray
72
+ private const val DEV_BUNDLE_FILE_NAME = "WorkletsDevBundle.js"
73
+ private const val CONNECT_TIMEOUT_MS = 5_000
74
74
 
75
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
76
- content = connection.inputStream.readAllBytes()
77
- } else {
78
- content = readBytes(connection.inputStream)
79
- }
75
+ private fun reactNativeDownloadedBundleFile(context: Context): File? {
76
+ val reactApplication = context.applicationContext as? ReactApplication ?: return null
77
+ val bundlePath =
78
+ reactApplication.reactHost
79
+ ?.devSupportManager
80
+ ?.downloadedJSBundleFile ?: return null
81
+ return File(bundlePath).takeIf { it.exists() }
82
+ }
80
83
 
81
- return String(content, StandardCharsets.UTF_8)
84
+ private fun downloadScriptToFile(
85
+ url: String,
86
+ outputFile: File,
87
+ ) {
88
+ val connection = URL(url).openConnection() as HttpURLConnection
89
+ connection.connectTimeout = CONNECT_TIMEOUT_MS
90
+ try {
91
+ val statusCode =
92
+ try {
93
+ connection.responseCode
94
+ } catch (e: IOException) {
95
+ throw RuntimeException("[Worklets] Could not connect to development server.\n", e)
96
+ }
97
+ if (statusCode != 200) {
98
+ throw RuntimeException(serverErrorMessage(url, statusCode, connection))
99
+ }
100
+ try {
101
+ val tmpFile = File(outputFile.path + ".tmp")
102
+ connection.inputStream.use { input ->
103
+ tmpFile.outputStream().use { output -> input.copyTo(output) }
104
+ }
105
+ if (!tmpFile.renameTo(outputFile)) {
106
+ throw IOException("Couldn't rename $tmpFile to $outputFile")
107
+ }
108
+ } catch (e: IOException) {
109
+ throw RuntimeException(
110
+ "[Worklets] Failed to store worklets bundle from URL $url: ${e.message}",
111
+ e,
112
+ )
113
+ }
82
114
  } finally {
83
115
  connection.disconnect()
84
116
  }
85
117
  }
86
118
 
87
- /** Reads all bytes from an InputStream into a byte array for SDKs below 33. */
88
- private fun readBytes(inputStream: InputStream): ByteArray {
89
- val bufferSize = 4096
90
- try {
91
- val byteBuffer = java.io.ByteArrayOutputStream()
92
- val buffer = ByteArray(bufferSize)
93
- var len: Int
94
- while (inputStream.read(buffer).also { len = it } != -1) {
95
- byteBuffer.write(buffer, 0, len)
96
- }
97
- return byteBuffer.toByteArray()
98
- } finally {
99
- inputStream.close()
119
+ private fun serverErrorMessage(
120
+ url: String,
121
+ statusCode: Int,
122
+ connection: HttpURLConnection,
123
+ ): String {
124
+ val body = connection.errorStream?.use { String(it.readBytes(), StandardCharsets.UTF_8) }
125
+ val metroMessage = parseMetroError(body)
126
+ if (metroMessage != null) {
127
+ return "[Worklets] $metroMessage"
128
+ }
129
+ return "[Worklets] The development server returned response error code: $statusCode\n\n" +
130
+ "URL: $url\n\n" +
131
+ "Body:\n$body"
132
+ }
133
+
134
+ private fun parseMetroError(body: String?): String? {
135
+ if (body.isNullOrEmpty()) {
136
+ return null
137
+ }
138
+ return try {
139
+ val json = JSONObject(body)
140
+ val fileName = json.getString("filename").substringAfterLast('/')
141
+ "${json.getString("message")}\n at $fileName:${json.getInt("lineNumber")}:${json.getInt("column")}"
142
+ } catch (e: JSONException) {
143
+ null
100
144
  }
101
145
  }
102
146
  }
@@ -14,7 +14,6 @@ import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
14
14
  import com.facebook.soloader.SoLoader
15
15
  import com.swmansion.worklets.runloop.AnimationFrameCallback
16
16
  import com.swmansion.worklets.runloop.AnimationFrameQueue
17
- import java.util.concurrent.atomic.AtomicBoolean
18
17
 
19
18
  @Suppress("KotlinJniMissingFunction")
20
19
  @ReactModule(name = WorkletsModule.NAME)
@@ -50,7 +49,8 @@ class WorkletsModule(
50
49
  * Invalidating concurrently could be fatal. It shouldn't happen in a normal flow, but it doesn't
51
50
  * cost us much to add synchronization for extra safety.
52
51
  */
53
- private val mInvalidated = AtomicBoolean(false)
52
+ private val mInvalidationLock = Any()
53
+ private var mInvalidated = false
54
54
 
55
55
  @OptIn(FrameworkAPI::class)
56
56
  private external fun initHybrid(
@@ -71,11 +71,9 @@ class WorkletsModule(
71
71
  val jsContext = checkNotNull(context.javaScriptContextHolder).get()
72
72
  val jsCallInvokerHolder = context.jsCallInvokerHolder as CallInvokerHolderImpl
73
73
 
74
- val sourceURL = context.sourceURL
75
-
76
74
  val scriptBufferWrapper: ScriptBufferWrapper? =
77
75
  if (bundleModeEnabled) {
78
- ScriptBufferWrapper(sourceURL!!, context.assets)
76
+ ScriptBufferWrapper(context.sourceURL, context)
79
77
  } else {
80
78
  null
81
79
  }
@@ -161,9 +159,17 @@ class WorkletsModule(
161
159
  return mSlowAnimationsEnabled
162
160
  }
163
161
 
162
+ override fun initialize() {
163
+ reactApplicationContext.addLifecycleEventListener(this)
164
+ }
165
+
164
166
  override fun invalidate() {
165
- if (mInvalidated.getAndSet(true)) {
166
- return
167
+ synchronized(mInvalidationLock) {
168
+ if (mInvalidated) {
169
+ return
170
+ }
171
+ mInvalidated = true
172
+ reactApplicationContext.removeLifecycleEventListener(this)
167
173
  }
168
174
  if (mHybridData != null && mHybridData!!.isValid) {
169
175
  invalidateCpp()
@@ -176,11 +182,21 @@ class WorkletsModule(
176
182
  private external fun startCpp()
177
183
 
178
184
  override fun onHostResume() {
179
- mAnimationFrameQueue.resume()
185
+ synchronized(mInvalidationLock) {
186
+ if (mInvalidated) {
187
+ return
188
+ }
189
+ mAnimationFrameQueue.resume()
190
+ }
180
191
  }
181
192
 
182
193
  override fun onHostPause() {
183
- mAnimationFrameQueue.pause()
194
+ synchronized(mInvalidationLock) {
195
+ if (mInvalidated) {
196
+ return
197
+ }
198
+ mAnimationFrameQueue.pause()
199
+ }
184
200
  }
185
201
 
186
202
  override fun onHostDestroy() {}
@@ -11,7 +11,6 @@ import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
11
11
  import com.facebook.soloader.SoLoader
12
12
  import com.swmansion.worklets.runloop.AnimationFrameCallback
13
13
  import com.swmansion.worklets.runloop.AnimationFrameQueue
14
- import java.util.concurrent.atomic.AtomicBoolean
15
14
 
16
15
  @Suppress("KotlinJniMissingFunction")
17
16
  @ReactModule(name = WorkletsModule.NAME)
@@ -46,7 +45,8 @@ class WorkletsModule(
46
45
  * Invalidating concurrently could be fatal. It shouldn't happen in a normal flow, but it doesn't
47
46
  * cost us much to add synchronization for extra safety.
48
47
  */
49
- private val mInvalidated = AtomicBoolean(false)
48
+ private val mInvalidationLock = Any()
49
+ private var mInvalidated = false
50
50
 
51
51
  @OptIn(FrameworkAPI::class)
52
52
  private external fun initHybrid(
@@ -67,11 +67,9 @@ class WorkletsModule(
67
67
  val jsContext = checkNotNull(context.javaScriptContextHolder).get()
68
68
  val jsCallInvokerHolder = context.jsCallInvokerHolder as CallInvokerHolderImpl
69
69
 
70
- val sourceURL = context.sourceURL
71
-
72
70
  val scriptBufferWrapper: ScriptBufferWrapper? =
73
71
  if (bundleModeEnabled) {
74
- ScriptBufferWrapper(sourceURL!!, context.assets)
72
+ ScriptBufferWrapper(context.sourceURL, context)
75
73
  } else {
76
74
  null
77
75
  }
@@ -117,9 +115,17 @@ class WorkletsModule(
117
115
  return mSlowAnimationsEnabled
118
116
  }
119
117
 
118
+ override fun initialize() {
119
+ reactApplicationContext.addLifecycleEventListener(this)
120
+ }
121
+
120
122
  override fun invalidate() {
121
- if (mInvalidated.getAndSet(true)) {
122
- return
123
+ synchronized(mInvalidationLock) {
124
+ if (mInvalidated) {
125
+ return
126
+ }
127
+ mInvalidated = true
128
+ reactApplicationContext.removeLifecycleEventListener(this)
123
129
  }
124
130
  if (mHybridData != null && mHybridData!!.isValid) {
125
131
  invalidateCpp()
@@ -132,11 +138,21 @@ class WorkletsModule(
132
138
  private external fun startCpp()
133
139
 
134
140
  override fun onHostResume() {
135
- mAnimationFrameQueue.resume()
141
+ synchronized(mInvalidationLock) {
142
+ if (mInvalidated) {
143
+ return
144
+ }
145
+ mAnimationFrameQueue.resume()
146
+ }
136
147
  }
137
148
 
138
149
  override fun onHostPause() {
139
- mAnimationFrameQueue.pause()
150
+ synchronized(mInvalidationLock) {
151
+ if (mInvalidated) {
152
+ return
153
+ }
154
+ mAnimationFrameQueue.pause()
155
+ }
140
156
  }
141
157
 
142
158
  override fun onHostDestroy() {}
@@ -9,6 +9,9 @@ using namespace worklets;
9
9
  class IOSUIScheduler : public UIScheduler, public std::enable_shared_from_this<IOSUIScheduler> {
10
10
  public:
11
11
  void scheduleOnUI(std::function<void()> job) override;
12
+
13
+ protected:
14
+ bool queryIsOnUIThread() const override;
12
15
  };
13
16
 
14
17
  } // namespace worklets
@@ -5,9 +5,14 @@ namespace worklets {
5
5
  using namespace facebook;
6
6
  using namespace react;
7
7
 
8
+ bool IOSUIScheduler::queryIsOnUIThread() const
9
+ {
10
+ return [NSThread isMainThread];
11
+ }
12
+
8
13
  void IOSUIScheduler::scheduleOnUI(std::function<void()> job)
9
14
  {
10
- if ([NSThread isMainThread]) {
15
+ if (isOnUIThread()) {
11
16
  job();
12
17
  return;
13
18
  }
@@ -0,0 +1,11 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ #import <worklets/Tools/ScriptBuffer.h>
4
+
5
+ #import <memory>
6
+
7
+ namespace worklets {
8
+
9
+ std::shared_ptr<const ScriptBuffer> getScript(NSURL *url);
10
+
11
+ } // namespace worklets
@@ -0,0 +1,124 @@
1
+ #import <worklets/apple/ScriptLoader.h>
2
+
3
+ namespace worklets {
4
+ namespace {
5
+
6
+ class NSDataScript : public JSBigString {
7
+ public:
8
+ explicit NSDataScript(NSData *data) : data_(data), size_([data length]) {}
9
+
10
+ bool isAscii() const override
11
+ {
12
+ return false;
13
+ }
14
+
15
+ const char *c_str() const override
16
+ {
17
+ return static_cast<const char *>(data_.bytes);
18
+ }
19
+
20
+ size_t size() const override
21
+ {
22
+ return size_;
23
+ }
24
+
25
+ private:
26
+ NSData *data_;
27
+ size_t size_;
28
+ };
29
+
30
+ NSData *loadScriptFromFile(NSURL *url)
31
+ {
32
+ NSError *error;
33
+ NSData *data = [NSData dataWithContentsOfFile:url.path options:NSDataReadingMappedIfSafe error:&error];
34
+
35
+ if (data == nil) [[unlikely]] {
36
+ NSString *errorMsg = [NSString stringWithFormat:@"[Worklets] Failed to load worklets bundle from file %@: %@",
37
+ url.path,
38
+ error.localizedDescription];
39
+ NSLog(@"%@", errorMsg);
40
+ throw std::runtime_error([errorMsg UTF8String]);
41
+ }
42
+
43
+ return data;
44
+ }
45
+
46
+ NSData *downloadScript(NSURL *url)
47
+ {
48
+ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
49
+ __block NSData *data = nil;
50
+ __block NSURLResponse *response = nil;
51
+ __block NSError *error = nil;
52
+
53
+ NSURLSessionDataTask *task = [[NSURLSession sharedSession]
54
+ dataTaskWithURL:url
55
+ completionHandler:^(NSData *taskData, NSURLResponse *taskResponse, NSError *taskError) {
56
+ data = taskData;
57
+ response = taskResponse;
58
+ error = taskError;
59
+ dispatch_semaphore_signal(semaphore);
60
+ }];
61
+ [task resume];
62
+ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
63
+
64
+ if (error != nil) [[unlikely]] {
65
+ NSString *errorMsg;
66
+ if ([error.domain isEqualToString:NSURLErrorDomain]) {
67
+ errorMsg = @"[Worklets] Could not connect to development server.\n";
68
+ } else {
69
+ errorMsg = [NSString stringWithFormat:@"[Worklets] Failed to load worklets bundle from URL %@: %@",
70
+ url,
71
+ error.localizedDescription];
72
+ }
73
+ NSLog(@"%@", errorMsg);
74
+ throw std::runtime_error([errorMsg UTF8String]);
75
+ }
76
+
77
+ NSInteger statusCode =
78
+ [response isKindOfClass:[NSHTTPURLResponse class]] ? [(NSHTTPURLResponse *)response statusCode] : 200;
79
+ if (statusCode != 200) [[unlikely]] {
80
+ NSString *description = nil;
81
+ NSDictionary *jsonError = data != nil ? [NSJSONSerialization JSONObjectWithData:data options:0 error:nil] : nil;
82
+ if ([jsonError isKindOfClass:[NSDictionary class]] && [jsonError[@"message"] isKindOfClass:[NSString class]] &&
83
+ [jsonError[@"message"] length] > 0) {
84
+ description = jsonError[@"message"];
85
+ } else {
86
+ description = [NSString stringWithFormat:@"Received status code %ld while fetching the bundle", (long)statusCode];
87
+ }
88
+ NSString *errorMsg =
89
+ [NSString stringWithFormat:@"[Worklets] Failed to load worklets bundle from URL %@: %@", url, description];
90
+ NSLog(@"%@", errorMsg);
91
+ throw std::runtime_error([errorMsg UTF8String]);
92
+ }
93
+
94
+ NSString *mimeType = response.MIMEType;
95
+ if (![mimeType isEqualToString:@"application/javascript"] && ![mimeType isEqualToString:@"text/javascript"])
96
+ [[unlikely]] {
97
+ NSString *errorMsg = [NSString
98
+ stringWithFormat:
99
+ @"[Worklets] Expected MIME-Type of the worklets bundle to be 'application/javascript' or 'text/javascript', but got '%@'",
100
+ mimeType];
101
+ NSLog(@"%@", errorMsg);
102
+ throw std::runtime_error([errorMsg UTF8String]);
103
+ }
104
+
105
+ return data;
106
+ }
107
+
108
+ } // namespace
109
+
110
+ std::shared_ptr<const ScriptBuffer> getScript(NSURL *url)
111
+ {
112
+ if (url == nil) [[unlikely]] {
113
+ NSString *errorMsg =
114
+ @"[Worklets] No script URL provided. Make sure the packager is running or you have embedded a "
115
+ @"JS bundle in your application bundle.";
116
+ NSLog(@"%@", errorMsg);
117
+ throw std::runtime_error([errorMsg UTF8String]);
118
+ }
119
+
120
+ NSData *data = url.isFileURL ? loadScriptFromFile(url) : downloadScript(url);
121
+ return std::make_shared<const ScriptBuffer>(std::make_shared<const NSDataScript>(data));
122
+ }
123
+
124
+ } // namespace worklets
@@ -6,22 +6,7 @@
6
6
 
7
7
  #import <worklets/NativeModules/WorkletsModuleProxy.h>
8
8
 
9
- #if __has_include(<React/RCTBundleConsumer.h>)
10
- // Bundle mode
11
- #import <React/NSBigStringBuffer.h>
12
- #import <React/RCTBundleConsumer.h>
13
- #endif // __has_include(<React/RCTBundleConsumer.h>)
14
-
15
- @interface WorkletsModule : RCTEventEmitter <
16
- NativeWorkletsModuleSpec,
17
- RCTCallInvokerModule,
18
- RCTInvalidating
19
- #if __has_include(<React/RCTBundleConsumer.h>)
20
- // Bundle mode
21
- ,
22
- RCTBundleConsumer
23
- #endif // __has_include(<React/RCTBundleConsumer.h>)
24
- >
9
+ @interface WorkletsModule : RCTEventEmitter <NativeWorkletsModuleSpec, RCTCallInvokerModule, RCTInvalidating>
25
10
 
26
11
  - (std::shared_ptr<worklets::WorkletsModuleProxy>)getWorkletsModuleProxy;
27
12
 
@@ -7,8 +7,11 @@
7
7
  #import <worklets/apple/AssertJavaScriptQueue.h>
8
8
  #import <worklets/apple/AssertTurboModuleManagerQueue.h>
9
9
  #import <worklets/apple/IOSUIScheduler.h>
10
+ #import <worklets/apple/ScriptLoader.h>
10
11
  #import <worklets/apple/WorkletsModule.h>
11
12
 
13
+ #import <Foundation/Foundation.h>
14
+
12
15
  #import <React/RCTBridge+Private.h>
13
16
  #import <React/RCTCallInvoker.h>
14
17
 
@@ -64,7 +67,7 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(installTurboModule : (BOOL)bundleModeEnab
64
67
 
65
68
  if (bundleModeEnabled) {
66
69
  NSURL *url = bundleManager_.bundleURL;
67
- script = [self getScript:url];
70
+ script = getScript(url);
68
71
  sourceURL = [[url absoluteString] UTF8String];
69
72
  }
70
73
 
@@ -127,21 +130,6 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(toggleSlowAnimationsOnUIRuntime)
127
130
  return std::make_shared<facebook::react::NativeWorkletsModuleSpecJSI>(params);
128
131
  }
129
132
 
130
- - (std::shared_ptr<const ScriptBuffer>)getScript:(NSURL *)url
131
- {
132
- NSData *data = [NSData dataWithContentsOfURL:url];
133
-
134
- if (!data) [[unlikely]] {
135
- NSString *errorMsg = [NSString stringWithFormat:@"[Worklets] Failed to load worklets bundle from URL: %@", url];
136
- NSLog(@"%@", errorMsg);
137
- throw std::runtime_error([errorMsg UTF8String]);
138
- }
139
-
140
- auto str = std::string(reinterpret_cast<const char *>([data bytes]), [data length]);
141
- auto bigString = std::make_shared<const JSBigStdString>(str);
142
- return std::make_shared<const ScriptBuffer>(bigString);
143
- }
144
-
145
133
  - (std::shared_ptr<RuntimeBindings>)getRuntimeBindings:(jsi::Runtime &)rnRuntime
146
134
  bundleModeEnabled:(BOOL)bundleModeEnabled
147
135
  {
package/changes.json CHANGED
@@ -53,6 +53,6 @@
53
53
  "to": "^7.8.5"
54
54
  }
55
55
  },
56
- "timestamp": "2026-07-26T00:57:50.717Z",
56
+ "timestamp": "2026-08-23T00:27:36.320Z",
57
57
  "totalUpdated": 13
58
58
  }
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "nightly": {
3
- "react-native": ["0.83", "0.84", "0.85", "0.86"]
3
+ "react-native": ["0.83", "0.84", "0.85", "0.86", "0.87"]
4
+ },
5
+ "0.12.x": {
6
+ "react-native": ["0.83", "0.84", "0.85", "0.86", "0.87"]
4
7
  },
5
8
  "0.11.x": {
6
9
  "react-native": ["0.83", "0.84", "0.85", "0.86"]
@@ -124,8 +124,8 @@ See https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting
124
124
  runOnUISync(worklet, scheduleStack) {
125
125
  return this.#workletsModuleProxy.runOnUISync(worklet, scheduleStack);
126
126
  }
127
- createWorkletRuntime(name, initializer, useDefaultQueue, customQueue, enableEventLoop) {
128
- return this.#workletsModuleProxy.createWorkletRuntime(name, initializer, useDefaultQueue, customQueue, enableEventLoop);
127
+ createWorkletRuntime(name, initializer, useDefaultQueue, customQueue, enableEventLoop, enableLocking) {
128
+ return this.#workletsModuleProxy.createWorkletRuntime(name, initializer, useDefaultQueue, customQueue, enableEventLoop, enableLocking);
129
129
  }
130
130
  scheduleOnRuntime(workletRuntime, serializableWorklet, scheduleStack) {
131
131
  return this.#workletsModuleProxy.scheduleOnRuntime(workletRuntime, serializableWorklet, scheduleStack);