agri-note inside

ウォーターセル株式会社 スマート農業システム開発部のブログです。

JAWS-UG 新潟 #4 がウォーターセルオフィスで開催されます

f:id:Nkzn:20190522113512p:plain

こんにちは、@Nkznです。

新潟市で活動しているAWSのユーザーグループ「JAWS-UG 新潟」の第4回のイベントが、今週末、5月25日(土)に行われます。

jawsug-niigata.connpass.com

ウォーターセルもAWSのユーザー企業であり、JAWS-UG 新潟の運営にも弊社メンバーが参加しているというご縁もあって、今回はウォーターセルオフィスの1F会議スペースで開催されることになりました👏

Amazon EC2とAmazon Lightsailを使って、WordPressサイトを構築するハンズオン形式のイベントとなっています。この機会に、AWSの使いかたを学んでみるのはいかがでしょうか。

AWSに興味がある方は、connpassのイベントページからお申し込みください。

ウォーターセルのオフィスを見学してみたい方は、参加者の中に紛れ込んでいる弊社メンバーを捕まえれば、ちょっとだけご案内できます。

Android Bazaar & Conference 2019 Springにウォーターセルのエンジニアが登壇します

f:id:Nkzn:20190522115145p:plain:w480

こんにちは、モバイルチームの中川@Nkznです。弊社エンジニアの登壇情報です……というか、私が登壇します。

今週末、5月26日(日)に東海大学高輪キャンパスで開催される、Android Bazaar & Conference 2019 Spring(ABC 2019 Spring)で、講演することになりました。

abc.android-group.jp

内容は「JavaでAndroidを始めた人のためのJava -> Kotlinライブコーディング」です。

中川が昨年書いたたった1日で基本が身に付く! Androidアプリ開発超入門という本では、あえてJava7相当の文法を中心にサンプルコードを構築しました。

これはこれで好評だったのですが、「Kotlinだとどう書くのか知りたい」という声がそれなりに届いていたので、今回は試しに、本書のサンプルをKotlinに書き直してみます。

まだKotlinが怖い、Android初心者の方におすすめです。興味がある方は、当日、東海大学高輪キャンパスでお会いしましょう!

宣伝

ウォーターセル社では現在、積極的に採用活動を行っております。新潟への転職をお考えの方がいれば、ABCの会場でお気軽にご相談ください。

water-cell.jp

WaterCell Tech Night #3 を開催しました

こんにちは、フロントエンドチームの @sug1t0m0_agrict です。 試用期間が終わり、正式にwater-cellの一員になりました。宜しくお願い致します。

ということで、GW前に3回目の社内開催のテックトークイベントをざっくりまとめていきたいと思います。

いつもどおり、🍕と🍺と発表者を、みんなで囲み、和気あいあいと開催されました。なお、今回からは、個人が持ち寄ったドリンクの支払いにKyashのバーコード決済が導入され、飲みたい人が飲みたいだけ飲めるようになりました!

f:id:sug1t0m0_agrict:20190507105143j:plain

テックトークイベントの様子

発表ダイジェスト

単純パーセプトロンで遊んでみたかった @sug1t0m0_agrict

speakerdeck.com

私の発表です。機械学習を基礎から学ぶべく、初歩の初歩を。

JavaScriptビルド概論 @Nkzn

speakerdeck.com フロントエンド開発初心者の私にとって、とてもためになる内容でした。これまでのJSの歴史なんかはJS歴数ヶ月の自分にとっては新鮮でした。トランスパイル,ポリフィル,依存性解決それぞれがアプローチしている問題を再確認することができました。

Project Eulerの話 @nozma

speakerdeck.com

「Project Euler」の存在自体知らなかったのですが、「Project Euler」はなんと、日常生活で比較的使わない概念を学習できちゃうらしいんです!!! 実際、「テトレーション」の記号も、今後お目にかかることがない気がします。

prefers-color-schemaの話 @circled9

speakerdeck.com まだ採用しているブラウザはかなり少ない「prefers-color-schema」ですが今後一般的になっていくのでしょうか。もし採用されるとなるとwebデザインの考え方自体も変わってきそうなので、今後の動向に注意しないといけない気がしました。

gitlab移行の話 @kam1nchu

ウォーターセルでは社内のソースコード管理やコードレビューにGitLabを活用しています。これまでは社内の物理サーバーでの運用でした。

f:id:Nkzn:20190508110855j:plain:w480
画像はイメージです

それがこの春、@kam1nchuさんの頑張りにより、AWSへの移行が行われました🎊

その際に考えたことについての発表ですが、資料は社外非公開です。新しいクラウドサービスが次々と展開される昨今、サービスの選択はもちろんのこと、サービスにあったセキュリティ対策をするのは難しいと思います。私も簡単なサービスから触って、慣れていく必要を感じました。

まとめ

かなり期間が空いてしまいましたが、第3回も無事に開催されました。 第4回は5月中に開催する予定です!!

WaterCell Tech Night #2 を開催しました

こんにちは、ブラウザチームの松井 @circled9 です。

社内開催のテックトークイベントの第二回を開催したので、その様子をレポしたいと思います。

f:id:circled9:20181226093824j:plain

今回も🍕や🍺を嗜みつつ、和気藹々と発表を行いました。

ちなみにブログ初登場です、はじめまして。

来年の抱負を言ったつもりだったんですがめでたく今年達成してしまいました。

発表ダイジェスト

ここをこうしたら速くなった。最近の気付き (画像処理初心者向け?) @sug1t0m0_agrict

speakerdeck.com

画像処理でここをこうしたら処理速度が速くなったよ!という発表でした。

画像処理周りは二次元配列に対する演算になるので、桁が変わるレベルで速度が変わるんですよね...

普段画像処理のコードは書かないので、大変な世界だなぁと思いました。

今年のアウトプット50連発(NDSの練習) by @Nkzn

speakerdeck.com

今年のアウトプットを紹介するよ!という発表でした。

一年の週の数を考えると週一ペースでアウトプットした計算になるんですよね... 見習わねばと思いました。

Excelでゲームを作るときに役立つ関数5選 by @10mikiya

speakerdeck.com

Excelでゲームを作る時に役立つ関数を5つ紹介するよという発表だったのですが、1つ説明するたびに「え、ちょっとそれどうやったの?」「え?今の何?」と本題の関数以外にも学びが1つにつき5個ぐらい得られる発表でした。

Excelでゲームウォッチ風のゲームを動かすデモは正直驚きました。

R Markdownを設計に活用する by @nozma

f:id:Nkzn:20181219174343p:plain

github.com

R Markdownで設計書を書こうという発表でした。

おもむろにR Studioがブラウザ上で立ち上がった時はびっくりしました。

ルーターの選び方その2 by @kam1nchu

speakerdeck.com

前回に引き続き、ルーター選定に役立つ話、今回はルーター選定の時に出てくる用語の説明でした。

何となく名前は聞いたことがあるけど雰囲気しかわからん、という単語が結構あって勉強になりました。

Hello React hooks / Bundle Transpile Rock'n'Roll by @circled9

speakerdeck.com

speakerdeck.com

私の発表です。

Reactの次のバージョンで実装される予定のHooks APIの紹介と、NDSでも発表したJSのバンドルとトランスパイルについての発表でした。

まとめ

というわけで、第二回も無事開催されました。

年末の12月は会社の全体発表会があったり、年末でばたばたするので次回は1月を予定しています。

では次回もお楽しみに!

WaterCell Tech Night #1 を開催しました

こんにちは、モバイルチームの中川@Nkznです。

社内でテックトークイベントを開いたので、その様子をレポしたいと思います。

f:id:Nkzn:20181122172740j:plain:w600

🍕や🍺を嗜みながら、和気あいあいと発表を行いました。

割と急にやることが決まり、週初めに告知、週末に実施という短い期間の中、9名が発表する良い規模のイベントとなりました。

発表ダイジェスト

各発表を簡単に紹介します。

続きを読む

React Native for Webをプロダクションで使ってみました

こんにちは、モバイルチームの中川[twitter:@nkzn]です。

5月22日にプレスリリースがあった提携で紹介されていたアプリでは、React Native及びReact Native for Webを採用しています。こちらについて技術的な側面から(当たり障りのない範囲で*1)事例を紹介します。

経緯

5/22に、農業総合研究所さんとの業務提携契約が公開されました。

www.agri-note.jp

農業総合研究所さんは、7000件以上の農家さんから野菜を集荷し、全国各地のスーパーなどに設置された直売コーナー「農家の直売所」に野菜を出荷している、農産物の流通・販売・コンサルティングを手がける農業ベンチャーです。

f:id:Nkzn:20180718110013p:plain

(上記のスクリーンショットは2018年7月18日現在のものです)

www.nousouken.co.jp

今回の業務提携により、共同でシステム開発を行っていくことになりました。プレスリリースにある通り、第一弾として農薬使用履歴を管理するアプリを開発しています。

技術選択

様々な要件や今後のウォーターセルとしての方針を加味したところ*2、Android, iOS, Webに向けてマルチプラットフォームで開発することになりました。

今回は特にAndroid, iOS, Webでプラットフォームごとに要件が違うということもなく、むしろ農総研さんの中での教育コストや、農家さんへの指導のコストを考えると、UIは揃っていたほうがよい、ということで、UIレベルでのソースコード共有を行う下地が整っていました。

AndroidとiOSについてはReact Nativeを使えばよいのですが、Webも、となると話が違ってきます。React NativeにはWeb向けのターゲットが用意されていません*3

React Native for Webとの出会い

ここで目をつけたのがReact Native for Webです。

github.com

React Native for Webは「React Nativeと同じ名前のコンポーネントに同じ名前のpropsが生えていて、スタイルの再現性が極めて高い、Web向けのUIコンポーネントライブラリ」です。次のように使います。

import { View, Text, StyleSheet } from "react-native-web";

このままでは「コンポーネントの種類とスタイルのクセがReact Nativeとよく似ているUIライブラリ」でしかありません。

実際に使う場合は、BabelやWebpackを使ってパッケージ名にエイリアスを付けることで、React Native向けに書かれたコードに対してパッケージを誤認させます。

import { View, Text, StyleSheet } from "react-native";
// Web環境向けにビルドした場合はWeb向けのViewが出てくる

このへんのやりかたについては公式チュートリアルで紹介されていますので、興味がある方はご覧ください。

React Native for Webは眉唾かどうか

パッと見ではかなり眉唾感のあるライブラリだと思います。が、検討していくうちに、外堀を見ている限りでは信頼に足るものなのでは?と思えるようになりました。

  • コードを読んでも特に怪しいところがなかった
  • 作者のnecolasがnormalize.cssを作った人(これだけでスタイルへの信頼性が爆上げ)
  • 作者のnecolasがTwitter Liteのテックリード(当時)
  • Twitter Liteでプロダクション投入されている*4

眉唾物のネタプロダクトと切って捨てるには、あまりにも素性が良かったのでした。

プロトタイピング

というわけで、触ってみることにしました。ExpoとReact Native for WebでUIコードを共有しつつ、画像や音声などのリソースも管理できるプロトタイプを作成しました。

React Native for WebとExpoを組み合わせてピコピコさせてみたよ - Qiita

github.com

感想としては次のようなものになりました。

  • スタイルについてはかなり再現性が高い
  • よく使うのに足りないコンポーネントがある(FlatListとか)ので代替実装を用意する必要がある
  • React NativeそのものではないのでNativeBaseやreact-native-elementsなどのUIライブラリは導入しづらそう
  • ネイティブモジュールを使う場合はWeb版にしっかりと代替実装を用意する必要がありそう*5

チームについて

技術選択は事業とチームにフィットするものであるべきです。ということで、どういったチームがあったお陰でこの選択をする気になったのか、という話もしなければなりません。

watercelldev.hatenablog.jp

以前にも記事にしたことがありましたが、弊社モバイルチームはiOSアプリをCordova+webpack+Reactで運用しており、Webアプリの開発については多少の経験があります。また、もともとAndroidエンジニアとしてゴリゴリ書いてきた人たちなので、Android側の挙動についてもトラブルシューティングは容易です。

iOSネイティブのトラブルシューティングだけは不安が残りましたが、こちらは外部の方にお手伝いしていただくことで解決できました。ここは今後の課題です。

各プラットフォームでネイティブな手段でも開発が可能なチームを作って、トラブルシューティングができる体制を整えられたのも、Webとネイティブの同時開発という選択ができた要因になりました。

結論

React Native for Web、けっこう行けるやん。ということで、採用と相成りました。*6

採用してみて良かったこと、困ったこと

実際に開発をしてみて、React Native for Webを採用して良かったこと、困ったことを挙げていきます。環境は次のとおりです。

モジュール バージョン
react / react-dom 16.0.0
react-native-web 0.5.4
react-native 0.51.1

良かったこと:ブラウザとStorybookでコンポーネント開発ができる

f:id:Nkzn:20180522121741p:plain

最も開発効率に寄与してくれたのが、Storybookの採用でした。React Native向けのコンポーネントをブラウザ上で動作確認するイテレーションを高速に回すことができました。

Storybookは前述のプロトタイプにも組み込んでありますので、興味がある方は御覧ください。

良かったこと:Webのマークアップエンジニアをアサインできた

Storybookを使ったことによるもう一つのメリットとして、普段WebフロントエンドのチームでReactコンポーネントの作成を担当しているマークアップエンジニアをアサインするのが、容易に実現できました。

  • HTML+CSSが主戦場
  • JSは得意ではない
  • Reactコンポーネントもレイアウトだけで良ければ作れる
  • CSS in JSは最近aphroditeで覚えた

くらいのスペックです。

実はStorybookにはReact Native向け実装がありまして、Androidエミュレータを立ててネイティブUIによる動作確認を行う方法もあります。しかし、今回彼をアサインするにあたり「今回のためだけにAndroid StudioやXcodeを入れてもらうのは申し訳ないな〜」という気持ちがありました*7

というわけで、今回は思い切って、彼にはブラウザのStorybookだけでコンポーネントの動作確認をしてもらうことにしました。

色々ありましたが(後述)、概ね上手くいったと思っています。当人も「 <View><Text> はdivとspanみたいなものだし、この2つのスタイルのクセを把握すれば、あとはその派生だったから問題なかった。React Nativeのコンポーネントづくりは、十分にマークアップエンジニアの土俵だった」という主旨の話をしており、手応えはあったようです。

困ったこと:本当にWebのノリでスタイルを書くとネイティブが壊れる

というわけで、マークアップエンジニア氏にはかなりの量のコンポーネントを作ってもらいました。そんな中で初期に困ったのが、「Webでしか使えないスタイル」の存在です。

React NativeのレイアウトシステムはYogaが使われているわけですが、これはCSSを再現するものではなく、Flexboxを再現するものです。そのため、Webでは一般的な記法でも、React Nativeでは使えないものがあります。当時に遭遇したものは widthmargin の値でした。Webでは良くてもReact Nativeではダメな例を挙げます。

// 値に単位をつけるのはNG
{
  width: '100px',
  marginLeft: '16px'
}

// 値を直接指定するのはOK
{
  width: 100,
  marginLeft: 16
}
// %やautoを使うのはNG(だった。現在では使えるらしい。後述)
{
  width: '100%',
}
{
  marginLeft: 'auto',
  marginRight: 'auto'
}

// Flexboxを使うのはOK
{
  flexDirection: 'row',
  flexGrow: 1
}
{
  justifyContent: 'center'
}
// ViewにText系のスタイルをつけるのはNG
<View style={{ color: "#F00" }}>
  <Text>ここには色がつかない</Text>
</View>

// 直接指定するのはOK
<View>
  <Text style={{ color: "#F00" }}>ここには色がつく</Text>
</View>

最後のTextスタイルの話は、違いを如実に表しています。CSSは内側に向かって再帰的に効きますが、React Nativeの装飾系のスタイルは適用したコンポーネントそのものにしか効きません。

当初、この問題は発覚しませんでした。ブラウザ上で動かしている分には普通のReactとReactDOMによる挙動に準拠するため、ちゃんと動いてしまっていたのです。その後、「そろそろAndroidでも動作確認するかー」と言い出したときにレイアウト崩れが起きて発覚した次第です。

発覚して以降は「Flexboxしか使えないならそれはそれで」ということで納得して書いてもらうことで、ほとんど問題は起きなくなりました。React Native公式ドキュメントでViewTextのスタイルを見ながらマークアップしていく様はとても頼もしいものでした。

アップデート:margin: autoや%表記について

検証した当時は marginLeft: 'auto'% 表記をするとエラーが出ていた記憶があるのですが、本記事を書くにあたって再度検証してみたところ、問題なく使えました。私たちは幻を見ていたのか……

v0.52.0でYogaのmarginLeft: auto%の扱いに手が入ったようなので、このときに直ったのかもしれません。

先程、マークアップエンジニア氏に報告したところ、強くガッツポーズをしておられました。

困ったこと?:flex-directionのデフォルトがcolumn

ネイティブとWebで挙動が逆で戸惑ったパターンとして、flex-directionのデフォルト値が違う、というものがありました。

プラットフォーム flex-direction
React Native (for Web) column
Web row

これに大きくハマったのがマークアップエンジニア氏で、align-itemsjustify-contentの効き方が逆になって、当初は大混乱していました(すぐに慣れました)。

普通のReact Nativeに慣れていた筆者にとってはいつもどおりだったので、まったく気になりませんでした・・・(逆に、たまに普通のWeb開発をするときにブラウザのデフォルトがrowなことに戸惑っています)

困ったこと:画面遷移は共通化できない(しづらい)

React Nativeの頻出課題として、画面遷移をどうやって実現するかという問題があります。AndroidとiOSで画面遷移の考え方が違っていて、どちらかというとiOSよりの考え方で作られていることが多いので、Android出身の筆者はよく混乱しています。

さて、Webの、しかもシングルページアプリケーションの画面遷移となると、ネイティブのそれとは輪をかけて違います。内部的にURIを定義したり、History APIでpushStateするくらいなら、ネイティブもWebも大差ない動きをするのですが、決定的に違う点として、Webでは直リンクやURL削りがカジュアルに行われます。どんなURLで来られても対応できるような対策がWebでの画面遷移には求められます。

画面遷移に関する考え方が違いすぎるので、画面遷移ライブラリはネイティブとWebで別々に用意して、Screenコンポーネントもネイティブ用とWeb用を用意し、Screenの中に配置するコンポーネント(or コンテナ)だけをネイティブとWebで共有することにしました。

それぞれのプラットフォーム向けに採用したライブラリは次のとおりです。

プラットフォーム ライブラリ
ネイティブ React Navigation
Web React Router

実はReact NavigationはWebで使えないこともないですし、React RouterはReact Nativeに正式対応しているのですが、下手に共通化するとまずそうな気配を感じたので避けました。もし今後、1つのライブラリで済ませる日が来るとしても、Screenコンポーネントはプラットフォームごとに分けると思います。

困ったこと:FlatListがない

React Native for Webのコンポーネントは、順次拡充されています。そのため、たまにコンポーネントが実装されていないことがあります。

一番困ったのはFlatListが未実装だったことです。リスト表示のパフォーマンスを向上させるためにも、できればあってほしかったのですが・・・

無いものは仕方ないので、内部実装を分岐することにしました。

// ChemicalList.tsx
export function ChemicalList(props: { chemicals: Chemical[] }) {
  return Platform.OS === "web"
    ? <ChemicalListWeb chemicals={props.chemicals} />
    : <ChemicalListNative chemicals={props.chemicals} />;
}

function ChemicalListNative(props: { chemicals: Chemical[] }) {
  return <FlatList ... />
}

function ChemicalListWeb(props: { chemicals: Chemical[] }) {
  return <ScrollView>
    { chemicals.map(chemical => ...) }
  </ScrollView>;
}

Web版の実装はデータ数が少ないので何とかなっているのですが、データが増えるとパフォーマンスに影響が出るので、時間を見つけてreact-virtualizedあたりに置き換えたいなあという気持ちはあります。

また、ChemicalList.native.tsxChemicalList.web.tsx にそれぞれ実装すれば勝手に切り替わる、くらいまでビルド環境が整備できればよかったのですが、時間切れで諦めてしまいました。そもそも native.tsnative.js という接尾辞をReact Nativeそのままでは認識できないはずなので、難易度は高そうです。サードパーティバンドラーのHaulにts-loaderを食わせる例ではnative.tsxを認識させようとしているので、今度はこの方向でチャレンジしてみたいです。

困ったこと:UIライブラリが使えなかった

前述のとおり、NativeBasereact-native-elements*8react-native-paperなどのUIライブラリがWeb側では使えませんでした。ボタンやヘッダーなどの共通コンポーネントはすべて自作しています(ここでもマークアップエンジニア氏にはめちゃくちゃ活躍してもらいました)。

困ったといえば困ったのですが、UIライブラリが使えたとしてもかなりのカスタマイズが入っていたことが予想されるので、もしかすると自作で良かった部分もあるのかもしれません。

こちらについても、.(android|ios|native).tsx.web.tsxの切り替えによって、各プラットフォーム向けのUIライブラリを採用できるビルド環境の整備を研究しているところです。

良かったこと:かなりの部分のソースコードが共有できた

結果的に、ソースコードのかなりの部分が共有できました。実際のファイル数を見ていきましょう。

今回のアプリケーションでは、Lernaでmonorepo化して依存性の方向を整理した、レイヤードアーキテクチャライクな構造になっています。プレゼンテーション層、アプリケーション層、ドメイン層、インフラストラクチャ層の4層になっています。ディレクトリ構造は次のとおりです。

  • packages/
    • presentation/src : UI周り。アプリとしての本体。
      • native/ : ネイティブにしか関心がない。
      • shared/ : 共有するコンポーネントやコンテナ。⭐
      • web/ : Webにしか関心がない。
    • application/src : 処理の交通整理を担当。⭐
    • domain/src : 仕様を直訳した型定義やアルゴリズムを担当。⭐
    • infrastructure/src : 外界との通信を担当。⭐

これらのうち、⭐が付いているディレクトリはネイティブからもWebからも参照される共有コードになっています。手元の最新リリースバージョンでファイル数をカウントしてみます。*9

$ find packages/presentation/src/native -type f | wc -l
      49
$ find packages/presentation/src/shared -type f | wc -l
     251
$ find packages/presentation/src/web -type f | wc -l
      65
$ find packages/application/src -type f | wc -l
      21
$ find packages/domain/src -type f | wc -l
      59
$ find packages/infrastructure/src -type f | wc -l
      87

(251+21+59+87)/(49+251+65+21+59+87) = 0.78571... で、約78.6%が共有できているようです。

共有したことに由来する問題は今のところチーム内でも聞いていませんし、まずまず共通化については大きな成果が出ているといえそうです。

良かったこと:普通に便利なコンポーネントがあった

ネイティブ開発の文脈だと「Flutterに比べて充実してないなあ」という気持ちになるReact Nativeのコンポーネントのラインナップですが、Webで使ってみると普通に嬉しいものがいくつかありました。

  • ScrollView
    • 何も考えなくても慣性スクロールがついてるの便利
    • スクロールを始めるとソフトウェアキーボードが隠れてくれる機能(keyboardDismissMode)が便利
  • ActivityIndicator
    • とりあえず置いとこう、みたいな気持ちでサクサク使える
    • ライブラリを見つけてきてもいいけど、ライブラリを探さなくても使えるのが重要
  • Switch
    • ライブラリを見つけてきてもいいけど、ライブラリを探さなくても使えるのが重要
  • TouchableOpacity
    • 指タッチに対応した、押すと透明度が変わる領域を簡単に作れる
    • これもCSSで弄りだすと手間がかかるので標準搭載されてるのが地味に嬉しい

今回の開発をしながら気付いたのですが、任意のUIフレームワークを自作していく際の材料としては、React Nativeのコンポーネント群は良いバランスをしているんじゃないかと思いました。Webしか開発しない場合にも使ってみたいです。

良かったこと?:contentContainerStyle問題がWebでもネイティブでも起きる

ScrollViewのスクロール部の内部にパディングをつけたい場合は、styleに指定するのではなく、 contentContainerStyle に付ける、というReact NativeのTIPSがあります。

なんとこのTIPS、React Native for WebでScrollViewを使っている場合にも有効です。

この挙動に気付いたときは「nicolasさんはどこまで再現しとるんや・・・」と戦慄したものでした。

まとめ

あまりReact Native for Webの導入事例を聞かないので、プレスリリースが出た記念にこれまでの開発を振り返ってみました。

一昔前までは眉唾または相当の無茶をしないと得られないと思っていた「Webとネイティブのコード共有」というテーマでしたが、そこまで無理をせずに実現できてしまい、戸惑いつつも嬉しく思っています。

「画面のサイズが同じなら、Webとネイティブでまったく同じ動きをしていい」という要件があったからこそ成り立ったものだったので、他所でも同じような選択ができるとは限らないと思いますが、参考になれば幸いです。

今後の保守や機能改善を行なっていく中で、また課題が見つかることもあるかと思いますが、各個撃破していきます。

We are Hiring!

ウォーターセル株式会社では、一緒にクロスプラットフォームなアプリケーションを作ってくれる、ReactエンジニアやAndroidエンジニアやiOSエンジニアを募集しています。

www.agri-note.jp

*1:弊社・農総研さんともに広報チェック済みです

*2:諸事情によりふわっとした言い方にしています

*3:などと言っていたら本当にそれっぽいものをシマンテックの人が作ってしまったのが5月半ばの話なのですが、これについては、個人ブログで紹介しましたので、興味のある方は御覧ください https://blog.nkzn.info/entry/2018/05/26/020312

*4:https://twitter.com/necolas/status/913877194199359488

*5:なお、ピコピコサンプルで音を出したときは、予想に反してWebのほうが実装は楽でした

*6:内容的に本記事と被るところも多いですが、導入の意義については個人ブログにもまとめさせてもらいました https://blog.nkzn.info/entry/2018/05/29/210030

*7:ディスク容量を結構持っていかれるのです

*8:Webへの対応を進めてはいるそうです

*9:本来であればandroid.js, ios.js, web.jsがshared内にある可能性を考慮すべきですが、今回は存在しないことを確認できているので、このカウント方法を採用しています

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