React Nativeのバージョンを0.38.0から0.42.3に根性でアップグレードした

業務でReact Nativeのバージョンを0.38.0から0.42.3にアップグレードしましたが、このバージョン間は色々と変更も多く作業苦労しました。その際に実施したことや注意点、気づいたこと等をまとめておきます。

## 主に実施した手順

### 1. react-native-git-upgrade 実行

```
react-native-git-upgrade 0.42.3
```

### 2. conflictを修正

`ios/`フォルダ以下のconflictがあまりに多く手動で解消できなかったため、iosフォルダはおいておき、それ以外の部分のconflictを解消。

### 3. `ios/`フォルダを削除し新たにblankプロジェクトよりコピー

react-native init で `0.42.3`のBlank Projectを作成した後、iosフォルダをコピー。

### 4. XCodeでプロジェクト設定

XCodeでプロジェクトを開き、元のバージョン(0.38.0)も開いた状態で比較しながら手動でプロジェクト設定を修正。

### 5. NativeModuleを利用しているライブラリを更新

unlinkした後バージョンを更新しlink。

```
rnpm unlink react-native-xxxxxxxxx
# package.jsonのバージョンを更新
npm install
rnpm link react-native-xxxxxxxxx
```


### 6. iOS、Androidそれぞれでビルド

エラーが出たら随時修正していく


### 7 flowのエラーを修正

コード修正、もしくは.flowconfigのignoreの追加


## 気づいた点

###  空のプロジェクトでそれぞれのバージョンを比較するとわかりやすい

事前にそれぞれのバージョンでBlankのプロジェクトを`react-native init`で作って比較すると、バージョンが上がって実際にどこが変わるのかがわかりやすい。

react-native initでのバージョン指定は以下のように行う。
```
react-native init ReactNativeGraphSample --version <指定バージョン>
```

ちなみに以下は0.38.0と0.42.3 でそれぞれ作った際の差分
https://github.com/gaishimo/react-native-blank-app/compare/0.38.0...0.42.3#diff-4893035e3eb377ae826f0419b59dda90R345

上の差分を見てみると、かなり変更点が多く、`*-tvOS/` 等のフォルダが新たに出来ていてApple TV用の設定ファイル等が追加されているのがなんとなくわかる。`ios/blankapp.xcodeproj/project.pbxproj`はかなり変更が多いのもわかる。。


### ios設定のコンフリクト

`react-native-git-upgrade` を実行した後、`project.pbxproj`に発生した大量のコンフリクトをエディタで直接編集して解消しようと試みたが死亡した。プロジェクトが壊れてXCodeで開けなくなったりした。開けるようになってもその後linkしたりするとおかしくなったり..

あまりにも差分が多くて直接変更するのは厳しかったので、今回はブランクのプロジェクト(今回上げるバージョンの)からiosディレクトリを持ってきて必要なファイルをコピーした。その後XCodeの画面でアップグレード前のバージョン設定と比較しながら元々あった設定を適用していった。その後、元々あった設定ファイルで適用が必要そうなものは手動でmergeした。

project.pbxprojは差分があまりにも多い場合は直接編集するのはなかなか厳しいという実感。差分が少ない時、あるいはある程度プロジェクトファイルの構造を把握できていれば直接編集するのもありかも。

※ Androidの設定(`android/`以下)についてはConflictがそこまでなかったため手動でmerge出来た


### react-native-git-upgradeをする前にNativeModuleをUnlinkしておくとよいのでは(完了後再度リンクする)

`react-native-git-upgrade` でアップグレードを行った場合、iosディレクトリで大量にconflictが発生するが、事前にNativeModuleをunlinkしておいてプロジェクトの設定をシンプルな状態にしておくと差分が少なくなりそう。
※ただし後で再度linkする必要がある


## 作業時に遭遇したエラー等のメモ

### アップグレード後iOSビルドでエラー

```
...
Installing build/Build/Products/Debug-iphonesimulator/APPNAME.app
An error was encountered processing the command (domain=NSPOSIXErrorDomain, code=22):
Failed to install the requested application
The bundle identifier of the application could not be determined.
Ensure that the application's Info.plist contains a value for CFBundleIdentifier.
Print: Entry, ":CFBundleIdentifier", Does Not Exist

Command failed: /usr/libexec/PlistBuddy -c Print:CFBundleIdentifier build/Build/Products/Debug-iphonesimulator/APPNAME.app/Info.plist
Print: Entry, ":CFBundleIdentifier", Does Not Exist
```

Info.plist では CFBundleIdentifierが以下のように定義されているが、

```
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
```

`PRODUCT_BUNDLE_IDENTIFIER` が設定されていないためエラーになっていた。
`PRODUCT_BUNDLE_IDENTIFIER`はXCodeのプロジェクト設定の[General]で指定できる。


### NativeModuleのunlink時エラー

NativeModuleをunlinkする際に以下のエラーが出た。

```
$ rnpm unlink react-native-vector-icons
rnpm-link info Unlinking react-native-vector-icons android dependency
rnpm-link info Android module react-native-vector-icons has been successfully unlinked
rnpm-link info Unlinking react-native-vector-icons ios dependency
/Users/gaishimo/.nodebrew/node/v6.10.1/lib/node_modules/rnpm/node_modules/xcode/lib/pbxProject.js:1147
            var matches = searchPaths.filter(function(p) {
                                      ^

TypeError: searchPaths.filter is not a function
    at pbxProject.removeFromLibrarySearchPaths (/Users/gaishimo/.nodebrew/node/v6.10.1/lib/node_modules/rnpm/node_modules/xcode/lib/pbxProject.js:1147:39)
    at removeFromStaticLibraries (/Users/gaishimo/.nodebrew/node/v6.10.1/lib/node_modules/rnpm/node_modules/rnpm-plugin-link/src/ios/removeFromStaticLibraries.js:17:11)
    at getProducts.forEach.product (/Users/gaishimo/.nodebrew/node/v6.10.1/lib/node_modules/rnpm/node_modules/rnpm-plugin-link/src/ios/unregisterNativeModule.js:36:5)
    at Array.forEach (native)
    at unregisterNativeModule (/Users/gaishimo/.nodebrew/node/v6.10.1/lib/node_modules/rnpm/node_modules/rnpm-plugin-link/src/ios/unregisterNativeModule.js:35:34)
    at unlinkDependencyIOS (/Users/gaishimo/.nodebrew/node/v6.10.1/lib/node_modules/rnpm/node_modules/rnpm-plugin-link/src/unlink.js:52:3)
    at Object.unlink [as func] (/Users/gaishimo/.nodebrew/node/v6.10.1/lib/node_modules/rnpm/node_modules/rnpm-plugin-link/src/unlink.js:94:3)
    at Command.runAction (/Users/gaishimo/.nodebrew/node/v6.10.1/lib/node_modules/rnpm/bin/cli:23:15)
    at Command.listener (/Users/gaishimo/.nodebrew/node/v6.10.1/lib/node_modules/rnpm/node_modules/commander/index.js:301:8)
    at emitTwo (events.js:106:13)
```

これに従ってツール(rnpm)のコードを直接いじって回避
https://github.com/facebook/react-native/issues/13160


## 雑感

バージョン間の変更が少なければスムーズに行く気がするが、変更が多いとかなり大変。根性が試される。日頃こまめにバージョンアップを行って、一度の作業のバージョン間があまり開かないようにできれば理想かも。