ExpoとReact Native Webを使ってコードを書かずに作ってみるPWA入門

この記事は[React Native Advent Calendar 2020](https://qiita.com/advent-calendar/2020/react-native) の18日目の記事です。

こんにちわ、最近さっぽろでコツコツ生きてるたかぎと申します。
人生初のアドベントカレンダーだったにもかかわらず当日まで何を書こうかまったく考えてなかったのは僕のせいじゃないと思います。

はてさて、今回は Expo と React Native Web という技術を使ってこれからのウェッブの時代の理想形である PWA（Progressive Web Application）をさくっと作ってみようという入門記事です。ほとんどコードは書きません。コードを書かなくても一応 PWA が作れるというめちゃくちゃお得な構成になっています。読んで損はないはずですです。

~~言い訳ですが、間違っている個所がたぶんにあると思いますがご了承ください。あと、内容があっさりしすぎているのは決して忙しいことを理由にまったく記事に手を付けていなかったからでもないです。たぶん~~

# TL;DR

コード自体を知りたい方は[こちらのリポジトリ](https://github.com/takagimeow/pwa-sample)を参照してください。

# 準備

まず Expo CLI をインストールします。

Expo CLI は Expo SDK を使って React Native アプリケーションの開発を行うのに必要なツールです。今回は PWA のみにターゲット絞って説明をしていくため本来のネイティブアプリの開発に必要な設定だったりの詳しい説明は省きます。

Expo SDK と JavaScript や TypeScript を組み合わせて iOS や Android 向けにアプリを開発し、Expo Client App を使って iPhone や Android スマートフォンの上で、開発したアプリを実行するためにはこの Expo CLI を使ってプロジェクトを外部に公開する必要がありますってことだけ覚えていてもらえれば良いです。あとは一番大事かもしれませんが、Expo CLI を使えば iOS と Android 向けのビルドが簡単にできるってこともテストに出ると思います。

```bash
$ npm install --global expo-cli
```

CLI のインストールが完了したら、プロジェクトを作成します。
おそらく土のテンプレートを使って開発を進めるか聞いてくると思います。

このとき上にアプリの名前やアプリ上での現在滞在しているスクリーンのタイトルを表示するヘッダーとか、ホームや設定といったアイコンを表示するボトムタブナビゲーションを最初から導入して開発したい場合は`tabs (TypeScript)`を選択してください。見た目も THE アプリという感じになってくれるので今回はこちらを選択します。

ちなみに、expo はなにも指定しなければ yarn を使って開発していくことになりますので、npm を使いたい場合は --npm オプションを渡してコマンドを実行してください。今回は yarn を使ってそのまま開発を進めます。

```bash

# yarn
$ expo init pwa-sample

# npm
$ expo init pwa-sample --npm
```

![expo_init.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577119/4502f018-6731-b6f2-a7c9-16c5da48935e.png)

![expo_init_done.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577119/2d12a9bb-f321-98a4-899c-eef1f6bc5b31.png)


プロジェクトの作成が完了したら `Your project is ready!`と表示されますので、プロジェクトに移動します。

```bash
$ cd pwa-sample
```

# Hello World を表示する

コードはほとんど書かないと言ったものの、なんかプログラミングぽいことしたいと思いました。なので最初に画面に表示される文言をすこし変えてみましょう。

screens/TabOneScreen.tsx を開きます。このファイルから export されているコンポーネントである TabOneScreen は navigations/BottomTabNavigator.tsx ファイルの BottomTabNavigator に最初の Screen として登録されている TabOneNavigator の最初のスクリーンとして登録されているので、最初にアクセスした際に一番初めに表示される画面の役割を担います。

Text コンポーネントに挟まれている Tab One という文字列を、Hello World に置き換えます。

```tsx
export default function TabOneScreen() {
	return (
		<View style={styles.container}>
			<Text style={styles.title}>Hello, World</Text>
			<View
				style={styles.separator}
				lightColor="#eee"
				darkColor="rgba(255,255,255,0.1)"
			/>
			<EditScreenInfo path="/screens/TabOneScreen.tsx" />
		</View>
	);
}
```

実際に表示されるか確認してみます。

```bash
$ yarn expo run start:web
```

Hello, World と表示されたらうまくいった証です。

![success.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577119/2b6971e5-cf7e-7f3c-f43a-3e6ebaaa1226.png)


# オフラインで動くようにする

ここからが本番です。アプリをインストール可能にするためにはインターネットにつながっていなくてもアプリがきちんと動けるようにしなくてはいけません。せっかくアプリをインストールしたのに真っ暗な画面しか表示されなかったりしたら悲しいですもんね。

Expo が自動的に作る Webpack の config には PWA の開発に必要なプラグインなどの記述がすでに設定されています。ですが、デフォルトではオフラインでのサポートのみオフ（false）になっています。
なので、これをオン（true）にしてあげることによってインストール可能な状態にしてあげます。

はじめに Webpack のコンフィグファイルをイジェクトします。

## イジェクトとは

イジェクトを説明する前に、ひとつだけ。
先ほどインストールした expo-cli、モバイルデバイスにインストールされる Expo Client App、そして[push notifications](https://docs.expo.io/push-notifications/overview/)、[build service](https://docs.expo.io/distribution/building-standalone-apps/)、[OTA（over-the-air）](https://docs.expo.io/guides/configuring-ota-updates/)を通して React Native のアプリを開発することは managed workflow と呼ばれています。なぜ Managed Workflow かというと、iOS や Android アプリを開発しようとするとどうしても関わってくる複雑な設定や手続きを Expo が代わりに補ってくれるから Managed Workflow という名前が付けられています。
このおかげで Xcode や Android Studio を使わなくても JavaScript でコードを書いてとアプリのアイコンとスプラッシュ画像（起動時に表示される画像）さえ用意すればいとも簡単に iOS と Android 向けにアプリを開発することができるというわけです。

ただ、Managed Workflow では完全なデバイスやサービスに対するコントロールを得られることができず、制約の中で開発を行っていかないといけません。これによって Firebase が提供してくれているサービスをフルに使うことができなかったりします。

> ただ、現在では Firebase のほとんどの機能を Managed Workiflow でもサポートされてきていますので、その点についてはあまり心配しなくてもいいように思います。

なのでその制限をとっぱらい、Expo の管理からの脱却を完全な制御を得る行為を Ejecting to Bare Workflow とといます。完全な制御を得た状態のことを、Managed Workflow の対極として Bare Workflow と名付けられています。

そして開発者が意識なくてもいいように Expo が自動的に設定してくれるコンフィグファイルの中には Webpack も存在するのですが、今回オフラインでのサポートを可能にするために手動で Webpack を設定しないといけません。そのために、webpack.config.js ファイルのみの eject を行いたいと思います。

> WebpackのWorkboxプラグインがPWAに必要なServiceWorkerをビルド時に自動で作成してくれます。

下記のコマンドを実行します。実行すると、スペースバーで選択可能なラジオボタンの一覧がリストで表示されます。その中から**webpack.config.js**を選択してください。

```bash
$ expo customize:web
```

プロジェクトのルートフォルダにイジェクトされた webpack.config.js ファイルの修正を行い、`offline: true`を`createExpoWebpackConfigAsync`メソッドにオプションとして与えます。スペースバーで選択したらエンターキーを押下します。

```js
// /webpack.config.js

const createExpoWebpackConfigAsync = require("@expo/webpack-config");

module.exports = async function (env, argv) {
	const config = await createExpoWebpackConfigAsync(
		{
			...env,
			offline: true,
		},
		argv
	);
	// Customize the config before returning it.
	return config;
};
```

## manifest.json ファイルと app.json ファイル

PWA としてインストール可能なアプリを開発するには manifest.json ファイルの存在が欠かせません。ただ、managed workflow を使って開発を行う場合は manifest.json ファイルを自分で作らなくても app.json ファイルに記述した内容がビルドしたときに manifest.json ファイルの内容として登録しらもらうことができます。

| キー                      | 説明 |
| ------------------------- | ---- |
| favicon                   |   ファビコン   |
| backgroundColor           |   スタイルシートが読み込まれる前に表示する背景色。つまり、アプリの起動時にスプラッシュ画像の背景として表示される色   |
| description               |   アプリの説明   |
| dir                       |   書字方向   |
| display                   |   アプリの表示モード   |
| lang                      |   アプリの主言語   |
| name                      |   アプリの名前   |
| orientation               |   画面の向き   |
| scope                     |   PWAとして認識されるナビゲーションスコープ   |
| shortName                 |   アプリの名前の略称   |
| startUrl                  |   アプリの起動時に表示されるURL   |
| themeColor                |   アプリのテーマカラー   |
| preferRelatedApplications |   今回はPWAを前提に進めているのでfalseで大丈夫   |

上記の内容は expo.web の中に記述します。

```json
{
	"expo": {
		"name": "PWA サンプル",
		"slug": "pwa-sample",
		"version": "1.0.0",
		"orientation": "portrait",
		"icon": "./assets/images/icon.png",
		"scheme": "pwa-sample",
		"userInterfaceStyle": "automatic",
		"splash": {
			"image": "./assets/images/splash.png",
			"resizeMode": "contain",
			"backgroundColor": "#ffffff"
		},
		"updates": {
			"fallbackToCacheTimeout": 0
		},
		"assetBundlePatterns": ["**/*"],
		"ios": {
			"supportsTablet": true
		},
		"android": {
			"adaptiveIcon": {
				"foregroundImage": "./assets/images/adaptive-icon.png",
				"backgroundColor": "#FFFFFF"
			}
		},
		"web": {
			"favicon": "./assets/images/favicon.png",
			"backgroundColor": "#ffffff",
			"description": "PWAのサンプル",
			"dir": "auto",
			"display": "standalone",
			"lang": "ja",
			"name": "PWA サンプル",
			"orientation": "portrait",
			"scope": "/",
			"shortName": "サンプル",
			"startUrl": "/",
			"themeColor": "#ffffff",
			"preferRelatedApplications": false
		}
	}
}
```

## PWA としてアプリを起動する

これでインストール可能な PWA の準備が終わりました。それでは早速ビルドしてみましょう。

```bash
expo build:web
```

ビルドが成功したら、ためしに以下のコマンドを実行して localhost 上でアプリを起動してみます。

```bash
npx serve web-build
```


実は、オフラインでアプリが動いてくれる最大の要因は裏で動いてくれている Service Worker です。詳しい説明は省きますが、この Service Worker が動いてくれる条件がセキュリティ上の理由で https といったセキュアなオリジンのみとなっています。ただし、localhost からのアクセスについてはこの限りではないため、今回は localhost 経由でアクセスを試みたいと思います。

localhost:5000 にアクセス。インストール可能な場合はプラスアイコンがURLバーに表示されています。

![install_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577119/494fa8cc-a0e2-6126-f32f-315fd63537f3.png)

インストールして、起動してみましょう。

![install_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577119/86c14e0c-2c33-7b3c-783c-157e714e978e.png)


![app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577119/680cb91b-bcec-7df1-17ff-35860c547979.png)

上記の画面が表示されれば成功です！！！



> 過去に localhost:5000 で何かを実行していて Service Worker 等が動いていた場合はその時のアプリが表示されている可能性があるので、dev tools を開いて、Application タブから Clear Site Storage を選んで Clear Site Data をクリックする。何回か更新をかければ今回作ったアプリが表示されます。

# まとめ

いかがだったでしょうか？Expo と React Native Web を使えばほとんどというかまったくコードを書かずに PWA を実装することができるのです。

今回作成したアプリは Firebase Hosting を使ったりしてデプロイすればすぐにでもインターネット上からアクセスしインストール可能な PWA が出来上がります。ぜひ試してみてください。

# 今回開発したアプリのリポジトリ

[PWA SAMPLE](https://github.com/takagimeow/pwa-sample)

# 参考記事

[Progressive Web Apps](https://docs.expo.io/guides/progressive-web-apps/)
[Enabling web service workers](https://github.com/expo/fyi/blob/master/enabling-web-service-workers.md)
[ウェブアプリマニフェスト](https://developer.mozilla.org/ja/docs/Web/Manifest)