Cordova + React で Webブラウザ+Android+iOS アプリ その1

#はじめに
先日、　Cordova + React　を利用したアプリのリリースを終え、改めて React Native や Native Base も触ってみたのですが、やはり現状ではフォームのバリデーションなどを考えると、パフォーマンスさえ気にしなければ既存の資産を活かしやすいCordova + React の方に軍配が上がる気がします。
しかし、2017年7月の時点ではそれほど日本語でのドキュメントも揃っているわけでは無く、あってもバージョンが古かったりして、制作には苦労しました。

そこで、改めて Cordova + React でハイブリッドアプリを制作する方法について、まとめてみたいと思います。
また、Webブラウザ上（React単体）でも完全動作することを目指しています。その為、他のサイトの説明とは若干違うかもしれません。

#前提
- MacOS High Sierra で開発
- 現時点(2018年5月)で最新のXcode と Android　Studioをインストール
- node は v9.5.0　 ([nodebrew](https://qiita.com/tabolog/items/da18143e70f40e356b5d) 利用)
- パッケージマネージャーは [yarn](https://yarnpkg.com/lang/ja/) を利用　（`brew install yarn --without-node`）

#必要なツールのインストール
react 部分の準備には　[create-react-app](create-react-app) を使わせてもらいます。あちこちで言及されていると思いますが、非常に便利です。なるべく簡単に作りたいので　eject等は行いません。諸事情により cordova のバージョンは v7.1.0です。

````bash
$ yarn global add cordova@7 
$ yarn global add create-react-app
````

#プロジェクトの作成
自身の作業フォルダにて

```bash
$ cordova create [プロジェクト名]
$ cd [プロジェクト名]
$ cerate-react-app app
$ mv app react
```
一部記述を修正：
余談ですが、この[プロジェクト名]を識別子としてパッケージIDとする場合が多いと思いますが、ハイフンやアンダーバーを含めているとGoogleやAppleに登録する時に[面倒なことになります](https://qiita.com/umi_uyura/items/c6269861870b5d2ec9d5)。また、先頭に数字も利用できません。ですので、この時点からプロジェクト名をそのような命名規則にしておくと、あまりトラブルがありません。わざわざ `app` という名前で作成してから `react` に変更しているのは、デフォルトだと指定できないからです。

#webアプリの起動
reactがWebで起動することを確認して下さい。

```bash
$ cd react
$ yarn start
```
ブラウザが自動的に起動し、 `http://localhost:3000/`　にて動作が確認出来ます。

#cordovaでの起動
##ファイルの修正
###ビルドディレクトリの変更
[プロジェクト名]以下は次のようなディレクトリ構成になります。

```
├── config.xml
├── package.json
・・・
├── react
│   ├── package.json
│   ・・・
│   ├── public
│   └── src
├── res
└── www
    ├── css
    ├── img
    ├── index.html
    └── js
```

corodvaをビルドすると、デフォルトでは `www/`　以下がマウントされます。
しかし、react (`react/`　以下)をビルドすると `react/build/` にビルドソースが吐き出されますので、次のように修正します。

```bash
$ rm -fR www
$ mkdir react/build
$ ln -s react/build www
```

次のようになります。

```
・・・
├── react
│   ├── build
│   ├── package.json
│   ・・・
│   ├── public
│   └── src
├── res
└── www -> react/build
```
###`react/package.json` の修正
react でビルドしたファイル構成をそのままcordovaでマウントしても、`index.html`内にjsやcssが絶対パスで記述されているため、モバイルデバイス内でのパス構造と一致せず、読み込むことが出来ません。
 `react/package.json`　に　`'homepage':".",` を追記するとビルドしたときに `index.html`からの相対パスでjs,css を読み込ませることが出来ます。




###`react/pubilc/index.html`の修正
reactプログラム内でcordovaの存在を認識させるために、 `react/public/index.html`で `cordova.js`の読み込みを行います。この `cordova.js`はwebブラウザアプリでは存在しないことになり、読み込みのエラーが発生しますが動作には問題ありません。
他のサイトでは `</body>` タグの直前に入れているケースが多いですが、今回は `<head>`内に入れます。理由は、この場所に入れておくと　`<base　href="" />` タグの出力をコントロールすることが出来るからです。Webアプリをサイトのサブディレクトリにマウントしたり、~~cordovaアプリで`react/public/`以下に設置した静的コンテンツの表示を行うことが可能になります。~~

```html:react/public/index.html
  <title>React App</title>
  <script type="text/javascript" src="cordova.js"></script>
  <script>
    if(window.cordova){
      //document.write('<base href="..."/>);
    }else{
      //document.write('<base href="..."/>);
    }
  </script>
```

その他、以下の修正を行いました。

- `html lang`を `en`から`ja`に変更
- `meta name="viewport"`に画面の拡大、縮小を禁止する記述を追記(`viewport-fit=cover`の記述については[iOS11対策です。](https://qiita.com/daisukeoda/items/2b8cc28f176e202976b7))
- `meta name="format-detection"` タグを追加。電話番号や住所表記に対してリンクが張られるのを防ぐ。
- `meta name="msapplication-tap-highlight"`　タグを追加。[リンクの強調表示を防ぐ](https://hacknote.jp/archives/10360/)。
- (追記)`meta http-equiv="Content-Security-Policy"` タグを追加。[ライブラリの実行制限を解除](https://qiita.com/s_of_p/items/925f512f8bec2e5959db)

```html:react/public/index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1,shrink-to-fit=no, minimum-scale=1, maximum-scale=1,user-scalable=0,viewport-fit=cover">
    <meta name="format-detection" content="telephone=no,email=no,address=no">
    <meta name="msapplication-tap-highlight" content="no">
    <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: file: https://ssl.gstatic.com ; style-src * 'self' 'unsafe-inline'; script-src * 'self' 'unsafe-inline' 'unsafe-eval'">
```
###`react/src/index.js`の修正
記述を忘れていたので追記

```javascript:react/src/index.js
ReactDOM.render(<App />,document.getElementById('root'));
registerServiceWorker();
```

の部分を以下のように変更

```javascript:react/src/index.js
const startApp = () => {
  ReactDOM.render(
    <App />,
    document.getElementById('root')
  );
}
if(window.cordova) {
  document.addEventListener('deviceready', startApp, false);
} else {
  startApp();
  registerServiceWorker();
}
```

cordovaのプラグインは　devicereadyイベントが発生してからでなければ利用できないため、起動プログラム部分をコンポーネント化して `document.addEventListener()`に入れています。
Webブラウザからの場合は今までと同じように起動します。
`registerServiceWorker（）`はcordovaからの起動の場合は必要無い（と思う）。

##cordova パッケージのインストール

```bash
$ cordova platform add android
$ cordova platform add ios
```
##ビルド

```bash
$ cd react
$ yarn build
$ cd ..
$ cordova emulate ios --iOSエミュレータが起動
$ cordova emulate android -- androidエミュレータが起動
```

#おわりに
興味があるようでしたら以下の内容で続きを書くかもしれません。
解説は今回と同レベルを維持したいと思います。
いくつかは最近ドキュメントが揃ってきているので不要かな？

- [React router v4](https://github.com/ReactTraining/react-router/tree/master/packages/react-router-dom) を使ったルーティング
- [redux-saga](https://github.com/redux-saga/redux-saga/blob/master/README_ja.md)を使ったFlux(大規模アプリに対応したディレクトリ構造)
- [redux-persist(v5)](https://github.com/rt2zz/redux-persist) を使った永続化
- [react-redux-form](https://github.com/davidkpiano/react-redux-form) を使ったフォーム入力、バリデーション
- iOS Androidのアプリの見た目を整える
- アプリのストアへの公開
- Firebaseを使ったアプリのプッシュ通知