3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ReactNativeでのアプリ開発、ExpoSDKがいい感じになってきたようなので試してみた:その4

Posted at

前回Expo Modulesの素振りをしたので、アプリに組み込んでいきます。

上記を参照すると、推奨方法として、前回参照したページのガイドが記載されていますが、あれだとモジュールが主でアプリが従のような形だったのでナゾ・・・。
で、別の方法として、モノレポにするか、npmへ公開する、とあります。
想定している使い方としては、製品としてのアプリ開発でプロプライエタリなライブラリを導入するケースなので、npmへ公開はナシです。

組み込み必要なライブラリが複数になることも考えて、モノレポにしようと思います。

モノレポ化する

こちらを参考に初回に作ったアプリをモノレポにします。

構造改革

まずはappspackagesディレクトリを作成し、今あるアプリのコードをapps/labappへ移動します。
.git.gitignoreyarn.lockはルートに残します)

ExpoLab % mkdir -p apps/labapp
ExpoLab % mkdir packages
ExpoLab % rm -rf node_modules/
ExpoLab % mv App.tsx assets babel.config.js *.json apps/labapp

移動したpackage.jsonnameを変更しておきます。

apps/labapp/package.json
  "name": "labapp"

次にルートに新しくpackage.jsonを作成します。

package.json
{
  "name": "expolab",
  "version": "1.0.0",
  "workspaces": ["apps/*", "packages/*"],
  "private": true
}

この状態でyarnを実行し、依存をインストールします。
ここまでを一旦コミットしておきましょう。

モノレポ下でのExpoLabアプリ実行

次にモノレポ環境に合わせてカスタムmetro.configを作成します。
apps/expolabへ移動して、まずは存在しないmetro.config.jsを生成します。

labapp % yarn expo customize metro.config.js

生成されたファイルの中身はこうなっていました。

metro.config.js
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');

module.exports = getDefaultConfig(__dirname);

サンプル通りnohoistなしのモノレポなので、依存パッケージはworkspaceルートのnode_modulesに入ります。Metro Bundlerがこれを参照するよう設定を変更します。

metro.config.js
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');
const path = require('path');

// Find the project and workspace directories
const projectRoot = __dirname;
// This can be replaced with `find-yarn-workspace-root`
const workspaceRoot = path.resolve(projectRoot, '../..');

const config = getDefaultConfig(projectRoot);

// 1. Watch all files within the monorepo
config.watchFolders = [workspaceRoot];
// 2. Let Metro know where to resolve packages and in what order
config.resolver.nodeModulesPaths = [
  path.resolve(projectRoot, 'node_modules'),
  path.resolve(workspaceRoot, 'node_modules'),
];
// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
config.resolver.disableHierarchicalLookup = true;

module.exports = config;

次にエントリーポイントを変更します。
Managedなアプリではデフォルトでindex.jsは存在せず、package.jsonmain"node_modules/expo/AppEntry.js"と設定されています。Expoが汎用エントリーポイントを用意してくれてるんですね。
このままだとモノレポでは動作しないようなので、index.jsを作成します。

index.js
import { registerRootComponent } from 'expo';

import App from './App';

// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

package.jsonmain"index.js"に変更します。

これでアプリは準備できたので、確認してみます。

yarn start

aでAndroidエミュレータ、iでiOSシミュレータが起動するので、アプリを確認できれば完了です。

モジュールをモノレポへ

前回作ったmy-moduleをモノレポへ移動します。
.gitnode_modulesは削除しておきます。

my-module % rm -rf .git node_modules
my-module % cd ..
expo % mv my-module ExpoLab/packages

my-module/yarn.lockの中身を全てコピーして、ExpoLab/yarn.lockの末尾に追加します。
yarn実行して依存インストールが済んだら、my-module/yarn.lockは削除しておきます。

my-module/exampleも不要なので削除してOKです。

アプリからモノレポ内のネイティブモジュールを利用する

labapp/package.jsonmy-moduleを追加します。

labapp/package.json
  "dependencies": {
    "my-module": "*",
    "expo": "~47.0.12",

追加したら、yarnしておきます。
次に、labapp/App.tsxmy-moduleを利用するコードを追加します。

labapp/App.tsx
import * as MyModule from 'my-module'; // <-- 追加

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Open up App.tsx to start working on your app!</Text>
      <Text>{MyModule.hello()}</Text>{/* <-- 追加*/}
      <StatusBar style="auto" />
    </View>
  );
}

では、これで動かしてみましょう。

labapp % yarn start
 :
› Press ? │ show all commands

Logs for your project will appear below. Press Ctrl+C to exit.

iOSシミュレータを起動してみます。

 ERROR  Error: Cannot find native module 'MyModule'
 ERROR  Invariant Violation: Failed to call into JavaScript module method AppRegistry.runApplication(). Module has not been registered as callable. Registered callable JavaScript modules (n = 11): Systrace, JSTimers, HeapCapture, SamplingProfiler, RCTLog, RCTDeviceEventEmitter, RCTNativeAppEventEmitter, GlobalPerformanceLogger, JSDevSupportModule, HMRClient, RCTEventEmitter.
        A frequent cause of the error is that the application entry file path is incorrect. This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native.

「MyModuleというネイティブモジュールがない」と怒られました。
これは、デフォルトの実行環境であるExpo Go(のネイティブ部分)に、MyModuleが存在しないため発生しているエラーです。
ならばMyModuleを含んだExpo Goがあれば解決・・・?

それがDevelopment Buildです。

Development Buildを試す

Expoアカウント作成

Development Build作成のためにEASを利用します。
ので、Expoアカウントが必要となりますので、ない場合は作ります。

# GitHubアカウント連携とかできて欲しい・・・

EASのセットアップ

eas-cliをプロジェクトにインストールします。
ガイドではグローバルへのインストールが推奨されていますが、複数プロジェクトを扱う場合にバージョンによる挙動の問題に遭遇したくないのでローカルへインストールします。

labapp % yarn add -D eas-cli

インストールできたらログインし、

labapp % yarn eas login

EAS Buildのセットアップを行います。

labapp % yarn eas build:configure

対象プラットフォーム、EASプロジェクトを作成するかどうか、を聞かれますが、すべてデフォルトでOKです。

expo-dev-clientを設定

expo-dev-clientをインストールします。

labapp % yarn expo install expo-dev-client

App.tsxに以下を追加します。

App.tsx
import 'expo-dev-client';

Development Build作成

Androidからいきます。

labapp %  yarn eas build -e development -p android

アプリケーションIDを聞かれますがデフォルトでもOKです。

しばらく待つと・・・ビルドが失敗しました。

✖ Build failed

🤖 Android build failed:
Gradle build failed with unknown error. See logs for the "Run gradlew" phase for more information.

上記では分かりにくいですが、"Run gradlew"の部分はリンクになっていて、開くとEASのビルドログの該当箇所が確認できます。

どうやら、Kotlinのバージョンミスマッチのようです。

そういえば、最初にcreate-expo-appした際に、あえて1つ前のSDKにしてたんでした。
ネイティブモジュール側と合わない状態になっているようです。
これが原因ですね。

では、予告通り(?)、SDKバージョンを上げましょう。

こちらを参考に、アップグレードします。

SDK48を入れて・・・

labapp % yarn add expo@^48.0.0

SDK48に合わせた更新をします。

labapp % yarn expo install --fix

・・・これだけです。
簡単すぎますね。

素のReactNativeでバージョンアップする場合、こちらのページで利用中のバージョンと更新先のバージョンを選択して表示される差分を取り込みます。

https://react-native-community.github.io/upgrade-helper/

手動です。辛いです。
利用してるライブラリが動かなくなることもあります。
(これはexpoでも利用するライブラリによっては発生するでしょうけど)
動作確認含めて、結構な時間が掛かります。

バージョンアップできたところで、EASビルドをリトライします。

・・・今度は無事にビルドが完了し、QRコードが表示されます。

🤖 Open this link on your Android devices (or scan the QR code) to install the app:
https://expo.dev/accounts/sakai-y/projects/ExpoLab/builds/aff9798f-823b-4ba5-a7f5-295acfxxxxxx

? Install and run the Android build on an emulator? › (Y/n)

最後に「エミュレータで実行する?」って聞かれているので実行します。
EASからビルドしたアプリがダウンロードされ、どのエミュレータを使うのか聞いてきます。
至れり尽くせりですね。

インストールが完了し、アプリが起動するとnpx expo start --dev-clientしろと表示されています。
package.jsonstartコマンドを書き換えましょう。

labapp/package.json
  "scripts": {
    "start": "expo start --dev-client",

expo startします。

labapp %  yarn start

aキーでエミュレータで実行中アプリにロードされます。

無事にlabappからMyModuleを利用したコードが実行できています。

この状態でアプリのコード(ReactNative部分)を変更すると即座に反映されます。
Expo Goの時と変わらない開発体験で、かつ、カスタムネイティブコードを含んだアプリの開発ができるということです。
なんと素晴らしい。

iOSシミュレータで実行する

iOSシミュレータでも実行してみます。
まずはeas.jsonにシミュレータ用のビルド設定を追加します。

labapp/eas.json
  "build": {
    "development-simulator": {
      "developmentClient": true,
      "distribution": "internal",
      "ios": {
        "resourceClass": "m-medium",
        "simulator": true
      }
    },

EASビルドします。

labapp % yarn eas build -e development-simulator -p ios

しばらく待つと完了し、Androidの時と同じようにシミュレータ起動するか聞いてきます。

✔ Build finished

🍎 iOS app:
https://expo.dev/artifacts/eas/6v2Spif98ubTZLxxxxxxxx.tar.gz

? Install and run the iOS build on a simulator? › (Y/n)

シミュレータにインストール完了して起動が確認できたら、先ほどと同様にyarn startしてiキーでシミュレータへロードします。

まとめ

ちょっと長くなりましたが、Managed workflowのアプリでカスタムネイティブコードを利用するために、モノレポ構成にした上で、Expo Modulesで作ったネイティブモジュールを組み込むところまでできました。

次は、プロプライエタリなライブラリをExpo Moduleとして作成して、アプリから利用してみたいと思います。

つづく

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?