expo-modules-jsi 58.0.6 → 58.0.8
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 +19 -0
- package/apple/Benchmarks/FreeFormBenchmarks.swift +61 -0
- package/apple/Benchmarks/RuntimeCacheBenchmarks.swift +71 -0
- package/apple/Benchmarks/UnownedDecodeBenchmarks.swift +121 -0
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Any.swift +500 -0
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Containers.swift +52 -10
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Data.swift +5 -0
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Date.swift +27 -5
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptCodable+Primitives.swift +75 -0
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptDecodable.swift +34 -5
- package/apple/Sources/ExpoModulesJSI/Coding/JavaScriptValueKinds.swift +73 -0
- package/apple/Sources/ExpoModulesJSI/Protocols/JSIRepresentable.swift +4 -2
- package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntime.swift +18 -13
- package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntimeCache.swift +74 -0
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptArray.swift +26 -0
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptObject.swift +30 -0
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptPromise.swift +14 -14
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptUnownedValue.swift +17 -0
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptValue.swift +64 -2
- package/apple/Sources/ExpoModulesJSI/Utilities/String+JSI.swift +4 -3
- package/apple/Sources/ExpoModulesJSI-Cxx/include/JSIUtils.h +27 -0
- package/apple/Tests/JavaScriptCodableAnyTests.swift +531 -0
- package/apple/Tests/JavaScriptDecodableKindsTests.swift +132 -0
- package/apple/Tests/JavaScriptRuntimeCacheTests.swift +117 -0
- package/apple/Tests/JavaScriptRuntimeTests.swift +59 -0
- package/apple/Tests/JavaScriptUnownedDecodeTests.swift +99 -0
- package/apple/Tests/JavaScriptUnownedElementsTests.swift +76 -0
- package/apple/scripts/build-xcframework.sh +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 58.0.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [iOS] Add `decodableKinds` to `JavaScriptDecodable`: the kinds of JavaScript value (`JavaScriptValueKinds`) that `decode` can accept, so code that picks between several types can skip the ones that can't match. ([#50905](https://github.com/expo/expo/pull/50905) by [@tsapeta](https://github.com/tsapeta))
|
|
8
|
+
- [iOS] Fix crash reports symbolicated on the device showing no function names for `ExpoModulesJSI` frames. ([#50698](https://github.com/expo/expo/pull/50698) by [@tsapeta](https://github.com/tsapeta))
|
|
9
|
+
- Return strings, objects and arrays from host functions and host object getters without cloning the engine handle, and build short ASCII strings from JS inline. ([#50937](https://github.com/expo/expo/pull/50937) by [@tsapeta](https://github.com/tsapeta))
|
|
10
|
+
- [iOS] Add `JavaScriptRuntime.cached(_:_:)` with typed `JavaScriptRuntime.Cache.Key`s, to create a value once per runtime and reuse it, for example a JavaScript constructor or a property name. A lookup reads one array slot, about 5× faster than the string-keyed `JavaScriptPropNameID.cached(_:_:)`. ([#50888](https://github.com/expo/expo/pull/50888) by [@tsapeta](https://github.com/tsapeta))
|
|
11
|
+
- [iOS] Add `JavaScriptValue.withUnownedValue(in:_:)`, and give the owning `JavaScriptDecodable.decode` a default that borrows the value and decodes it through the `JavaScriptUnownedValue` overload, so a conformer can implement only that one. Arrays, dictionaries, dates, records and enums now decode unowned values without copying them first. ([#50960](https://github.com/expo/expo/pull/50960) by [@tsapeta](https://github.com/tsapeta))
|
|
12
|
+
- [iOS] Add `JavaScriptArray.mapUnowned(_:)`, `JavaScriptObject.withUnownedProperty(_:_:)`, and `isArray()` and `getArray(in:)` on `JavaScriptUnownedValue`. Arrays, dictionaries and dates now decode through their unowned overload without copying the value or wrapping each element in a `JavaScriptValue`, and their owning decodes forward to it. ([#50980](https://github.com/expo/expo/pull/50980) by [@tsapeta](https://github.com/tsapeta))
|
|
13
|
+
- [iOS] Keep checkout paths out of the Swift compilation cache key so modules importing ExpoModulesCore can reuse cached compilation results across checkouts and worktrees. ([#50354](https://github.com/expo/expo/pull/50354) by [@janicduplessis](https://github.com/janicduplessis))
|
|
14
|
+
|
|
15
|
+
## 58.0.7
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Bump precompiled artifacts due to ABI breakage in expo-modules-jsi@58.0.6. ([#50881](https://github.com/expo/expo/pull/50881) by [@kitten](https://github.com/kitten))
|
|
20
|
+
- [iOS] Add conversions between JavaScript values and free-form `Any`, `[Any]` and `[String: Any]` values. ([#50660](https://github.com/expo/expo/pull/50660) by [@tsapeta](https://github.com/tsapeta))
|
|
21
|
+
|
|
3
22
|
## 58.0.6
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Copyright 2026-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import ExpoModulesJSI
|
|
4
|
+
import Testing
|
|
5
|
+
|
|
6
|
+
/// Benchmarks for the free-form (`Any`) conversions. Each pair compares the dedicated fast path for
|
|
7
|
+
/// `[String: Any]` against the generic path that converts through `Any` and casts, on the same
|
|
8
|
+
/// attributes-like object of string, number and boolean values.
|
|
9
|
+
extension JSIBenchmarks {
|
|
10
|
+
private static let attributesSource = """
|
|
11
|
+
({ screen: 'home', build: 'release', region: 'eu', retries: 3, latency: 12.5, cached: true, visible: false, count: 42 })
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@Test
|
|
15
|
+
func `decode free-form dictionary`() async throws {
|
|
16
|
+
try await benchmarkCase { runtime in
|
|
17
|
+
let value = try runtime.eval(Self.attributesSource)
|
|
18
|
+
try benchmark("JavaScriptValue.decodeAnyDictionary(_:in:)", runtime: runtime) { iterations in
|
|
19
|
+
for _ in 0..<iterations {
|
|
20
|
+
_ = try JavaScriptValue.decodeAnyDictionary(value, in: runtime)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Test
|
|
27
|
+
func `decode free-form dictionary through the generic path`() async throws {
|
|
28
|
+
try await benchmarkCase { runtime in
|
|
29
|
+
let value = try runtime.eval(Self.attributesSource)
|
|
30
|
+
try benchmark("JavaScriptValue.decodeAny(_:as: [String: Any].self, in:)", runtime: runtime) { iterations in
|
|
31
|
+
for _ in 0..<iterations {
|
|
32
|
+
_ = try JavaScriptValue.decodeAny(value, as: [String: Any].self, in: runtime)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Test
|
|
39
|
+
func `encode free-form dictionary`() async throws {
|
|
40
|
+
try await benchmarkCase { runtime in
|
|
41
|
+
let attributes = try JavaScriptValue.decodeAnyDictionary(runtime.eval(Self.attributesSource), in: runtime)
|
|
42
|
+
try benchmark("JavaScriptValue.encodeAnyDictionary(_:in:)", runtime: runtime) { iterations in
|
|
43
|
+
for _ in 0..<iterations {
|
|
44
|
+
_ = try JavaScriptValue.encodeAnyDictionary(attributes, in: runtime)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Test
|
|
51
|
+
func `encode free-form dictionary through the generic path`() async throws {
|
|
52
|
+
try await benchmarkCase { runtime in
|
|
53
|
+
let attributes: Any = try JavaScriptValue.decodeAnyDictionary(runtime.eval(Self.attributesSource), in: runtime)
|
|
54
|
+
try benchmark("JavaScriptValue.encodeAny(_:in:): [String: Any]", runtime: runtime) { iterations in
|
|
55
|
+
for _ in 0..<iterations {
|
|
56
|
+
_ = try JavaScriptValue.encodeAny(attributes, in: runtime)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Copyright 2026-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import ExpoModulesJSI
|
|
4
|
+
import Testing
|
|
5
|
+
|
|
6
|
+
private let answerPropNameKey = JavaScriptRuntime.Cache.Key<JavaScriptPropNameID>()
|
|
7
|
+
|
|
8
|
+
/// Benchmarks for values cached per runtime with ``JavaScriptRuntime/cached(_:_:)``, read against
|
|
9
|
+
/// the string-keyed ``JavaScriptPropNameID/cached(_:_:)`` registry.
|
|
10
|
+
extension JSIBenchmarks {
|
|
11
|
+
@Test
|
|
12
|
+
func `runtime cache hit`() async throws {
|
|
13
|
+
try await benchmarkCase { runtime in
|
|
14
|
+
_ = runtime.cached(answerPropNameKey) { JavaScriptPropNameID(runtime, string: "answer") }
|
|
15
|
+
try benchmark("JavaScriptRuntime.cached(_:_:): hit", runtime: runtime) { iterations in
|
|
16
|
+
for _ in 0..<iterations {
|
|
17
|
+
_ = runtime.cached(answerPropNameKey) { JavaScriptPropNameID(runtime, string: "answer") }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Test
|
|
24
|
+
func `object property by runtime-cached PropNameID`() async throws {
|
|
25
|
+
try await benchmarkCase { runtime in
|
|
26
|
+
let object = try runtime.eval("({ answer: 42 })").getObject()
|
|
27
|
+
try benchmark("JavaScriptObject.getProperty(_:): runtime-cached PropNameID", runtime: runtime) { iterations in
|
|
28
|
+
for _ in 0..<iterations {
|
|
29
|
+
_ = object.getProperty(runtime.cached(answerPropNameKey) { JavaScriptPropNameID(runtime, string: "answer") })
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test
|
|
36
|
+
func `string-keyed PropNameID cache hit`() async throws {
|
|
37
|
+
try await benchmarkCase { runtime in
|
|
38
|
+
try benchmark("JavaScriptPropNameID.cached(_:_:): hit", runtime: runtime) { iterations in
|
|
39
|
+
for _ in 0..<iterations {
|
|
40
|
+
_ = JavaScriptPropNameID.cached(runtime, "answer")
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Test
|
|
47
|
+
func `object property by string-keyed cached PropNameID`() async throws {
|
|
48
|
+
try await benchmarkCase { runtime in
|
|
49
|
+
let object = try runtime.eval("({ answer: 42 })").getObject()
|
|
50
|
+
try benchmark("JavaScriptObject.getProperty(_:): string-cached PropNameID", runtime: runtime) { iterations in
|
|
51
|
+
for _ in 0..<iterations {
|
|
52
|
+
_ = object.getProperty(.cached(runtime, "answer"))
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Test
|
|
59
|
+
func `object property by prebuilt PropNameID`() async throws {
|
|
60
|
+
try await benchmarkCase { runtime in
|
|
61
|
+
let object = try runtime.eval("({ answer: 42 })").getObject()
|
|
62
|
+
let propName = JavaScriptPropNameID(runtime, string: "answer")
|
|
63
|
+
try benchmark("JavaScriptObject.getProperty(_:): prebuilt PropNameID", runtime: runtime) { iterations in
|
|
64
|
+
for _ in 0..<iterations {
|
|
65
|
+
_ = object.getProperty(propName)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
withExtendedLifetime(propName) {}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Copyright 2026-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import ExpoModulesJSI
|
|
4
|
+
import Foundation
|
|
5
|
+
import Testing
|
|
6
|
+
|
|
7
|
+
/// Decoding from a borrowed `JavaScriptUnownedValue`, the path a `@JS` argument takes, and from an owning
|
|
8
|
+
/// `JavaScriptValue`, the path a nested value takes. A type without its own unowned decode copies the
|
|
9
|
+
/// value into an owning `JavaScriptValue` first.
|
|
10
|
+
extension JSIBenchmarks {
|
|
11
|
+
@Test
|
|
12
|
+
func `unowned decode of an array`() async throws {
|
|
13
|
+
try await benchmarkCase { runtime in
|
|
14
|
+
let buffer = JavaScriptValuesBuffer.allocate(in: runtime, with: try runtime.eval("[1, 2, 3, 4, 5, 6, 7, 8]"))
|
|
15
|
+
try benchmark("[Double].decode(unowned): 8 elements", runtime: runtime) { iterations in
|
|
16
|
+
for _ in 0..<iterations {
|
|
17
|
+
_ = try [Double].decode(buffer.unownedValue(at: 0), in: runtime)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Test
|
|
24
|
+
func `unowned decode of a dictionary`() async throws {
|
|
25
|
+
try await benchmarkCase { runtime in
|
|
26
|
+
let buffer = JavaScriptValuesBuffer.allocate(in: runtime, with: try runtime.eval("({ a: 1, b: 2, c: 3, d: 4 })"))
|
|
27
|
+
try benchmark("[String: Double].decode(unowned): 4 entries", runtime: runtime) { iterations in
|
|
28
|
+
for _ in 0..<iterations {
|
|
29
|
+
_ = try [String: Double].decode(buffer.unownedValue(at: 0), in: runtime)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test
|
|
36
|
+
func `unowned decode of a date`() async throws {
|
|
37
|
+
try await benchmarkCase { runtime in
|
|
38
|
+
let buffer = JavaScriptValuesBuffer.allocate(in: runtime, with: try runtime.eval("1700000000000"))
|
|
39
|
+
try benchmark("Date.decode(unowned): number", runtime: runtime) { iterations in
|
|
40
|
+
for _ in 0..<iterations {
|
|
41
|
+
_ = try Date.decode(buffer.unownedValue(at: 0), in: runtime)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Test
|
|
48
|
+
func `owning decode of an array`() async throws {
|
|
49
|
+
try await benchmarkCase { runtime in
|
|
50
|
+
let value = try runtime.eval("[1, 2, 3, 4, 5, 6, 7, 8]")
|
|
51
|
+
try benchmark("[Double].decode(owning): 8 elements", runtime: runtime) { iterations in
|
|
52
|
+
for _ in 0..<iterations {
|
|
53
|
+
_ = try [Double].decode(value, in: runtime)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Test
|
|
60
|
+
func `owning decode of a dictionary`() async throws {
|
|
61
|
+
try await benchmarkCase { runtime in
|
|
62
|
+
let value = try runtime.eval("({ a: 1, b: 2, c: 3, d: 4 })")
|
|
63
|
+
try benchmark("[String: Double].decode(owning): 4 entries", runtime: runtime) { iterations in
|
|
64
|
+
for _ in 0..<iterations {
|
|
65
|
+
_ = try [String: Double].decode(value, in: runtime)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@Test
|
|
72
|
+
func `owning decode of nested arrays`() async throws {
|
|
73
|
+
try await benchmarkCase { runtime in
|
|
74
|
+
let value = try runtime.eval("[[1, 2], [3, 4], [5, 6], [7, 8]]")
|
|
75
|
+
try benchmark("[[Double]].decode(owning): 4 x 2 elements", runtime: runtime) { iterations in
|
|
76
|
+
for _ in 0..<iterations {
|
|
77
|
+
_ = try [[Double]].decode(value, in: runtime)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@Test
|
|
84
|
+
func `unowned decode of nested arrays`() async throws {
|
|
85
|
+
try await benchmarkCase { runtime in
|
|
86
|
+
let buffer = JavaScriptValuesBuffer.allocate(
|
|
87
|
+
in: runtime,
|
|
88
|
+
with: try runtime.eval("[[1, 2], [3, 4], [5, 6], [7, 8]]")
|
|
89
|
+
)
|
|
90
|
+
try benchmark("[[Double]].decode(unowned): 4 x 2 elements", runtime: runtime) { iterations in
|
|
91
|
+
for _ in 0..<iterations {
|
|
92
|
+
_ = try [[Double]].decode(buffer.unownedValue(at: 0), in: runtime)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@Test
|
|
99
|
+
func `unowned decode of a date string`() async throws {
|
|
100
|
+
try await benchmarkCase { runtime in
|
|
101
|
+
let buffer = JavaScriptValuesBuffer.allocate(in: runtime, with: try runtime.eval("'2026-10-02T12:00:00Z'"))
|
|
102
|
+
try benchmark("Date.decode(unowned): ISO string", runtime: runtime) { iterations in
|
|
103
|
+
for _ in 0..<iterations {
|
|
104
|
+
_ = try Date.decode(buffer.unownedValue(at: 0), in: runtime)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@Test
|
|
111
|
+
func `unowned decode of a date object`() async throws {
|
|
112
|
+
try await benchmarkCase { runtime in
|
|
113
|
+
let buffer = JavaScriptValuesBuffer.allocate(in: runtime, with: try runtime.eval("new Date(1700000000000)"))
|
|
114
|
+
try benchmark("Date.decode(unowned): Date object", runtime: runtime) { iterations in
|
|
115
|
+
for _ in 0..<iterations {
|
|
116
|
+
_ = try Date.decode(buffer.unownedValue(at: 0), in: runtime)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|