expo-modules-core 56.0.4 → 56.0.5

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.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,13 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 56.0.5 — 2026-05-08
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [iOS] Fix dictionary/array returns from sync functions invoked in the worklet runtime. ([#45419](https://github.com/expo/expo/pull/45419) by [@nishan](https://github.com/intergalacticspacehighway))
18
+ - [Android] Fix unsetting border width on views with border radius causing views to disappear ([#45467](https://github.com/expo/expo/pull/45467) by [@fractalbeauty](https://github.com/fractalbeauty))
19
+
13
20
  ## 56.0.4 — 2026-05-07
14
21
 
15
22
  _This version does not introduce any user-facing changes._
@@ -1,6 +1,5 @@
1
- import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask
2
- import expo.modules.plugin.gradle.ExpoModuleExtension
3
1
  import groovy.json.JsonSlurper
2
+ import expo.modules.plugin.gradle.ExpoModuleExtension
4
3
  import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
5
4
 
6
5
  buildscript {
@@ -17,7 +16,6 @@ buildscript {
17
16
  classpath("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:${kotlinVersion}")
18
17
  }
19
18
 
20
- classpath("org.apache.commons:commons-text:1.14.0")
21
19
  }
22
20
  }
23
21
 
@@ -29,7 +27,7 @@ if (shouldIncludeCompose) {
29
27
  }
30
28
 
31
29
  group = 'host.exp.exponent'
32
- version = '56.0.4'
30
+ version = '56.0.5'
33
31
 
34
32
  def isExpoModulesCoreTests = {
35
33
  Gradle gradle = getGradle()
@@ -96,7 +94,7 @@ android {
96
94
  defaultConfig {
97
95
  consumerProguardFiles 'proguard-rules.pro'
98
96
  versionCode 1
99
- versionName "56.0.4"
97
+ versionName "56.0.5"
100
98
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
101
99
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
102
100
 
@@ -265,55 +263,56 @@ if (shouldTurnWarningsIntoErrors) {
265
263
  }
266
264
  }
267
265
 
268
- // Generates the PCH file during sync if it doesn't exist yet
269
- def generatePCHTask = tasks.register("generatePCH") {
270
- def configureTaskName = "configureCMakeDebug"
271
- dependsOn(configureTaskName)
266
+ // Generates minimal stub PCH files from an empty header so the IDE's C++ engine
267
+ // doesn't fail during sync. Near-instant unlike the full ExpoHeader.pch.
268
+ // The actual build regenerates proper PCH files via ninja.
269
+ def cxxDir = project.file(".cxx")
270
+ def generateStubPCHTask = tasks.register("generateStubPCH") {
271
+ dependsOn("configureCMakeDebug")
272
272
 
273
273
  doLast {
274
- reactNativeArchitectures().each { abi ->
275
- def configureTaskNameForAbi = configureTaskName + "[" + abi + "]"
276
- ExternalNativeBuildJsonTask configureTask = tasks.named(configureTaskNameForAbi).get() as ExternalNativeBuildJsonTask
277
-
278
- // Gets CxxModel for the given ABI
279
- File cxxBuildFolder = configureTask.abi.cxxBuildFolder
274
+ if (!cxxDir.exists()) {
275
+ return
276
+ }
280
277
 
281
- // Gets compile_commands.json file to find the command to generate the PCH file
282
- File compileCommandsFile = new File(cxxBuildFolder, "compile_commands.json")
283
- if (!compileCommandsFile.exists()) {
278
+ cxxDir.eachFileRecurse { file ->
279
+ if (file.name != "compile_commands.json") {
284
280
  return
285
281
  }
286
282
 
287
- def parsedJson = new JsonSlurper().parseText(compileCommandsFile.text)
288
- for (int i = 0; i < parsedJson.size(); i++) {
289
- def commandObj = parsedJson[i]
290
-
291
- def path = commandObj.file
292
- if (!path.endsWith("cmake_pch.hxx.cxx")) {
293
- continue
283
+ new JsonSlurper().parseText(file.text).each { entry ->
284
+ if (!entry.file.endsWith("cmake_pch.hxx.cxx")) {
285
+ return
294
286
  }
295
287
 
296
- def generatedFilePath = path.substring(0, path.length() - ".cxx".length()) + ".pch"
297
- // Checks if the file already exists, and skip if so
298
- if (new File(generatedFilePath).exists()) {
299
- continue
288
+ def pchFile = new File(entry.file.substring(0, entry.file.length() - ".cxx".length()) + ".pch")
289
+
290
+ if (!pchFile.exists() || pchFile.length() == 0) {
291
+ def stubHeader = new File(entry.directory, "stub_pch.hxx")
292
+ stubHeader.text = ""
293
+
294
+ def cmd = entry.command
295
+ // Replace the forced-include path: `-Xclang -include -Xclang <path>/cmake_pch.hxx`
296
+ .replaceAll(/-Xclang -include -Xclang [^\s]+cmake_pch\.hxx(?=\s)/, "-Xclang -include -Xclang ${stubHeader.absolutePath}")
297
+ // Replace the source file operand: `<path>/cmake_pch.hxx.cxx`
298
+ .replaceAll(/[^\s]+cmake_pch\.hxx\.cxx/, stubHeader.absolutePath)
299
+
300
+ def process = new ProcessBuilder(cmd.split(" ").toList())
301
+ .directory(new File(entry.directory))
302
+ .redirectErrorStream(true)
303
+ .start()
304
+ if (process.waitFor() != 0) {
305
+ throw new GradleException("Stub PCH generation failed: ${process.inputStream.text}")
306
+ }
300
307
  }
301
308
 
302
- def tokenizer = new org.apache.commons.text.StringTokenizer(commandObj.command, " ")
303
- def tokens = tokenizer.tokenList
304
-
305
- def workingDirFile = new File(commandObj.directory)
306
-
307
- providers.exec {
308
- workingDir(providers.provider { workingDirFile }.get())
309
- commandLine(tokens)
310
- }.getResult().get().assertNormalExitValue()
309
+ // Ensure PCH is older than source so ninja rebuilds the real one during build
310
+ pchFile.setLastModified(new File(entry.file).lastModified() - 1)
311
311
  }
312
312
  }
313
313
  }
314
314
  }
315
315
 
316
- // This task will run on the IDE project sync, ensuring the PCH file is generated early enough
317
316
  tasks.register("prepareKotlinBuildScriptModel") {
318
- dependsOn(generatePCHTask)
317
+ dependsOn(generateStubPCHTask)
319
318
  }
@@ -64,7 +64,7 @@ private fun <T : View> ViewDefinitionBuilder<T>.UseBorderWidthProps() {
64
64
  BackgroundStyleApplicator.setBorderWidth(
65
65
  view,
66
66
  edge,
67
- width ?: Float.NaN
67
+ width
68
68
  )
69
69
  }
70
70
  }
@@ -138,13 +138,20 @@ public struct Conversions {
138
138
  `unknownToJavaScriptValue` which handles type-erased values through `NSNumber`/`NSDictionary` matching.
139
139
  */
140
140
  static func anyToJavaScriptValue<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
141
+ return try anyToJavaScriptValue(value, appContext: appContext, in: appContext.runtime)
142
+ }
143
+
144
+ /**
145
+ Variant that creates JS values in the given `runtime`, used by the worklet conversion.
146
+ */
147
+ static func anyToJavaScriptValue<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
141
148
  if ValueType.self is AnyOptional.Type, Optional.isNil(value) {
142
149
  return .null
143
150
  }
144
151
  if let value = value as? AnyArgument {
145
- return try type(of: value).getDynamicType().castToJS(value, appContext: appContext)
152
+ return try type(of: value).getDynamicType().castToJS(value, appContext: appContext, in: runtime)
146
153
  }
147
- return try unknownToJavaScriptValue(value, appContext: appContext)
154
+ return try unknownToJavaScriptValue(value, appContext: appContext, in: runtime)
148
155
  }
149
156
 
150
157
  /**
@@ -153,12 +160,18 @@ public struct Conversions {
153
160
  can call this without re-entering the `AnyArgument` check and causing infinite recursion.
154
161
  */
155
162
  static func unknownToJavaScriptValue(_ value: Any, appContext: AppContext) throws -> JavaScriptValue {
163
+ return try unknownToJavaScriptValue(value, appContext: appContext, in: appContext.runtime)
164
+ }
165
+
166
+ /**
167
+ Variant that creates JS values in the given `runtime` — used by the worklet conversion
168
+ path so produced dicts/arrays live in the calling runtime, not the main runtime.
169
+ */
170
+ static func unknownToJavaScriptValue(_ value: Any, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
156
171
  if let value = value as? JavaScriptRepresentable {
157
- return .representing(value: value, in: try appContext.runtime)
172
+ return .representing(value: value, in: runtime)
158
173
  }
159
174
 
160
- let runtime = try appContext.runtime
161
-
162
175
  switch value {
163
176
  case is Void:
164
177
  return .undefined
@@ -179,14 +192,14 @@ public struct Conversions {
179
192
  case let array as NSArray:
180
193
  let jsArray = runtime.createArray(length: array.count)
181
194
  for (index, element) in array.enumerated() {
182
- try jsArray.set(value: anyToJavaScriptValue(element, appContext: appContext), at: index)
195
+ try jsArray.set(value: anyToJavaScriptValue(element, appContext: appContext, in: runtime), at: index)
183
196
  }
184
197
  return jsArray.asValue()
185
198
  case let dict as NSDictionary:
186
199
  let jsObject = runtime.createObject()
187
200
  for (key, element) in dict {
188
201
  guard let key = key as? String else { continue }
189
- jsObject.setProperty(key, value: try anyToJavaScriptValue(element, appContext: appContext))
202
+ jsObject.setProperty(key, value: try anyToJavaScriptValue(element, appContext: appContext, in: runtime))
190
203
  }
191
204
  return jsObject.asValue()
192
205
  default:
@@ -36,6 +36,11 @@ public protocol AnyDynamicType: CustomStringConvertible, Sendable {
36
36
 
37
37
  func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue
38
38
 
39
+ /**
40
+ Runtime-aware `castToJS`.
41
+ */
42
+ func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue
43
+
39
44
  /**
40
45
  Converts the given native value directly to `JavaScriptValue`.
41
46
  The default implementation uses `convertResult` and then `castToJS`, but dynamic types
@@ -43,6 +48,11 @@ public protocol AnyDynamicType: CustomStringConvertible, Sendable {
43
48
  */
44
49
  func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue
45
50
 
51
+ /**
52
+ Runtime-aware `convertToJS`.
53
+ */
54
+ func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue
55
+
46
56
  /**
47
57
  Converts function's result to the type that can later be converted to a JS value.
48
58
  For instance, types such as records, enumerables and shared objects need special handling
@@ -60,11 +70,21 @@ extension AnyDynamicType {
60
70
  return try Conversions.unknownToJavaScriptValue(value, appContext: appContext)
61
71
  }
62
72
 
73
+ // Default forwards to the legacy `castToJS`, dropping `runtime`
74
+ public func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
75
+ return try castToJS(value, appContext: appContext)
76
+ }
77
+
63
78
  public func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
64
79
  let result = Conversions.convertFunctionResult(value, appContext: appContext, dynamicType: self)
65
80
  return try castToJS(result, appContext: appContext)
66
81
  }
67
82
 
83
+ public func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
84
+ let result = Conversions.convertFunctionResult(value, appContext: appContext, dynamicType: self)
85
+ return try castToJS(result, appContext: appContext, in: runtime)
86
+ }
87
+
68
88
  func convertResult<ResultType>(_ result: ResultType, appContext: AppContext) throws -> Any {
69
89
  return result
70
90
  }
@@ -55,13 +55,16 @@ internal struct DynamicArrayType: AnyDynamicType {
55
55
  are already in their post-conversion shape, such as `JavaScriptValue.undefined`.
56
56
  */
57
57
  func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
58
+ return try castToJS(value, appContext: appContext, in: try appContext.runtime)
59
+ }
60
+
61
+ func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
58
62
  guard let array = value as? [Any] else {
59
- return try Conversions.anyToJavaScriptValue(value, appContext: appContext)
63
+ return try Conversions.anyToJavaScriptValue(value, appContext: appContext, in: runtime)
60
64
  }
61
- let runtime = try appContext.runtime
62
65
  let jsArray = runtime.createArray(length: array.count)
63
66
  for (index, element) in array.enumerated() {
64
- try jsArray.set(value: try elementType.castToJS(element, appContext: appContext), at: index)
67
+ try jsArray.set(value: try elementType.castToJS(element, appContext: appContext, in: runtime), at: index)
65
68
  }
66
69
  return jsArray.asValue()
67
70
  }
@@ -71,13 +74,16 @@ internal struct DynamicArrayType: AnyDynamicType {
71
74
  to use their own direct conversion paths before any array-level normalization.
72
75
  */
73
76
  func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
77
+ return try convertToJS(value, appContext: appContext, in: try appContext.runtime)
78
+ }
79
+
80
+ func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
74
81
  guard let array = value as? [Any] else {
75
- return try Conversions.anyToJavaScriptValue(value, appContext: appContext)
82
+ return try Conversions.anyToJavaScriptValue(value, appContext: appContext, in: runtime)
76
83
  }
77
- let runtime = try appContext.runtime
78
84
  let jsArray = runtime.createArray(length: array.count)
79
85
  for (index, element) in array.enumerated() {
80
- try jsArray.set(value: try elementType.convertToJS(element, appContext: appContext), at: index)
86
+ try jsArray.set(value: try elementType.convertToJS(element, appContext: appContext, in: runtime), at: index)
81
87
  }
82
88
  return jsArray.asValue()
83
89
  }
@@ -61,14 +61,17 @@ internal struct DynamicDictionaryType: AnyDynamicType {
61
61
  are already in their post-conversion shape, such as `JavaScriptValue.undefined`.
62
62
  */
63
63
  func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
64
+ return try castToJS(value, appContext: appContext, in: try appContext.runtime)
65
+ }
66
+
67
+ func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
64
68
  guard let dict = value as? [AnyHashable: Any] else {
65
- return try Conversions.anyToJavaScriptValue(value, appContext: appContext)
69
+ return try Conversions.anyToJavaScriptValue(value, appContext: appContext, in: runtime)
66
70
  }
67
- let runtime = try appContext.runtime
68
71
  let jsObject = runtime.createObject()
69
72
  for (key, element) in dict {
70
73
  guard let key = key as? String else { continue }
71
- jsObject.setProperty(key, value: try valueType.castToJS(element, appContext: appContext))
74
+ jsObject.setProperty(key, value: try valueType.castToJS(element, appContext: appContext, in: runtime))
72
75
  }
73
76
  return jsObject.asValue()
74
77
  }
@@ -78,14 +81,17 @@ internal struct DynamicDictionaryType: AnyDynamicType {
78
81
  to use their own direct conversion paths before any dictionary-level normalization.
79
82
  */
80
83
  func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
84
+ return try convertToJS(value, appContext: appContext, in: try appContext.runtime)
85
+ }
86
+
87
+ func convertToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
81
88
  guard let dict = value as? [AnyHashable: Any] else {
82
- return try Conversions.anyToJavaScriptValue(value, appContext: appContext)
89
+ return try Conversions.anyToJavaScriptValue(value, appContext: appContext, in: runtime)
83
90
  }
84
- let runtime = try appContext.runtime
85
91
  let jsObject = runtime.createObject()
86
92
  for (key, element) in dict {
87
93
  guard let key = key as? String else { continue }
88
- jsObject.setProperty(key, value: try valueType.convertToJS(element, appContext: appContext))
94
+ jsObject.setProperty(key, value: try valueType.convertToJS(element, appContext: appContext, in: runtime))
89
95
  }
90
96
  return jsObject.asValue()
91
97
  }
@@ -38,6 +38,10 @@ internal struct DynamicRawType<InnerType>: AnyDynamicType {
38
38
  }
39
39
 
40
40
  func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
41
+ return try castToJS(value, appContext: appContext, in: try appContext.runtime)
42
+ }
43
+
44
+ func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
41
45
  if Optional.isNil(value) {
42
46
  return .null
43
47
  }
@@ -46,9 +50,9 @@ internal struct DynamicRawType<InnerType>: AnyDynamicType {
46
50
  // handlers like `DynamicEnumType.castToJS`. Guarded against infinite recursion: the
47
51
  // dispatch only kicks in when the value's runtime type differs from `innerType`.
48
52
  if InnerType.self != type(of: value as Any), let argument = value as? AnyArgument {
49
- return try type(of: argument).getDynamicType().castToJS(argument, appContext: appContext)
53
+ return try type(of: argument).getDynamicType().castToJS(argument, appContext: appContext, in: runtime)
50
54
  }
51
- return try Conversions.unknownToJavaScriptValue(value, appContext: appContext)
55
+ return try Conversions.unknownToJavaScriptValue(value, appContext: appContext, in: runtime)
52
56
  }
53
57
 
54
58
  func convertResult<ResultType>(_ result: ResultType, appContext: AppContext) throws -> Any {
@@ -28,8 +28,12 @@ internal struct DynamicStringType: AnyDynamicType {
28
28
  }
29
29
 
30
30
  func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
31
+ return try castToJS(value, appContext: appContext, in: try appContext.runtime)
32
+ }
33
+
34
+ func castToJS<ValueType>(_ value: ValueType, appContext: AppContext, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
31
35
  if let string = value as? String {
32
- return JavaScriptValue(try appContext.runtime, string)
36
+ return JavaScriptValue(runtime, string)
33
37
  }
34
38
  throw Conversions.ConversionToJSFailedException((kind: .string, nativeType: ValueType.self))
35
39
  }
@@ -101,7 +101,7 @@ public class SyncFunctionDefinition<Args, FirstArgType, ReturnType>: AnySyncFunc
101
101
  func call(_ appContext: AppContext, in runtime: JavaScriptRuntime, this: JavaScriptValue, arguments: consuming JavaScriptValuesBuffer) throws(Exception) -> JavaScriptValue {
102
102
  let result = try runBody(appContext, in: runtime, this: this, arguments: arguments)
103
103
  do {
104
- return try appContext.converter.toJS(result, returnType)
104
+ return try appContext.converter.toJS(result, returnType, in: runtime)
105
105
  } catch let error as Exception {
106
106
  throw FunctionCallException(name).causedBy(error)
107
107
  } catch {
@@ -42,4 +42,12 @@ public struct MainValueConverter: ~Copyable {
42
42
  public func toJS(_ value: Any, _ type: AnyDynamicType) throws -> JavaScriptValue {
43
43
  return try type.convertToJS(value, appContext: appContext)
44
44
  }
45
+
46
+ /**
47
+ `toJS` variant that targets a specific runtime. Useful for Worklet runtime conversions.
48
+ */
49
+ @JavaScriptActor
50
+ public func toJS(_ value: Any, _ type: AnyDynamicType, in runtime: JavaScriptRuntime) throws -> JavaScriptValue {
51
+ return try type.convertToJS(value, appContext: appContext, in: runtime)
52
+ }
45
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "56.0.4",
3
+ "version": "56.0.5",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@expo/expo-modules-macros-plugin": "~0.0.8",
50
- "expo-modules-jsi": "~56.0.1",
50
+ "expo-modules-jsi": "~56.0.2",
51
51
  "invariant": "^2.2.4"
52
52
  },
53
53
  "peerDependencies": {
@@ -66,7 +66,7 @@
66
66
  "@types/invariant": "^2.2.33",
67
67
  "expo-module-scripts": "56.0.2"
68
68
  },
69
- "gitHead": "d7b4e5edff4bf2e619d2c2f16d158798c6d592ef",
69
+ "gitHead": "a30353e69ca0d72b9fac5830abc631feda1ba3ae",
70
70
  "scripts": {
71
71
  "build": "expo-module build",
72
72
  "clean": "expo-module clean",