ExpoのBare WorkflowとExpoKitについてのメモ

Expoを使っていて、Managed Workflowからejectする時のためのメモです。
はっきりとした情報が少なく調べきれていない感じがするので、大体の感じを掴むための参考になればと思います。

**[2020/04/03追記]**
**SDK37がリリースされ、ExpoKitからBare Workflowへのリプレイスが完了した模様です。**
https://dev.to/expo/expo-sdk-37-is-now-available-69g

# `expo eject`してみる

通常のeject（素のRN環境）、Bare、ExpoKitと３種類の選択肢があるのかと思っていましたが、現在（Expo v34.0以降）のところ`eject`コマンドを行う際BareかExpoKitのどちらかを選ぶことになります。

```console:Ejectしてみる
$ expo eject
? How would you like to eject your app?
  Read more: https://docs.expo.io/versions/latest/expokit/eject/
❯ Bare: I'd like a bare React Native project.
  ExpoKit: I'll create or log in with an Expo account to use React Native and th
e Expo SDK.
  Cancel: I'll continue with my current project structure.
```

# Bare Workflow

Bare（裸の） Workflowは通常のReactNativeプロジェクトに[`react-native-unimodules`](https://github.com/unimodules/react-native-unimodules)を導入したものと同じである、と考えて差し支えないと思います（細かく差分はあるかもしれませんが、機能的な違いはないはず）。

`react-native-unimodules`は、Expoを使うための最低限のコア部分をまとめたものです。
これを導入することによって、素のReactNativeプロジェクトでもExpo Clientを使ったManaged Workflowと同じような感覚で開発することができます。
unimodulesのなかで使用しないものは`Podfile`や`build.gradle`内のunimodulesを呼んでいるところに引数を渡して除外することができるようです。

```text:ios/Podfile
use_unimodules!(exclude: ['expo-face-detector'])
```

```gradle:android/build.gradle
addUnimodulesDependencies([exclude: ['expo-face-detector']])
```

ただし、このBare Workflow（ejectとしたこと）によって使用できるようになるAPIもあれば使用できなくなるAPIもあります。
詳しくは[こちらの一覧](https://docs.expo.io/versions/latest/bare/unimodules-full-list/)を確認してください。
特にNotificationsが使用できなくなるのが痛い...

ちなみに下記の記事ではunimodulesにNotificationsを追加する予定のようなことも書いてあります。
https://blog.expo.io/you-can-now-use-expo-apis-in-any-react-native-app-7c3a93041331#6246
ExpoKitからunimodulesにNotificationsを抜き出すというissueがあって、
https://github.com/expo/expo/issues/4376
SDK37で現在In progressになっているようです。
https://github.com/orgs/expo/projects/5#card-30177888
ただし、unimodulesにNotificationsが移行されればBare WorkflowでNotificationsが使えるということなのかはわかりませんでした。

**[2020/04/03]**
使えるようになりました。詳しくは以下の記事を参考にしてください。
https://qiita.com/mildsummer/items/5acc267152449c86b2b1

ひとまずBare Workflowの感じを見てみます。先ほどの`eject`コマンドで`Bare`を選択した後は、app.jsonの内容はこのようにコンパクトになりました。
`ios`や`android`などの設定項目や`icon`や`splash`などExpoの管理から外れることがわかります。

```json:app.json（サンプルアプリ）
{
  "name": "NotificationTest",
  "displayName": "通知テスト",
  "expo": {
    "name": "NotificationTest",
    "slug": "expo-template-bare",
    "privacy": "unlisted",
    "sdkVersion": "36.0.0",
    "version": "1.0.0",
    "entryPoint": "node_modules/expo/AppEntry.js",
    "platforms": [
      "ios",
      "android",
      "web"
    ]
  }
}
```

ios/android各ディレクトリがこのように生成され、ネイティブのコードや様々な設定を直接変更できるようになります。
![ios.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/9486e135-2fbd-b2ee-3f77-e46464bcb6be.png)
![android.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/5289d1b7-0c9e-980c-8837-f1b8b06422d5.png)
少なくともスプラッシュスクリーンやアプリアイコンを設定すると思いますが、iOSの場合`ios/[プロジェクト名]/Base.lproj/LaunchScreen.xib`（スプラッシュスクリーン）や`ios/[プロジェクト名]/Images.xcassets/Appicon.appiconset`（アプリアイコン）で、Androidの場合`android/app/src/main/res`内のリソースを編集することになります。

Androidのbuild.gradleを見ても、Expo特有の処理をしている感じはありません。

```gradle:android/build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext {
        buildToolsVersion = "28.0.3"
        minSdkVersion = 21
        compileSdkVersion = 28
        targetSdkVersion = 27
        supportLibVersion = "28.0.0"
    }
    repositories {
        google()
        jcenter()
    }x
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    
}

allprojects {
    repositories {
        mavenLocal()
        google()
        jcenter()
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url "$rootDir/../node_modules/react-native/android"
        }
        maven {
            // Android JSC is installed from npm
            url("$rootDir/../node_modules/jsc-android/dist")
        }
    }
}


task wrapper(type: Wrapper) {
    gradleVersion = '4.7'
    distributionUrl = distributionUrl.replace("bin", "all")
}
```

基本的な構成はReact Nativeと同様なので、`npm start`ではMetro Bundlerが立ち上がるだけです。
Managed Workflowと同じようにExpo Clientで開発する場合は

```console
$ expo start
```

こちらを叩くとbundlerに加えてExpoのDev Toolが立ち上がります。
Expo Clientで起動する際はJS層のソースだけが反映されるため、ネイティブ層に追加した機能や設定は使うことができないはずです。
ネイティブ層の変更も反映させて確認したい場合は、まずはiOSのPodsをインストールし、

```console
$ cd ios
$ pod install
```

もちろんCocoaPodsやXCode、AndroidSDKなどネイティブアプリの開発に必要なものはインストールした上で、[通常のReact Nativeと同じ方法](https://facebook.github.io/react-native/docs/running-on-device)でスタンドアロンアプリとしてビルドする必要があります。

# ExpoKit

eject時にExpoKitを選択するとReactNativeプロジェクトにExpoのAPIが丸ごと全て搭載される状態になるわけですが、
下記のドキュメントを見ると、やはりこちらもNotificationsがサポートされていません。

**Ejecting to ExpoKit**
https://docs.expo.io/versions/latest/expokit/eject/#you-should-not-eject-if

また、先ほど引用したunimodulesへの抜き出しに関するissueを含め、

**Migrating from ExpoKit**
https://docs.expo.io/versions/latest/bare/migrating-from-expokit/

このように、ExpoKitからBare Workflowへの移行に関する案内や、

**Ejecting to Bare Workflow**
https://docs.expo.io/versions/v35.0.0/workflow/customizing/
>Note: We don't recommend the ExpoKit eject option, it is less flexible and we will stop supporting it in the near future.

という注意書きを見る限り今後Expoプロジェクトをejectする際はBare Workflowを選択するのが主流になりそうです。
素のReactNativeと比べてXCodeでビルドする際に失敗することも多い印象です（というか最近のバージョンでは未だ成功していない気がする...）

ちなみに、ejectしてExpoKitを選択した段階でアプリアイコンやスプラッシュ画面は`app.json`の設定に従ってアセットファイルが作成されます。Bare Workflowと違ってxibやXMLなどを触らなくても最低限の体裁は整います。

また、下記のAPI群はBare Workflowでは使えませんが、ExpoKitでは使うことができます。

* ScreenOrientation
* AppLoading and SplashScreen
* AR
* AuthSession
* Updates