Repository navigation
Experiment: Expo SDK 57 example with precompiled RN XCFrameworks + CI build timing - #324
Conversation
…d-timing job Expo SDK 57 (RN 0.86) example modeled on expo55-example; iOS builds use Expo's precompiled React Native XCFrameworks + precompiled Expo Modules by default. The uncached build-expo57-ios CI job measures cold pod install + xcodebuild times for comparison against existing build-ios runs. The babel config converts builder-bob's string include matcher to a function: SDK 57's @expo/metro-config loads the config without a filename when computing its transform cache key, and Babel rejects string/RegExp matchers there.
|
First timing results from run 28862816990 (both jobs cold, same runner pool, macos-15 / Xcode 26.3):
Historical cold Reading: the expo57 example compiles a much larger dependency set from source (reanimated, worklets, screens, gesture-handler, router native bits — third-party precompiled pods are EAS-only) plus RNRive/Rive, and still comes in slightly under the bare example. The precompiled RN core + Expo modules roughly offset the entire extra Expo/third-party surface. A few more runs will tighten the numbers. |
|
Second Standings across all cold samples: expo57 16.9 / 22.2 min (n=2, mean ~19.5) vs bare example 17.6–31.3 min (n=9, median ~27). Both expo57 samples sit below the bare-example median even though the expo app compiles a much larger third-party set from source. |
…ld-expo57-ios Sets EXPO_PRECOMPILED_MODULES_BASE_URL to the public bucket EAS Build uses, so reanimated and worklets install as prebuilt XCFrameworks instead of compiling from source. Unofficial endpoint; on 404 or outage pod install falls back to source builds. Verified locally: both download and link, app boots and renders the Quick Start demo.
|
With the EAS bucket enabled (run 28877621406): pod install 115s, xcodebuild 642s (10.7 min) — down from 16.9/22.2 min without third-party prebuilds. CI log confirms RNReanimated and RNWorklets installed as prebuilt XCFrameworks; screens/safe-area-context/async-storage 404'd for this version tuple and built from source as expected. Cold-build summary so far: bare example median ~27 min (n=9) → expo57 precompiled RN core ~17–22 min (n=2) → expo57 + EAS third-party bucket ~10.7 min (n=1). |
… feat/expo57-prebuilt-xcframeworks
Adds harness wiring to expo57-example (rn-harness config with the expo-router entry, jest roots pointing at the shared example tests, harness babel preset) plus a test-harness-expo57-ios CI job that builds the app with precompiled XCFrameworks + the EAS prebuilt bucket. Two fixes were needed to run the harness against an Expo app: 1. Out-of-root shared assets (../example/assets) 404 through the harness's plain Metro server because URL normalization strips the leading '..' from classic asset paths; expo57's metro config now uses the unstable_path publicPath form, which Metro's server handles. 2. The harness entry-point resolver only swaps the app entry for the harness runtime when the request textually matches the configured entryPoint and originates from the project root. Expo apps request the entry via Expo's server-root-relative URL (or the .expo/.virtual-metro-entry alias), so the swap never fired and the real app booted instead of the harness host — every render() then timed out. Patched @react-native-harness/bundler-metro (yarn patch) to accept monorepo server-root origins and the Expo virtual entry. All 4 shared suites pass locally against the expo57 app.
|
test-harness-expo57-ios passed on its first CI run (run 28919441701): the full shared harness suite (4 suites) runs green against the Expo 57 app. Timings: expo prebuild 6s, pod install 93s (reanimated + worklets prebuilt from the EAS bucket), xcodebuild 8m05s, harness tests 4m18s — 21m13s total, uncached. For scale: the bare test-harness-ios/-legacy jobs in the same run (cold, same runner pool) were still building/running past the 40-minute mark when this one finished. Two fixes were needed to run the harness against an Expo app (details in commit b3957da): the unstable_path publicPath form for out-of-root shared assets, and a yarn patch on @react-native-harness/bundler-metro so the entry-point swap recognizes Expo's server-root-relative / virtual-entry requests — worth upstreaming to Callstack. |
The gallery only needs plain View/Text; removes components/, hooks/, constants/, the @/ path alias, and the unused expo-symbols and @expo/vector-icons deps. Also fixes the TS 6 baseUrl deprecation and the pre-existing boilerplate typecheck errors — the workspace typecheck is clean now.
Replaces the bare-example build-ios job and the experimental build-expo57-ios job with a single build-ios job that builds expo57-example (precompiled RN XCFrameworks + EAS prebuilt pods) with caching: an exact-key lookup-only probe short-circuits the whole job when inputs are unchanged, a Pods cache covers pod install, and a prefix-fallback build-dir cache makes changed-input builds incremental. The bare example is still built and exercised by the test-harness-ios jobs.
Renames test-harness-expo57-ios to test-harness-ios and switches every harness job (ios/android, experimental/legacy) to the Expo 57 example, with prebuild + caching (iOS build cache shared with build-ios; legacy variants use separate cache keys). Adds the android runner to the expo57 harness config. Verified locally against expo57 on both backends and both platforms: all 4 shared suites pass in each combination. Gotcha: the prebuild-generated android gradle.properties has no trailing newline, so the legacy flag must be appended with a leading newline or it glues onto the previous property and silently does nothing.
… feat/expo57-prebuilt-xcframeworks # Conflicts: # .github/workflows/ci.yml
|
Final matrix after consolidating all harness jobs onto expo57-example (run 28925999512, all cold — cache keys changed):
All four passed on the first harness attempt. Notes: Android cold builds are ~4.5 min slower than the bare example's (~7.5 min) — the added Expo surface costs even with prebuilt Expo modules — but the test phase is faster and stabler (3m vs 11m with retries), so totals are a wash, and warm-cache runs skip the build entirely. Android already gets prebuilt RN core (Maven, since 0.71) and prebuilt Expo modules (SDK 53+ default, 15/18 modules as Maven artifacts in our build); there is no Android equivalent of the EAS third-party bucket. |
build-android now builds expo57-example with the same cache key as test-harness-android (exact-key short-circuit + cache warmer), mirroring build-ios. The bare RN example moves to a new bare-example workflow: weekly schedule + manual dispatch + PRs touching example/**, building iOS and Android on both Rive backends (build-only).
Replaces the default create-expo-app icons with an icon set generated from the official Rive R mark (GitHub org avatar, supersampled and re-thresholded for clean edges at 1024px): app icon, light/dark splash icons, Android adaptive foreground/background/monochrome, and favicon. Also removes the unused React logo assets.
…ebug logs Mitigation for ios-harness-startup-hang (#328): the harness sometimes wedges after Metro starts but before launching the app, and plain process cleanup between retries leaves the wedged CoreSimulator in place, so all attempts hang identically. nuke_harness now kills CoreSimulatorService and reboots the simulator. HARNESS_DEBUG=1 will show the exact hang point if it recurs.
… feat/expo57-prebuilt-xcframeworks # Conflicts: # .github/workflows/ci.yml
… assets The icon is now a proper tile: dark gradient background with the React atom orbits in React blue and the Rive R as the nucleus (Android adaptive foreground/background and favicon follow the same design; monochrome and splash stay plain R for legibility). Also removes expo57-example/assets/rive/ — nothing referenced those copies; all shared pages and tests load .riv files from example/assets/rive through Metro, and the harness suite passes after the removal.
Keeps the full React atom orbits but knocks them out along the (dilated) R silhouette instead of a circular hole, so the R can be much larger without erasing the orbit crossings. Verified legible down to 60px and on the Android launcher.
Asset-only changes (icons, fonts, the expo-asset-embedded .riv) are baked into the app binaries but were not part of the cache keys, so such pushes reused stale cached builds.
Adds an
expo57-exampleworkspace (Expo SDK 57 / RN 0.86) and makes it the default iOS build on CI, using Expo's precompiled React Native XCFrameworks (https://expo.dev/blog/faster-ios-builds-with-precompiled-xcframeworks) plus the EAS prebuilt third-party bucket (reanimated/worklets; unofficial endpoint with automatic source fallback).build-iosnow builds expo57-example with caching (exact-key short-circuit, Pods cache, incremental build-dir cache). Cold xcodebuild is ~8 min vs ~26 min median for the bare example; timing details in the comments below.test-harness-expo57-iosruns the shared harness suite against the Expo app. This needed theunstable_pathasset publicPath (out-of-root shared assets) and a yarn patch on@react-native-harness/bundler-metro— its entry-point swap didn't recognize Expo's server-root-relative/virtual-entry requests, so the real app booted instead of the harness host (worth upstreaming).test-harness-ios/-legacyjobs, which build it themselves.Verified locally (app renders, Quick Start data binding works, all 4 harness suites pass) and on CI (all jobs green).