diff --git a/apps/bare-expo/ios/Podfile.lock b/apps/bare-expo/ios/Podfile.lock
index 37fd7189916cb5..4d2dd6f8124e44 100644
--- a/apps/bare-expo/ios/Podfile.lock
+++ b/apps/bare-expo/ios/Podfile.lock
@@ -3113,7 +3113,7 @@ PODS:
- ReactNativeDependencies
- RNWorklets
- Yoga
- - RNScreens (4.26.0):
+ - RNScreens (4.27.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -3135,9 +3135,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- - RNScreens/common (= 4.26.0)
+ - RNScreens/common (= 4.27.0)
- Yoga
- - RNScreens/common (4.26.0):
+ - RNScreens/common (4.27.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -3534,7 +3534,7 @@ DEPENDENCIES:
- "RNDateTimePicker (from `../../../node_modules/.pnpm/@react-native-community+datetimepicker@8.6.0_expo@packages+expo_react-native@0.86.0_@ba_27696d7be0628c17e11142421589ecd6/node_modules/@react-native-community/datetimepicker`)"
- "RNGestureHandler (from `../../../node_modules/.pnpm/react-native-gesture-handler@3.1.0_react-native@0.86.0_@babel+core@7.29.7_@react-native_4aa76a0e73afa3150b371a7bceafc5bf/node_modules/react-native-gesture-handler`)"
- "RNReanimated (from `../../../node_modules/.pnpm/react-native-reanimated@4.5.1_patch_hash=f1adeb1c26dfb66311eb34b353a16b2ae27f5a95a65e9d_b6ef5a1d7ea42cde643b3b398d3f65d1/node_modules/react-native-reanimated`)"
- - "RNScreens (from `../../../node_modules/.pnpm/react-native-screens@4.26.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_095d8f27a4d7296418051aaa10e4a8e3/node_modules/react-native-screens`)"
+ - "RNScreens (from `../../../node_modules/.pnpm/react-native-screens@4.27.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_fbb4148dd8c191603315f880208118ec/node_modules/react-native-screens`)"
- "RNSVG (from `../../../node_modules/.pnpm/react-native-svg@15.15.4_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-pres_100382bcc91f7310e67ae9af27ee7676/node_modules/react-native-svg`)"
- "RNWorklets (from `../../../node_modules/.pnpm/react-native-worklets@0.10.1_patch_hash=b72ac0fc85273472ec4b8974adb8d4eba75672291aa7501_3c2da4b685ce12ff0e10894541b0a9fd/node_modules/react-native-worklets`)"
- TestExpoUi (from `../modules/test-expo-ui/ios`)
@@ -4001,7 +4001,7 @@ EXTERNAL SOURCES:
RNReanimated:
:path: "../../../node_modules/.pnpm/react-native-reanimated@4.5.1_patch_hash=f1adeb1c26dfb66311eb34b353a16b2ae27f5a95a65e9d_b6ef5a1d7ea42cde643b3b398d3f65d1/node_modules/react-native-reanimated"
RNScreens:
- :path: "../../../node_modules/.pnpm/react-native-screens@4.26.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_095d8f27a4d7296418051aaa10e4a8e3/node_modules/react-native-screens"
+ :path: "../../../node_modules/.pnpm/react-native-screens@4.27.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_fbb4148dd8c191603315f880208118ec/node_modules/react-native-screens"
RNSVG:
:path: "../../../node_modules/.pnpm/react-native-svg@15.15.4_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-pres_100382bcc91f7310e67ae9af27ee7676/node_modules/react-native-svg"
RNWorklets:
@@ -4060,7 +4060,7 @@ SPEC CHECKSUMS:
ExpoHaptics: 4503d2da51ff7109712ccdfe0e4939f66818183e
ExpoImage: 4c1e7634db6033185a98959fb028d8d4b1c89695
ExpoImageManipulator: 40455ca0112c38e41defb14cb323c6372134eeac
- ExpoImagePicker: ba36db5da890daafa3da9e061497df47e2252ee4
+ ExpoImagePicker: 8c6898eba27bb4086c4970ce5599e50bce5d7c80
ExpoInsights: c7b7de693d2e81734a650086884016708e9d57ab
ExpoKeepAwake: c26f14275017370cc8a4b7b43a0e23361f2053a5
ExpoLinearGradient: 903b3f5fe566c666ff78b2599add51d094483613
@@ -4092,7 +4092,7 @@ SPEC CHECKSUMS:
ExpoSMS: ac35f6c85b72ee5b18a7c6bb06550fbd4a683775
ExpoSpeech: 0e90904e2af5d6f166d4d2ffbdab535686597938
ExpoSplashScreen: 247464c0fe484f766892db0d66c3fe54f92a624e
- ExpoSQLite: ddd65ed74bdebcff20f6766e682f9321ecb28ab8
+ ExpoSQLite: 7ea4f5b0ebd0b6f15927c2349db7624a5abf6beb
ExpoStoreReview: 2a0f6b8e04112aea2f6e3e1cb84109d78f4d041f
ExpoSymbols: 7c7c7bd3c52f0b6dcbba6e8af4088b0dc1ea7d29
ExpoSystemUI: ff3142b323ae7b4d11dc0b1dc4acfa30d92be7dc
@@ -4104,10 +4104,10 @@ SPEC CHECKSUMS:
ExpoVideoThumbnails: 0a7d37113fe51af5c302461ad0344dc529161316
ExpoWebBrowser: 9b98d939d5f1c5dd8f523f0a0683056fb1204434
EXStructuredHeaders: 1423c3602a30eb078e03b63b251ef912267dd4c0
- EXUpdates: 01f1b6c3ef1df0a981f84af9a91dc027baed4a07
+ EXUpdates: e8c071fa483afb3d9d1de09e8f4f9427278af0a1
EXUpdatesInterface: 92d2aa5194b6fb93ca8ab749db75665cb28b2e60
FBLazyVector: b3e7ad108f0d882e30445c5527d774e3fd432f3d
- hermes-engine: ebe8b908c0614fa2c4e3655af31dcbadd9e73c67
+ hermes-engine: dd9a9191125ffe9f57c224173db85afb0210cd23
JestMockSchema: 96b4cfec246644f6323d1c30693b6a02044be1e6
libavif: 5f8e715bea24debec477006f21ef9e95432e254d
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
@@ -4203,7 +4203,7 @@ SPEC CHECKSUMS:
RNDateTimePicker: b9e20c2a3af26f4ab10646359777205bbad1fdac
RNGestureHandler: a426f18ab9021991fe2cbc8bf1677b740e20e70c
RNReanimated: 33a84b527cb75525b45bb101f4a33bf24f698f3f
- RNScreens: 62693e770c4b73093c018aa7845b691f5ba9988d
+ RNScreens: de068bbd7d91b820fedac25c597d4b14830de74b
RNSVG: 3fe8590403ac9f107b4d14591bc6e54da4894e5c
RNWorklets: 6a415fb05d596e7b7c248d14cceeeb26f41731a0
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
diff --git a/apps/bare-expo/package.json b/apps/bare-expo/package.json
index 1d1fe21f58f786..939310d494761b 100644
--- a/apps/bare-expo/package.json
+++ b/apps/bare-expo/package.json
@@ -66,7 +66,7 @@
"react-native-pager-view": "8.0.2",
"react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "5.7.0",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-svg": "15.15.4",
"react-native-view-shot": "4.0.3",
"react-native-webview": "13.16.1",
diff --git a/apps/brownfield-tester/expo-app/package.json b/apps/brownfield-tester/expo-app/package.json
index 5161bf0d9f9677..655497af7242e0 100644
--- a/apps/brownfield-tester/expo-app/package.json
+++ b/apps/brownfield-tester/expo-app/package.json
@@ -34,7 +34,7 @@
"react-native-gesture-handler": "~3.1.0",
"react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "~5.7.0",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.10.1"
},
diff --git a/apps/expo-go/ios/Podfile.lock b/apps/expo-go/ios/Podfile.lock
index cf4b87591f5e98..06073a60e914c7 100644
--- a/apps/expo-go/ios/Podfile.lock
+++ b/apps/expo-go/ios/Podfile.lock
@@ -3625,7 +3625,7 @@ PODS:
- RNWorklets
- SocketRocket
- Yoga
- - RNScreens (4.26.0):
+ - RNScreens (4.27.0):
- boost
- DoubleConversion
- fast_float
@@ -3652,10 +3652,10 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - RNScreens/common (= 4.26.0)
+ - RNScreens/common (= 4.27.0)
- SocketRocket
- Yoga
- - RNScreens/common (4.26.0):
+ - RNScreens/common (4.27.0):
- boost
- DoubleConversion
- fast_float
@@ -4156,7 +4156,7 @@ DEPENDENCIES:
- "RNDateTimePicker (from `../../../node_modules/.pnpm/@react-native-community+datetimepicker@9.1.0_expo@packages+expo_react-native@0.86.0_@ba_5988bfa80b1bbd47ddf5007ef6d2eedf/node_modules/@react-native-community/datetimepicker`)"
- "RNGestureHandler (from `../../../node_modules/.pnpm/react-native-gesture-handler@3.1.0_react-native@0.86.0_@babel+core@7.29.7_@react-native_4aa76a0e73afa3150b371a7bceafc5bf/node_modules/react-native-gesture-handler`)"
- "RNReanimated (from `../../../node_modules/.pnpm/react-native-reanimated@4.5.1_patch_hash=f1adeb1c26dfb66311eb34b353a16b2ae27f5a95a65e9d_b6ef5a1d7ea42cde643b3b398d3f65d1/node_modules/react-native-reanimated`)"
- - "RNScreens (from `../../../node_modules/.pnpm/react-native-screens@4.26.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_095d8f27a4d7296418051aaa10e4a8e3/node_modules/react-native-screens`)"
+ - "RNScreens (from `../../../node_modules/.pnpm/react-native-screens@4.27.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_fbb4148dd8c191603315f880208118ec/node_modules/react-native-screens`)"
- "RNSVG (from `../../../node_modules/.pnpm/react-native-svg@15.15.4_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-pres_100382bcc91f7310e67ae9af27ee7676/node_modules/react-native-svg`)"
- "RNWorklets (from `../../../node_modules/.pnpm/react-native-worklets@0.10.1_patch_hash=b72ac0fc85273472ec4b8974adb8d4eba75672291aa7501_3c2da4b685ce12ff0e10894541b0a9fd/node_modules/react-native-worklets`)"
- SocketRocket (~> 0.7.1)
@@ -4535,7 +4535,7 @@ EXTERNAL SOURCES:
RNReanimated:
:path: "../../../node_modules/.pnpm/react-native-reanimated@4.5.1_patch_hash=f1adeb1c26dfb66311eb34b353a16b2ae27f5a95a65e9d_b6ef5a1d7ea42cde643b3b398d3f65d1/node_modules/react-native-reanimated"
RNScreens:
- :path: "../../../node_modules/.pnpm/react-native-screens@4.26.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_095d8f27a4d7296418051aaa10e4a8e3/node_modules/react-native-screens"
+ :path: "../../../node_modules/.pnpm/react-native-screens@4.27.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_fbb4148dd8c191603315f880208118ec/node_modules/react-native-screens"
RNSVG:
:path: "../../../node_modules/.pnpm/react-native-svg@15.15.4_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-pres_100382bcc91f7310e67ae9af27ee7676/node_modules/react-native-svg"
RNWorklets:
@@ -4582,7 +4582,7 @@ SPEC CHECKSUMS:
ExpoHaptics: 4503d2da51ff7109712ccdfe0e4939f66818183e
ExpoImage: 4c1e7634db6033185a98959fb028d8d4b1c89695
ExpoImageManipulator: 40455ca0112c38e41defb14cb323c6372134eeac
- ExpoImagePicker: ba36db5da890daafa3da9e061497df47e2252ee4
+ ExpoImagePicker: 8c6898eba27bb4086c4970ce5599e50bce5d7c80
ExpoKeepAwake: c26f14275017370cc8a4b7b43a0e23361f2053a5
ExpoLinearGradient: 903b3f5fe566c666ff78b2599add51d094483613
ExpoLinking: 5a796f9284535c0349537a844dd40680229b7fcb
@@ -4607,7 +4607,7 @@ SPEC CHECKSUMS:
ExpoSharing: dcf5cb459c99f344aabf126b171eb61d2e49276f
ExpoSMS: ac35f6c85b72ee5b18a7c6bb06550fbd4a683775
ExpoSpeech: 0e90904e2af5d6f166d4d2ffbdab535686597938
- ExpoSQLite: ddd65ed74bdebcff20f6766e682f9321ecb28ab8
+ ExpoSQLite: 7ea4f5b0ebd0b6f15927c2349db7624a5abf6beb
ExpoStoreReview: 2a0f6b8e04112aea2f6e3e1cb84109d78f4d041f
ExpoSymbols: 7c7c7bd3c52f0b6dcbba6e8af4088b0dc1ea7d29
ExpoSystemUI: ff3142b323ae7b4d11dc0b1dc4acfa30d92be7dc
@@ -4618,7 +4618,7 @@ SPEC CHECKSUMS:
ExpoVideoThumbnails: 0a7d37113fe51af5c302461ad0344dc529161316
ExpoWebBrowser: 9b98d939d5f1c5dd8f523f0a0683056fb1204434
EXStructuredHeaders: 1423c3602a30eb078e03b63b251ef912267dd4c0
- EXUpdates: 00ce8a947f59a52bebeec08cb5ae24fe6b455bd6
+ EXUpdates: 8e5e35811d676bd1db539abf98c58efa052cbce3
EXUpdatesInterface: 92d2aa5194b6fb93ca8ab749db75665cb28b2e60
fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6
FBLazyVector: d153c0a7ab3a2c865dd311cb3a5418cd66b658e6
@@ -4635,7 +4635,7 @@ SPEC CHECKSUMS:
GoogleAppMeasurement: 8a82b93a6400c8e6551c0bcd66a9177f2e067aed
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
- hermes-engine: 1aac0336ab06465103e89322838baf2911318c89
+ hermes-engine: 349ded4feea539299cee22cb680b0d50402116e5
libavif: 5f8e715bea24debec477006f21ef9e95432e254d
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
@@ -4733,7 +4733,7 @@ SPEC CHECKSUMS:
RNDateTimePicker: 73ffdd45f0ce1d00ff981031679a05206e619fdc
RNGestureHandler: d9aaa02b8cdfc140cc27bd79cb76936e827f6a17
RNReanimated: f48581c623af353394b1ec1cc5d9f7e6c0646693
- RNScreens: abcfa9ad7536fcd68ad6c14c8e7d4b5af8ac3f33
+ RNScreens: 5a327eb3ea0fa862ff3ae39f7a07a0cdc83e533d
RNSVG: c6acd5c597d26625214295105756c5e488f5ae4c
RNWorklets: c081a75b6c836c87ed3546b6b68313bc8286a0df
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
diff --git a/apps/expo-go/package.json b/apps/expo-go/package.json
index 4a8aacea07cf5b..de6e12813776ec 100644
--- a/apps/expo-go/package.json
+++ b/apps/expo-go/package.json
@@ -89,7 +89,7 @@
"react-native-pager-view": "8.0.2",
"react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "5.7.0",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-svg": "15.15.4",
"react-native-view-shot": "4.0.3",
"react-native-webview": "13.16.1",
diff --git a/apps/minimal-tester/ios/Podfile.properties.json b/apps/minimal-tester/ios/Podfile.properties.json
index fdda159b3358aa..60cf5acaef7f2f 100644
--- a/apps/minimal-tester/ios/Podfile.properties.json
+++ b/apps/minimal-tester/ios/Podfile.properties.json
@@ -3,7 +3,7 @@
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
"ios.forceStaticLinking": "[]",
"apple.privacyManifestAggregationEnabled": "true",
- "ios.buildReactNativeFromSource": "true",
+ "ios.buildReactNativeFromSource": "false",
"ios.useFrameworks": "static",
"ios.brownfieldTargetName": "minimaltesterbrownfield"
}
diff --git a/apps/native-component-list/package.json b/apps/native-component-list/package.json
index efd2b5dbe52aee..5c093e861bbd18 100644
--- a/apps/native-component-list/package.json
+++ b/apps/native-component-list/package.json
@@ -134,7 +134,7 @@
"react-native-pager-view": "8.0.2",
"react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "5.7.0",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-svg": "15.15.4",
"react-native-view-shot": "4.0.3",
"react-native-web": "~0.21.0",
diff --git a/apps/native-tests/ios/NativeTests.xcodeproj/project.pbxproj b/apps/native-tests/ios/NativeTests.xcodeproj/project.pbxproj
index e107c7bd2b83ee..e9d09a7096429a 100644
--- a/apps/native-tests/ios/NativeTests.xcodeproj/project.pbxproj
+++ b/apps/native-tests/ios/NativeTests.xcodeproj/project.pbxproj
@@ -431,7 +431,7 @@
" ",
);
PODFILE_DIR = "$(SRCROOT)";
- REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/.pnpm/react-native@0.86.0_@babel+core@7.29.0_@react-native+jest-preset@0.86.0_@babel+core@7.2_ef63ad2607bf1660bcad07b5e75613ee/node_modules/react-native";
+ REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/.pnpm/react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-preset@0.86.0_@babel+core@7.2_05fe3c1b42d0a26b4dba0087fc75e51f/node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
@@ -501,7 +501,7 @@
" ",
);
PODFILE_DIR = "$(SRCROOT)";
- REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/.pnpm/react-native@0.86.0_@babel+core@7.29.0_@react-native+jest-preset@0.86.0_@babel+core@7.2_ef63ad2607bf1660bcad07b5e75613ee/node_modules/react-native";
+ REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/.pnpm/react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-preset@0.86.0_@babel+core@7.2_05fe3c1b42d0a26b4dba0087fc75e51f/node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
USE_HERMES = true;
diff --git a/apps/native-tests/ios/Podfile.lock b/apps/native-tests/ios/Podfile.lock
index 3c6e573d8ba7fd..f2efe2629b87f0 100644
--- a/apps/native-tests/ios/Podfile.lock
+++ b/apps/native-tests/ios/Podfile.lock
@@ -2582,7 +2582,7 @@ PODS:
- ReactNativeDependencies
- RNWorklets
- Yoga
- - RNScreens (4.26.0):
+ - RNScreens (4.27.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2604,9 +2604,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- - RNScreens/common (= 4.26.0)
+ - RNScreens/common (= 4.27.0)
- Yoga
- - RNScreens/common (4.26.0):
+ - RNScreens/common (4.27.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2845,7 +2845,7 @@ DEPENDENCIES:
- "RNCMaskedView (from `../../../node_modules/.pnpm/@react-native-masked-view+masked-view@0.3.2_react-native@0.86.0_@babel+core@7.29.7_@rea_0b1cb48f64769e8cdee0e87c7385a41f/node_modules/@react-native-masked-view/masked-view`)"
- "RNGestureHandler (from `../../../node_modules/.pnpm/react-native-gesture-handler@3.1.0_react-native@0.86.0_@babel+core@7.29.7_@react-native_4aa76a0e73afa3150b371a7bceafc5bf/node_modules/react-native-gesture-handler`)"
- "RNReanimated (from `../../../node_modules/.pnpm/react-native-reanimated@4.5.1_patch_hash=f1adeb1c26dfb66311eb34b353a16b2ae27f5a95a65e9d_b6ef5a1d7ea42cde643b3b398d3f65d1/node_modules/react-native-reanimated`)"
- - "RNScreens (from `../../../node_modules/.pnpm/react-native-screens@4.26.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_095d8f27a4d7296418051aaa10e4a8e3/node_modules/react-native-screens`)"
+ - "RNScreens (from `../../../node_modules/.pnpm/react-native-screens@4.27.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_fbb4148dd8c191603315f880208118ec/node_modules/react-native-screens`)"
- "RNWorklets (from `../../../node_modules/.pnpm/react-native-worklets@0.10.1_patch_hash=b72ac0fc85273472ec4b8974adb8d4eba75672291aa7501_3c2da4b685ce12ff0e10894541b0a9fd/node_modules/react-native-worklets`)"
- UMAppLoader (from `../../../packages/unimodules-app-loader/ios`)
- UMAppLoader/Tests (from `../../../packages/unimodules-app-loader/ios`)
@@ -3091,7 +3091,7 @@ EXTERNAL SOURCES:
RNReanimated:
:path: "../../../node_modules/.pnpm/react-native-reanimated@4.5.1_patch_hash=f1adeb1c26dfb66311eb34b353a16b2ae27f5a95a65e9d_b6ef5a1d7ea42cde643b3b398d3f65d1/node_modules/react-native-reanimated"
RNScreens:
- :path: "../../../node_modules/.pnpm/react-native-screens@4.26.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_095d8f27a4d7296418051aaa10e4a8e3/node_modules/react-native-screens"
+ :path: "../../../node_modules/.pnpm/react-native-screens@4.27.0_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-p_fbb4148dd8c191603315f880208118ec/node_modules/react-native-screens"
RNWorklets:
:path: "../../../node_modules/.pnpm/react-native-worklets@0.10.1_patch_hash=b72ac0fc85273472ec4b8974adb8d4eba75672291aa7501_3c2da4b685ce12ff0e10894541b0a9fd/node_modules/react-native-worklets"
UMAppLoader:
@@ -3123,10 +3123,10 @@ SPEC CHECKSUMS:
ExpoRouter: 224087330cc8f5a39700e6f4d7a55f55ecb2f90d
ExpoTaskManager: d52427f67bf8f00fa56d52f9603919049c67cfc5
EXStructuredHeaders: 1423c3602a30eb078e03b63b251ef912267dd4c0
- EXUpdates: 01f1b6c3ef1df0a981f84af9a91dc027baed4a07
+ EXUpdates: e8c071fa483afb3d9d1de09e8f4f9427278af0a1
EXUpdatesInterface: 92d2aa5194b6fb93ca8ab749db75665cb28b2e60
FBLazyVector: b3e7ad108f0d882e30445c5527d774e3fd432f3d
- hermes-engine: ebe8b908c0614fa2c4e3655af31dcbadd9e73c67
+ hermes-engine: dd9a9191125ffe9f57c224173db85afb0210cd23
libavif: 5f8e715bea24debec477006f21ef9e95432e254d
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
@@ -3208,7 +3208,7 @@ SPEC CHECKSUMS:
RNCMaskedView: eb2b2e538afa907f05a5848a1a1ac26092e6fec9
RNGestureHandler: a426f18ab9021991fe2cbc8bf1677b740e20e70c
RNReanimated: 33a84b527cb75525b45bb101f4a33bf24f698f3f
- RNScreens: 62693e770c4b73093c018aa7845b691f5ba9988d
+ RNScreens: de068bbd7d91b820fedac25c597d4b14830de74b
RNWorklets: 6a415fb05d596e7b7c248d14cceeeb26f41731a0
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
diff --git a/apps/notification-tester/package.json b/apps/notification-tester/package.json
index 964ae68bb80414..099c1382e31948 100644
--- a/apps/notification-tester/package.json
+++ b/apps/notification-tester/package.json
@@ -39,7 +39,7 @@
"react-native-gesture-handler": "~3.1.0",
"react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "5.7.0",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-worklets": "0.10.1",
"test-suite": "workspace:*"
},
diff --git a/apps/observe-tester/package.json b/apps/observe-tester/package.json
index 8032efeb68ee7d..b642418f3712e0 100644
--- a/apps/observe-tester/package.json
+++ b/apps/observe-tester/package.json
@@ -44,7 +44,7 @@
"react-dom": "19.2.3",
"react-native": "0.86.0",
"react-native-safe-area-context": "5.7.0",
- "react-native-screens": "4.26.0"
+ "react-native-screens": "4.27.0"
},
"devDependencies": {
"@expo/config": "workspace:*",
diff --git a/apps/router-e2e/package.json b/apps/router-e2e/package.json
index 1dd393ddf66409..7a80d1072cfb6e 100644
--- a/apps/router-e2e/package.json
+++ b/apps/router-e2e/package.json
@@ -87,7 +87,7 @@
"react-native-gesture-handler": "~3.1.0",
"react-native-pager-view": "^8.0.2",
"react-native-safe-area-context": "5.7.0",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-tab-view": "^4.3.0",
"react-native-web": "^0.21.0",
"react-native-webview": "13.16.1"
diff --git a/apps/sandbox/package.json b/apps/sandbox/package.json
index 100a37b253e5fb..b145bf8150f515 100644
--- a/apps/sandbox/package.json
+++ b/apps/sandbox/package.json
@@ -18,7 +18,7 @@
"react": "19.2.3",
"react-native": "0.86.0",
"react-native-safe-area-context": "5.7.0",
- "react-native-screens": "4.26.0"
+ "react-native-screens": "4.27.0"
},
"devDependencies": {
"babel-preset-expo": "workspace:*"
diff --git a/docs/pages/additional-resources/index.mdx b/docs/pages/additional-resources/index.mdx
index c2dfe89a1bf081..911e717d8038e3 100644
--- a/docs/pages/additional-resources/index.mdx
+++ b/docs/pages/additional-resources/index.mdx
@@ -1,5 +1,5 @@
---
-modificationDate: August 18, 2026
+modificationDate: August 19, 2026
title: Additional resources
description: A reference of resources that are useful to learn about Expo tooling and services.
---
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/group.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/group.mdx
index be28621ced7ddc..196978d4a2679e 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/group.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/group.mdx
@@ -8,6 +8,7 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { ComponentExample } from '~/ui/components/ComponentExample';
Expo UI Group matches the official SwiftUI [Group API](https://developer.apple.com/documentation/swiftui/group) and groups views together without introducing additional layout structure.
@@ -21,23 +22,33 @@ Expo UI Group matches the official SwiftUI [Group API](https://developer.apple.c
Groups are useful for applying modifiers to multiple views at once or organizing views without affecting layout.
-```tsx BasicGroupExample.tsx
-import { Host, Group, Text } from '@expo/ui/swift-ui';
+
+
+```tsx
+import { Group, Host, Text, VStack } from '@expo/ui/swift-ui';
import { foregroundStyle } from '@expo/ui/swift-ui/modifiers';
export default function BasicGroupExample() {
return (
-
-
- First item
- Second item
- Third item
-
+
+
+
+ First item
+ Second item
+ Third item
+
+
);
}
```
+
+
## API
```tsx
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/lazyhstack.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/lazyhstack.mdx
index 3adf3f48d4ab68..8dc3932ef077b5 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/lazyhstack.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/lazyhstack.mdx
@@ -8,6 +8,7 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { ComponentExample } from '~/ui/components/ComponentExample';
Expo UI LazyHStack matches the official SwiftUI [LazyHStack API](https://developer.apple.com/documentation/swiftui/lazyhstack) and arranges its children horizontally, creating items only as needed (when they become visible during scrolling).
@@ -23,7 +24,13 @@ Expo UI LazyHStack matches the official SwiftUI [LazyHStack API](https://develop
LazyHStack should be used inside a `ScrollView` with `axes="horizontal"` to enable lazy rendering.
-```tsx BasicLazyHStackExample.tsx
+
+
+```tsx
import {
Host,
ScrollView,
@@ -46,11 +53,19 @@ export default function BasicLazyHStackExample() {
}
```
+
+
### With alignment
The `alignment` prop controls vertical alignment of children. Available options are: `top`, `center`, `bottom`, `firstTextBaseline`, and `lastTextBaseline`.
-```tsx LazyHStackAlignmentExample.tsx
+
+
+```tsx
import {
Host,
ScrollView,
@@ -61,7 +76,9 @@ import { frame } from '@expo/ui/swift-ui/modifiers';
export default function LazyHStackAlignmentExample() {
return (
-
+
+
## API
```tsx
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/lazyvstack.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/lazyvstack.mdx
index accf6e2a6834cd..2b702402ee5ea9 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/lazyvstack.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/lazyvstack.mdx
@@ -8,6 +8,7 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { ComponentExample } from '~/ui/components/ComponentExample';
Expo UI LazyVStack matches the official SwiftUI [LazyVStack API](https://developer.apple.com/documentation/swiftui/lazyvstack) and arranges its children vertically, creating items only as needed (when they become visible during scrolling).
@@ -23,7 +24,13 @@ Expo UI LazyVStack matches the official SwiftUI [LazyVStack API](https://develop
LazyVStack should be used inside a `ScrollView` to enable lazy rendering.
-```tsx BasicLazyVStackExample.tsx
+
+
+```tsx
import {
Host,
ScrollView,
@@ -46,11 +53,19 @@ export default function BasicLazyVStackExample() {
}
```
+
+
### With alignment
The `alignment` prop controls horizontal alignment of children. Available options are: `leading`, `center`, and `trailing`.
-```tsx LazyVStackAlignmentExample.tsx
+
+
+```tsx
import {
Host,
ScrollView,
@@ -61,7 +76,9 @@ import { frame } from '@expo/ui/swift-ui/modifiers';
export default function LazyVStackAlignmentExample() {
return (
-
+
+
## API
```tsx
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/scrollview.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/scrollview.mdx
index 7ffd92b2f47cc5..8783274dcac857 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/scrollview.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/scrollview.mdx
@@ -8,6 +8,7 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { ComponentExample } from '~/ui/components/ComponentExample';
> **info** For cross-platform usage, see the universal [`ScrollView`](../universal/scrollview) β it renders the appropriate native component per platform.
@@ -23,7 +24,13 @@ Expo UI ScrollView matches the official SwiftUI [ScrollView API](https://develop
A simple vertically scrollable list of text items.
-```tsx ScrollViewVerticalExample.tsx
+
+
+```tsx
import { Host, ScrollView, VStack, Text } from '@expo/ui/swift-ui';
import { padding } from '@expo/ui/swift-ui/modifiers';
@@ -44,11 +51,19 @@ export default function ScrollViewVerticalExample() {
}
```
+
+
### Horizontal scroll view
Use the `axes` prop to scroll horizontally.
-```tsx ScrollViewHorizontalExample.tsx
+
+
+```tsx
import {
Host,
ScrollView,
@@ -82,6 +97,8 @@ export default function ScrollViewHorizontalExample() {
}
```
+
+
### Hidden scroll indicators
Set `showsIndicators` to `false` to hide the scroll bars.
@@ -114,7 +131,13 @@ The `scrollPosition` modifier also works on other scrollable containers like `La
> **warning** Writes to `state.value` must run on the UI runtime. Wrap the write in `scheduleOnUI` from `react-native-worklets`, or call them from inside a `'worklet'` function. Writes from the JS runtime trip Main Thread Checker, Xcode's runtime tool that flags UIKit calls made from a background thread.
-```tsx ScrollViewSharedPositionExample.tsx
+
+
+```tsx
import {
Button,
Host,
@@ -173,6 +196,8 @@ export default function ScrollViewSharedPositionExample() {
}
```
+
+
## API
```tsx
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/swipeactions.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/swipeactions.mdx
index d2d5ead9121aa7..9d65c7a2e24f8a 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/swipeactions.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/swipeactions.mdx
@@ -8,6 +8,7 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { ComponentExample } from '~/ui/components/ComponentExample';
Expo UI SwipeActions matches the official SwiftUI [swipeActions]() modifier and lets you attach leading or trailing actions to row content.
@@ -17,7 +18,13 @@ Expo UI SwipeActions matches the official SwiftUI [swipeActions](
+
+```tsx
import {
Button,
Host,
@@ -61,6 +68,8 @@ export default function SwipeActionsExample() {
}
```
+
+
## API
```tsx
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/text.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/text.mdx
index 002ba841707c3a..38cef7fd8d14e8 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/text.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/text.mdx
@@ -237,13 +237,19 @@ export default function FontDesignsExample() {
Use the `font` modifier with a `family` parameter to use custom fonts. You can load custom fonts using [`expo-font`](/versions/latest/sdk/font/) library.
-```tsx CustomFontExample.tsx
+
+
+```tsx
import { Host, Text, VStack } from '@expo/ui/swift-ui';
import { font } from '@expo/ui/swift-ui/modifiers';
export default function CustomFontExample() {
return (
-
+
@@ -259,6 +265,8 @@ export default function CustomFontExample() {
}
```
+
+
### Text with line limit
Use the `lineLimit` modifier to truncate text after a certain number of lines.
diff --git a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/group.mdx b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/group.mdx
index 4cc27947c1e3a0..a7bd5e3de3019c 100644
--- a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/group.mdx
+++ b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/group.mdx
@@ -8,6 +8,7 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { ComponentExample } from '~/ui/components/ComponentExample';
Expo UI Group matches the official SwiftUI [Group API](https://developer.apple.com/documentation/swiftui/group) and groups views together without introducing additional layout structure.
@@ -21,23 +22,33 @@ Expo UI Group matches the official SwiftUI [Group API](https://developer.apple.c
Groups are useful for applying modifiers to multiple views at once or organizing views without affecting layout.
-```tsx BasicGroupExample.tsx
-import { Host, Group, Text } from '@expo/ui/swift-ui';
+
+
+```tsx
+import { Group, Host, Text, VStack } from '@expo/ui/swift-ui';
import { foregroundStyle } from '@expo/ui/swift-ui/modifiers';
export default function BasicGroupExample() {
return (
-
-
- First item
- Second item
- Third item
-
+
+
+
+ First item
+ Second item
+ Third item
+
+
);
}
```
+
+
## API
```tsx
diff --git a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/lazyhstack.mdx b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/lazyhstack.mdx
index 23d28d05c30b55..b3f3955bfb8f42 100644
--- a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/lazyhstack.mdx
+++ b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/lazyhstack.mdx
@@ -8,6 +8,7 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { ComponentExample } from '~/ui/components/ComponentExample';
Expo UI LazyHStack matches the official SwiftUI [LazyHStack API](https://developer.apple.com/documentation/swiftui/lazyhstack) and arranges its children horizontally, creating items only as needed (when they become visible during scrolling).
@@ -23,7 +24,13 @@ Expo UI LazyHStack matches the official SwiftUI [LazyHStack API](https://develop
LazyHStack should be used inside a `ScrollView` with `axes="horizontal"` to enable lazy rendering.
-```tsx BasicLazyHStackExample.tsx
+
+
+```tsx
import {
Host,
ScrollView,
@@ -46,11 +53,19 @@ export default function BasicLazyHStackExample() {
}
```
+
+
### With alignment
The `alignment` prop controls vertical alignment of children. Available options are: `top`, `center`, `bottom`, `firstTextBaseline`, and `lastTextBaseline`.
-```tsx LazyHStackAlignmentExample.tsx
+
+
+```tsx
import {
Host,
ScrollView,
@@ -61,7 +76,9 @@ import { frame } from '@expo/ui/swift-ui/modifiers';
export default function LazyHStackAlignmentExample() {
return (
-
+
+
## API
```tsx
diff --git a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/lazyvstack.mdx b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/lazyvstack.mdx
index bd0fa67c9d85ef..d48e91b0ad31ee 100644
--- a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/lazyvstack.mdx
+++ b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/lazyvstack.mdx
@@ -8,6 +8,7 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { ComponentExample } from '~/ui/components/ComponentExample';
Expo UI LazyVStack matches the official SwiftUI [LazyVStack API](https://developer.apple.com/documentation/swiftui/lazyvstack) and arranges its children vertically, creating items only as needed (when they become visible during scrolling).
@@ -23,7 +24,13 @@ Expo UI LazyVStack matches the official SwiftUI [LazyVStack API](https://develop
LazyVStack should be used inside a `ScrollView` to enable lazy rendering.
-```tsx BasicLazyVStackExample.tsx
+
+
+```tsx
import {
Host,
ScrollView,
@@ -46,11 +53,19 @@ export default function BasicLazyVStackExample() {
}
```
+
+
### With alignment
The `alignment` prop controls horizontal alignment of children. Available options are: `leading`, `center`, and `trailing`.
-```tsx LazyVStackAlignmentExample.tsx
+
+
+```tsx
import {
Host,
ScrollView,
@@ -61,7 +76,9 @@ import { frame } from '@expo/ui/swift-ui/modifiers';
export default function LazyVStackAlignmentExample() {
return (
-
+
+
## API
```tsx
diff --git a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/scrollview.mdx b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/scrollview.mdx
index ca4b9e9b0d923b..037f5e8ec56812 100644
--- a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/scrollview.mdx
+++ b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/scrollview.mdx
@@ -8,6 +8,7 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { ComponentExample } from '~/ui/components/ComponentExample';
> **info** For cross-platform usage, see the universal [`ScrollView`](../universal/scrollview) β it renders the appropriate native component per platform.
@@ -23,7 +24,13 @@ Expo UI ScrollView matches the official SwiftUI [ScrollView API](https://develop
A simple vertically scrollable list of text items.
-```tsx ScrollViewVerticalExample.tsx
+
+
+```tsx
import { Host, ScrollView, VStack, Text } from '@expo/ui/swift-ui';
import { padding } from '@expo/ui/swift-ui/modifiers';
@@ -44,11 +51,19 @@ export default function ScrollViewVerticalExample() {
}
```
+
+
### Horizontal scroll view
Use the `axes` prop to scroll horizontally.
-```tsx ScrollViewHorizontalExample.tsx
+
+
+```tsx
import {
Host,
ScrollView,
@@ -82,6 +97,8 @@ export default function ScrollViewHorizontalExample() {
}
```
+
+
### Hidden scroll indicators
Set `showsIndicators` to `false` to hide the scroll bars.
@@ -114,7 +131,13 @@ The `scrollPosition` modifier also works on other scrollable containers like `La
> **warning** Writes to `state.value` must run on the UI runtime. Wrap the write in `scheduleOnUI` from `react-native-worklets`, or call them from inside a `'worklet'` function. Writes from the JS runtime trip Main Thread Checker, Xcode's runtime tool that flags UIKit calls made from a background thread.
-```tsx ScrollViewSharedPositionExample.tsx
+
+
+```tsx
import {
Button,
Host,
@@ -173,6 +196,8 @@ export default function ScrollViewSharedPositionExample() {
}
```
+
+
## API
```tsx
diff --git a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/swipeactions.mdx b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/swipeactions.mdx
index ac68557f0f72b2..e0686178df532e 100644
--- a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/swipeactions.mdx
+++ b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/swipeactions.mdx
@@ -8,6 +8,7 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { ComponentExample } from '~/ui/components/ComponentExample';
Expo UI SwipeActions matches the official SwiftUI [swipeActions]() modifier and lets you attach leading or trailing actions to row content.
@@ -17,7 +18,13 @@ Expo UI SwipeActions matches the official SwiftUI [swipeActions](
+
+```tsx
import {
Button,
Host,
@@ -61,6 +68,8 @@ export default function SwipeActionsExample() {
}
```
+
+
## API
```tsx
diff --git a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/text.mdx b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/text.mdx
index cc6f7d84319016..a1b16b09051dc6 100644
--- a/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/text.mdx
+++ b/docs/pages/versions/v57.0.0/sdk/ui/swift-ui/text.mdx
@@ -237,13 +237,19 @@ export default function FontDesignsExample() {
Use the `font` modifier with a `family` parameter to use custom fonts. You can load custom fonts using [`expo-font`](/versions/latest/sdk/font/) library.
-```tsx CustomFontExample.tsx
+
+
+```tsx
import { Host, Text, VStack } from '@expo/ui/swift-ui';
import { font } from '@expo/ui/swift-ui/modifiers';
export default function CustomFontExample() {
return (
-
+
@@ -259,6 +265,8 @@ export default function CustomFontExample() {
}
```
+
+
### Text with line limit
Use the `lineLimit` modifier to truncate text after a certain number of lines.
diff --git a/docs/public/static/images/expo-ui/examples/group-basic-ios-dark.webp b/docs/public/static/images/expo-ui/examples/group-basic-ios-dark.webp
new file mode 100644
index 00000000000000..c69615413ebbc0
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/group-basic-ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/group-basic-ios-light.webp b/docs/public/static/images/expo-ui/examples/group-basic-ios-light.webp
new file mode 100644
index 00000000000000..837bb2c058060e
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/group-basic-ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/lazyhstack-alignment-ios-dark.webp b/docs/public/static/images/expo-ui/examples/lazyhstack-alignment-ios-dark.webp
new file mode 100644
index 00000000000000..97ec727502a161
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/lazyhstack-alignment-ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/lazyhstack-alignment-ios-light.webp b/docs/public/static/images/expo-ui/examples/lazyhstack-alignment-ios-light.webp
new file mode 100644
index 00000000000000..9f0cc5a3798d29
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/lazyhstack-alignment-ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/lazyhstack-basic-ios-dark.webp b/docs/public/static/images/expo-ui/examples/lazyhstack-basic-ios-dark.webp
new file mode 100644
index 00000000000000..3f52d720ba6dc6
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/lazyhstack-basic-ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/lazyhstack-basic-ios-light.webp b/docs/public/static/images/expo-ui/examples/lazyhstack-basic-ios-light.webp
new file mode 100644
index 00000000000000..bfbfbf093e92c1
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/lazyhstack-basic-ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/lazyvstack-alignment-ios-dark.webp b/docs/public/static/images/expo-ui/examples/lazyvstack-alignment-ios-dark.webp
new file mode 100644
index 00000000000000..530ec1fdcbe860
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/lazyvstack-alignment-ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/lazyvstack-alignment-ios-light.webp b/docs/public/static/images/expo-ui/examples/lazyvstack-alignment-ios-light.webp
new file mode 100644
index 00000000000000..f4b9f24e5924c9
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/lazyvstack-alignment-ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/lazyvstack-basic-ios-dark.webp b/docs/public/static/images/expo-ui/examples/lazyvstack-basic-ios-dark.webp
new file mode 100644
index 00000000000000..b833af8f8983cc
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/lazyvstack-basic-ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/lazyvstack-basic-ios-light.webp b/docs/public/static/images/expo-ui/examples/lazyvstack-basic-ios-light.webp
new file mode 100644
index 00000000000000..546371951dd2ac
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/lazyvstack-basic-ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/scrollview-horizontal-ios-dark.webp b/docs/public/static/images/expo-ui/examples/scrollview-horizontal-ios-dark.webp
new file mode 100644
index 00000000000000..9735ec38fa266e
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/scrollview-horizontal-ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/scrollview-horizontal-ios-light.webp b/docs/public/static/images/expo-ui/examples/scrollview-horizontal-ios-light.webp
new file mode 100644
index 00000000000000..def695dd9b8df9
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/scrollview-horizontal-ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/scrollview-shared-position-ios-dark.webp b/docs/public/static/images/expo-ui/examples/scrollview-shared-position-ios-dark.webp
new file mode 100644
index 00000000000000..b84071e342af78
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/scrollview-shared-position-ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/scrollview-shared-position-ios-light.webp b/docs/public/static/images/expo-ui/examples/scrollview-shared-position-ios-light.webp
new file mode 100644
index 00000000000000..504f20deda7d9a
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/scrollview-shared-position-ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/scrollview-vertical-ios-dark.webp b/docs/public/static/images/expo-ui/examples/scrollview-vertical-ios-dark.webp
new file mode 100644
index 00000000000000..13386f1d9711aa
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/scrollview-vertical-ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/scrollview-vertical-ios-light.webp b/docs/public/static/images/expo-ui/examples/scrollview-vertical-ios-light.webp
new file mode 100644
index 00000000000000..886ec19a8612fe
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/scrollview-vertical-ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/swipeactions-basic-ios-dark.webp b/docs/public/static/images/expo-ui/examples/swipeactions-basic-ios-dark.webp
new file mode 100644
index 00000000000000..0379c536421692
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/swipeactions-basic-ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/swipeactions-basic-ios-light.webp b/docs/public/static/images/expo-ui/examples/swipeactions-basic-ios-light.webp
new file mode 100644
index 00000000000000..3c29b2e51b6913
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/swipeactions-basic-ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/text-custom-font-ios-dark.webp b/docs/public/static/images/expo-ui/examples/text-custom-font-ios-dark.webp
new file mode 100644
index 00000000000000..a2fd6cab0aad84
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/text-custom-font-ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/examples/text-custom-font-ios-light.webp b/docs/public/static/images/expo-ui/examples/text-custom-font-ios-light.webp
new file mode 100644
index 00000000000000..141e705ec2fb84
Binary files /dev/null and b/docs/public/static/images/expo-ui/examples/text-custom-font-ios-light.webp differ
diff --git a/docs/public/static/talks.ts b/docs/public/static/talks.ts
index 137ceeac04f8c3..f50d7e1f01d057 100644
--- a/docs/public/static/talks.ts
+++ b/docs/public/static/talks.ts
@@ -1,4 +1,36 @@
export const TALKS = [
+ {
+ title: 'The future of mobile development with Expo',
+ event: 'Chain React 2026',
+ description: 'Charlie Cheever',
+ videoId: 'jTZWOS9-JTw',
+ uploadDate: '2026-08-19',
+ home: true,
+ },
+ {
+ title: 'How is your app actually doing in production',
+ event: 'Chain React 2026',
+ description: 'Kadi Kraman',
+ videoId: 'G9RoD_QpJYQ',
+ uploadDate: '2026-08-19',
+ home: true,
+ },
+ {
+ title: 'Go All The Way',
+ event: 'Chain React 2026',
+ description: 'Seth Webster',
+ videoId: 'SWQtWHsAMk0',
+ uploadDate: '2026-08-19',
+ home: true,
+ },
+ {
+ title: 'Prompt Once: Scaling Expo Brownfield Across 100+ Apps',
+ event: 'Chain React 2026',
+ description: 'Gabriel Donadel',
+ videoId: 'Yk5KjVx6i54',
+ uploadDate: '2026-08-19',
+ home: true,
+ },
{
title: 'Expo keynote',
event: 'App.js Conf 2026',
diff --git a/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-a/package.json b/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-a/package.json
index 7246ecaa432e8f..335c2707793a29 100644
--- a/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-a/package.json
+++ b/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-a/package.json
@@ -13,7 +13,7 @@
"react-dom": "19.2.3",
"react-native": "0.86.0",
"react-native-safe-area-context": "~5.6.2",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-web": "~0.21.0"
}
}
diff --git a/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-b/package.json b/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-b/package.json
index c0ff094bca39e8..76445af33b4237 100644
--- a/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-b/package.json
+++ b/packages/@expo/cli/e2e/fixtures/with-monorepo/apps/app-b/package.json
@@ -13,7 +13,7 @@
"react-dom": "19.2.3",
"react-native": "0.86.0",
"react-native-safe-area-context": "~5.6.2",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-web": "~0.21.0"
}
}
diff --git a/packages/@expo/cli/e2e/fixtures/with-router-typed-routes/package.json b/packages/@expo/cli/e2e/fixtures/with-router-typed-routes/package.json
index 3be09d7ad3aea0..7195a6955e9e51 100644
--- a/packages/@expo/cli/e2e/fixtures/with-router-typed-routes/package.json
+++ b/packages/@expo/cli/e2e/fixtures/with-router-typed-routes/package.json
@@ -13,7 +13,7 @@
"react-dom": "19.2.3",
"react-native": "0.86.0",
"react-native-safe-area-context": "~5.6.2",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-web": "~0.21.0"
}
}
diff --git a/packages/@expo/cli/e2e/fixtures/with-router/package.json b/packages/@expo/cli/e2e/fixtures/with-router/package.json
index 82cab6b2a5b122..a77533a547a2e8 100644
--- a/packages/@expo/cli/e2e/fixtures/with-router/package.json
+++ b/packages/@expo/cli/e2e/fixtures/with-router/package.json
@@ -12,7 +12,7 @@
"react-dom": "19.2.3",
"react-native": "0.86.0",
"react-native-safe-area-context": "~5.6.2",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-web": "~0.21.0"
}
}
diff --git a/packages/expo-maps/CHANGELOG.md b/packages/expo-maps/CHANGELOG.md
index 7850332c4a56c1..2484a0f8cb43e6 100644
--- a/packages/expo-maps/CHANGELOG.md
+++ b/packages/expo-maps/CHANGELOG.md
@@ -6,6 +6,8 @@
### π New features
+- Add `Circle` type to the `GoogleMaps` namespace. ([#49124](https://github.com/expo/expo/pull/49124) by [@CatLover01](https://github.com/CatLover01))
+
### π Bug fixes
- [Android] Return the `Promise` from `GoogleMaps.View`'s imperative `setCameraPosition` so callers can `await` it and catch the `Animation cancelled` rejection (matches iOS). ([#46421](https://github.com/expo/expo/pull/46421) by [@chownation](https://github.com/chownation))
diff --git a/packages/expo-maps/src/index.ts b/packages/expo-maps/src/index.ts
index a1392e79793669..98f80e557d780f 100644
--- a/packages/expo-maps/src/index.ts
+++ b/packages/expo-maps/src/index.ts
@@ -28,6 +28,8 @@ export namespace GoogleMaps {
export type MapView = GoogleTypes.GoogleMapsViewType;
export type StreetViewProps = GoogleTypes.GoogleStreetViewProps;
+
+ export type Circle = GoogleTypes.GoogleMapsCircle;
}
/**
diff --git a/packages/expo-modules-autolinking/CHANGELOG.md b/packages/expo-modules-autolinking/CHANGELOG.md
index a50aa32112e25b..fa86a5ff454d2f 100644
--- a/packages/expo-modules-autolinking/CHANGELOG.md
+++ b/packages/expo-modules-autolinking/CHANGELOG.md
@@ -6,6 +6,7 @@
### π New features
+- [iOS] Detect React Native versions that ship self-contained XCFrameworks (no VFS overlay) during precompile and pod install, falling back to the legacy VFS overlay integration on pre-0.87 versions. ([#47256](https://github.com/expo/expo/pull/47256) by [@chrfalch](https://github.com/chrfalch))
- [Android] Set `CMAKE_OBJECT_PATH_MAX=1024` by default for the app and all library subprojects that build native code with CMake, so long object file paths (for example in pnpm monorepos on Windows) no longer fail the build. Configurable with the `expo.android.cmakeObjectPathMax` Gradle property. ([#47791](https://github.com/expo/expo/pull/47791) by [@ide](https://github.com/ide))
- [Android] Support linking published Gradle plugins. ([#48334](https://github.com/expo/expo/pull/48334) by [@jakex7](https://github.com/jakex7))
diff --git a/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb b/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb
index 2bb1c2567b472c..9c5a4651d88a57 100644
--- a/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb
+++ b/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb
@@ -185,6 +185,7 @@ def perform_post_install(installer)
configure_header_search_paths(installer)
configure_codegen_for_prebuilt_modules(installer)
stub_bundled_pod_targets(installer)
+ quote_swift_compatibility_header_search_paths(installer)
end
# Runs all precompiled module pre-install steps.
@@ -828,6 +829,23 @@ def disable_swift_interface_verification(installer)
# build time when switching DebugβRelease configurations.
def configure_use_frameworks(installer)
return unless prebuilt_react_active?
+
+ # Modern modular prebuilt React (React.xcframework + flattened ReactNativeHeaders, no VFS
+ # overlay): React Native's rncore.rb wires the header resolution β and
+ # `@import React` via the framework, and the relocated namespaces (, ,
+ # RCTDeprecation/, ...) via a clang module map at React-Core-prebuilt/Headers/module.modulemap.
+ # But RN's pod-target loop only covers the React-* pods; it misses every Expo pod (Expo,
+ # ExpoModulesCore, ExpoImage, ...). Re-apply that module-map coverage to the pods RN missed so
+ # their explicit-module precompile resolves the lowercase namespaces modularly
+ # instead of tripping -Wnon-modular-include-in-framework-module. (The coverage self-skips if the
+ # prebuilt did not actually ship the module map β see ensure_modular_react_header_flags.)
+ unless prebuilt_react_uses_vfs?(installer)
+ ensure_modular_react_header_flags(installer)
+ return
+ end
+
+ # Legacy VFS prebuilt React: the use_frameworks! compat below only applies when use_frameworks!
+ # is active.
return if linkage(installer).nil?
react_prebuilt_dir = File.join(installer.sandbox.root, 'React-Core-prebuilt')
@@ -844,6 +862,123 @@ def configure_use_frameworks(installer)
Pod::UI.puts "[Expo] ".blue + "Created non-framework React modulemap for use_frameworks! compatibility"
end
+ # Central discriminator for how the prebuilt React-Core-prebuilt pod exposes its headers. Two
+ # layouts ship today; everything that branches on "are we modular?" should route through here
+ # rather than re-deriving it (the signals diverged once before β keying on the transient
+ # Headers/module.modulemap β and silently broke the build):
+ #
+ # - Legacy VFS: a single React.xcframework whose headers live at React.xcframework/Headers;
+ # / are made resolvable via a generated clang VFS overlay. Expo must
+ # install use_frameworks! compatibility (non-framework modulemap + -isystem) for it.
+ # - Modern framework-resolved: ReactNativeHeaders' headers are flattened into a top-level
+ # React-Core-prebuilt/Headers/ directory and vended as ordinary public pod headers, while
+ # resolves through the framework. No VFS, no module-map flag, no Expo compat.
+ #
+ # The presence of the flattened top-level Headers/ directory is the stable signal: it is the
+ # pod's header_mappings_dir output (re-created by prepare_command on every build-time re-extract),
+ # unlike Headers/module.modulemap which the modern layout copies in transiently and then discards.
+ def prebuilt_react_uses_vfs?(installer)
+ return false unless prebuilt_react_active?
+
+ react_prebuilt_dir = File.join(installer.sandbox.root, 'React-Core-prebuilt')
+ !Dir.exist?(File.join(react_prebuilt_dir, 'Headers'))
+ end
+
+ # Mirrors React Native's rncore.rb add_prebuilt_header_search_paths for the pods RN's own loop
+ # misses (it only covers the React-* pod targets, not the Expo pods). Re-applies the flattened
+ # ReactNativeHeaders module map + header search path idempotently, skipping any xcconfig that
+ # already carries the flag (those RN already covered).
+ #
+ # Gated on the module map actually existing: the Maven prebuilt path (RCT_USE_PREBUILT_RNCORE
+ # without RCT_TESTONLY_RNCORE_TARBALL_PATH) can install a React-Core-prebuilt that does not ship
+ # Headers/module.modulemap. Pointing -fmodule-map-file at a missing file fails `ScanDependencies`
+ # ("module map file ... not found"), so when it is absent we inject nothing and warn β the
+ # faithful path is a tarball that ships ReactNativeHeaders via RCT_TESTONLY_RNCORE_TARBALL_PATH.
+ def ensure_modular_react_header_flags(installer)
+ module_map_file = File.join(installer.sandbox.root, 'React-Core-prebuilt', 'Headers', 'module.modulemap')
+ unless File.exist?(module_map_file)
+ Pod::UI.warn "[Expo] Prebuilt React-Core-prebuilt is missing Headers/module.modulemap β " \
+ "skipping Expo module-map coverage. If the build fails with a missing module map or " \
+ "-Wnon-modular-include, the installed prebuilt React does not match React Native's " \
+ "CocoaPods scripts; install a tarball that ships ReactNativeHeaders via " \
+ "RCT_TESTONLY_RNCORE_TARBALL_PATH."
+ return
+ end
+
+ module_map = '$(PODS_ROOT)/React-Core-prebuilt/Headers/module.modulemap'
+ header_path = '"$(PODS_ROOT)/React-Core-prebuilt/Headers"'
+ # Quoted exactly as RN's rncore.rb writes it, for two reasons: a $(PODS_ROOT) containing
+ # spaces has to stay a single clang argument, and the already-present checks below only
+ # recognise RN's own injection β and skip re-adding it β if the spelling matches.
+ cflags_flag = %("-fmodule-map-file=#{module_map}")
+ swift_flag = "-Xcc #{cflags_flag}"
+ umbrella_flag = '-Xcc -Wno-incomplete-umbrella'
+
+ patched = 0
+ Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |xcconfig_path|
+ content = File.read(xcconfig_path)
+ original = content.dup
+
+ # Each flag is ensured INDEPENDENTLY (not "skip the pod if the module-map flag is present").
+ # A pod can carry the -fmodule-map-file flag from its own podspec (e.g. react-native-reanimated
+ # 4.x adds it) yet still lack the flattened-headers search path β in which case the module map
+ # activates the yoga/react modules but & co. textually fail to resolve
+ # (the modules' headers live under React-Core-prebuilt/Headers/). Add whichever piece is absent.
+ #
+ # OTHER_CPLUSPLUSFLAGS needs its own copy: Xcode does not fold OTHER_CFLAGS into it, so once a
+ # target sets it (RN's new_architecture.rb does, for -DRCT_NEW_ARCH_ENABLED) that value replaces
+ # OTHER_CFLAGS for every .mm/.cpp/.cc translation unit. Without it the module map never reaches
+ # the C++ sources that consume the relocated react/ and yoga/ namespaces.
+ content = append_xcconfig_flag(content, 'HEADER_SEARCH_PATHS', header_path)
+ content = append_xcconfig_flag(content, 'OTHER_CFLAGS', cflags_flag)
+ content = append_xcconfig_flag(content, 'OTHER_CPLUSPLUSFLAGS', cflags_flag)
+ content = append_xcconfig_flag(content, 'OTHER_SWIFT_FLAGS', umbrella_flag)
+ content = append_xcconfig_flag(content, 'OTHER_SWIFT_FLAGS', swift_flag)
+
+ next if content == original
+ File.write(xcconfig_path, content)
+ patched += 1
+ end
+
+ Pod::UI.puts "[Expo] ".blue + "Ensured modular React header flags on #{patched} xcconfig(s)"
+ end
+
+ # Re-quote unquoted `.../Swift Compatibility Header` entries in HEADER_SEARCH_PATHS. The path
+ # has a space, and CocoaPods drops the podspec's quotes when it's supplied via a joined
+ # `pod_target_xcconfig` string, so clang space-splits it and a cross-pod `#import
+ # "-Swift.h"` fails in the static-library source build (no framework bundle, and no VFS
+ # overlay since RN removed it). Runs on the final xcconfigs; RN's post-install preserves
+ # quotes, so this stays authoritative. Idempotent (already-quoted tokens are skipped).
+ def quote_swift_compatibility_header_search_paths(installer)
+ # $(β¦)/${β¦}PODS_CONFIGURATION_BUILD_DIR / / "Swift Compatibility Header", unquoted only.
+ token = /(? 0
+ end
+
+ # Appends `value` to an xcconfig `key` line (preserving $(inherited)), or adds the key if absent.
+ # No-ops when `key` already carries `value`.
+ #
+ # The already-present check is scoped to `key`'s own line, not the whole file: the same flag
+ # legitimately belongs on several keys (OTHER_CFLAGS and OTHER_CPLUSPLUSFLAGS both need the
+ # module map), so a file-wide check would let whichever key is written first silently suppress
+ # every later one.
+ def append_xcconfig_flag(content, key, value)
+ line = /^(#{Regexp.escape(key)}\s*=.*)$/
+ existing = content[line, 1]
+ return content if existing&.include?(value)
+ return content.sub(line) { "#{$1} #{value}" } if existing
+
+ (content.end_with?("\n") ? content : content + "\n") + "#{key} = $(inherited) #{value}\n"
+ end
+
# TODO(ExpoModulesJSI-xcframework): Remove this method when ExpoModulesJSI.xcframework
# is built and distributed separately. At that point, pods can depend on ExpoModulesJSI
# directly and this header search path workaround won't be needed.
diff --git a/packages/expo-modules-jsi/CHANGELOG.md b/packages/expo-modules-jsi/CHANGELOG.md
index c64195ab1712eb..3e811f793935ac 100644
--- a/packages/expo-modules-jsi/CHANGELOG.md
+++ b/packages/expo-modules-jsi/CHANGELOG.md
@@ -20,6 +20,7 @@
- [iOS] Fixed `JavaScriptPropNameID(_:string:)` and the array's string-keyed subscript truncating non-ASCII property keys: they passed `String.count` (the grapheme-cluster count) as the UTF-8 byte length to `PropNameID::forUtf8`, so keys like `"cafΓ©"` or `"π"` were built from mangled bytes and no longer matched the intended property. ([#48329](https://github.com/expo/expo/pull/48329) by [@tsapeta](https://github.com/tsapeta))
- [iOS] Fixed a use-after-free when a non-owning `JavaScriptRuntime` wrapper outlives its runtime (e.g. it is captured by a task abandoned on reload): its cached `jsi::PropNameID`s were destroyed against the freed runtime when the wrapper deallocated. The teardown sweep now flushes the cache on the JavaScript thread while the runtime is still valid. ([#47927](https://github.com/expo/expo/pull/47927) by [@tsapeta](https://github.com/tsapeta))
- [iOS] `JavaScriptPromise` no longer traps when a resolve or reject call throws, which can realistically only happen against a runtime that is being torn down: a failed resolver call rejects the promise instead and a failed rejecter call is dropped. ([#47862](https://github.com/expo/expo/pull/47862) by [@tsapeta](https://github.com/tsapeta))
+- [iOS] Fixed the xcframework prebuild failing under Xcode 27 due to new foreign reference ownership warnings emitted for `RuntimeScheduler` constructors. ([#49120](https://github.com/expo/expo/pull/49120) by [@tsapeta](https://github.com/tsapeta))
### π‘ Others
diff --git a/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI-Cxx/include/RuntimeScheduler.h b/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI-Cxx/include/RuntimeScheduler.h
index 957527c8537928..6115e87a6e430f 100644
--- a/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI-Cxx/include/RuntimeScheduler.h
+++ b/packages/expo-modules-jsi/apple/Sources/ExpoModulesJSI-Cxx/include/RuntimeScheduler.h
@@ -50,7 +50,7 @@ class RuntimeScheduler {
`scheduleTask` dispatches through `fn`, which the host implements against
the real react::RuntimeScheduler.
*/
- RuntimeScheduler(void *scheduler, ScheduleFn fn) noexcept
+ SWIFT_RETURNS_RETAINED RuntimeScheduler(void *scheduler, ScheduleFn fn) noexcept
: nativeScheduler(scheduler), scheduleFn(fn) {}
/**
@@ -58,7 +58,7 @@ class RuntimeScheduler {
caller's thread β intended for standalone runtimes (e.g. tests) that have
no React scheduler.
*/
- RuntimeScheduler() {}
+ SWIFT_RETURNS_RETAINED RuntimeScheduler() {}
RuntimeScheduler(const RuntimeScheduler &) = delete;
diff --git a/packages/expo-router/CHANGELOG.md b/packages/expo-router/CHANGELOG.md
index eb0cd450b107f5..a83ac32dcc9c2a 100644
--- a/packages/expo-router/CHANGELOG.md
+++ b/packages/expo-router/CHANGELOG.md
@@ -37,6 +37,7 @@
- Re-export drawer content components and types (`DrawerContentScrollView`, `DrawerItem`, `DrawerItemList`, `DrawerContentComponentProps`, `DrawerNavigationProp`, and more) from `expo-router/drawer` ([#46635](https://github.com/expo/expo/pull/46635) by [@Ubax](https://github.com/Ubax))
- Add `pageHeaders` config plugin option for declaring per-path response headers ([#47429](https://github.com/expo/expo/pull/47429) by [@hassankhan](https://github.com/hassankhan))
- Upgrade react-native-screens to 4.26.0 ([#47770](https://github.com/expo/expo/pull/47770) by [@Ubax](https://github.com/Ubax))
+- Upgrade react-native-screens to 4.27.0 ([#49122](https://github.com/expo/expo/pull/49122) by [@gabrieldonadel](https://github.com/gabrieldonadel))
- Improve standard-navigation types for `createProps`. ([#47825](https://github.com/expo/expo/pull/47825) by [@Ubax](https://github.com/Ubax))
- Add a `processScreens` option to the `standard-navigation` integration. ([#48290](https://github.com/expo/expo/pull/48290) by [@Ubax](https://github.com/Ubax))
- Hide the splash screen when the built-in `+not-found` screen renders ([#48721](https://github.com/expo/expo/pull/48721) by [@Ubax](https://github.com/Ubax))
diff --git a/packages/expo-router/package.json b/packages/expo-router/package.json
index 14f2780ef2af46..58402eb2d8db30 100644
--- a/packages/expo-router/package.json
+++ b/packages/expo-router/package.json
@@ -293,7 +293,7 @@
"react-fast-compare": "^3.2.2",
"react-is": "^19.1.0",
"react-native-drawer-layout": "^4.2.2",
- "react-native-screens": "^4.26.0",
+ "react-native-screens": "^4.27.0",
"server-only": "^0.0.1",
"sf-symbols-typescript": "^2.1.0",
"shallowequal": "^1.1.0",
@@ -321,7 +321,7 @@
"react-native-pager-view": "^8.0.2",
"react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "~5.7.0",
- "react-native-screens": "^4.26.0",
+ "react-native-screens": "^4.27.0",
"react-native-tab-view": "^4.3.0",
"react-native-web": "~0.21.0",
"react-server-dom-webpack": "~19.0.8"
@@ -339,7 +339,7 @@
"react-native-gesture-handler": "*",
"react-native-reanimated": "*",
"react-native-safe-area-context": ">= 5.4.0",
- "react-native-screens": "^4.26.0",
+ "react-native-screens": "^4.27.0",
"react-native-web": "*",
"react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4"
},
diff --git a/packages/expo-sqlite/dev-plugin-webui/package.json b/packages/expo-sqlite/dev-plugin-webui/package.json
index e1e9a5b9f2eee2..77135223f0da56 100644
--- a/packages/expo-sqlite/dev-plugin-webui/package.json
+++ b/packages/expo-sqlite/dev-plugin-webui/package.json
@@ -29,7 +29,7 @@
"react-dom": "19.2.3",
"react-native": "0.86.0",
"react-native-safe-area-context": "5.7.0",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-web": "~0.21.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
diff --git a/packages/expo/bundledNativeModules.json b/packages/expo/bundledNativeModules.json
index c02500ee8fd650..9eefb92e000422 100644
--- a/packages/expo/bundledNativeModules.json
+++ b/packages/expo/bundledNativeModules.json
@@ -108,7 +108,7 @@
"react-native-pager-view": "8.0.2",
"react-native-worklets": "0.10.1",
"react-native-reanimated": "4.5.1",
- "react-native-screens": "~4.26.0",
+ "react-native-screens": "~4.27.0",
"react-native-safe-area-context": "~5.7.0",
"react-native-svg": "15.15.4",
"react-native-view-shot": "5.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3bc679475cdc53..77d18a0c4d53ca 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -235,8 +235,8 @@ importers:
specifier: 5.7.0
version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-screens:
- specifier: 4.26.0
- version: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ specifier: 4.27.0
+ version: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-svg:
specifier: 15.15.4
version: 15.15.4(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
@@ -325,7 +325,7 @@ importers:
version: 15.1.1(expo-font@packages+expo-font)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
'@react-navigation/bottom-tabs':
specifier: ^7.15.5
- version: 7.15.5(44d7af03b6d5746cc08731897c61b466)
+ version: 7.15.5(a2c72522bbca03204a3089e0f828e9b1)
'@react-navigation/elements':
specifier: ^2.9.10
version: 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
@@ -396,8 +396,8 @@ importers:
specifier: ~5.7.0
version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-screens:
- specifier: 4.26.0
- version: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ specifier: 4.27.0
+ version: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-web:
specifier: ~0.21.0
version: 0.21.2(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -674,8 +674,8 @@ importers:
specifier: 5.7.0
version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-screens:
- specifier: 4.26.0
- version: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ specifier: 4.27.0
+ version: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-svg:
specifier: 15.15.4
version: 15.15.4(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
@@ -1103,8 +1103,8 @@ importers:
specifier: 5.7.0
version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-screens:
- specifier: 4.26.0
- version: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ specifier: 4.27.0
+ version: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-svg:
specifier: 15.15.4
version: 15.15.4(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
@@ -1232,7 +1232,7 @@ importers:
version: 15.1.1(expo-font@packages+expo-font)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
'@react-navigation/bottom-tabs':
specifier: ^7.15.5
- version: 7.15.5(44d7af03b6d5746cc08731897c61b466)
+ version: 7.15.5(a2c72522bbca03204a3089e0f828e9b1)
'@react-navigation/native':
specifier: ^7.1.33
version: 7.1.33(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
@@ -1294,8 +1294,8 @@ importers:
specifier: 5.7.0
version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-screens:
- specifier: 4.26.0
- version: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ specifier: 4.27.0
+ version: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-worklets:
specifier: 0.10.1
version: 0.10.1(patch_hash=b72ac0fc85273472ec4b8974adb8d4eba75672291aa7501270a7b08e58ba7dec)(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
@@ -1326,13 +1326,13 @@ importers:
version: 15.1.1(expo-font@packages+expo-font)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
'@react-navigation/bottom-tabs':
specifier: ^7.15.5
- version: 7.15.5(44d7af03b6d5746cc08731897c61b466)
+ version: 7.15.5(a2c72522bbca03204a3089e0f828e9b1)
'@react-navigation/native':
specifier: ^7.1.33
version: 7.1.33(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
'@react-navigation/native-stack':
specifier: ^7.14.5
- version: 7.14.5(44d7af03b6d5746cc08731897c61b466)
+ version: 7.14.5(a2c72522bbca03204a3089e0f828e9b1)
expo:
specifier: workspace:*
version: link:../../packages/expo
@@ -1403,8 +1403,8 @@ importers:
specifier: 5.7.0
version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-screens:
- specifier: 4.26.0
- version: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ specifier: 4.27.0
+ version: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
devDependencies:
'@expo/config':
specifier: workspace:*
@@ -1482,8 +1482,8 @@ importers:
specifier: 5.7.0
version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-screens:
- specifier: 4.26.0
- version: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ specifier: 4.27.0
+ version: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-tab-view:
specifier: ^4.3.0
version: 4.3.0(react-native-pager-view@8.0.2(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
@@ -1526,7 +1526,7 @@ importers:
dependencies:
'@react-navigation/bottom-tabs':
specifier: ^7.15.5
- version: 7.15.5(44d7af03b6d5746cc08731897c61b466)
+ version: 7.15.5(a2c72522bbca03204a3089e0f828e9b1)
'@react-navigation/native':
specifier: ^7.1.33
version: 7.1.33(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
@@ -1555,8 +1555,8 @@ importers:
specifier: 5.7.0
version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-screens:
- specifier: 4.26.0
- version: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ specifier: 4.27.0
+ version: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
devDependencies:
babel-preset-expo:
specifier: workspace:*
@@ -5378,8 +5378,8 @@ importers:
specifier: ^4.2.2
version: 4.2.2(fe048f68a60a5b35919ae811510cf1ab)
react-native-screens:
- specifier: ^4.26.0
- version: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ specifier: ^4.27.0
+ version: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
server-only:
specifier: ^0.0.1
version: 0.0.1
@@ -5742,8 +5742,8 @@ importers:
specifier: 5.7.0
version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-screens:
- specifier: 4.26.0
- version: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ specifier: 4.27.0
+ version: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
react-native-web:
specifier: ~0.21.0
version: 0.21.2(encoding@0.1.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -10164,11 +10164,6 @@ packages:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
- agent-cli-detector@0.1.2:
- resolution: {integrity: sha512-qdZ/9JFORtTKJNhT/IczMeEfEUbUU0K5umYeiIQHX+AjHs+Y9SXVzSgaYlpZeyNMrvuh2HpZiOTpvS57iPfBkQ==}
- engines: {node: '>=18.18'}
- hasBin: true
-
agent-cli-detector@0.1.6:
resolution: {integrity: sha512-vKrPeEVN3upDF3GjWxsWBbwQgMtNJ8VB1cduPvK3svmmz4ENpS7yaPxbogsbe3w+xp9xp2Cu+0Ar41rjAR4+lA==}
engines: {node: '>=18.18'}
@@ -13890,8 +13885,8 @@ packages:
react: '*'
react-native: 0.86.0
- react-native-screens@4.26.0:
- resolution: {integrity: sha512-8DkONnr4496K6ECMfprqyYM4ya1IQO5mA3ROOAJ1P6OVvNL6rVtjD2Elikd/INC7CrePdFTOHUEahOMSHmWgUA==}
+ react-native-screens@4.27.0:
+ resolution: {integrity: sha512-IUukKYilhKhdJqGh59BmxrAjgIO84CaOkdowEY20+RmapOAFpqS//xOIcq2iqDQ4xVoxuwCHGWhY/Tbz6lTblA==}
peerDependencies:
react: '*'
react-native: 0.86.0
@@ -18019,7 +18014,7 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.14
- '@react-navigation/bottom-tabs@7.15.5(44d7af03b6d5746cc08731897c61b466)':
+ '@react-navigation/bottom-tabs@7.15.5(a2c72522bbca03204a3089e0f828e9b1)':
dependencies:
'@react-navigation/elements': 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
'@react-navigation/native': 7.1.33(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
@@ -18027,7 +18022,7 @@ snapshots:
react: 19.2.3
react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3)
react-native-safe-area-context: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
- react-native-screens: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ react-native-screens: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
sf-symbols-typescript: 2.2.0
transitivePeerDependencies:
- '@react-native-masked-view/masked-view'
@@ -18056,7 +18051,7 @@ snapshots:
optionalDependencies:
'@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
- '@react-navigation/native-stack@7.14.5(44d7af03b6d5746cc08731897c61b466)':
+ '@react-navigation/native-stack@7.14.5(a2c72522bbca03204a3089e0f828e9b1)':
dependencies:
'@react-navigation/elements': 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
'@react-navigation/native': 7.1.33(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
@@ -18064,7 +18059,7 @@ snapshots:
react: 19.2.3
react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3)
react-native-safe-area-context: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
- react-native-screens: 4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
+ react-native-screens: 4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)
sf-symbols-typescript: 2.2.0
warn-once: 0.1.1
transitivePeerDependencies:
@@ -19092,8 +19087,6 @@ snapshots:
agent-base@7.1.4: {}
- agent-cli-detector@0.1.2: {}
-
agent-cli-detector@0.1.6: {}
ajv-formats@2.1.1(ajv@8.20.0):
@@ -23431,7 +23424,7 @@ snapshots:
react: 19.2.3
react-native: 0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3)
- react-native-screens@4.26.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3):
+ react-native-screens@4.27.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/jest-preset@0.86.0(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3):
dependencies:
react: 19.2.3
react-freeze: 1.0.4(react@19.2.3)
diff --git a/templates/expo-template-default/package.json b/templates/expo-template-default/package.json
index a7b4c6310dcc44..9e29c923d4160e 100644
--- a/templates/expo-template-default/package.json
+++ b/templates/expo-template-default/package.json
@@ -37,7 +37,7 @@
"react-native-gesture-handler": "~3.1.0",
"react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "~5.7.0",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.10.1"
},
diff --git a/templates/expo-template-tabs/package.json b/templates/expo-template-tabs/package.json
index 0ae43c2332da60..7961db61b5a121 100644
--- a/templates/expo-template-tabs/package.json
+++ b/templates/expo-template-tabs/package.json
@@ -30,7 +30,7 @@
"react-native": "0.86.2",
"react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "~5.7.0",
- "react-native-screens": "4.26.0",
+ "react-native-screens": "4.27.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.10.1"
},
diff --git a/tools/src/prebuilds/Dependencies.ts b/tools/src/prebuilds/Dependencies.ts
index 124aed6f5a693c..be649ea4ec27c7 100644
--- a/tools/src/prebuilds/Dependencies.ts
+++ b/tools/src/prebuilds/Dependencies.ts
@@ -244,21 +244,36 @@ export const Dependencies = {
}
);
- // Generate VFS overlay and stage missing headers for stock Maven xcframework.
- // The xcframework itself is never modified (preserves Meta's code signature).
- // Skipped if already generated and RN source version hasn't changed.
+ // React header resolution comes in two flavors:
+ // - Modern modular artifact: ships ReactNativeHeaders.xcframework with a flattened clang
+ // module map. Nothing to generate β the module map is consumed directly.
+ // - Legacy artifact: ships only React.xcframework, so we synthesize a VFS overlay (and stage
+ // missing headers) without ever modifying the xcframework (preserves Meta's code signature).
const reactNativeSourcePath = resolvePackagePath('react-native');
const xcframeworkPath = path.join(reactNativePath, 'React.xcframework');
- if (fs.existsSync(xcframeworkPath) && !isVFSGenerated(reactNativePath, reactNativeSourcePath)) {
- logger.verbose('π Generating VFS overlay for stock React.xcframework...');
- await transformReactXCFrameworkAsync({
- outputPath: reactNativePath,
- reactNativePath: reactNativeSourcePath,
- });
- }
+ const usesModularHeaders = fs.existsSync(
+ path.join(reactNativePath, 'ReactNativeHeaders.xcframework')
+ );
+ if (usesModularHeaders) {
+ logger.verbose(
+ 'π¦ Modular React headers detected (ReactNativeHeaders.xcframework) β skipping VFS overlay'
+ );
+ } else {
+ // Skipped if already generated and RN source version hasn't changed.
+ if (
+ fs.existsSync(xcframeworkPath) &&
+ !isVFSGenerated(reactNativePath, reactNativeSourcePath)
+ ) {
+ logger.verbose('π Generating VFS overlay for stock React.xcframework...');
+ await transformReactXCFrameworkAsync({
+ outputPath: reactNativePath,
+ reactNativePath: reactNativeSourcePath,
+ });
+ }
- // Generate the VFS overlay file from the template (resolves ${ROOT_PATH} placeholders)
- await resolveVFSOverlayTemplate(reactNativePath);
+ // Generate the VFS overlay file from the template (resolves ${ROOT_PATH} placeholders)
+ await resolveVFSOverlayTemplate(reactNativePath);
+ }
return {
hermes: hermesPath,
@@ -325,8 +340,11 @@ export const Dependencies = {
spinner.succeed('Artifacts already up-to-date in local .dependencies folder');
}
- // Resolve the VFS overlay template with the correct paths
- await resolveVFSOverlayTemplate(rnDest);
+ // Legacy artifacts only: resolve the VFS overlay template with the correct paths. Modular
+ // artifacts (ReactNativeHeaders.xcframework) ship a module map and need no overlay.
+ if (!fs.existsSync(path.join(rnDest, 'ReactNativeHeaders.xcframework'))) {
+ await resolveVFSOverlayTemplate(rnDest);
+ }
} else {
const spinner = createAsyncSpinner('Verifying artifacts for local dependencies', pkg);
diff --git a/tools/src/prebuilds/SPMPackage.test.ts b/tools/src/prebuilds/SPMPackage.test.ts
index b7b1bba536c0f5..7a4e11565e2aa8 100644
--- a/tools/src/prebuilds/SPMPackage.test.ts
+++ b/tools/src/prebuilds/SPMPackage.test.ts
@@ -1,6 +1,8 @@
import assert from 'node:assert/strict';
+import fs from 'node:fs';
+import os from 'node:os';
import path from 'node:path';
-import { describe, it } from 'node:test';
+import { afterEach, describe, it } from 'node:test';
import type { SPMProduct, SwiftTarget } from './SPMConfig.types';
import {
@@ -10,6 +12,18 @@ import {
type ExternalDepResolver,
} from './SPMPackage';
+/** Builds an ArtifactPaths fixture whose React cache slot we then populate per-format. */
+function makeArtifactPaths(cachePath: string, version: string) {
+ return {
+ hermes: path.join(cachePath, 'hermes'),
+ reactNativeDependencies: path.join(cachePath, 'deps'),
+ react: path.join(cachePath, 'react', version, 'debug'),
+ cachePath,
+ hermesVersion: '1.0.0',
+ reactNativeVersion: version,
+ };
+}
+
function makeProduct(name: string, targetDeps: string[] = []): SPMProduct {
return {
name,
@@ -192,3 +206,72 @@ describe('buildSwiftSettings ExpoModulesMacros plugin flags', () => {
assert.equal(hasMacroPluginFlags(settings), false);
});
});
+
+describe('React header flags: modular module map vs legacy VFS overlay', () => {
+ const version = '1000.0.0';
+ const tmpDirs: string[] = [];
+
+ afterEach(() => {
+ while (tmpDirs.length) {
+ fs.rmSync(tmpDirs.pop()!, { recursive: true, force: true });
+ }
+ });
+
+ function withCache(populate: (debugBase: string) => void): string {
+ const cachePath = fs.mkdtempSync(path.join(os.tmpdir(), 'spm-react-flags-'));
+ tmpDirs.push(cachePath);
+ const debugBase = path.join(cachePath, 'react', version, 'debug');
+ fs.mkdirSync(debugBase, { recursive: true });
+ populate(debugBase);
+ return cachePath;
+ }
+
+ it('emits -fmodule-map-file + -I when ReactNativeHeaders.xcframework is present', () => {
+ const cachePath = withCache((debugBase) => {
+ const headersDir = path.join(
+ debugBase,
+ 'ReactNativeHeaders.xcframework',
+ 'ios-arm64',
+ 'Headers'
+ );
+ fs.mkdirSync(headersDir, { recursive: true });
+ fs.writeFileSync(
+ path.join(headersDir, 'module.modulemap'),
+ 'module ReactNativeHeaders_react {}'
+ );
+ });
+
+ const settings = buildSwiftSettings(
+ ['React'],
+ makeArtifactPaths(cachePath, version),
+ path.join(cachePath, 'pkg'),
+ 'Debug'
+ );
+ const joined = settings.join(' ');
+
+ // clang requires the joined form `-fmodule-map-file=`; the space-separated variant errors.
+ assert.match(joined, /-fmodule-map-file=\S*module\.modulemap/);
+ assert.ok(!joined.includes('-ivfsoverlay'), 'must not fall back to the VFS overlay');
+ });
+
+ it('falls back to -ivfsoverlay when only the legacy React-VFS overlay is present', () => {
+ const cachePath = withCache((debugBase) => {
+ // Legacy artifact: a generated VFS overlay, no ReactNativeHeaders.xcframework.
+ fs.writeFileSync(
+ path.join(debugBase, 'React-VFS.yaml'),
+ 'version: 0\ncase-sensitive: false\nroots: []\n'
+ );
+ });
+
+ const settings = buildSwiftSettings(
+ ['React'],
+ makeArtifactPaths(cachePath, version),
+ path.join(cachePath, 'pkg'),
+ 'Debug'
+ );
+ const joined = settings.join(' ');
+
+ assert.ok(joined.includes('-ivfsoverlay'), 'should use the VFS overlay for legacy artifacts');
+ assert.ok(!joined.includes('-fmodule-map-file'), 'must not emit a module map flag');
+ });
+});
diff --git a/tools/src/prebuilds/SPMPackage.ts b/tools/src/prebuilds/SPMPackage.ts
index fdb636dc17b039..5e21e52f3b451d 100644
--- a/tools/src/prebuilds/SPMPackage.ts
+++ b/tools/src/prebuilds/SPMPackage.ts
@@ -9,13 +9,13 @@ import fs from 'fs-extra';
import { glob } from 'glob';
import path from 'path';
+import { getPrecompileDir } from '../Directories';
+import { getPackageByName } from '../Packages';
import type { DownloadedDependencies } from './Artifacts.types';
import type { SPMPackageSource } from './ExternalPackage';
import { getExternalPackageByProductName } from './ExternalPackage';
import { Frameworks } from './Frameworks';
import { BuildFlavor } from './Prebuilder.types';
-import { getPrecompileDir } from '../Directories';
-import { getPackageByName } from '../Packages';
import {
ObjcTarget,
SwiftTarget,
@@ -66,6 +66,43 @@ function findXCFrameworkHeadersDir(xcframeworkPath: string): string | null {
return null;
}
+/**
+ * Finds the Headers directory of a headers-only xcframework (one whose slices contain a `Headers/`
+ * directory directly, with no `.framework` wrapper β e.g. ReactNativeHeaders.xcframework). The
+ * directory is identified by the presence of a `module.modulemap` and is architecture-independent,
+ * so the first matching slice is used.
+ * @param xcframeworkPath Absolute path to the .xcframework directory
+ * @returns Absolute path to the slice's Headers directory, or null if not found
+ */
+function findModularHeadersDir(xcframeworkPath: string): string | null {
+ if (!fs.existsSync(xcframeworkPath)) {
+ return null;
+ }
+ for (const entry of fs.readdirSync(xcframeworkPath, { withFileTypes: true })) {
+ if (!entry.isDirectory()) {
+ continue;
+ }
+ const headersDir = path.join(xcframeworkPath, entry.name, 'Headers');
+ if (fs.existsSync(path.join(headersDir, 'module.modulemap'))) {
+ return headersDir;
+ }
+ }
+ return null;
+}
+
+/**
+ * Detects whether a React artifact still uses the legacy VFS overlay. The modern modular artifact
+ * ships ReactNativeHeaders.xcframework (and drops the React-VFS template that the overlay was
+ * generated from), so its absence means we should fall back to the VFS path.
+ * @param basePath Flavor-specific artifact base path (contains React.xcframework)
+ */
+function reactArtifactIsModular(basePath?: string): boolean {
+ if (!basePath) {
+ return false;
+ }
+ return fs.existsSync(path.join(basePath, 'ReactNativeHeaders.xcframework'));
+}
+
/**
* Escapes a string for use in a Swift string literal.
* Handles backslashes and double quotes.
@@ -496,6 +533,13 @@ const ARTIFACT_RELATIVE_PATHS: Record<
xcframeworkPath: string;
includeDirectories: string[];
vfsOverlayFile?: string;
+ /**
+ * Headers-only xcframework (e.g. ReactNativeHeaders.xcframework) shipping a flattened clang
+ * module map under /Headers/module.modulemap. When present in the artifact, this is the
+ * modern modular replacement for the VFS overlay: consumers get `-fmodule-map-file` + `-I` to
+ * the headers dir instead of `-ivfsoverlay`.
+ */
+ moduleMapXcframework?: string;
/** Display name used in Package.swift */
displayName: string;
/** Key on ArtifactPaths for the flavor-specific base path */
@@ -518,6 +562,7 @@ const ARTIFACT_RELATIVE_PATHS: Record<
xcframeworkPath: 'React.xcframework',
includeDirectories: ['Headers', 'React_Core'],
vfsOverlayFile: 'React-VFS.yaml',
+ moduleMapXcframework: 'ReactNativeHeaders.xcframework',
displayName: 'React',
artifactKey: 'react',
cacheDirName: 'react',
@@ -1238,6 +1283,47 @@ function collectVfsAndHeaderMapFlags(
buildType
);
if (config) {
+ const lowerName = depName.toLowerCase();
+ const artifactConfig = ARTIFACT_RELATIVE_PATHS[lowerName];
+
+ // Modern modular React: when the artifact ships ReactNativeHeaders.xcframework, the lowercase
+ // `react/`, `yoga/`, β¦ namespaces are served by its flattened clang module map instead of a
+ // VFS overlay. Activate the module map (so the includes are modular) and add the headers dir
+ // to the search path (so they resolve). `` keeps resolving via the React.framework
+ // binary target. This replaces the entire -ivfsoverlay + -I roots dance below.
+ //
+ // The `continue` also skips `config.includeDirectories`, which on the legacy path contributes
+ // `-I React.xcframework/Headers` and `-I React.xcframework/React_Core`. Both are genuinely
+ // redundant here β verified by prebuilding ExpoModulesCore (C++ target, consumes the react/
+ // and yoga/ namespaces) and ExpoCrypto against 0.87.0-rc.3 artifacts: neither root is emitted
+ // and both flavors compose and verify. Keep them out when the VFS branch below is deleted;
+ // dropping the `continue` without replacing it would silently reintroduce them.
+ if (lowerName === 'react' && artifactConfig?.moduleMapXcframework) {
+ const isModular =
+ reactArtifactIsModular(config.debugBasePath) ||
+ reactArtifactIsModular(config.releaseBasePath);
+ if (isModular) {
+ const pushModularFlags = (flags: string[], basePath?: string) => {
+ if (!basePath) {
+ return;
+ }
+ const headersDir = findModularHeadersDir(
+ path.join(basePath, artifactConfig.moduleMapXcframework!)
+ );
+ if (headersDir) {
+ // clang requires the joined `-fmodule-map-file=` form (unlike `-ivfsoverlay`,
+ // it rejects the space-separated variant). `-I` adds the headers dir to the search
+ // path so the ``, `` includes resolve.
+ flags.push(`-fmodule-map-file=${path.join(headersDir, 'module.modulemap')}`);
+ flags.push('-I', headersDir);
+ }
+ };
+ pushModularFlags(debug, config.debugBasePath);
+ pushModularFlags(release, config.releaseBasePath);
+ continue;
+ }
+ }
+
// Add VFS overlay per configuration β each flavor has its own VFS YAML
// with absolute paths pointing to its specific artifact directory.
// Paths are emitted as absolute strings since Package.swift is a generated file.
diff --git a/tools/src/prebuilds/Utils.test.ts b/tools/src/prebuilds/Utils.test.ts
index 3c3cbe6da697b6..9b44217403418b 100644
--- a/tools/src/prebuilds/Utils.test.ts
+++ b/tools/src/prebuilds/Utils.test.ts
@@ -9,6 +9,7 @@ import { before, describe, it, afterEach } from 'node:test';
import {
getVersionsInfoAsync,
isNonInteractive,
+ resolveHermesVersion,
selectDistributedPackages,
setForceNonInteractive,
IOS_PREBUILD_PACKAGES,
@@ -117,6 +118,60 @@ describe('selectDistributedPackages', () => {
// consuming app links V1 Hermes (HERMES_V1_VERSION_NAME) β release crashes
// from struct-layout drift.
+// The selection itself is pure, so every combination is covered here regardless of which
+// React Native happens to be installed. The integration tests below can only exercise the
+// polarity the pinned RN's version.properties actually exposes β dual-engine RN skips the
+// single-engine case and vice versa β so they cannot stand in for these.
+describe('resolveHermesVersion', () => {
+ const DUAL = { HERMES_VERSION_NAME: '0.17.0', HERMES_V1_VERSION_NAME: '250829098.0.14' };
+ const SINGLE = { HERMES_VERSION_NAME: '0.18.0' };
+ const NO_TAGS = { classic: null, v1: null };
+
+ it('prefers the V1 key when RCT_HERMES_V1_ENABLED is unset', () => {
+ assert.equal(resolveHermesVersion(DUAL, NO_TAGS, {}), '250829098.0.14');
+ });
+
+ it('prefers the V1 key when RCT_HERMES_V1_ENABLED is "1"', () => {
+ assert.equal(
+ resolveHermesVersion(DUAL, NO_TAGS, { RCT_HERMES_V1_ENABLED: '1' }),
+ '250829098.0.14'
+ );
+ });
+
+ it('uses the classic key only when RCT_HERMES_V1_ENABLED is "0"', () => {
+ assert.equal(resolveHermesVersion(DUAL, NO_TAGS, { RCT_HERMES_V1_ENABLED: '0' }), '0.17.0');
+ });
+
+ it('falls back to the classic key when there is no V1 key (single-engine RN)', () => {
+ assert.equal(resolveHermesVersion(SINGLE, NO_TAGS, {}), '0.18.0');
+ });
+
+ it('prefers the matching tag over version.properties, normalizing it', () => {
+ assert.equal(
+ resolveHermesVersion(DUAL, { classic: 'hermes-2024', v1: 'v250829098.0.99' }, {}),
+ '250829098.0.99'
+ );
+ assert.equal(
+ resolveHermesVersion(
+ DUAL,
+ { classic: 'hermes-2024', v1: 'v250829098.0.99' },
+ {
+ RCT_HERMES_V1_ENABLED: '0',
+ }
+ ),
+ '2024'
+ );
+ });
+
+ it('ignores the V1 tag when falling back to the classic key', () => {
+ assert.equal(resolveHermesVersion(SINGLE, { classic: null, v1: 'v9.9.9' }, {}), '0.18.0');
+ });
+
+ it('throws when neither key resolves', () => {
+ assert.throws(() => resolveHermesVersion({}, NO_TAGS, {}), /could not be resolved/);
+ });
+});
+
describe('getVersionsInfoAsync β Hermes V1 polarity', () => {
let classicVersion;
let v1Version;
@@ -176,4 +231,17 @@ describe('getVersionsInfoAsync β Hermes V1 polarity', () => {
const { hermesVersion } = await getVersionsInfoAsync({});
assert.equal(hermesVersion, classicVersion);
});
+
+ // React Native 0.87+ collapsed the dual engines back to a single HERMES_VERSION_NAME.
+ // With V1 enabled by default, resolution must fall back to HERMES_VERSION_NAME instead
+ // of throwing because the (now absent) HERMES_V1_VERSION_NAME key can't be read.
+ it('falls back to HERMES_VERSION_NAME when there is no V1 key (single-engine RN)', async (t) => {
+ if (!(classicVersion && !v1Version)) {
+ t.skip('version.properties exposes a V1 key; single-engine fallback not applicable');
+ return;
+ }
+ delete process.env.RCT_HERMES_V1_ENABLED;
+ const { hermesVersion } = await getVersionsInfoAsync({});
+ assert.equal(hermesVersion, classicVersion);
+ });
});
diff --git a/tools/src/prebuilds/Utils.ts b/tools/src/prebuilds/Utils.ts
index 7cd52ccbef245e..4344c27da412b1 100644
--- a/tools/src/prebuilds/Utils.ts
+++ b/tools/src/prebuilds/Utils.ts
@@ -401,6 +401,48 @@ export const verifyAllPackagesAsync = async (
);
};
+const normalizeHermesVersion = (value: string) =>
+ value
+ .replace(/^hermes-?/i, '')
+ .replace(/^v/i, '')
+ .trim();
+
+/**
+ * Picks which Hermes engine's version to use, given the keys `version.properties` exposes.
+ *
+ * React Native 0.85.x shipped two engines side by side, keyed as HERMES_VERSION_NAME (classic)
+ * and HERMES_V1_VERSION_NAME (V1); the podspec defaulted to V1 unless RCT_HERMES_V1_ENABLED was
+ * set to "0". Newer RN (0.87+) collapsed back to a single HERMES_VERSION_NAME. So prefer the V1
+ * key only when it actually exists, and fall back to HERMES_VERSION_NAME otherwise β matching
+ * whatever hermes-engine.podspec reads. Getting the polarity wrong downloads classic Hermes
+ * headers while the app links V1, which crashes on struct-layout drift.
+ *
+ * Kept pure and exported so every combination is testable without a matching RN checkout.
+ *
+ * @param properties Parsed `sdks/hermes-engine/version.properties`.
+ * @param tags Contents of the `.hermesversion` / `.hermesv1version` tag files, if present.
+ * @param env Environment to read `RCT_HERMES_V1_ENABLED` from.
+ * @returns The normalized Hermes version, preferring the tag matching the selected engine.
+ */
+export const resolveHermesVersion = (
+ properties: Record,
+ tags: { classic?: string | null; v1?: string | null },
+ env: Record
+): string => {
+ const useV1 = env.RCT_HERMES_V1_ENABLED !== '0' && !!properties.HERMES_V1_VERSION_NAME;
+ const version = useV1 ? properties.HERMES_V1_VERSION_NAME : properties.HERMES_VERSION_NAME;
+
+ if (!version) {
+ throw new Error(
+ 'Hermes version could not be resolved from version.properties. ' +
+ 'Provide --hermes-version explicitly.'
+ );
+ }
+
+ const tag = useV1 ? tags.v1 : tags.classic;
+ return normalizeHermesVersion(tag ?? version);
+};
+
/**
* Tries to resolve versions for React Native and Hermes.
* @param options Options containing optional versions if none is found.
@@ -472,40 +514,24 @@ export const getVersionsInfoAsync = async (options: {
let classicTag: string | null = null;
let v1Tag: string | null = null;
+ // The .hermesversion / .hermesv1version tag files only exist in the React Native
+ // monorepo source β they are not shipped in published npm tarballs, so a miss here
+ // is expected and harmless (we fall back to the version from version.properties).
try {
classicTag = await readHermesTag(reactNativePath);
} catch (error) {
- logger.warn(`Could not read .hermesversion: ${String((error as Error).message || error)}`);
+ logger.verbose(`Could not read .hermesversion: ${String((error as Error).message || error)}`);
}
try {
v1Tag = await readHermesV1Tag(reactNativePath);
} catch (error) {
- logger.warn(`Could not read .hermesv1version: ${String((error as Error).message || error)}`);
- }
-
- const normalizeHermesVersion = (value: string) =>
- value
- .replace(/^hermes-?/i, '')
- .replace(/^v/i, '')
- .trim();
-
- // Matches hermes-engine.podspec polarity: V1 is the default, classic is
- // selected only when the consuming app explicitly sets the env var to "0".
- const isHermesV1Enabled = process.env.RCT_HERMES_V1_ENABLED !== '0';
- const version = isHermesV1Enabled
- ? properties.HERMES_V1_VERSION_NAME
- : properties.HERMES_VERSION_NAME;
- const tag = isHermesV1Enabled ? v1Tag : classicTag;
-
- if (!version) {
- throw new Error(
- 'Hermes version could not be resolved from version.properties. ' +
- 'Provide --hermes-version explicitly.'
+ logger.verbose(
+ `Could not read .hermesv1version: ${String((error as Error).message || error)}`
);
}
- return normalizeHermesVersion(tag ?? version);
+ return resolveHermesVersion(properties, { classic: classicTag, v1: v1Tag }, process.env);
};
const reactNativeVersion =