この記事は React Native Advent Calendar 2017 6 日目の記事です。5 日目は Quipper 同僚の @hotchemi によるQuipperにおけるReact Native活用事例でした。
こんにちは、Quipper で Software Engineer をやっている @yuya-takeyama です。 入社以来ほとんど Web のサーバサイド・クライアントサイドをやってきましたが、最近は React Native アプリのプロジェクトで TypeScript を書いています。
昨日の記事でも軽く触れてますが、Quipper で最近運用が始まった OTA (Over The Air) によるアプリ配信の運用とその自動化について詳しく紹介します。
なお、実際に動くコード例として以下のリポジトリを用意しました。
CircleCI の設定や、スクリプトについてはコピペして対象のアプリ名等を手直しすればそのまま使えると思うので、よければお試しください。 (なお、現在は CircleCI との連携を止めています)
OTA (Over The Air) とは
この記事においては React Native における OTA についてなので、アプリ中の JS 部分 (JS Bundle) だけを HTTP 経由で配信することを指します。
React Native を知らない方向けに説明すると、JS 部分というのはアプリケーション層のほぼ全体と言えます。
コンパイルされるコードとしてはもちろん Objective-C や Java によるネイティブのコードも含まれますが、それらは各プラットフォームの UI コンポーネントをブリッジする部分のようなフレームワーク層であり、開発者が触るアプリケーション層はほぼ全て JavaScript で実装可能です。 つまり、ちょっとした不具合の修正だったり、スタイルの微調整などをいつでも行うことができます。
逆に、ネイティブのコードを含むモジュール等を追加したような場合は、CodePush での更新はできません。
今回紹介する CodePush は 2 年前にはすでにサービスが始まっており、以下の記事で紹介されています。
最近の CodePush 事情
もともと CodePush は Microsoft が運営する独立したサービスでしたが、つい先日 Visual Studio App Center というサービスに統合されました。
アプリのリリースとほぼ同タイミングだったのでちょっと驚きましたが、SDK はそのままでも使えるので特に問題ありませんでした。運営も変わらず Microsoft です。
App Center が何かというと CI とか MBaaS とかが全部入りのサービスで、React Native に限らず Cordova によるアプリや通常の Android/iOS ネイティブアプリ等も対応しています。
今のところビルドとテストは CircleCI、クラッシュリポートは Sentry、プッシュ通知は Firebase という感じで使い分けており、今は CodePush 以外の機能は使っていません。
CodePush のセットアップ
セットアップについては公式のドキュメント通りで問題ないのでそちらを参照してください。
完全に新規のアプリにインストールするときはほぼ問題ないと思いますが、react-native-navigation のようなネイティブのコードを含むモジュールが既に入っている場合はうまくいかないこともあるので、注意が必要です。
react-native link
コマンドで自動修正が行われた AppDelegate.m
等のファイルはよく注意が必要です。
私たちの場合は以下のようなコードになってしまっていたため、CodePush の分岐に絶対に入らないようになってしまっていました。
#ifdef DEBUG #ifdef DEBUG jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; #else // 本当はここの分岐に入って欲しいけど、よく見ると絶対にこの分岐には入らない jsCodeLocation = [CodePush bundleURL]; #endif #else jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; #endif
CodePush の変更適用後、アプリを再起動するとロールバックしてしまうという現象に悩まされましたが、以下のように直すことで解消できました。
#ifdef DEBUG jsCodeLocation = [CodePush bundleURL]; #else jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; #endif
教訓としては react-native link
は過度に信用しないように、ということで。
CodePush による自動 OTA Updates のフロー
基本的には Git Flow とほぼ変わらなくて、リリース方法が CodePush とバイナリリリースとで分かれているので、その部分だけ手を加えています。
master
ブランチは fastlane でのバイナリの自動リリースに使うつもりですが、頻度は高くないので後回しにしてます。
ビルドやデプロイは先に書いた通り CircleCI を使って、GitHub でのマージをトリガーに実行しています。
App Center の機能が便利そうならそっちを使ってみてもいいかもしれませんが、今のところは特に困っていることもないので考えていません。
必要なツールのインストール
CodePush へのアプリの追加や Deployment と呼ばれる環境の追加、デプロイの実行等は基本的に appcenter-cli
と呼ばれるツールが必要です。手動操作用と自動化用にグローバル・ローカル両方にインストールします。
# グローバルインストール $ npm install -g appcenter-cli # プロジェクトへのローカルインストール $ yarn add -D appcenter-cli
ところでこの appcenter-cli ですが、ここ数日の統合のタイミングでドカドカと Pull Request が作られてまとめて取り込まれたせいもあってか、割と致命的な不具合が見つかっては直されている状態なので、正直品質はかなりアレかもしれません。必要に応じては旧 code-push-cli
を使うこともあるかもしれません。OSS なので自分で直して Pull Request を送るのも良いと思います。
App Center への登録
登録は GitHub アカウントでのログイン等で簡単にできます。
Organization の作成
会社で運用する上では Organization を作っておくのが良いでしょう。Web 上のコンソールから作成します。
App の作成
作った Organization に紐付く形で App を作成します。これも Web 上のコンソールから。
間違って個人アカウントに紐づけてしまっても、各 App ごとの Settings 画面から Transfer app to organization というメニューで移管が可能なので問題ありません。
アクセストークンの作成
デプロイを実行するためのアクセストークンを作成します。
$ appcenter tokens create -d '用途の説明'
アクセストークンは後からの参照はできません。CircleCI であれば環境変数として登録しておきます。
わからなくなった場合は古いトークンを削除して再発行し直すのがいいので、メモする必要はないでしょう。
Deployment の作成
Deployment というのは CodePush においては環境のことだと思ってください。デフォルトでは Production と Staging というのが作られます。
Quipper では以下のように使っています。
- Development:
develop
ブランチにマージしたものを自動でデプロイ - Release: リリーステスト用。
release/codepush/v*
にマージすると自動デプロイ - Production: 本番用。
deploy/codepush
にマージすると自動デプロイ
必要に応じて以下のように作成します。
$ appcenter codepush deployment add --app OurOrg/AwesomeApp NewDeploymentName
デプロイのためのスクリプトの用意
自動デプロイのために以下のコマンドを用意します。
yarn codepush:login
: アクセストークンによるログインyarn codepush:deploy
: CodePush へのデプロイの実行
今回は上述の通り CircleCI の環境変数にアクセストークンを持たせているので、$CODEPUSH_ACCESS_TOKEN
として参照します。
{ "scripts": { "codepush:login": "appcenter login --token $CODEPUSH_ACCESS_TOKEN", "codepush:deploy": "./scripts/deploy_codepush Production" } }
yarn codepush:deploy
は ./scripts/deploy_codepush
というシェルスクリプトに切り出してみました。
ここでは --target-binary-option
というオプションを指定しています。これはネイティブモジュールの追加・修正があった場合、CodePush だけではうまく変更が適用できないので、Semantic Versioning でバイナリリリースのバージョンを指定するという機能です。
TARGET_BINARY_VERSION
というファイルでバージョンを指定しています。
# The content of this file is specified as `--target-binary-version` in the deploy script `./scripts/deploy_codepush`. # When you introduced a new native module, please release the binary first and update this file. # You can specify it by following Semantic Versioning. # ref: https://docs.microsoft.com/en-us/appcenter/distribution/codepush/cli#target-binary-version-parameter 1.2.0
本来は Semantic Versioning で (マージ後、v1.0.6 としてリリースされました)1.2.x
や ~1.2.0
といった指定ができるはずですが、今は不具合があるので 1.2.0
のような固定バージョンを指定する必要があります。以下の Pull Request がリリースされれば解消されるはずです。
なお、現在のプロジェクトでは TypeScript を使っているので、実際には事前に yarn build
コマンドで JS ファイルへのトランスパイルを実行しています。
CircleCI での自動デプロイの設定
CircleCI 2.0 の Workflow を使っています。
あとは release/codepush/v*
といった名前のブランチを作ればリリース用の JS Bundle がデプロイされ、QA を通れば deploy/codepush
にマージすることでユーザ向けにデプロイされます。
リリース用 PR の自動作成
せっかくなのでそのリリース用のブランチ・Pull Request の作成も自動化します。
こんなスクリプトを用意していみました。
これは手元で手動実行してもいいですが、Jenkins からボタン一発で実行するようにセットアップしています。
環境変数 $CODEPUSH_ACCESS_TOKEN
をセットして yarn codepush:login
を実行した後、環境変数 $GITHUB_ACCESS_TOKEN
に GitHub のアクセストークンが必要です。
また、初回だけは deploy/codepush
ブランチを事前に用意して置く必要があります。
実行すると以下のような Pull Request が作成されます。
スクリプト内では以下のようなことを行なっています。
Production
の現行のバージョンを取得- バージョンをインクリメントして次のバージョンを算出
- 次のバージョンを元に
release/codepush/v*
といったブランチをdevelop
から作成 deploy/codepush
との差分を元に、コミット一覧からマージした Pull Request の一覧を作成- Pull Request を作成
これで、必要な機能が develop
ブランチにマージされて入れば、そのあとのリリースのプロジェクトは開発者以外でも行えるようになりました。
あとは開発に集中して高速のリリースを回して行きましょう!