react-native-worklets 0.13.0-nightly-20260916-1fb70959f → 0.13.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 (31) hide show
  1. package/Common/cpp/worklets/RunLoop/AsyncQueue.h +28 -0
  2. package/Common/cpp/worklets/RunLoop/AsyncQueueImpl.cpp +9 -0
  3. package/Common/cpp/worklets/RunLoop/AsyncQueueImpl.h +6 -0
  4. package/Common/cpp/worklets/RunLoop/EventLoop.cpp +18 -2
  5. package/Common/cpp/worklets/RunLoop/EventLoop.h +3 -0
  6. package/Common/cpp/worklets/Tools/ScriptBuffer.h +0 -5
  7. package/Common/cpp/worklets/Tools/WorkletsJSIUtils.h +15 -10
  8. package/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp +24 -10
  9. package/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h +6 -0
  10. package/android/CMakeLists.txt +7 -23
  11. package/android/build.gradle.kts +4 -13
  12. package/apple/worklets/apple/Networking/WorkletsURLSessionDelegate.h +1 -1
  13. package/apple/worklets/apple/Networking/WorkletsURLSessionDelegate.mm +4 -5
  14. package/compatibility.json +2 -2
  15. package/lib/module/debug/jsVersion.js +1 -1
  16. package/lib/module/debug/jsVersion.js.map +1 -1
  17. package/lib/typescript/debug/jsVersion.d.ts +1 -1
  18. package/lib/typescript/debug/jsVersion.d.ts.map +1 -1
  19. package/package.json +13 -7
  20. package/plugin-oxc/babel.d.ts +7 -0
  21. package/plugin-oxc/babel.js +200 -0
  22. package/plugin-oxc/index.d.ts +33 -0
  23. package/plugin-oxc/index.js +36 -0
  24. package/plugin-oxc/worklets-oxc-plugin.darwin-arm64.node +0 -0
  25. package/plugin-oxc/worklets-oxc-plugin.darwin-x64.node +0 -0
  26. package/plugin-oxc/worklets-oxc-plugin.linux-arm64.node +0 -0
  27. package/plugin-oxc/worklets-oxc-plugin.linux-x64.node +0 -0
  28. package/plugin-oxc/worklets-oxc-plugin.win32-arm64.node +0 -0
  29. package/plugin-oxc/worklets-oxc-plugin.win32-x64.node +0 -0
  30. package/src/debug/jsVersion.ts +1 -1
  31. package/Common/cpp/worklets/Compat/ReactNativeVersionCompat.h +0 -7
@@ -2,13 +2,41 @@
2
2
 
3
3
  #include <jsi/jsi.h>
4
4
 
5
+ #include <cstdint>
6
+ #include <utility>
7
+
5
8
  namespace worklets {
6
9
 
10
+ /**
11
+ * Identifies the owner of a job, so that a queue shared between several owners
12
+ * can cancel the jobs of one of them without touching the jobs of the others.
13
+ *
14
+ * Worklet Runtimes send their RuntimeId as a token.
15
+ */
16
+ using AbortToken = uint64_t;
17
+
18
+ inline constexpr AbortToken defaultToken{0};
19
+
7
20
  class AsyncQueue : public facebook::jsi::NativeState {
8
21
  public:
9
22
  ~AsyncQueue() override = default;
10
23
 
11
24
  virtual void push(std::function<void()> &&job) = 0;
25
+
26
+ /**
27
+ * Pushes a job on behalf of the owner the token belongs to. The default
28
+ * implementation discards the token.
29
+ */
30
+ virtual void push(std::function<void()> &&job, AbortToken /* abortToken */) {
31
+ push(std::move(job));
32
+ }
33
+
34
+ /**
35
+ * Drops the jobs that are still pending, destroying them on the calling
36
+ * thread. A queue shared between several owners drops only the jobs pushed
37
+ * with this token. The default implementation keeps every job.
38
+ */
39
+ virtual void abortPending(AbortToken /* abortToken */) {}
12
40
  };
13
41
 
14
42
  } // namespace worklets
@@ -5,6 +5,7 @@
5
5
  #endif // ANDROID
6
6
 
7
7
  #include <memory>
8
+ #include <queue>
8
9
  #include <string>
9
10
  #include <thread>
10
11
  #include <utility>
@@ -92,6 +93,14 @@ void AsyncQueueImpl::push(std::function<void()> &&job) {
92
93
  state_->cv.notify_one();
93
94
  }
94
95
 
96
+ void AsyncQueueImpl::abortPending(AbortToken /* abortToken */) {
97
+ std::queue<std::function<void()>> pendingJobs;
98
+ {
99
+ std::unique_lock<std::mutex> lock(state_->mutex);
100
+ std::swap(pendingJobs, state_->queue);
101
+ }
102
+ }
103
+
95
104
  AsyncQueueUI::AsyncQueueUI(const std::shared_ptr<UIScheduler> &uiScheduler) : uiScheduler_(uiScheduler) {}
96
105
 
97
106
  void AsyncQueueUI::push(std::function<void()> &&job) {
@@ -25,8 +25,12 @@ class AsyncQueueImpl : public AsyncQueue {
25
25
 
26
26
  ~AsyncQueueImpl() override;
27
27
 
28
+ using AsyncQueue::push;
29
+
28
30
  void push(std::function<void()> &&job) override;
29
31
 
32
+ void abortPending(AbortToken abortToken) override;
33
+
30
34
  private:
31
35
  static void runLoop(const std::shared_ptr<AsyncQueueState> &state);
32
36
 
@@ -39,6 +43,8 @@ class AsyncQueueUI : public AsyncQueue {
39
43
 
40
44
  ~AsyncQueueUI() override = default;
41
45
 
46
+ using AsyncQueue::push;
47
+
42
48
  void push(std::function<void()> &&job) override;
43
49
 
44
50
  private:
@@ -10,10 +10,12 @@ namespace worklets {
10
10
 
11
11
  EventLoop::EventLoop(
12
12
  const std::string &name,
13
+ const AbortToken abortToken,
13
14
  const std::shared_ptr<jsi::Runtime> &runtime,
14
15
  const std::shared_ptr<AsyncQueue> &queue,
15
16
  const std::shared_ptr<std::recursive_mutex> &runtimeMutex)
16
- : runtime_(runtime),
17
+ : abortToken_(abortToken),
18
+ runtime_(runtime),
17
19
  queue_(queue),
18
20
  runtimeMutex_(runtimeMutex),
19
21
  timeoutsQueueState_(std::make_shared<TimeoutsQueueState>()),
@@ -88,12 +90,26 @@ void EventLoop::pushTask(std::function<void(jsi::Runtime &rt)> &&job) {
88
90
  job(*runtime);
89
91
  runtime->drainMicrotasks();
90
92
  }
91
- });
93
+ },
94
+ abortToken_);
95
+ }
96
+
97
+ void EventLoop::abortPending() {
98
+ std::vector<Timeout> pendingTimeouts;
99
+ {
100
+ std::unique_lock<std::mutex> lock(timeoutsQueueState_->mutex);
101
+ timeoutsQueueState_->running = false;
102
+ std::swap(pendingTimeouts, timeoutsQueueState_->queue);
103
+ }
104
+ timeoutsQueueState_->cv.notify_all();
92
105
  }
93
106
 
94
107
  void EventLoop::pushTimeout(std::function<void(jsi::Runtime &rt)> &&job, int64_t delay) {
95
108
  {
96
109
  std::unique_lock<std::mutex> lock(timeoutsQueueState_->mutex);
110
+ if (!timeoutsQueueState_->running) {
111
+ return;
112
+ }
97
113
  const auto targetTime = getCurrentTimeInMs() + delay;
98
114
  const auto timeout = Timeout{std::move(job), targetTime};
99
115
  auto &queue = timeoutsQueueState_->queue;
@@ -33,6 +33,7 @@ class EventLoop : public std::enable_shared_from_this<EventLoop> {
33
33
  public:
34
34
  EventLoop(
35
35
  const std::string &name,
36
+ AbortToken abortToken,
36
37
  const std::shared_ptr<jsi::Runtime> &runtime,
37
38
  const std::shared_ptr<AsyncQueue> &queue,
38
39
  const std::shared_ptr<std::recursive_mutex> &runtimeMutex);
@@ -40,8 +41,10 @@ class EventLoop : public std::enable_shared_from_this<EventLoop> {
40
41
  void run();
41
42
  void pushTask(std::function<void(jsi::Runtime &rt)> &&job);
42
43
  void pushTimeout(std::function<void(jsi::Runtime &rt)> &&job, int64_t delay);
44
+ void abortPending();
43
45
 
44
46
  private:
47
+ const AbortToken abortToken_;
45
48
  const std::shared_ptr<jsi::Runtime> runtime_;
46
49
  const std::shared_ptr<AsyncQueue> queue_;
47
50
  const std::shared_ptr<std::recursive_mutex> runtimeMutex_;
@@ -1,13 +1,8 @@
1
1
  #pragma once
2
2
 
3
3
  #include <jsi/jsi.h>
4
- #include <worklets/Compat/ReactNativeVersionCompat.h>
5
4
 
6
- #if REACT_NATIVE_VERSION_MINOR >= 84
7
5
  #include <cxxreact/JSBigString.h>
8
- #else
9
- #include <jsireact/JSIExecutor.h>
10
- #endif
11
6
 
12
7
  #include <memory>
13
8
  #include <utility>
@@ -183,21 +183,26 @@ void addMethod(jsi::Runtime &rt, jsi::Object &obj, const char *name, TFun &&func
183
183
  rt,
184
184
  jsi::PropNameID::forAscii(rt, name),
185
185
  TLength,
186
- [func = std::forward<TFun>(func), name](
186
+ [func = std::forward<TFun>(func)](
187
187
  jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) mutable -> jsi::Value {
188
188
  using TReturn =
189
189
  std::invoke_result_t<TFun &, jsi::Runtime &, const jsi::Value &, const jsi::Value(&)[TLength]>;
190
+ auto invoke = [&](const jsi::Value(&typed)[TLength]) -> jsi::Value {
191
+ if constexpr (std::is_void_v<TReturn>) {
192
+ func(rt, thisVal, typed);
193
+ return jsi::Value::undefined();
194
+ } else {
195
+ return func(rt, thisVal, typed);
196
+ }
197
+ };
190
198
  if (count < TLength) {
191
- throw jsi::JSError(
192
- rt, std::string("[Worklets] ") + name + " expects " + std::to_string(TLength) + " arguments.");
193
- }
194
- auto &typed = *reinterpret_cast<const jsi::Value(*)[TLength]>(args);
195
- if constexpr (std::is_void_v<TReturn>) {
196
- func(rt, thisVal, typed);
197
- return jsi::Value::undefined();
198
- } else {
199
- return func(rt, thisVal, typed);
199
+ jsi::Value padded[TLength];
200
+ for (size_t i = 0; i < count; ++i) {
201
+ padded[i] = jsi::Value(rt, args[i]);
202
+ }
203
+ return invoke(padded);
200
204
  }
205
+ return invoke(*reinterpret_cast<const jsi::Value(*)[TLength]>(args));
201
206
  }));
202
207
  }
203
208
 
@@ -88,11 +88,23 @@ WorkletRuntime::WorkletRuntime(
88
88
  jsi::Runtime &rt = *runtime_;
89
89
  WorkletRuntimeCollector::install(rt);
90
90
  if (enableEventLoop) {
91
- eventLoop_ = std::make_shared<EventLoop>(name_, runtime_, queue_, runtimeMutex_);
91
+ eventLoop_ = std::make_shared<EventLoop>(name_, abortToken(), runtime_, queue_, runtimeMutex_);
92
92
  eventLoop_->run();
93
93
  }
94
94
  }
95
95
 
96
+ WorkletRuntime::~WorkletRuntime() {
97
+ auto lock = acquireRuntimeLock();
98
+ if (eventLoop_) {
99
+ eventLoop_->abortPending();
100
+ eventLoop_.reset();
101
+ }
102
+ if (queue_) {
103
+ queue_->abortPending(abortToken());
104
+ queue_.reset();
105
+ }
106
+ }
107
+
96
108
  void WorkletRuntime::init(const std::shared_ptr<JSIWorkletsModuleProxy> &jsiWorkletsModuleProxy) {
97
109
  jsi::Runtime &rt = *runtime_;
98
110
 
@@ -244,15 +256,17 @@ void WorkletRuntime::scheduleImpl(ScheduledJob job) const {
244
256
  "[Worklets] Tried to invoke `schedule` on a Worklet Runtime but the "
245
257
  "async queue is not set. Recreate the runtime with a valid async queue.");
246
258
 
247
- queue_->push([job = std::move(job), weakThis = weak_from_this()] {
248
- const auto strongThis = weakThis.lock();
249
- if (!strongThis) {
250
- return;
251
- }
252
-
253
- auto lock = strongThis->acquireRuntimeLock();
254
- job(*strongThis);
255
- });
259
+ queue_->push(
260
+ [job = std::move(job), weakThis = weak_from_this()] {
261
+ const auto strongThis = weakThis.lock();
262
+ if (!strongThis) {
263
+ return;
264
+ }
265
+
266
+ auto lock = strongThis->acquireRuntimeLock();
267
+ job(*strongThis);
268
+ },
269
+ abortToken());
256
270
  }
257
271
 
258
272
  /* #endregion */
@@ -261,6 +261,8 @@ class WorkletRuntime : public jsi::HostObject, public std::enable_shared_from_th
261
261
  bool enableLocking = true,
262
262
  bool enableNetworking = true);
263
263
 
264
+ ~WorkletRuntime() override;
265
+
264
266
  void init(const std::shared_ptr<JSIWorkletsModuleProxy> &jsiWorkletsModuleProxy);
265
267
 
266
268
  /**
@@ -439,6 +441,10 @@ class WorkletRuntime : public jsi::HostObject, public std::enable_shared_from_th
439
441
 
440
442
  void legacyModeInit(const std::shared_ptr<UnpackerLoader> &unpackerLoader);
441
443
 
444
+ [[nodiscard]] AbortToken abortToken() const noexcept {
445
+ return static_cast<AbortToken>(runtimeId_);
446
+ }
447
+
442
448
  [[nodiscard]] std::unique_lock<std::recursive_mutex> acquireRuntimeLock() const {
443
449
  if (enableLocking_) {
444
450
  return std::unique_lock<std::recursive_mutex>(*runtimeMutex_);
@@ -19,16 +19,10 @@ add_compile_options(${folly_FLAGS})
19
19
  string(APPEND CMAKE_CXX_FLAGS " -DWORKLETS_VERSION=${WORKLETS_VERSION}\
20
20
  -DWORKLETS_FEATURE_FLAGS=\"${WORKLETS_FEATURE_FLAGS}\"")
21
21
 
22
- # HERMES_V1_ENABLED is centralized in react-native-flags.cmake for RN >= 84.
23
- # Only define manually for older versions to avoid macro redefinition error.
24
- if(ReactAndroid_VERSION_MINOR LESS 84)
25
- string(APPEND CMAKE_CXX_FLAGS " -DHERMES_V1_ENABLED=${HERMES_V1_ENABLED}")
26
- else()
27
- # Reference the value so CMake records it as used, otherwise it warns:
28
- # "Manually-specified variables were not used by the project:
29
- # HERMES_V1_ENABLED"
30
- set(HERMES_V1_ENABLED "${HERMES_V1_ENABLED}")
31
- endif()
22
+ # HERMES_V1_ENABLED is centralized in react-native-flags.cmake. Reference the
23
+ # value so CMake records it as used, otherwise it warns: "Manually-specified
24
+ # variables were not used by the project: HERMES_V1_ENABLED"
25
+ set(HERMES_V1_ENABLED "${HERMES_V1_ENABLED}")
32
26
 
33
27
  string(APPEND CMAKE_CXX_FLAGS " -fno-omit-frame-pointer -fstack-protector-all")
34
28
 
@@ -63,14 +57,8 @@ target_precompile_headers(worklets PRIVATE "${ANDROID_CPP_DIR}/WorkletsPCH.h")
63
57
  target_compile_options(
64
58
  worklets PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-Xclang;-fno-pch-timestamp>")
65
59
 
66
- if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
67
- include(
68
- "${REACT_NATIVE_DIR}/ReactCommon/cmake-utils/react-native-flags.cmake")
69
- target_compile_reactnative_options(worklets PUBLIC)
70
- else()
71
- string(APPEND CMAKE_CXX_FLAGS
72
- " -fexceptions -frtti -std=c++${CMAKE_CXX_STANDARD} -Wall -Werror")
73
- endif()
60
+ include("${REACT_NATIVE_DIR}/ReactCommon/cmake-utils/react-native-flags.cmake")
61
+ target_compile_reactnative_options(worklets PUBLIC)
74
62
 
75
63
  # includes
76
64
  target_include_directories(worklets PUBLIC "${COMMON_CPP_DIR}"
@@ -94,11 +82,7 @@ set_target_properties(worklets PROPERTIES LINKER_LANGUAGE CXX)
94
82
  target_link_libraries(worklets android log ReactAndroid::reactnative
95
83
  ReactAndroid::jsi fbjni::fbjni)
96
84
 
97
- if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 82)
98
- target_link_libraries(worklets hermes-engine::hermesvm)
99
- else()
100
- target_link_libraries(worklets hermes-engine::libhermes)
101
- endif()
85
+ target_link_libraries(worklets hermes-engine::hermesvm)
102
86
 
103
87
  if(${HERMES_ENABLE_DEBUGGER})
104
88
  string(APPEND CMAKE_CXX_FLAGS " -DHERMES_ENABLE_DEBUGGER=1")
@@ -69,22 +69,13 @@ fun getReactNativeVersion(): String {
69
69
  return reactProperties.getProperty("VERSION_NAME")
70
70
  }
71
71
 
72
- fun getReactNativeMinorVersion(): Int {
73
- val reactNativeVersion = getReactNativeVersion()
74
- return if (reactNativeVersion.startsWith("0.0.0-")) 1000 else reactNativeVersion.split(".")[1].toInt()
75
- }
76
-
77
72
  fun getHermesV1Enabled(): Boolean {
78
73
  // Even though `HERMES_V1_ENABLED` is now centralized
79
- // in `react-native-flags.cmake` for React Native >= 0.84
80
- // that CMake file depends on definitions provided
81
- // in local `externalNativeBuild` configuration of the LIBRARY.
74
+ // in `react-native-flags.cmake`, that CMake file depends
75
+ // on definitions provided in local `externalNativeBuild`
76
+ // configuration of the LIBRARY.
82
77
  // I hope this is only a temporary workaround.
83
- return if (getReactNativeMinorVersion() >= 84) {
84
- safeAppExtGet("hermesV1Enabled", true)?.toString()?.toBoolean() ?: true
85
- } else {
86
- safeAppExtGet("hermesV1Enabled", false)?.toString()?.toBoolean() ?: false
87
- }
78
+ return safeAppExtGet("hermesV1Enabled", true)?.toString()?.toBoolean() ?: true
88
79
  }
89
80
 
90
81
  fun getWorkletsVersion(): String {
@@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
26
26
 
27
27
  - (void)sendRequest:(worklets::RequestConfig &&)config
28
28
  requestId:(uint64_t)requestId
29
- listener:(const std::shared_ptr<worklets::NetworkRequestListener> &)listener;
29
+ listener:(std::shared_ptr<worklets::NetworkRequestListener>)listener;
30
30
  - (void)abortRequest:(uint64_t)requestId;
31
31
  - (void)invalidate;
32
32
 
@@ -248,7 +248,7 @@ static BOOL isSameOrigin(NSURL *lhs, NSURL *rhs)
248
248
 
249
249
  - (void)sendRequest:(RequestConfig &&)config
250
250
  requestId:(uint64_t)requestId
251
- listener:(const std::shared_ptr<NetworkRequestListener> &)listener
251
+ listener:(std::shared_ptr<NetworkRequestListener>)listener
252
252
  {
253
253
  NSString *urlString = [NSString stringWithUTF8String:config.url.c_str()];
254
254
  NSURL *url = urlString != nil ? [NSURL URLWithString:urlString] : nil;
@@ -287,22 +287,21 @@ static BOOL isSameOrigin(NSURL *lhs, NSURL *rhs)
287
287
  request.HTTPShouldHandleCookies = config.withCredentials;
288
288
  request.timeoutInterval = config.timeoutMs > 0 ? config.timeoutMs / 1000.0 : kNoTimeoutInterval;
289
289
 
290
- const auto sharedListener = listener;
291
290
  const auto timeoutMs = config.timeoutMs;
292
291
  const auto withCredentials = config.withCredentials;
293
292
 
294
293
  [_delegateQueue addOperationWithBlock:^{
295
294
  if (atomic_load(&self->_invalidated)) {
296
- sharedListener->onError(RequestError::Aborted, "The networking session was invalidated.");
295
+ listener->onError(RequestError::Aborted, "The networking session was invalidated.");
297
296
  return;
298
297
  }
299
298
  NSURLSessionDataTask *task = [[self sessionWithCredentials:withCredentials] dataTaskWithRequest:request];
300
299
  if (task == nil) {
301
- sharedListener->onError(RequestError::Network, "Failed to create a data task for the request.");
300
+ listener->onError(RequestError::Network, "Failed to create a data task for the request.");
302
301
  return;
303
302
  }
304
303
  WorkletsRequestState *state = [WorkletsRequestState new];
305
- state->listener = sharedListener;
304
+ state->listener = listener;
306
305
  state->data = [NSMutableData new];
307
306
  state->requestId = requestId;
308
307
  state->expectedContentLength = -1;
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "nightly": {
3
- "react-native": ["0.83", "0.84", "0.85", "0.86", "0.87"]
3
+ "react-native": ["0.86", "0.87", "0.88"]
4
4
  },
5
5
  "0.13.x": {
6
- "react-native": ["0.83", "0.84", "0.85", "0.86", "0.87"]
6
+ "react-native": ["0.86", "0.87", "0.88"]
7
7
  },
8
8
  "0.12.x": {
9
9
  "react-native": ["0.83", "0.84", "0.85", "0.86", "0.87"]
@@ -5,5 +5,5 @@
5
5
  * version used to build the native part of the library in runtime. Remember to
6
6
  * keep this in sync with the version declared in `package.json`
7
7
  */
8
- export const jsVersion = '0.13.0-nightly-20260916-1fb70959f';
8
+ export const jsVersion = '0.13.0';
9
9
  //# sourceMappingURL=jsVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["jsVersion"],"sourceRoot":"../../../src","sources":["debug/jsVersion.ts"],"mappings":"AAAA,YAAY;;AAEZ;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMA,SAAS,GAAG,mCAAmC","ignoreList":[]}
1
+ {"version":3,"names":["jsVersion"],"sourceRoot":"../../../src","sources":["debug/jsVersion.ts"],"mappings":"AAAA,YAAY;;AAEZ;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMA,SAAS,GAAG,QAAQ","ignoreList":[]}
@@ -3,5 +3,5 @@
3
3
  * version used to build the native part of the library in runtime. Remember to
4
4
  * keep this in sync with the version declared in `package.json`
5
5
  */
6
- export declare const jsVersion = "0.13.0-nightly-20260916-1fb70959f";
6
+ export declare const jsVersion = "0.13.0";
7
7
  //# sourceMappingURL=jsVersion.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"jsVersion.d.ts","sourceRoot":"","sources":["../../../src/debug/jsVersion.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,SAAS,sCAAsC,CAAC"}
1
+ {"version":3,"file":"jsVersion.d.ts","sourceRoot":"","sources":["../../../src/debug/jsVersion.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,SAAS,WAAW,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-worklets",
3
- "version": "0.13.0-nightly-20260916-1fb70959f",
3
+ "version": "0.13.0",
4
4
  "description": "The React Native multithreading library",
5
5
  "keywords": [
6
6
  "react-native",
@@ -9,7 +9,7 @@
9
9
  "worklets"
10
10
  ],
11
11
  "scripts": {
12
- "build": "yarn workspace babel-plugin-worklets build && yarn set-version && yarn run --top-level bob build",
12
+ "build": "yarn workspace babel-plugin-worklets build && yarn workspace worklets-oxc-plugin build && yarn set-version && yarn run --top-level bob build",
13
13
  "circular-dependency-check": "yarn madge --extensions js,jsx --circular lib",
14
14
  "find-unused-code:js": "knip",
15
15
  "format": "yarn format:js && yarn format:plugin && yarn format:common && yarn format:android && yarn format:apple",
@@ -62,7 +62,7 @@
62
62
  "@babel/core": "*",
63
63
  "@react-native/metro-config": "*",
64
64
  "react": "*",
65
- "react-native": "0.83 - 0.87"
65
+ "react-native": "0.86 - 0.88"
66
66
  },
67
67
  "dependencies": {
68
68
  "@babel/generator": "^7.27.1",
@@ -77,6 +77,7 @@
77
77
  "@babel/preset-typescript": "^7.28.5",
78
78
  "@babel/traverse": "^7.27.1",
79
79
  "@babel/types": "^7.27.1",
80
+ "@jridgewell/remapping": "^2.3.5",
80
81
  "convert-source-map": "^2.0.0",
81
82
  "semver": "^7.7.4",
82
83
  "whatwg-fetch": "^3.6.20"
@@ -85,8 +86,8 @@
85
86
  "@babel/cli": "7.28.3",
86
87
  "@babel/core": "7.29.6",
87
88
  "@react-native-community/cli": "20.2.0",
88
- "@react-native/eslint-config": "0.87.0",
89
- "@react-native/jest-preset": "0.87.0",
89
+ "@react-native/eslint-config": "0.88.0-rc.1",
90
+ "@react-native/jest-preset": "0.88.0-rc.1",
90
91
  "@types/node": "24.7.0",
91
92
  "@types/react": "19.2.18",
92
93
  "clang-format-node": "1.3.5",
@@ -94,8 +95,8 @@
94
95
  "is-tree-shakable": "0.5.0",
95
96
  "knip": "5.61.3",
96
97
  "madge": "8.0.0",
97
- "react": "19.2.8",
98
- "react-native": "0.87.0",
98
+ "react": "19.3.0",
99
+ "react-native": "0.88.0-rc.1",
99
100
  "typescript": "5.9.3"
100
101
  },
101
102
  "main": "./lib/module/index",
@@ -119,6 +120,11 @@
119
120
  "jest",
120
121
  "plugin/index.js",
121
122
  "plugin/index.d.ts",
123
+ "plugin-oxc/index.js",
124
+ "plugin-oxc/index.d.ts",
125
+ "plugin-oxc/babel.js",
126
+ "plugin-oxc/babel.d.ts",
127
+ "plugin-oxc/*.node",
122
128
  "Package.swift",
123
129
  "*.podspec",
124
130
  "react-native.config.js",
@@ -0,0 +1,7 @@
1
+ import type * as babel from '@babel/core';
2
+
3
+ declare function workletsPluginOxcBabelShim(
4
+ babelApi: typeof babel
5
+ ): babel.PluginObj;
6
+
7
+ export = workletsPluginOxcBabelShim;
@@ -0,0 +1,200 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+
5
+ const oxc = require('./index.js');
6
+
7
+ // Previous steps can pass their output sourcemap
8
+ // it needs to be merged with this plugin's one
9
+ // to preserve correct mappings
10
+ const remapping = require('@jridgewell/remapping');
11
+
12
+ const PARSE_ERROR_CODE = 'WORKLETS_ERR_PARSE';
13
+ const FLOW_ERROR_CODE = 'WORKLETS_ERR_FLOW';
14
+
15
+ let warnedAboutIgnoredOptions = false;
16
+ let cachedWorkletsPkgDir;
17
+ let cachedSyntaxJsx;
18
+ let cachedSyntaxTypescript;
19
+
20
+ /**
21
+ * @param {typeof import('@babel/core')} babelApi
22
+ * @returns {import('@babel/core').PluginObj}
23
+ */
24
+ function workletsPluginOxcBabelShim(babelApi) {
25
+ return {
26
+ name: 'worklets-oxc-plugin',
27
+ visitor: {
28
+ Program: {
29
+ enter(programPath, state) {
30
+ if (state.file.__workletsOxcRan) {
31
+ return;
32
+ }
33
+ state.file.__workletsOxcRan = true;
34
+
35
+ const filename = state.filename;
36
+ if (filename == null) {
37
+ throw new Error(
38
+ '[Worklets] the OXC transform needs a filename to name worklets ' +
39
+ 'and to place their generated files, but Babel was given none.'
40
+ );
41
+ }
42
+
43
+ const result = transform(state.file.code, filename, state);
44
+ if (!result.changed) {
45
+ return;
46
+ }
47
+
48
+ adoptSourceMap(result, state);
49
+ programPath.replaceWith(
50
+ reparse(babelApi, result.code, filename, state)
51
+ );
52
+ },
53
+ },
54
+ },
55
+ };
56
+ }
57
+
58
+ /**
59
+ * @param {string} sourceText
60
+ * @param {string} filename
61
+ * @param {import('@babel/core').PluginPass} state
62
+ * @returns {import('./index').TransformResult}
63
+ */
64
+ function transform(sourceText, filename, state) {
65
+ warnAboutIgnoredOptions(state.opts);
66
+ try {
67
+ return oxc.transform(sourceText, filename, {
68
+ ...state.opts,
69
+ envName: state.file.opts.envName,
70
+ workletsPackageDir: resolveWorkletsPkgDir(),
71
+ });
72
+ } catch (error) {
73
+ const message = (error && error.message) || '';
74
+ if (message.includes(FLOW_ERROR_CODE)) {
75
+ return { code: sourceText, files: [], changed: false };
76
+ }
77
+ if (message.includes(PARSE_ERROR_CODE)) {
78
+ throw new Error(
79
+ `[Worklets] ${filename} could not be parsed, so no worklets in it ` +
80
+ `were compiled.\n${message}`
81
+ );
82
+ }
83
+ throw error;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * @param {import('./index').PluginOptions} options
89
+ * @returns {void}
90
+ */
91
+ function warnAboutIgnoredOptions(options) {
92
+ if (warnedAboutIgnoredOptions) {
93
+ return;
94
+ }
95
+ const hasExtras =
96
+ (options?.extraPlugins?.length ?? 0) > 0 ||
97
+ (options?.extraPresets?.length ?? 0) > 0;
98
+ if (!hasExtras) {
99
+ return;
100
+ }
101
+ warnedAboutIgnoredOptions = true;
102
+ console.warn(
103
+ '[Worklets] `extraPlugins`/`extraPresets` are accepted for option-surface ' +
104
+ 'compatibility with `react-native-worklets/plugin` but ignored — the OXC transform ' +
105
+ 'cannot dispatch arbitrary Babel plugins. Compose them around this plugin in ' +
106
+ 'babel.config.js instead.'
107
+ );
108
+ }
109
+
110
+ /** @returns {string} */
111
+ function resolveWorkletsPkgDir() {
112
+ if (cachedWorkletsPkgDir === undefined) {
113
+ try {
114
+ cachedWorkletsPkgDir = path.dirname(
115
+ require.resolve('react-native-worklets/package.json')
116
+ );
117
+ } catch (error) {
118
+ throw new Error(
119
+ "[Worklets] couldn't find the react-native-worklets package on disk, " +
120
+ 'so the generated worklet files have nowhere to go. ' +
121
+ `Make sure it's installed. Cause: ${error.message}`
122
+ );
123
+ }
124
+ }
125
+ return cachedWorkletsPkgDir;
126
+ }
127
+
128
+ /**
129
+ * @param {import('./index').TransformResult} result
130
+ * @param {import('@babel/core').PluginPass} state
131
+ * @returns {void}
132
+ */
133
+ function adoptSourceMap(result, state) {
134
+ if (!result.map) {
135
+ return;
136
+ }
137
+ const map = JSON.parse(result.map);
138
+ const sourceFileName = state.file.opts.generatorOpts?.sourceFileName;
139
+ if (sourceFileName) {
140
+ map.sources = [sourceFileName];
141
+ }
142
+ const previous = state.file.inputMap;
143
+ if (previous) {
144
+ const previousMap = previous.toObject();
145
+ const composed = remapping([map, previousMap], () => null, true);
146
+ state.file.inputMap = { toObject: () => composed };
147
+ return;
148
+ }
149
+ state.file.inputMap = { toObject: () => map };
150
+ }
151
+
152
+ /**
153
+ * @param {typeof import('@babel/core')} babelApi
154
+ * @param {string} code
155
+ * @param {string} filename
156
+ * @param {import('@babel/core').PluginPass} state
157
+ * @returns {import('@babel/types').Program}
158
+ */
159
+ function reparse(babelApi, code, filename, state) {
160
+ const parse = (babelApi && babelApi.parse) || require('@babel/core').parse;
161
+ const parserOpts = state.file.opts.parserOpts ?? {};
162
+ const ast = parse(code, {
163
+ sourceType:
164
+ parserOpts.sourceType ?? state.file.opts.sourceType ?? 'unambiguous',
165
+ parserOpts: { ...parserOpts },
166
+ babelrc: false,
167
+ configFile: false,
168
+ plugins: reparseSyntaxPlugins(filename),
169
+ });
170
+ return ast.program;
171
+ }
172
+
173
+ /**
174
+ * @param {string} filename
175
+ * @returns {import('@babel/core').PluginItem[]}
176
+ */
177
+ function reparseSyntaxPlugins(filename) {
178
+ if (filename.endsWith('.tsx')) {
179
+ return [[syntaxTypescript(), { isTSX: true }]];
180
+ }
181
+ if (/\.(ts|mts|cts)$/.test(filename)) {
182
+ return [[syntaxTypescript(), { isTSX: false }]];
183
+ }
184
+ return [[syntaxJsx()]];
185
+ }
186
+
187
+ /** @returns {string} */
188
+ function syntaxTypescript() {
189
+ cachedSyntaxTypescript ??= require.resolve('@babel/plugin-syntax-typescript');
190
+ return cachedSyntaxTypescript;
191
+ }
192
+
193
+ /** @returns {string} */
194
+ function syntaxJsx() {
195
+ cachedSyntaxJsx ??= require.resolve('@babel/plugin-syntax-jsx');
196
+ return cachedSyntaxJsx;
197
+ }
198
+
199
+ module.exports = workletsPluginOxcBabelShim;
200
+ module.exports.default = workletsPluginOxcBabelShim;
@@ -0,0 +1,33 @@
1
+ export interface PluginOptions {
2
+ extraPlugins?: string[];
3
+ extraPresets?: string[];
4
+ importForwarding?: {
5
+ moduleNames?: string[];
6
+ relativePaths?: string[];
7
+ };
8
+ }
9
+
10
+ export interface TransformOptions extends PluginOptions {
11
+ envName?: string;
12
+ pluginVersion?: string;
13
+ workletsPackageDir?: string;
14
+ }
15
+
16
+ export interface EmittedFile {
17
+ path: string;
18
+ content: string;
19
+ }
20
+
21
+ export interface TransformResult {
22
+ code: string;
23
+ map?: string;
24
+ files: EmittedFile[];
25
+ /** Whether the transform rewrote anything; if not, the input AST still stands. */
26
+ changed: boolean;
27
+ }
28
+
29
+ export function transform(
30
+ sourceText: string,
31
+ filename: string,
32
+ options?: TransformOptions
33
+ ): TransformResult;
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ const { existsSync } = require('fs');
4
+ const { join } = require('path');
5
+
6
+ const platform = process.platform;
7
+ const arch = process.arch;
8
+
9
+ function candidates() {
10
+ return [
11
+ join(__dirname, `worklets-oxc-plugin.${platform}-${arch}.node`),
12
+ join(__dirname, 'worklets-oxc-plugin.node'),
13
+ ];
14
+ }
15
+
16
+ let binding = null;
17
+ let lastError = null;
18
+ for (const p of candidates()) {
19
+ if (existsSync(p)) {
20
+ try {
21
+ binding = require(p);
22
+ break;
23
+ } catch (e) {
24
+ lastError = e;
25
+ }
26
+ }
27
+ }
28
+
29
+ if (!binding) {
30
+ throw new Error(
31
+ `[Worklets] Could not load native binding. Run \`yarn build\` (or \`cargo build --release\`) in ${__dirname}. Last error: ${lastError && lastError.message}`
32
+ );
33
+ }
34
+
35
+ module.exports = binding;
36
+ module.exports.default = binding;
@@ -5,4 +5,4 @@
5
5
  * version used to build the native part of the library in runtime. Remember to
6
6
  * keep this in sync with the version declared in `package.json`
7
7
  */
8
- export const jsVersion = '0.13.0-nightly-20260916-1fb70959f';
8
+ export const jsVersion = '0.13.0';
@@ -1,7 +0,0 @@
1
- #pragma once
2
-
3
- #if __has_include(<React/Utils.h>)
4
- #include <React/Utils.h>
5
- #else
6
- #include <cxxreact/ReactNativeVersion.h>
7
- #endif