📱

React Nativeでネイティブコードを使う (CNGとExpo Prebuild編)

2024/12/15に公開

この記事はReact Native 全部俺 Advent Calendar 15目の記事です。

https://adventar.org/calendars/10741

このアドベントカレンダーについて

このアドベントカレンダーは @itome が全て書いています。

基本的にReact NativeおよびExpoの公式ドキュメントとソースコードを参照しながら書いていきます。誤植や編集依頼はXにお願いします。

React Nativeでネイティブコードを使う (CNGとExpo Prebuild編)

React NativeはJavaScriptでモバイルアプリを開発できるフレームワークとして人気がありますが、実際の開発では完全にネイティブコードから解放されるわけではありません。今回は、React Nativeプロジェクトでネイティブコードを扱う方法、特に新しいCNGとExpo Prebuildの仕組みについて解説します。

React Nativeとネイティブコード

React NativeはJavaScript/TypeScriptでマルチプラットフォーム開発ができるフレームワークですが、ネイティブコードが全く必要ないわけではありません。カメラやセンサーなどのハードウェアを使う場合、ネイティブコードが必要になることがよくあります。

そのためにReact Nativeのプロジェクトには/android/iosといったディレクトリがあり、それぞれのOSのネイティブコードを管理できます。

my-app/
├── android/         # Androidのネイティブコード
│   ├── app/
│   ├── gradle/
│   └── ...
├── ios/            # iOSのネイティブコード
│   ├── Podfile
│   ├── MyApp/
│   └── ...
├── src/            # React Nativeのコード
└── package.json

これはFlutterにも同様の仕組みがありますが、この方式には以下のような問題がありました:

  • AndroidとiOSのネイティブ開発知識が必要
  • プラットフォーム間で挙動が異なることがある
  • 使っていない機能のコードが残りやすい
  • ネイティブモジュールとJavaScriptのバージョン不整合

例えばFlutterはこの問題をPluginという仕組みでかなり解決していますが、パーミッション設定などはやはりネイティブプロジェクトに依存します。例えばflutter_sharing_intentなどは多くのネイティブコードを追加する必要があります。

これはReact Native開発者にとって長年の課題で、React Native Surveyでもライブラリのアップグレードの難しさが指摘されていました。

Expoはまさにこの問題を解決するために、必要なネイティブモジュールをあらかじめ組み込んでおくことで対応していましたが、未対応のネイティブモジュールを使いたい場合はExpoの利用をやめて素のReact Nativeアプリに戻す必要がありました。

CNGとは

これらの問題を解決するために導入されたのが、CNGとExpo Prebuildです。

CNGとExpo Prebuildの関係は少し分かりづらいのですが、Expo PrebuildはExpoでネイティブコードの管理に関する問題を解決するために導入された仕組みのことで、CNGはそれを他のフレームワークやプラットフォームにも応用できるように抽象化した概念のことです。

CNGは概念。Expo  Prebuildはそれに含まれる実装の一つ

とはいえ現実世界にCNGの実装は現在のところExpo Prebuildしかないので、CNG=Expo Prebuildという理解で問題ないと思います。

まずCNGの概念を理解してしまった方がExpo Prebuildについても理解しやすいと思うので、先にCNGとは何かについて見ていきましょう。

CNGとはContinuous Native Generation(継続的ネイティブ生成)の略で、ネイティブプロジェクトを直接編集する代わりに、設定ファイルやライブラリのコードから自動生成する仕組みです。
こうすることで、開発者はネイティブアプリのプロジェクトを直接管理する必要がなく、必要な部分をjsonなどの設定ファイルに書いておけば、そこからネイティブプロジェクトを毎回生成することができるようになります。

例えば、Xcodeプロジェクトの設定にはInfo.plist, .xcodeprojファイル, .entitlementファイルをそれぞれ変更する必要があり、同様にAndroidではbuild.gradleAndroidManifest.xmlファイルをそれぞれお変更する必要があります。
CNGではこれらを直接編集する代わりに、設定ファイルをjsonなどで用意しておき、必要に応じて新しいネイティブプロジェクトを作成することで、OS固有の設定ファイルを書く必要がなくなります。

個人的には「継続的」というより「宣言的」な方が適切な気がします。Reactのコンポーネントが同じPropsから同じUIを生成するように、CNGも同じ設定から同じネイティブプロジェクトを生成します。

Expo Prebuildの使い方

Expo PrebuildはCNGの実装の一つで、expo prebuildコマンドを実行することで、以下の入力からネイティブプロジェクトを生成します:

  1. アプリ設定ファイル
  2. コマンドライン引数
  3. Expoのバージョンに対応するテンプレート
  4. package.jsonのAutolinking
  5. EAS Credentials

例えばapp.jsonでカメラの設定を行う場合:

{
  "expo": {
    "plugins": [
      [
        "expo-camera",
        {
          "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera?"
        }
      ]
    ]
  }
}

このように設定を記述するだけで、expo prebuild実行時に必要なネイティブコードが生成されます。

また、package.jsonにreact-native-firebaseなどのライブラリを追加すると、Autolinkingによって必要なネイティブコードが自動で組み込まれます。

生成されたネイティブプロジェクトは/android/iosに保存されますが、これらは再生成されるため直接編集は避け、.gitignoreに追加することをお勧めします。

ちなみにprebuildというコマンド名がちょっとミスリーディングなんですが、npx expo prebuildでは実際にネイティブアプリのビルドが走るわけではありません。あくまでネイティブプロジェクトのソースコードが生成されるだけです。

まとめ

Expo Prebuildを使うことで:

  • 設定ファイルベースでネイティブコードを管理できる
  • プラットフォーム間の差異を減らせる
  • 不要なコードを自動で削除できる
  • バージョン不整合を防げる

という利点があります。

ExpoがこれをCNGという抽象的な概念に昇華させていることも注目するべきだと思います。
例えば、FlutterやCompose Multiplatformなどの他のフレームワークにCNGの実装を導入することで、開発者が自分のアプリのコアロジックにより集中することができるようになると思います。

ライブラリがないネイティブ機能にアクセスしたい場合は、自作のネイティブモジュールを作ることもできます。これについては明日の記事で紹介します。

Discussion