Skip to content

[pull] main from expo:main#867

Merged
pull[bot] merged 9 commits into
code:mainfrom
expo:main
May 15, 2026
Merged

[pull] main from expo:main#867
pull[bot] merged 9 commits into
code:mainfrom
expo:main

Conversation

@pull

@pull pull Bot commented May 15, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

Kudo and others added 9 commits May 15, 2026 14:15
## Why 

After the precompiled XCFramework feature shipped, pod install times in
EAS Build regressed. Investigation pointed at
Expo::PrecompiledModules.resolve_prebuilt_status — and the File.exist?
calls inside resolve_own_prebuilt_info/resolve_prebuilt_tarball — being
invoked many times per pod from ~10 different call sites.

With ~28 prebuilt pods × ~10 traversal sites, every install repeats
hundreds of stat calls plus a recursive dependency walk per query.


## How

Memoized resolve_prebuilt_status by pod_name. The result is
deterministic for the duration of one pod install:

- build_from_source? patterns are frozen after
AutolinkingManager#initialize.
- pod_lookup_map[pod_name] is built once.
- resolve_prebuilt_tarball resolves to the same path on every subsequent
call (downloads happen lazily but the returned path is stable).

The recursive call passes a non-empty visiting set for cycle detection;
the cache only kicks in on top-level calls (visiting.empty?), so the
cycle-break special case can't leak into the cache.

# Test Plan

Three back-to-back runs of ./apps/bare-expo/ios/run-pod-install.sh 1
with warm CocoaPods caches:

| Sample | Baseline (no cache) | Cached |
| ------ | ------------------- | ------ |
| 1      | 54.25s              | 48.16s |
| 2      | 51.67s              | 45.78s |
| 3      | 49.38s              | 38.65s |

# Checklist

- [x] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)

---------

Co-authored-by: Expo Bot <34669131+expo-bot@users.noreply.github.com>
# Why

Raw arguments are passed here from parsed input. While this isn't
inherently insecure on trusted inputs, passing to `spawnAsync` makes
this safe.

# How

- Replace `execSync` with `spawnAsync` calls in `SPMBuild`

# Test Plan

**Note:** Speculative/untested

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
-->

- [ ] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
## Why

Prebuild dependency expansion only checks whether a dependency
xcframework existed - this works well in a CI environment.

If a local dependency such as `@expo/ui` had changed after its
xcframework was built, dependent packages like expo-widgets could still
compile against the stale binary and fail.

## How

Compare each local dependency product's newest package input mtime
against the requested flavor's xcframework mtime before deciding to skip
it. Auto-add the dependency when the requested output is missing or
stale, and include fresh dependencies when --clean is requested.

Move versioned/non-versioned xcframework lookup into Frameworks so path
discovery lives with the rest of the framework path helpers.

## Test-plan

✅ pnpm --dir tools test

```
touch packages/expo-ui/ios/ChartView.swift
et prebuild -f Debug --skip-artifacts --skip-generate --skip-build --skip-compose --skip-verify -j 1 expo-widgets
```

Expo/UI should be included in the list of targets to build 👆
## Why

Prebuild dependency expansion resolves local dependencies by package
name, including scoped names like `@expo/ui`. Scoped workspace packages
whose directory differs from their package name may not be reachable
through the direct package path, so callers had to prewarm the package
list before getPackageByName could find them.

This PR supersedes #45787

## How

Make `getPackageByName` populate the cached workspace package list
synchronously on direct lookup miss, then resolve from that list. Share
`package.json` glob patterns and package construction between the async
and sync package scans.

Add a test that simulates `@expo/ui/package.json` being unavailable
through direct resolution and verifies `getPackageByName('@expo/ui')`
still resolves from the workspace list.

## Test-plan

`pnpm --dir tools test`
# Summary

Three fixes for `expo-widgets` Live Activity rendering on iOS:

### 1. Bug: `widgetEnvironment` not passed to render call (Utils.swift)

`getLiveActivityNodes` builds a `widgetEnvironment` dict with an
injected `timestamp` field, but then passes the original `environment`
parameter to `__expoWidgetRender` instead. This means the `date`
environment value is never set for Live Activity renders.

**Before:** `withArguments: [propsDict, environment]`
**After:** `withArguments: [propsDict, widgetEnvironment]`

### 2. Performance: redundant JS evaluations per Dynamic Island update
(WidgetLiveActivity.swift)

`LiveActivitySectionView` calls `getLiveActivityNodes` independently for
each Dynamic Island section. With 7 sections (4 expanded + compact
leading/trailing + minimal), this means 7 redundant JS context creations
and layout evaluations per single update.

Fix: hoist the `getLiveActivityNodes` call into the `dynamicIsland`
closure so it runs once, and pass the pre-computed `nodes` dictionary to
a lightweight `LiveActivityNodeView`.

### 3. Performance: JSContext recreated on every call
(WidgetContext.swift)

`createWidgetContext` creates a new `JSContext`, reads
`ExpoWidgets.bundle` from disk, and evaluates the JS bundle on every
invocation. For widgets and Live Activities that render frequently, this
is wasteful.

Fix: cache `JSContext` instances keyed by the layout string. Repeated
renders with the same layout reuse the cached context.

## Test Plan

- [ ] Live Activity renders correctly with Dynamic Island (expanded,
compact, minimal)
- [ ] `date` environment value is available in Live Activity layout
renders
- [ ] Widget timeline rendering still works correctly
- [ ] No regression in banner view rendering

---------

Co-authored-by: Jakub Grzywacz <kontakt@jakubgrzywacz.pl>
@pull pull Bot locked and limited conversation to collaborators May 15, 2026
@pull pull Bot added the ⤵️ pull label May 15, 2026
@pull pull Bot merged commit cdab101 into code:main May 15, 2026
3 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants