Yuri’s Tech Note

技術系アウトプット

React Native0.56アップデート(失敗した選択たちとその選択理由・正解の解説)

React Native本体のbreaking changeなアップデートを始めて自分で担当した。
その道中たくさんの選択ミスをした。
とても大変だったので2度と苦しまないよう

  • 起こったこと
  • それに対し行なったこと(またそれを行なった理由)
  • 正解だった選択肢とその理由

の3点を時系列に記して行く。

第一の壁 アップデート方法

いつもはreact-native-git-upgradeを使っている。

起こったこと

ものすごく差分が出た。

それに対し行なったこと(またそれを行なった理由)

差分が多すぎる上に何が変更されたのか差分を見て理解できなかった。
アップデート作業を担当する以上、後々の不具合に備えて何が変更されたのかを自覚しておくべきなのでまずはchange logを読んでどんな変更が行われたのかを把握し、記事を検索して差分を見ながら手動で差分を当てるという手法を選択した。

ビルドして失敗する理由がライブラリのせいである可能性も考えると、原因の追求先を減らすためにまずは本体(react-nativeとreact)だけアップデートし、ビルドした上で動作確認してライブラリを1つずつ確認していこうという方針が安全だと自分では思った。

正解だった選択肢とその理由

react-native-git-upgradeを使うこと。
上記のやり方が間違っているというわけではないが、「アプリは動くが他のライブラリが互換性的に最適とはいえないバージョンのまま」という状態ができあがった。

当時かなり多くのライブラリが最新バージョンではなかった。
これを解消しようと他のタスクに「ライブラリのアップデート」というタスクを切ってあったので、今回は本当に安全にReact Native本体のバージョンだけをあげ、そこの安全確保ができた上で1つずつライブラリをあげる作業をすればいいやという考えで、その目的は果たしていたが、そこまで臆病にならずともreact-native-git-upgradeをした方が効率良いということだった。

また今回はreact-native-git-upgradeによるコンフリクト解消は一緒にみてもらった。

第二の壁 Babel

起こったこと

・Babelに必要なモジュールがなくてビルドできない。
・Babel7になったことでライブラリ内の構文がBabel7のルールに即していないと怒られてビルドできない。

// エラー内容
SyntaxError: A trailing comma is not permitted after the rest element

// 該当箇所
// 1行で記述する際に末尾に","があると怒られる。
const {shouldUpdated, ...props, } = Props;
それに対し行なったこと(またそれを行なった理由)

・下記モジュールを追加した。

babel-preset-react-native v5.0.2
@babel/core v7.0.0-beta.47
babel/core v^7.0.0-bridge.0

・Babel7に対応してないライブラリも対応できるようになるとchange logにも紹介のあるbabel-bridgeの導入を試みた。

正解だった選択肢とその理由

babel-preset-react-nativeと@babel/coreに関してはchange logに記載があったがbabel-coreも必要だった。
しかもバーションは微妙に異なる。
なんとなくbabel/coreと@babel/coreはバージョンを揃えるもんだと思い込みもあって記事の通り両方v7.0.0-beta.47にしようとして「babel/coreはそんなバージョンないぞ」と怒られた。
npm outdate コマンドを打てばbabel/coreにv7.0.0-beta.47が本当にないことがわかったのでwantedのバージョン(v7.0.0-bridge.0)を指定したらうまくいった。
(しかしこれに関してはどこかのissueの回答にも両方v7.0.0-beta.47にしろと書いてあったのも頑なにそうだと思い込む一因となってしまった)

途中で気が付いたが上記のエラーが起きたライブラリのgithubをのぞいたらどれも構文(RN0.56)に対応してくれていた。
結論、該当ライブラリを最新にアップデートをするだけでよかった。
この時点でbabel-bridgeの必要は無くなったので導入しなかった。
そもそも先に各ライブラリがRN0.56に対応しているかどうかを調べるということをしていれば時間を浪費しなくてすんだ

第二の壁 Jest

起こったこと

・jestが実行できない
・setTimeout関数を使うライブラリで`TypeError: _setter is not a function`エラー
・mockの方法が変わった

それに対し行なったこと(またそれを行なった理由)

ここは分担して主に他のメンバーにやってもらった。

正解だった選択肢とその理由

・jestが実行できない問題の解消
jestもBabel7の影響を受けているようでbabel/coreと@babel/coreを7のバージョンを使うこと、またReact Native内部のjestプロセッサーを使うようにすることで解消ができた。
具体的には下記のコードをpackage.jsonに追記した。

"jest": {
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
    },
}

・setTimeout関数を使うライブラリで`TypeError: _setter is not a function`が多発
JavaScriptのコードはオブジェクトに格納されている必要があり、その格納するオブジェクトの最上位に位置する単一のオブジェクトはグローバルオブジェクトと呼ぶが、これはWebブラウザでの実装の場合「window」、Node.jsの場合は「global」がグローバルオブジェクトとなる
setTimeoutを提供してくれているreact-timer-mixinの中でこのグローバルオブジェクトを設定する処理があった。

var GLOBAL = typeof window === 'undefined' ? global : window;

package.jsonに下記の記述をしていたためグローバルオブジェクトがwindowに設定されてしまっていた。
この記述をまるっと削除することで解決した。

"globals": {
      "window": {
        "navigator": {
          "userAgent": false
        }
      }
    },

・mockの方法が変わった
今まで1つのテストファイルの頭でmockを用意している箇所があった。
これが効かなくなったライブラリがいくつかある。
対応としてはテストファイルの頭でしていたmockをマニュアルモックとして__mocks__配下につくりなおした。

// 削除
jest.mock('shortid', () => ({
    generate: () => 'shortID_mock',
}));

@__mocks__/shortid.js

// 新規作成
export default {
    generate: () => 'shortID_mock',
};

第三の壁 react-native-scrollable-tab-view

起こったこと

0.56アップデートとは直接関係なかったが番外編として。

自社のプロダクトでは様々なケースでreact-native-scrollable-tab-viewを利用している。
左側はタブビューが入れ子になる場合。
右側はタブビューの中にtextIputが大量にあり、抱えている情報が大量になる場合。
f:id:yuri_iOS:20180907002307p:plainf:id:yuri_iOS:20180907002317p:plain
これらに対してreact-native-scrollable-tab-viewとreact-native-scrollable-tab-view-latestという元が同じライブラリから派生したライブラリを画面ごとに使い分けていた。

この2つのライブラリが両方ともbabel7の構文にひっかかって修正する必要があった。

それに対し行なったこと(またそれを行なった理由)

react-native-scrollable-tab-viewの本体が0.56対応して更新されていたのでアップデートが必要なことに加え、この期に統一を図ろうとした。

正解だった選択肢とその理由

実はこのreact-native-scrollable-tab-viewは入れ子にして使うことは非推奨であり、本体の方はそれを考慮したつくりになっていないためにタブビューを入れ子にするとandroidで不具合が起きる。
これに対応するためreact-native-scrollable-tab-viewはandroid入れ子にしても動くような修正をした今まで使っていたライブラリを構文だけ修正して使う必要があり、textInputが大量にあって画面で抱えるデータが多くなる画面ではパフォーマンスが改善されている本家最新版を使った方が良いのでこれもまた今まで使っていたライブラリを構文だけ修正して使うことになった。

問題だったのは、担当した私が「なんでこれわかれてるんだっけ?」状態であったこと。
わからないのでひとまず本家最新にして動作確認するところからはじめたので不具合が生じたときにreact-nativeのバグなのか、react-native-scrollable-tab-viewのバグなのか、はたまた分けているライブラリの意図を自分が汲めていないのかでさんざん時間を使って調査・検証して頭を悩ませるはめになった。

ここでは正解というより、適切にコメントを書くことは大事という教訓を得た。

第四の壁 Twitter

起こったこと

・端末にTwitterアプリがない状態でTwitterサインインやログインができない
 (以前はWebViewが開いてサインインもログインもできた。)

iOSアーカイブビルドのみfail

clang: error: no such file or directory: '/Users/MyUser/react-native-twitter-signin/Example/ios/build/Build/Products/Release-iphonesimulator/libRNTwitterSignIn.a'
それに対し行なったこと(またそれを行なった理由)

Twitterログインの問題は他のメンバーが担当。
アーカイブビルドに関しては古いキャッシュが残っているのだろうと下記キャッシュの削除を一通りおこなった。

・rm -rf node_modules/
・rm -rf ios/Pods
・npm cache clean --force
・npm install
・pod install
・npm start -- --reset-cache
・XcodeでBuild Clean
正解だった選択肢とその理由

・端末にTwitterアプリがない状態でTwitterサインインやログインができない
これはアプリ側ではなく、開発者アカウントの設定の問題だった。

iOSアーカイブビルドのみfail
Xcode > Library配下のRNTwitterSignin.xcodeprojとPods.xcodeproj > Pods配下のTwitterCoreとTwitterKitがバッティングを起こしていた。
そもそもreact-native linkの挙動が変わったことが原因となる。
以前のreact-native linkではXcode > Library配下にRNTwitterSignin.xcodeprojが生成されていたのだが、いつからかPodsfileに下記記述が加わるようになった。

pod 'react-native-twitter-signin',: path => '../node_modules/react-native-twitter-signin'

以前他のネィティブモジュールでも似たようなことがあったのだが、xcodeprojとPodsのモジュールはどちらかがあればよいらしく、むしろ両方あるとどこかでバッティングを起こしビルドできないというケースを引き起こす。
README.mdにしたがって書かれている手順をそのまま実行してライブラリを入れているが、そこに書かれたことを行なってどうなればよいのか。
こうあるべきを知らないが故に解決できなかった。

クロスプラットフォーム開発をするということは両方のネィティブの知識がある程度ないとできないということを痛感した。

大きなアップデートがあった時は古いものは基本消したほうが良いというのと、キャッシュクリアだけではなく今後はxcodeprojとPods配下にも目を配るようにしたい。

第五の壁 Podfile.lock

起こったこと

忘れてしまったがなんかpod installがうまくいかなかった?

それに対し行なったこと(またそれを行なった理由)

ここらへんではもう心がおれかけていたのでまた特に考えずキャッシュクリアをしていた。
ただライブラリやモジュールのバージョンを変えない方が良いだろうとxx.lockファイル系は削除せずにライブラリやモジュールを取得し直した。
(これの前にpackage.jsonを削除して必要ないものまでアップデートしてしまってjestが失敗するということがっあったこともあって、絶対に.lockは消さない方が良いと思い込んでいた。)

正解だった選択肢とその理由

xxx.lockは他の開発メンバーがまったく同じ環境でライブラリをインストールできるために使うのであって、だれかのブランチを持ってきたときはもちろん消さずにlockファイルを頼りにinstallするべきだが、RNのアップデートでは古いバージョンをひきづっていることによる不具合の方が考えられるので削除して各種新しいバージョンで入れ直した方が良い。



RNアップデートは初めてということもあるがネィティブに詳しくなかったり過去の修正を記憶してないと大変。
他にも
・何度もやっているとnpm installが遅くなる
・./gradlew cleanBuildCacheがやたら時間かかる
・./gradlew cleanBuildCache後のAndroid Studioがビルドできるまでにやたらめった時間がかかる
など時間もかかるし1回の検証に時間かかる割に経験が少ない故に原因の追求先が多くて、あえて一人でやりきろうと抱え込むのは学習のコスパ悪かった気がする。


ただ次は3割増くらいにはうまくできるのではないか。