プリコンパイル済み XCFramework で iOS ビルドを高速化する
Expo SDK 56 は iOS でデフォルトでプリコンパイル済み XCFrameworks を有効にし、ビルドコストを大幅に削減するとともに、CocoaPods から SwiftPM への移行を開始します。
日本語
コピー

Expo SDK 56 以降、iOS では Expo モジュールを XCFrameworks としてプリコンパイルして利用できるようになりました。多くのプロジェクトでネイティブビルドのコストを大幅に削減できます。
プリコンパイルされた XCFrameworks はローカルでも EAS Build でもデフォルトで有効になり、設定は一切不要です。
その裏側では、Expo エコシステムのより大きな転換が始まっています。CocoaPods から Swift Package Manager(SwiftPM)への移行です。SwiftPM は Apple が依存関係管理とネイティブビルドのために用意した現代的な仕組みです。
この記事では以下を掘り下げます。
-
なぜこの移行に踏み切るのか
-
プリコンパイルされた Expo モジュールはどう動くのか
-
どのようなアーキテクチャ上の課題があるのか
-
そしてこれが iOS における Expo の未来に何を開くのか
アプリを変えずにビルドを速くする
これまで iOS では、Expo と React Native のライブラリはすべてアプリのプロジェクト内でソースからコンパイルされていました。つまりローカルビルドでも EAS Build でも、毎回ゼロから以下を再コンパイルしていたわけです。
-
React Native
-
Expo モジュール
-
ネイティブコードを含むサードパーティライブラリ
https://expo.dev/changelog/sdk-56 のプリコンパイル済み XCFrameworks がこれを変えます。
現在、多くの Expo モジュールは npm を通じてプリコンパイル済み XCFrameworks として配布され、アプリをビルドするたびに再ビルドするのではなく直接リンクできます。実際の効果はネイティブのコンパイル手順が減り、ローカルのイテレーションが速くなり、EAS Build も速くなり、ネイティブビルド環境の再現性も上がることです。
何より楽なのは、これがすべて自動で起きることです。移行作業は要りません。
💡 XCFrameworks とは? XCFramework は Apple がプリコンパイル済みネイティブライブラリを配布するためのフォーマットです。アプリごとにローカルでコンパイルするソースを配る代わりに、iOS デバイス、シミュレータ、複数のアーキテクチャ向けにすでにコンパイル済みのバイナリ成果物としてフレームワークを配布できます。各 XCFramework には、特定のプラットフォームとアーキテクチャ向けのフレームワークの「スライス」が個別に含まれます。React Native は内部ですでに XCFrameworks を使っており、Expo SDK 56 はこのパターンを Expo エコシステム全体に広げます。
プリコンパイルされた XCFramework が重要な理由
この取り組みは、React Native エコシステムに長く存在する 2 つの大きな問題を解決します。
1. CocoaPods はレガシーインフラになりつつある
Expo、React Native、そしてほとんどの React Native ライブラリは現在、CocoaPods に大きく依存しています。
CocoaPods はこれまで依存関係の解決、ネイティブの自動リンク、プロジェクトへの統合、ビルドのオーケストレーションを担ってきました。しかし Ruby ベースの古いシステムであり、いずれ廃止され、2026 年 12 月には読み取り専用になります。
Swift Package Manager は、ネイティブの依存関係、ビルド、パッケージ配布、ビルドの拡張性における Apple の標準ツールになりました。
React Native 自身も内部で SwiftPM サポートへの移行を始めており、React Native の一部をプリコンパイル済み XCFramework として配布する動きも含まれます。
Expo SDK 56 はこの流れに沿って進むものです。
2. ネイティブビルドはコストが高い
アプリが大きくなるにつれて、ネイティブビルド時間は伸び続けます。
これは特に CI 環境、EAS Build、そしてネイティブ依存の多い大規模な monorepo で顕著です。プリコンパイルされた XCFramework を使えば、その作業の大部分をパイプラインのより早い段階に前倒しできます。Framework は一度だけコンパイルされ、パッケージ化されて各ビルドで再利用されます。
これにより、重複するネイティブコンパイル作業が大幅に減ります。
なぜこれが聞こえるほど簡単ではないのか
React Native と Expo はもともと CocoaPods とソースベースのコンパイルを前提に作られました。
その環境は極めて寛容でした。ヘッダーはグローバルに利用でき、ソースファイルはほぼ何でもインポートでき、pod target 間の分離は緩く、モジュールの境界も曖昧でした。
XCFramework が課すルールはもっと厳格です。
各 framework は、完全にモジュール化され、自己完結し、モジュール境界の外にある実装の詳細から切り離されている必要があります。
CocoaPods の下ではまったく問題なかった多くの前提が、配布可能な XCFramework をビルドしようとした途端に通用しなくなります。
ネイティブアーキテクチャ全体をゼロから書き直すのは現実的ではないので、私たちは漸進的な互換レイヤーとビルドインフラに焦点を移しました。
ビルド時間への影響
以下の数値は Apple M4 Max(64 GB メモリ)で、素の Expo アプリのクリーンな iOS ビルドを対象に、プリコンパイル済み XCFramework を段階的に有効にして測定したものです。
SDK 55 では、プリコンパイル済み React Native バイナリによる改善がすでに見えていました。次の表は、プリコンパイルされた Expo Modules とサードパーティモジュールがデフォルトの Expo プロジェクトにもたらす追加の効果を示しています。
| ビルド構成 | 前の行との比較 | 全ソースビルドとの比較 |
|---|---|---|
| すべてソースからビルド | — | — |
| + React Native コア | ~44% | ~44% |
| + Expo モジュールをプリコンパイル | ~10% | ~50% |
| + サードパーティライブラリをプリコンパイル | ~30% | ~65% |
プロジェクトが大きくなるほど、効果はカバレッジ次第になる。React Native と Expo のモジュールは常にプリコンパイルされるが、サードパーティライブラリは最もよく使われるものだけがプリコンパイルされる。そのため、あまり使われていないネイティブ依存に頼っているプロジェクトでは、それらを依然としてソースからビルドする必要があり、ビルド時間の短縮幅は小さくなる可能性がある。
Expo Modules Core を Swift Package Manager に対応させる
最初の重要なステップは expo-modules-core の作り直しだった。
ほぼすべての Expo モジュールがこれに依存しているため、依存グラフの根元に位置する。これがモジュール化された XCFramework としてビルドできなければ、他のすべては成り立たない。
そのためには、いくつかのアーキテクチャ上の問題を解決する必要があった。
不正なヘッダー公開の除去
これまで Expo Modules Core の公開ヘッダーの一部は、React Native のヘッダーを直接露出させていた:
#import <React/RCTView.h>
CocoaPods では、ビルド中にすべてのヘッダーがグローバルに可視であるため、これは問題にならなかった。
Framework のインターフェースではそれが許されない。
💡依存自体が正しくモジュール化されていない限り、ある framework が別の framework のヘッダーを公開することはできない。
これらの問題の多くは、公開インターフェースをリファクタリングし、React Native の内部実装を分離し、API 構造を調整することで解決した。非モジュール化された依存がエクスポートされたインターフェースに漏れ出さなくなった。
Swift ↔ Objective-C の循環依存を断ち切る
混在言語の target では、Swift Package Manager は CocoaPods よりもはるかに厳格だ。
Expo Modules Core には長らく循環依存があった。Objective-C が Swift の型を参照し、Swift が Objective-C の型を参照していた。
SwiftPM は target 間に明確な依存の向きを要求する。そこで新しいインターフェース抽象を導入し、実装層を分割し、いくつかの内部 API をリファクタリングした。
少数のケースでは、Objective-C ランタイムのリフレクションを使って Swift の実装を動的に呼び出すことにより、不正なコンパイル時依存を持ち込まずに済ませた。
SwiftPM 向けにソースツリーを分割する
Swift Package Manager はソースの帰属にも厳格だ。同じソースファイルを、同一の package graph 内の複数の target に属させることはできない。
Expo のリポジトリ構造は、そもそもこの前提で設計されていなかった。リポジトリを恒久的に再編成するのではなく、ビルド中にシンボリックリンク、生成ディレクトリ、ビルド時のソース分離によって、一時的な隔離ソース構造を生成している。
これにより既存のリポジトリレイアウトを保ったまま、SwiftPM の隔離要件を満たしている。
React Native と Virtual File System overlay
もう一つの大きな難題は React Native のヘッダー構造だった。
React Native の現在の XCFramework 対応は、CocoaPods が生成する旧式のヘッダーレイアウトに依然として強く依存している。このレイアウトは Swift Package Manager のビルドには自然には存在しない。
このギャップを埋めるため、React Native 内部に Clang Virtual File System(VFS)overlay のサポートを追加することにした。
VFS overlay を使うと、コンパイラは物理的なファイルシステム構造とは異なる仮想的なヘッダーレイアウトを「見る」ことができる。
これにより既存の include パスを維持し、ソースの大規模なリファクタリングを避け、React Native のソースツリーを物理的に再編成することなく、モジュール化された構造をコンパイラに提示できる。
巨大なレガシーネイティブコードベースを配布可能なモジュール型 framework に近代化する際の定石だ。
Package.swift ファイルの自動生成
Swift Package Manager は Package.swift manifest で target、依存、プラットフォーム、ビルド設定を定義する。Expo の各 package でこれらを手作業で管理するのは、すぐに困難でエラーを招く作業になる。
そこで expo-tools に新しいツールチェーンを導入し、Package.swift manifest、隔離されたソース構造、依存グラフ、XCFramework のパッケージング手順を自動生成できるようにした。
この基盤は完全に CI 上で動作するよう設計されており、最終的には大規模なプリコンパイル済みパッケージ配布を支える。
CocoaPods と SwiftPM の併存
この移行には時間がかかる。
React Native エコシステムは依然として CocoaPods に強く依存しており、多くのライブラリは Swift Package Manager にまだ対応していない。そのため SDK 56 は置き換えではなく併存に重点を置いている。
Expo モジュールは現在、ソースからのビルドとプリコンパイル済み XCFramework の直接利用を切り替えられる。
これにより既存のアプリは引き続き動作し、CocoaPods もサポートされ続ける一方で、エコシステムは基盤から徐々に近代化していく。
必要であれば、プリコンパイル済みモジュールを無効にすることもできる:
EXPO_USE_PRECOMPILED_MODULES=0
長期的には、Expo の autolinking、ネイティブ統合、ビルドツールの多くが Swift Package Manager 自体へ移行していくと見ている。
より大規模なモダナイゼーションの一環として
XCFramework のプリコンパイルは Expo SDK 56 におけるモダナイゼーション全体の一部であり、同時期にはインラインネイティブモジュールの導入、React Native の継続的なモダナイゼーション、そして将来のネイティブツールチェーンを見据えたインフラ改善も進められている。
これらの変更が相まって、Expo はより高速なネイティブビルド、よりクリーンなモジュールアーキテクチャ、そして Apple の現代的な開発エコシステムとのより深い統合に向けて前進している。
今後の見通し
現在の焦点は、互換性の安定化、パッケージカバレッジの拡大、ビルド性能向上の検証、そして React Native 上流との協働の継続にある。
これは Expo の iOS 側でこれまでに行われたインフラ移行の中で、最大規模のものの一つだ。
だが、これによって Apple プラットフォーム上の React Native 開発には、よりスケールしやすい未来が開ける。より高速なビルド、より優れたツール、より明確なネイティブの境界、そして最終的には CocoaPods が不要になる世界へと向かう。
SDK 56 はその移行の出発点だ。