expo-modules-jsi 58.0.1 → 58.0.3
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 +12 -0
- package/apple/Products/ExpoModulesJSI.xcframework/ios-arm64/ExpoModulesJSI.framework/ExpoModulesJSI +0 -0
- package/apple/Products/ExpoModulesJSI.xcframework/ios-arm64_x86_64-maccatalyst/ExpoModulesJSI.framework/ExpoModulesJSI +0 -0
- package/apple/Products/ExpoModulesJSI.xcframework/ios-arm64_x86_64-simulator/ExpoModulesJSI.framework/ExpoModulesJSI +0 -0
- package/apple/Products/ExpoModulesJSI.xcframework/macos-arm64_x86_64/ExpoModulesJSI.framework/ExpoModulesJSI +0 -0
- package/apple/Products/ExpoModulesJSI.xcframework/tvos-arm64/ExpoModulesJSI.framework/ExpoModulesJSI +0 -0
- package/apple/Products/ExpoModulesJSI.xcframework/tvos-arm64_x86_64-simulator/ExpoModulesJSI.framework/ExpoModulesJSI +0 -0
- package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntime.swift +50 -2
- package/apple/Tests/JavaScriptArrayBufferTests.swift +1 -2
- package/apple/Tests/JavaScriptNativeStateTests.swift +17 -9
- package/apple/Tests/JavaScriptRuntimeTests.swift +1 -1
- package/apple/Tests/JavaScriptValuesBufferTests.swift +1 -1
- package/apple/Tests/JavaScriptWeakObjectTests.swift +2 -9
- package/apple/scripts/create-stub-xcframework.sh +1 -1
- package/package.json +2 -2
- package/.npmignore +0 -19
- package/apple/.gitignore +0 -16
- package/turbo.json +0 -22
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 58.0.3 — 2026-09-21
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [iOS] Fix `pod install` failing when the Command Line Tools SDK is newer than the selected Xcode. ([#50316](https://github.com/expo/expo/pull/50316) by [@alanjhughes](https://github.com/alanjhughes))
|
|
18
|
+
|
|
19
|
+
## 58.0.2 — 2026-09-15
|
|
20
|
+
|
|
21
|
+
### 🎉 New features
|
|
22
|
+
|
|
23
|
+
- [iOS] Add a `JavaScriptRuntime.collectGarbage(passes:cause:until:)` overload that collects repeatedly until the given condition holds, for tests asserting on a release that a single collection doesn't always complete. ([#50188](https://github.com/expo/expo/pull/50188) by [@tsapeta](https://github.com/tsapeta))
|
|
24
|
+
|
|
13
25
|
## 58.0.1 — 2026-09-14
|
|
14
26
|
|
|
15
27
|
_This version does not introduce any user-facing changes._
|
package/apple/Products/ExpoModulesJSI.xcframework/ios-arm64/ExpoModulesJSI.framework/ExpoModulesJSI
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/apple/Products/ExpoModulesJSI.xcframework/tvos-arm64/ExpoModulesJSI.framework/ExpoModulesJSI
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -691,10 +691,15 @@ open class JavaScriptRuntime: Equatable, Identifiable, @unchecked Sendable {
|
|
|
691
691
|
|
|
692
692
|
// MARK: - Garbage collection
|
|
693
693
|
|
|
694
|
+
// Both `collectGarbage` overloads below are for tests and memory diagnostics only. Don't call
|
|
695
|
+
// either from production code: a forced collection stops the world for as long as the heap takes
|
|
696
|
+
// to trace, and the engine already collects on its own schedule with far better information about
|
|
697
|
+
// when that is worth paying for.
|
|
698
|
+
|
|
694
699
|
/// Requests a full, synchronous garbage collection of the JavaScript heap.
|
|
695
700
|
///
|
|
696
|
-
///
|
|
697
|
-
/// production code
|
|
701
|
+
/// - Important: For tests and memory diagnostics only. This blocks the JavaScript thread for the
|
|
702
|
+
/// length of a full collection, so calling it in production code costs more than it saves.
|
|
698
703
|
///
|
|
699
704
|
/// - Note: This is a no-op on engines whose runtime doesn't implement GC instrumentation. JSI's
|
|
700
705
|
/// default implementation does nothing; Hermes overrides it with a real collection.
|
|
@@ -705,6 +710,49 @@ open class JavaScriptRuntime: Equatable, Identifiable, @unchecked Sendable {
|
|
|
705
710
|
expo.collectGarbage(pointee, std.string(cause))
|
|
706
711
|
}
|
|
707
712
|
|
|
713
|
+
/// Collects garbage repeatedly until `condition` holds, or until the pass budget runs out.
|
|
714
|
+
///
|
|
715
|
+
/// - Important: For tests and memory diagnostics only, and more so than the single-pass overload:
|
|
716
|
+
/// this runs up to `passes` full collections back to back, blocking the JavaScript thread for
|
|
717
|
+
/// all of them. A production caller that reaches for this wants a weak reference or an explicit
|
|
718
|
+
/// release hook, not a forced collection.
|
|
719
|
+
///
|
|
720
|
+
/// One collection does not always finish the job. Releasing a detached native state means
|
|
721
|
+
/// finalizing the decoration that owns its `shared_ptr`, and a single pass does not always get
|
|
722
|
+
/// there: measured over 3000 attempts against a populated heap, 4.4% needed a second pass and a
|
|
723
|
+
/// handful needed a third. Asserting on a release after exactly one collection therefore tests
|
|
724
|
+
/// the engine's scheduling as much as the code under test, which is what made several suites
|
|
725
|
+
/// flaky on busy CI machines.
|
|
726
|
+
///
|
|
727
|
+
/// Collecting again is what makes progress here; waiting does not substitute for it. The same
|
|
728
|
+
/// measurement with a microtask drain and a millisecond of sleep in place of the extra passes
|
|
729
|
+
/// left 18 times as many unreleased.
|
|
730
|
+
///
|
|
731
|
+
/// This does not weaken the assertion that follows it: a value that is genuinely leaked never
|
|
732
|
+
/// satisfies `condition`, exhausts the budget, and still fails.
|
|
733
|
+
///
|
|
734
|
+
/// - Note: This is a no-op on engines whose runtime doesn't implement GC instrumentation, in
|
|
735
|
+
/// which case `condition` is evaluated once per pass and the budget is spent in full.
|
|
736
|
+
/// - Parameters:
|
|
737
|
+
/// - passes: Maximum number of collections to run. The default sits far above what an
|
|
738
|
+
/// unleaked value needs, so exhausting it means the value is leaked, not merely unlucky.
|
|
739
|
+
/// - cause: Reason for the collection, as the engine should report it in its logs.
|
|
740
|
+
/// Defaults to the calling function's name.
|
|
741
|
+
/// - condition: Evaluated after each collection. Collecting stops as soon as it returns `true`.
|
|
742
|
+
@JavaScriptActor
|
|
743
|
+
public func collectGarbage(
|
|
744
|
+
passes: Int = 10,
|
|
745
|
+
cause: String = #function,
|
|
746
|
+
until condition: () -> Bool
|
|
747
|
+
) {
|
|
748
|
+
for _ in 0..<passes {
|
|
749
|
+
collectGarbage(cause: cause)
|
|
750
|
+
if condition() {
|
|
751
|
+
return
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
708
756
|
// MARK: - Equatable
|
|
709
757
|
|
|
710
758
|
public static func == (lhs: JavaScriptRuntime, rhs: JavaScriptRuntime) -> Bool {
|
|
@@ -182,8 +182,7 @@ struct JavaScriptArrayBufferTests {
|
|
|
182
182
|
// Buffer goes out of scope here
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
runtime.collectGarbage()
|
|
185
|
+
runtime.collectGarbage { flag.pointee }
|
|
187
186
|
|
|
188
187
|
cleanupCalled = flag.pointee
|
|
189
188
|
flag.deallocate()
|
|
@@ -73,12 +73,18 @@ struct JavaScriptNativeStateTests {
|
|
|
73
73
|
func `reattaches after the previous pointee was released`() throws {
|
|
74
74
|
let object1 = runtime.createObject()
|
|
75
75
|
let object2 = runtime.createObject()
|
|
76
|
+
var previousPointeeReleased = false
|
|
76
77
|
let nativeState = CustomNativeState()
|
|
78
|
+
nativeState.setDeallocator { _ in
|
|
79
|
+
previousPointeeReleased = true
|
|
80
|
+
}
|
|
77
81
|
object1.setNativeState(nativeState)
|
|
78
82
|
object1.unsetNativeState()
|
|
79
83
|
|
|
80
|
-
//
|
|
81
|
-
|
|
84
|
+
// Collect until the previous C++ pointee is dropped, so the reattach below exercises the
|
|
85
|
+
// expired-weak-pointer path rather than reusing a pointee that happens to still be alive.
|
|
86
|
+
runtime.collectGarbage { previousPointeeReleased }
|
|
87
|
+
#expect(previousPointeeReleased == true)
|
|
82
88
|
|
|
83
89
|
// Reattaching transparently materializes a fresh pointee.
|
|
84
90
|
object2.setNativeState(nativeState)
|
|
@@ -105,8 +111,7 @@ struct JavaScriptNativeStateTests {
|
|
|
105
111
|
object.setNativeState(nativeState)
|
|
106
112
|
object.unsetNativeState()
|
|
107
113
|
|
|
108
|
-
|
|
109
|
-
runtime.collectGarbage()
|
|
114
|
+
runtime.collectGarbage { deallocatorCalled }
|
|
110
115
|
|
|
111
116
|
#expect(deallocatorCalled == true)
|
|
112
117
|
}
|
|
@@ -123,14 +128,17 @@ struct JavaScriptNativeStateTests {
|
|
|
123
128
|
object1.setNativeState(nativeState)
|
|
124
129
|
object2.setNativeState(nativeState)
|
|
125
130
|
|
|
126
|
-
// Unset from the first object — deallocator should not fire yet.
|
|
131
|
+
// Unset from the first object — deallocator should not fire yet. `object2` still holds a
|
|
132
|
+
// strong ref, so no number of collections may release the pointee; this assertion keeps a
|
|
133
|
+
// fixed collection rather than a retry loop, because it waits for something that must
|
|
134
|
+
// never happen.
|
|
127
135
|
object1.unsetNativeState()
|
|
128
136
|
runtime.collectGarbage()
|
|
129
137
|
#expect(deallocatorCallCount == 0)
|
|
130
138
|
|
|
131
139
|
// Unset from the second object — now the deallocator should fire exactly once.
|
|
132
140
|
object2.unsetNativeState()
|
|
133
|
-
runtime.collectGarbage
|
|
141
|
+
runtime.collectGarbage { deallocatorCallCount == 1 }
|
|
134
142
|
#expect(deallocatorCallCount == 1)
|
|
135
143
|
}
|
|
136
144
|
|
|
@@ -183,7 +191,7 @@ struct JavaScriptNativeStateTests {
|
|
|
183
191
|
|
|
184
192
|
object1.setNativeState(nativeState)
|
|
185
193
|
object1.unsetNativeState()
|
|
186
|
-
runtime.collectGarbage
|
|
194
|
+
runtime.collectGarbage { deallocatorCallCount == 1 }
|
|
187
195
|
#expect(deallocatorCallCount == 1)
|
|
188
196
|
|
|
189
197
|
// Re-attaching builds a new C++ pointee; the wrapper-level deallocator
|
|
@@ -192,7 +200,7 @@ struct JavaScriptNativeStateTests {
|
|
|
192
200
|
#expect(object2.getNativeState() === nativeState)
|
|
193
201
|
|
|
194
202
|
object2.unsetNativeState()
|
|
195
|
-
runtime.collectGarbage
|
|
203
|
+
runtime.collectGarbage { deallocatorCallCount == 2 }
|
|
196
204
|
#expect(deallocatorCallCount == 2)
|
|
197
205
|
}
|
|
198
206
|
|
|
@@ -219,7 +227,7 @@ struct JavaScriptNativeStateTests {
|
|
|
219
227
|
// Once detached and GC'd, the C++ pointee dies, releases the Unmanaged,
|
|
220
228
|
// and the Swift wrapper finally deallocates.
|
|
221
229
|
object.unsetNativeState()
|
|
222
|
-
runtime.collectGarbage
|
|
230
|
+
runtime.collectGarbage { weakWrapper == nil }
|
|
223
231
|
#expect(weakWrapper == nil)
|
|
224
232
|
}
|
|
225
233
|
|
|
@@ -55,7 +55,7 @@ struct JavaScriptValuesBufferTests {
|
|
|
55
55
|
_ = JavaScriptValuesBuffer.allocate(in: runtime, with: object.asValue())
|
|
56
56
|
_ = consume object
|
|
57
57
|
|
|
58
|
-
runtime.collectGarbage()
|
|
58
|
+
runtime.collectGarbage { weak.lock() == nil }
|
|
59
59
|
|
|
60
60
|
let stillAlive = weak.lock() != nil
|
|
61
61
|
#expect(stillAlive == false)
|
|
@@ -56,12 +56,8 @@ struct JavaScriptWeakObjectTests {
|
|
|
56
56
|
|
|
57
57
|
#expect((weakObject?.lock() != nil) == true)
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
runtime.collectGarbage()
|
|
59
|
+
runtime.collectGarbage { weakObject?.lock() == nil }
|
|
61
60
|
|
|
62
|
-
// Object should be collected now
|
|
63
|
-
// Note: This test may be flaky depending on GC behavior
|
|
64
|
-
// In practice, lock() should return nil after GC, but timing may vary
|
|
65
61
|
#expect((weakObject?.lock() == nil) == true)
|
|
66
62
|
}
|
|
67
63
|
|
|
@@ -78,11 +74,8 @@ struct JavaScriptWeakObjectTests {
|
|
|
78
74
|
#expect(value?.isObject() == true)
|
|
79
75
|
}
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
runtime.collectGarbage()
|
|
77
|
+
runtime.collectGarbage { weakObject?.asValue().isUndefined() == true }
|
|
83
78
|
|
|
84
|
-
// After collection, asValue should return undefined
|
|
85
|
-
// Note: This test may be flaky depending on GC behavior
|
|
86
79
|
#expect(weakObject?.asValue().isUndefined() == true)
|
|
87
80
|
}
|
|
88
81
|
|
|
@@ -31,7 +31,7 @@ echo -e "${BLUE}[Expo]${RESET} Ensuring required slices in ${PACKAGE_NAME}.xcfra
|
|
|
31
31
|
# reuse the same binary for every slice that needs stamping.
|
|
32
32
|
mkdir -p "$XCFRAMEWORK_PATH"
|
|
33
33
|
STUB_SOURCE="${XCFRAMEWORK_PATH}/.stub-binary"
|
|
34
|
-
echo "" | clang -x c - -dynamiclib \
|
|
34
|
+
echo "" | xcrun --sdk macosx clang -x c - -dynamiclib \
|
|
35
35
|
-o "$STUB_SOURCE" \
|
|
36
36
|
-install_name "@rpath/${PACKAGE_NAME}.framework/${PACKAGE_NAME}"
|
|
37
37
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-jsi",
|
|
3
|
-
"version": "58.0.
|
|
3
|
+
"version": "58.0.3",
|
|
4
4
|
"description": "The JavaScript Interface for Expo Modules",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"sideEffects": [],
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"./apple/scripts/test.sh"
|
|
42
42
|
]
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "ebb1fd7569c9c9e172cb9abd73dfede027c1d674",
|
|
45
45
|
"scripts": {
|
|
46
46
|
"benchmark": "TEST_RUNNER_EXPO_BENCHMARK=1 DESTINATION=\"${DESTINATION:-platform=macOS,variant=Mac Catalyst}\" apple/scripts/test.sh -configuration Release -only-testing Benchmarks -enableCodeCoverage NO",
|
|
47
47
|
"build:xcframework": "apple/scripts/build-xcframework.sh",
|
package/.npmignore
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# @generated by expo-module-scripts
|
|
2
|
-
|
|
3
|
-
# Exclude all top-level hidden directories by convention
|
|
4
|
-
/.*/
|
|
5
|
-
|
|
6
|
-
# Exclude tarballs generated by `npm pack`
|
|
7
|
-
/*.tgz
|
|
8
|
-
|
|
9
|
-
__mocks__
|
|
10
|
-
__tests__
|
|
11
|
-
__rsc_tests__
|
|
12
|
-
|
|
13
|
-
/e2e
|
|
14
|
-
/e2e-cli
|
|
15
|
-
/babel.config.js
|
|
16
|
-
/oxlint.config.mjs
|
|
17
|
-
/android/src/androidTest/
|
|
18
|
-
/android/src/test/
|
|
19
|
-
/ios/Tests
|
package/apple/.gitignore
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
.DS_Store
|
|
2
|
-
.build
|
|
3
|
-
.DerivedData
|
|
4
|
-
.swiftpm
|
|
5
|
-
|
|
6
|
-
# Generated module map for the `jsi` Clang module (written by build-xcframework.sh)
|
|
7
|
-
.generated
|
|
8
|
-
|
|
9
|
-
# Staged slices for incremental xcframework builds
|
|
10
|
-
.xcframework-slices
|
|
11
|
-
|
|
12
|
-
# Symlinks to host-app xcframeworks used by the test target (written by scripts/test.sh)
|
|
13
|
-
.test-frameworks
|
|
14
|
-
|
|
15
|
-
# Built xcframework output
|
|
16
|
-
Products
|
package/turbo.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://turborepo.dev/schema.json",
|
|
3
|
-
"extends": ["//"],
|
|
4
|
-
"tasks": {
|
|
5
|
-
"precompile-ios": {
|
|
6
|
-
"inputs": [
|
|
7
|
-
"spm.config.json",
|
|
8
|
-
"*.podspec",
|
|
9
|
-
"apple/**",
|
|
10
|
-
"!apple/.DerivedData/**",
|
|
11
|
-
"!apple/.build/**",
|
|
12
|
-
"!apple/.swiftpm/**",
|
|
13
|
-
"!apple/.generated/**",
|
|
14
|
-
"!apple/Products/**",
|
|
15
|
-
"node_modules/react-native/package.json",
|
|
16
|
-
"node_modules/react-native/sdks/hermes-engine/version.properties",
|
|
17
|
-
"$TURBO_ROOT$/tools/src/prebuilds/**"
|
|
18
|
-
],
|
|
19
|
-
"dependsOn": []
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|