React Native >= 60.0で追加されたautolinkについて解説


** 注意

この記事の内容は今後のReact Nativeの仕様変更により、大きく仕組みが変わる可能性があります。
現状はこのような仕様になっているという点だけご参考にしていただければと思います。

概要

ExpoなしのReact Nativeを使うなら、0.60系以上を使うと幸せになれますよというお話です。
この仕組みを理解するとネイティブモジュールの自作やネイティブビルドでのトラブルを回避しやすくなります。

背景

React Nativeにはネイティブブリッジという機能があり、一度ネイティブのコードをReact Native Moduleとしてライブラリにしてしまうとその利用者はネイティブコードを書くことなくそのJSのラッパーからネイティブコードを読むことができるようになります。

0.60よりも前のバージョンでは、linkという機能によってこれらのライブラリの初期設定を開発者に委ねることができていましたが、これらはよくバージョンアップに寄る非互換性を引き起こし、これによって、ブリッジのための依存解決を正常に設定できていないパッケージでは自身で依存解決の設定を手動でしなければいけなくなり、更にその設定の自由度が高く設定方法のトラブルの解決方法が多く存在するため、通常と異なる設定方法を使ってしまった結果、バージョンアップによって他のライブラリに影響を与えてしまったり、設定方法が分からなくなってパッケージの設定を最初からやり直さなければいけなくなるなどのトラブルが私たちの開発環境でも後を立ちませんでした。

0.60以降ではautolinkという機能によってネイティブの知識がある人によって正しく作られたライブラリでは、一切の環境設定を行う必要がなくなり、React Nativeでよく話題にされるバージョンアップの困難さにたいする苦痛が一気に解消され、ネイティブの知識がないWEBエンジニアでも非常に利用しやすいフレームワークになったように思います。

つまりメンテナがちゃんとライブラリをメンテしてautolinkさえ機能させるように維持していれば、誰のプロジェクトでも安全にバージョンアップの対応ができるようになったのです。

読者の対象は自作でReactNativeライブラリを作成する人ですが、それ以外の方もなぜautolinkという機能を実現することができるのかを知ることで安心してReact Nativeを使うことができるようになるでしょう。

react-native configコマンドについて (Auto Link事前知識)

まずは、react-nativeの適当なプロジェクトで

react-native config

を実行してみましょう。様々な情報が出力されます。

{
  "root": "$root",
  "reactNativePath": "$project",
  "dependencies": {
    "@react-native-community/async-storage": {
      ...
    }
    ...
  }

この情報の取得方法は以下のソースコードを読んでみるとpackage.json上あるいはreact-native.config.js上の依存パッケージ全てに対してpackage.json上のreact-native.config.jsを再帰的に読みこみ、情報を取得するというような動作をしています。

これにより、自作RNモジュールをパッケージ化する際はreact-native.config.jsを用意するとパッケージとして認識してもらえることがわかりました。また、それらがパッケージとして認識されるように、ネイティブモジュールに対応するplatformごとにバリデーションがあります。

ちなみにこの行の
https://github.com/react-native-community/cli/blob/master/packages/platform-android/src/config/index.ts#L132-L134
importPathはautolinkで自動生成する際に使われる情報にもなるので、ここの対応性は崩さないようにしましょう。

react-native linkコマンドの解説

従来のlinkコマンドでも、このconfigのdependenciesの情報を利用してpackageの情報を取得してパッケージをlinkしていました。

特に、linkの実態は、このlinkDependencyという関数になります。

そしてこの関数では

のlinkConfigという関数がありそれぞれのplatformに対して処理の分岐を行なっています。
そしてこのlinkConfigという関数は実はplatformごとに実装が切り出されており、抽象化されています。つまり、新しいデバイスに対応するプラットフォームが追加されていても、拡張性を担保するように設計されている様子が伺えますね。

以下に詳しいドキュメントがあります。

リポジトリ的にはここにあります。

ここで、それぞれのplatformでの処理の分岐をみてみましょう。

iOSの場合

react-native config時にこれらの情報が取得されてきます

type ProjectConfigIOST = {
  sourceDir: string;
  folder: string;
  pbxprojPath: string;
  podfile: null;
  podspecPath: null;
  projectPath: string;
  projectName: string;
  libraryFolder: string;
  sharedLibraries: Array<any>;
  plist: Array<any>;
};

また、linkConfigというラッパーの中でregisterNativeModuleIOSという関数があり、ここで、ライブラリの情報をxcodeprojに紐づけています。

また、podfileがある場合は

の実装を読み込むように以下のコードから分岐しています。

つまり、実際のソースコードを読んでみるとreact-native configによって取得されたiosのプロジェクトのxcodeprojあるいはPodfileから元のprojectの依存関係に追加する処理が実装されていることになります。また、この時、podに紐づいているhookも、install時に読み込まれるようになっているため、react-native-moduleからライブラリのhookで処理できることも全て追加できることがわかります。

Androidの場合

react-native configでみられる情報はこちら

type ProjectConfigAndroidT = {
  sourceDir: string;
  isFlat: boolean;
  folder: string;
  stringsPath: string;
  manifestPath: string;
  buildGradlePath: string;
  settingsGradlePath: string;
  assetsPath: string;
  mainFilePath: string;
  packageName: string;
};

同じようにAndroidのlinkConfig時の実態をみてみましょう。

同じように実装の流れが記述されていますね。ここで、build.gradleに追加のパッケージ,applyするrecourse,追加の記述などを追加しています。詳細は

などリソースの情報に応じてファイルが書かれていますが、要するにビルドに必要な情報の追記とbuild scriptの追加を行なっています。
つまり、Androidの場合は、react-native configのパッケージの情報があればsettings.gradleあるいはbuild.gradleの情報を元のビルドスクリプトに追加して実行してくれるという動作になっていることがわかります。

どのようにautolinkしているか

に詳細が書いてあります。

iOSの場合

自分のプロジェクトファイルにuse_native_modules!を読ませることによって、autolinkが走ります。
この時、react-native.config.jsのパスを指定していなければ、react-native configコマンドを元にパッケージの情報を収集します。

さらに、podspecが存在し、scriptPhasesが存在していれば、自動でそれらを追加していってくれます。

よって、iOSで自作のReact Native Moduleをautolinkに対応しようと思った場合は、react-nativeをインストールしているjsのパッケージを追加することに加えて、native部分をプロジェクトに切り出してpodspecを記述するだけでよく非常に簡単です。

Androidの場合

react-nativeのパッケージ内でMainApplication.javaの初期ファイルとビルドスクリプトapplyNativeModulesSettingsGradleとapplyNativeModulesAppBuildGradleを自動生成し、ビルド時にその中で取得されたパッケージをincludeする処理が走ります。
これらのgradle scriptはreact-naitve initで作成したプロジェクトではbuild.gradleで読み込まれるように設定されています。

settings.gradle
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
app/build.gradle
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

androidの場合も、ネイティブモジュールを自作する際はreact-native-から始まるjsのパッケージを追加し、projectのsettings.gradleとbuild.gradleの設定を定義するだけでnative modulesとして読み込まれるようになります。

まとめ

一連の流れをまとめるとiOSとAndroidのネイティブモジュールを自作するときに、以上の流れを知っておくと、自分で作ったライブラリがどのように紐付けされるかがわかるため、怖がらずにautolinkへの対応ができるようになります。
必要なものはreact-native.config.jsと、プラットフォームごとにはpodspecとgradleの設定を用意するだけです。
それぞれのbuild phaseでreact-native configの実行結果を自動で読み込み、その情報を元にautolink対応のライブラリだと認識してそれぞれのプロジェクトのビルド設定packageに取り込んでくれます。

特にAndroidの場合、

このように豪快に取得したpackageを自動でファイルを生成し紐づけてくれる仕組みを用意するなど考えた人の頭の良さと努力に感動したため、記事にしました。この記事で少しでもreact nativeの動作原理に興味をもっていただける人が増えたら幸いです。

10日目は@shinnokiさんのReact Navigation v5 傾向と対策です。

[引用] React Native CLI