AndroidのProduct FlavorsっぽいものをCreate React Appで再現したかった

こんにちは、モバイルチームの中川です。今回はJavaScript文化圏の記事になります。モバイルチームはユーザーの手のひらに価値を届けるためなら何でもするチームなので、必要ならJavaScriptだってやるんです(自分に言い聞かせるように)。それでは始めます。

アプリの細部を切り替えたい

ソースコードとしてはほとんど同じなんだけど、次のような振り分けをしてアプリとしては別々のものにしたいことってありますよね。

  • ビジネス上の都合でテーマを切り替えた版を作りたい
  • ボタンを出したり出さなかったりしたい
  • 接続先を切り替えたい

Androidアプリの開発では、build.gradleの中でBuild VariantsのProduct Flavorsとして定義することで、無料版と有料版を切り替えるみたいなことが簡単に行えます。iOSでも(本来の使い方からは外れるかもしれませんが)SchemeやTargetを切り替えることで似たようなビルド体験を実現できます。

Webでもやりたい

以前にも少し触れましたが、弊社にはCordova+ReactなiOSアプリがあります。最近は少しビルド環境を整理して、webpack2でゴリゴリ書いていたのを、Create React Appに閉じ込めることに成功しています。webpack.config.jsをメンテしなくていい生活は最高です。

さて、このたび、このアプリにもProduct Flavors的な実装をすることになりましたが、少し困ったことになりました。webpackならwebpack.config.jsごと差し替えるなり、内部で動的にエントリーポイントを差し替えるなりすればよいのですが、CRAではビルド環境に手を加えられる場所はほとんどありません。

早めの結論

Androidと全く同じくProduct Flavorsと同名のフォルダにコードを入れておけば自動で切り替えてくれるような、そういったものはできませんでした。webpackなら頑張ればできそうな気もしますが、少なくともCRAでは無理です。

ソースコードやリソースごと切り替えるというのは諦めて、アプリケーション内部で動的に差し替える方法を模索することになりました。

伝統と信頼の環境変数

動的に差し替えるにしても、アプリケーションに自分がどちらの環境で動いているのか教えてあげなければいけませんね。

そんなときこそ環境変数です。ビルド時点で外から依存性を突っ込んであげましょう。Androidから名前を借りて、今回も依存性の名前をflavorと呼んでいきたいと思います。

package.jsonにflavorを定義する

まずは変更前の状態です。dev serverをhttpsで動かしたかった関係で、 HTTPS=true が入っていたりしますが、至って普通のCRAです。

"scripts": {
  "start": "HTTPS=true react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test --env=jsdom",
  "eject": "react-scripts eject"
}

ここに、appAとappBという2つのflavorを環境変数で定義します。REACT_APP_から始まる環境変数はビルドに取り込んでもらえる機能があるので、これを活用した形になります。

 "scripts": {
-  "start": "HTTPS=true react-scripts start",
+  "start:appA": "REACT_APP_FLAVOR=appA HTTPS=true react-scripts start",
+  "start:appB": "REACT_APP_FLAVOR=appB HTTPS=true react-scripts start",
-  "build": "react-scripts build",
+  "build:appA": "REACT_APP_FLAVOR=appA react-scripts build",
+  "build:appB": "REACT_APP_FLAVOR=appB react-scripts build",
   "test": "react-scripts test --env=jsdom",
   "eject": "react-scripts eject"
 }

実用してみる

REACT_APP_ な環境変数は process.env に生えているので、次のように呼び出せます。

class App extends Component {
  render() {
    return (
      <div className="App">
        {/* 省略 */}
        <p>
          This is {process.env.REACT_APP_FLAVOR}
        </p>
      </div>
    );
  }
}

それでは、まずはappAとして実行してみましょう。

$ yarn start:appA # or npm run start:appA

f:id:Nkzn:20180307123746p:plain

ちゃんとappAとして表示されましたね。

次はappBとして実行してみましょう。

それでは、まずはappAとして実行してみましょう。

$ yarn start:appB # or npm run start:appB

f:id:Nkzn:20180307123848p:plain

上手くいきました。

余談: デバッグ・リリース・テストで環境を切り替えたい

デバッグ時とリリース時とテスト時で切り替えたい値としては、接続先のホスト名などがありますね。今回の記事の内容を踏まえると、これもnpm scriptsに環境変数を仕込めばいいように思えますが、実はこのへんには専用の機能が用意されています。

.env(.development|.production|.test)?(.local)? みたいな名前のファイルに REACT_APP_ な環境変数を書いておくことで、ビルドの種別に応じてreact-scriptsが勝手に採用する環境変数を切り替えてくれるのです。ファイル名の全パターンはこちらを見てください。

Androidアプリ開発ではBuild Typeに近い概念で分かりやすかったので、やはり開発時には重宝しています。

使い方

例として、接続先のホストを切り替える場合を考えてみましょう。

開発用の .env.development には次のような設定を書いておきます。

REACT_APP_HOST=https://dev.your-domain.com

そして、プロダクション用の .env.production には次のような設定を書いておきます。

REACT_APP_HOST=https://your-domain.com

これで、アプリ内では process.env.REACT_APP_HOST を参照して接続先を決定できるようになりました。

やはり便利なので使っていきたい機能です。

まとめ

REACT_APP_ な環境変数は、アプリケーション内でグローバルに参照できて、環境を振り分けるには便利なので、使っていきたい機能ですね。