概要
Expo SDK 56 は、iOS 向けに XCFramework としてプリコンパイルされた Expo モジュールのサポートを導入しました。これにより、多くのプロジェクトでネイティブビルドのオーバーヘッドが大幅に削減されます。プリコンパイル済みの XCFramework はローカル環境と EAS Build の両方でデフォルトで有効になっており、特別な設定は不要です。
この変更は表面的にはビルドの高速化をもたらしますが、裏側では Expo エコシステムにおけるより大きな移行の始まりでもあります。つまり CocoaPods から Apple のモダンな依存管理・ネイティブビルドシステムである Swift Package Manager(SPM)へ段階的に移行していくという方向です。本記事では以下を解説します:
- なぜこの移行を行うのか
- プリコンパイルされた Expo モジュールがどのように動作するか
- 関連するアーキテクチャ上の課題
- そして iOS 上の Expo にとって将来に何をもたらすか
アプリを変更せずに得られる高速化
従来、iOS 上のほとんどの Expo および React Native ライブラリはアプリプロジェクト内でソースからコンパイルされてきました。つまり、ローカルビルドや EAS Build のたびに以下が毎回再コンパイルされます:
- React Native
- Expo モジュール
- ネイティブコードを含むサードパーティライブラリ
SDK 56 では多くの Expo モジュールが npm 経由でプリコンパイルされた XCFramework として配布され、アプリのコンパイル時に毎回再ビルドされるのではなく直接リンクされます。実際には、ネイティブコンパイルのステップが減り、ローカルでの反復が速くなり、EAS Build が高速化され、ネイティブビルド環境の再現性も向上します。最良なのは、このプロセスが自動で行われ、移行手順が不要である点です。
💡 XCFramework とは?
XCFramework は Apple が定めたプリコンパイル済みネイティブライブラリの配布形式です。ソースコードを配布して各アプリがローカルでコンパイルする代わりに、iOS デバイス、シミュレータ、複数アーキテクチャ向けに既にコンパイルされたバイナリ成果物として配布できます。各 XCFramework は特定のプラットフォームとアーキテクチャ向けに独立したフレームワーク "スライス" を含みます。React Native は内部的に既に XCFramework を利用しており、Expo SDK 56 はこのモデルを Expo エコシステム全体に拡張しています。
プリコンパイル済み XCFramework が重要な理由
この取り組みは、React Native エコシステムにおける長期的な 2 つの大きな問題を解決します。
-
CocoaPods はレガシーになりつつある
Expo、React Native、および多くの RN ライブラリは現在 CocoaPods に大きく依存しています。CocoaPods は依存解決、ネイティブの自動リンク、プロジェクト統合、ビルドのオーケストレーションを歴史的に担ってきました。しかし Ruby ベースの古いシステムであり、最終的に非推奨化され、2026 年 12 月には読み取り専用状態になる予定です。
一方で Swift Package Manager は Apple の標準ツールとして位置づけられ、ネイティブ依存、ビルド、パッケージ配布、ビルド拡張性を担います。React Native 自体も内部で SPM サポートへ移行を始めており、React Native の一部をプリコンパイル済み XCFramework として配布しています。Expo SDK 56 はこの方向性を踏襲します。
-
ネイティブビルドはコストが大きい
アプリが大きくなるに連れて、ネイティブのビルド時間は増加します。これは CI 環境、EAS Build、大きなモノレポで多くのネイティブ依存を抱える場合に特に顕著です。プリコンパイル済み XCFramework によって、その多くの作業をパイプラインの早い段階に移すことができます。フレームワークは一度コンパイルしてパッケージ化され、ビルド間で再利用されます。これにより繰り返し発生するネイティブコンパイルの作業が劇的に減少します。
実際は思ったより難しい理由
React Native と Expo は本来 CocoaPods とソースベースのコンパイルを前提に構築されてきました。その環境は非常に寛容で、ヘッダはグローバルに利用可能、ソースファイルはほとんど何でもインポートでき、pod ターゲットはゆるく分離され、モジュール境界は曖昧になりがちです。
しかし XCFramework はより厳格なルールを課します。すべてのフレームワークは完全にモジュール化され、自己完結しており、モジュール境界外の実装詳細から隔離されている必要があります。CocoaPods では問題のなかった多くの前提が、配布可能な XCFramework を構築する際に破綻します。
ネイティブアーキテクチャを一から書き直すことは現実的ではないため、我々は互換性レイヤーとビルドインフラを段階的に導入することに集中しました。
ビルド時間への影響
以下の数字は Apple M4 Max(64 GB)上で、既定の Expo アプリのクリーンな iOS ビルドを行い、プリコンパイル層を順次有効化した際の測定に基づきます。SDK 55 ではプリコンパイル済みの React Native バイナリを使用した改善が見られました。SDK 56 ではさらにプリコンパイルされた Expo Modules やサードパーティモジュールの追加効果が確認できます。
- 大きなプロジェクトにおける利益はカバレッジに依存します。React Native と Expo モジュールは常にプリコンパイルされますが、サードパーティのライブラリは最も広く使われているものだけがプリコンパイルされる傾向にあります。したがって、あまり一般的でないネイティブ依存に頼るプロジェクトでは、それらがソースからコンパイルされ続け、全体的な短縮効果は小さくなる可能性があります。
expo-modules-core を Swift Package Manager に対応させる
最初の大きなステップは expo-modules-core の適応でした。ほぼすべての Expo モジュールがこれに依存しているため、これがモジュール化された XCFramework としてビルドできなければ他は動きません。これにはいくつかのアーキテクチャ上の問題解決が必要でした。
不正なヘッダエクスポートの削除
歴史的に、一部の Expo Modules Core の公開ヘッダは React Native のヘッダを直接公開していました: #import <React/RCTView.h>
これは CocoaPods では機能します(コンパイル時にすべてのヘッダがグローバルに利用可能)が、フレームワークのインターフェースでは許可されません。
💡 フレームワークは別のフレームワークのヘッダを公開することはできない(その依存側が正しくモジュール化されている場合を除く)
多くの問題は公開インターフェースのリファクタリング、React Native 内部の隔離、非モジュール依存がエクスポートインターフェースに漏れないような API 再構成によって解決しました。
Swift ↔ Objective-C のサイクルの解消
SPM は混在言語ターゲットに関して CocoaPods より厳格です。Expo Modules Core は歴史的に Objective-C が Swift 型を参照し、同時に Swift が Objective-C 型を参照するような循環依存を含んでいました。SPM はターゲット間に明確な依存方向を要求します。
これを解決するために、我々は新しいインターフェース抽象化を導入し、実装レイヤーを分離し、内部 API をリファクタリングしました。いくつかのケースでは、コンパイル時の不正な依存を導入せずに Swift 実装を動的に呼び出すために Objective-C ランタイムのリフレクションを使用しています。
SPM 向けのソースツリー分離
Swift Package Manager はソースの所有権に関しても厳格で、同一パッケージグラフ内で同一ソースファイルを複数のターゲットに所属させることはできません。Expo のリポジトリ構造はその想定で設計されていませんでした。
リポジトリを恒久的に再編成する代わりに、ビルド時にシンボリックリンク、生成フォルダ、ビルド時のソース分離を用いて一時的な孤立ソース構造を生成します。これにより既存のリポジトリ配置を維持しつつ SPM の分離要件を満たします。
React Native と Virtual File System(VFS)オーバーレイ
もう一つの大きな課題は React Native のヘッダ構造です。React Native の現行の XCFramework サポートは依然として CocoaPods によって生成される旧来のヘッダレイアウトに強く依存しています。そのレイアウトは SPM ビルドでは自然には存在しません。
このギャップを埋めるために、React Native 内で Clang Virtual File System(VFS)オーバーレイのサポートを追加しました。VFS オーバーレイによりコンパイラは実際のファイルシステムとは異なる仮想ヘッダレイアウトを“見る”ことができます。これによって既存の include パスを維持し、大規模なソースリファクタを回避しつつ、コンパイラに対してモジュール化された構造を提示できます。これは大規模なレガシーなネイティブコードベースを配布可能なモジュール化フレームワークへ近代化する際によく使われる手法です。
Package.swift ファイルの自動生成
Swift Package Manager は Package.swift マニフェストでターゲット、依存、プラットフォーム、ビルド設定を定義します。これらを Expo パッケージ群で手動管理するとすぐに困難でエラーが起きやすくなります。
この課題に対処するために、expo-tools に Package.swift マニフェスト、孤立ソース構造、依存グラフ、XCFramework パッケージング手順を自動生成する新しいツールチェーンを追加しました。このインフラは CI 上で完全に動作するよう設計されており、最終的には大規模なプリコンパイル済みパッケージ配布を支える予定です。
CocoaPods と SPM の共存サポート
この移行には時間がかかります。React Native エコシステムはまだ CocoaPods に大きく依存しており、多くのライブラリがまだ SPM に対応していません。そこで SDK 56 は置換ではなく共存に焦点を当てています。
Expo モジュールは現在、ソースからビルドするかプリコンパイル済み XCFramework を消費するかを切り替えられます。これにより既存のアプリは従来通り動作し、CocoaPods のサポートを維持しつつ、エコシステムを徐々にモダン化できます。
必要に応じてプリコンパイル済みモジュールを無効化することも可能です: EXPO_USE_PRECOMPILED_MODULES=0
長期的には、Expo の自動リンク、ネイティブ統合、ビルドツール群の多くが Swift Package Manager 自体に移行していくことが期待されます。
より広範なモダナイゼーションの一部
プリコンパイル済み XCFramework は、Inline native modules、React Native の継続的な近代化作業、および将来のネイティブツールリングのためのインフラ改善といった SDK 56 全体で進められている広範なモダナイゼーション努力の一部です。これらの変更により、Expo はより高速なネイティブビルド、よりクリーンなモジュラーアーキテクチャ、Apple のモダンな開発エコシステムとの深い統合に近づきます。
今後の見通し
現在の重点は互換性の安定化、パッケージカバレッジの拡大、ビルド性能改善の検証、そして React Native 側との上流での協力継続です。これは iOS 側で我々が取り組んできた最大規模のインフラ移行の一つですが、よりスケーラブルな将来を切り開きます:ビルドの高速化、より良いツール、明確なネイティブ境界、そして最終的には CocoaPods のない世界へ。
SDK 56 はその移行の始まりです。