[React] 途中から既存アプリをPWA対応したくなった時 [なるべくコピペで]

# はじめに
この記事では，`React`アプリ開発を開始して，割と時間はたったけど「**やっぱりこのアプリPWA対応したくなったな**」って方に向けた記事です．

# 筆者の環境
```
WSL: Ubuntu-20.04
npm: 6.14.13
ブラウザ: Google Chrome
```

# もくじ
- [前提](#前提)
    - [現状確認](#現状確認)
        - [ディレクトリ構成](#ディレクトリ構成)
        - [package.json](#packagejson)
        - [manifest.json](#manifestjson)
        - [index.js](#indexjs)
        - [npm start](#npm-start)
- [PWA対応していく](#pwa対応していく)
    - [PWA対応ひな型を作成する](#pwa対応ひな型を作成する)
    - [必要ファイルをコピー](#必要ファイルをコピー)
    - [package.json書き換え](#packagejson書き換え)
    - [index.jsの書き換え](#indexjsの書き換え)
    - [manifest.jsonの書き換え](#manifestjsonの書き換え)
    - [server.jsを作成](#serverjsを作成)
    - [ホスティング設定](#ホスティング設定)
    - [最終的なディレクトリツリー](#最終的なディレクトリツリー)
- [PWAを動作確認](#pwaを動作確認)
    - [インストールして起動](#インストールして起動)
    - [PWAアプリのアンインストール](#pwaアプリのアンインストール)
- [おわりに](#おわりに)
- [参考記事](#参考記事)

# 前提
1. `npx create-react-app app-name`でプロジェクトを始めた人
1. アプリ開発を開始して少し時間たったけど，やっぱり`PWA`対応したくなった人
1. PWAとは何か知っている人

最初からPWA対応を視野に入れている方は，

```bash:コンソール
# Javascriptなら
npx create-react-app app-name --template cra-template-pwa

# Typescriptなら
npx create-react-app app-name --template cra-template-pwa-typescript
```
でPWA対応のReactアプリケーションのひな型で開発を始められます．

今回は，仮に`qiita`というReact(js)アプリの開発が既に進んでいるとして，このアプリをpwa対応していきます．

## 現状確認
今回書き換えを行うファイルの現状を確認していきます．
※あくまで例なので，注目すべきは完成後との差分です.
### ディレクトリ構成
```bash:ディレクトリ ツリー
qiita
├── README.md
├── package-lock.json
├── package.json
├── public/
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
└── src/
    ├── App.css
    ├── App.js
    ├── index.css
    ├── index.js
    └── logo.svg
```

### package.json
```json:package.json
{
  "name": "qiita",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.2",
    "@testing-library/react": "^12.1.3",
    "@testing-library/user-event": "^13.5.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "5.0.0",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
```
### manifest.json
```json:public/manifest.json
{
  "short_name": "React App",
  "name": "Create React App Sample",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    },
    {
      "src": "logo192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "logo512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}
```
### index.js
```jsx:src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
```
### npm start
[lighthouse][lh]でPWA対応していないことも確かめました．

|localhost|lighthouse|
|--|--|
|![スクリーンショット 2022-03-01 154514.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506931/be083573-3649-f848-ae44-accd652908f4.png)|![スクリーンショット 2022-03-01 154447.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506931/d9fcf847-b6b6-8d36-5fff-1ec28f018d0d.png)|

# PWA対応していく
なるべくコピペで行えるようにしたいと思います！
常にディレクトリはPWA化したいプロジェクトのルートディレクトリ(`qiita/`)とします．

:::note warn
言語は`JavaScript`を使用します！
`TypeScript`の方は完全なコピペとはいかないかもしれませんが，参考程度に使ってください
:::

## PWA対応ひな型を作成する
親ディレクトリに新たにPWA用ひな型に沿ったアプリ作成して、必要なファイルを拝借するための準備を行います．

```bash:コンソール
npx create-react-app ../pwa --template cra-template-pwa

# Typescriptなら
npx create-react-app ../pwa --template cra-template-pwa-typescript
```
## 必要ファイルをコピー
ひな型から，ServiceWorkerを拝借します．

```bash:コンソール
cp ../pwa/src/service-worker.js ./src
cp ../pwa/src/serviceWorkerRegistration.js ./src
```

## package.json書き換え
必要なライブラリを`dependencies`に追記していきます．

```js:package.json
{
  "name": "qiita",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.2",
    "@testing-library/react": "^12.1.3",
    "@testing-library/user-event": "^13.5.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "5.0.0",
    "web-vitals": "^2.1.4",
// -- ここから追加　--
    "workbox-background-sync": "^5.1.4",
    "workbox-broadcast-update": "^5.1.4",
    "workbox-cacheable-response": "^5.1.4",
    "workbox-core": "^5.1.4",
    "workbox-expiration": "^5.1.4",
    "workbox-google-analytics": "^5.1.4",
    "workbox-navigation-preload": "^5.1.4",
    "workbox-precaching": "^5.1.4",
    "workbox-range-requests": "^5.1.4",
    "workbox-routing": "^5.1.4",
    "workbox-strategies": "^5.1.4",
    "workbox-streams": "^5.1.4"
// -- ここまで --
  },
// ※省略
  }
}
```
インストールします．

```bash:コンソール
npm install
```

## index.jsの書き換え
`src/index.js`をPWA対応用に書き換えます．

```jsx:src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';  // 追加

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

serviceWorkerRegistration.register();  // 追加
```

## manifest.jsonの書き換え
[SimiCart][sc]などの`manifest.json`とアイコン画像群を作成してくれるサービスで，簡単にPWA対応用のマニフェストを作成できます．
必要項目を入力して，「GENERATE MANIFEST」ボタンをクリックします．
アイコンには512x512の画像ファイルを入れてください．

すると，zipファイルがダウンロードされ，アイコンのセットとマニフェストが入っています．

|SimiCart|zipの中身|
|-|-|
|![スクリーンショット 2022-03-01 162748.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506931/6c64ad20-fbdf-387c-b8cb-428f001cce41.png)|![スクリーンショット 2022-03-01 154447.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506931/30c22502-aed3-66ac-a692-3732adcb4901.png)|

manifest.json内の`icons`の配列内をコピペしてきます．
192x192にはAndroidのインストール時に使用されるマスカブルアイコンの設定として，`"purpose": "maskable"`を追加しておきます．

既に使用したいアイコンファイルがあって，全サイズ独自に用意された方は，適宜ファイル名を読み替えてください．

```js:src/manifest.json
{
  "short_name": "React App",
  "name": "Create React App Sample",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    },
// -- ここから書き換え --
    {
      "src": "/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable"
    },
    {
      "src": "/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    },
    {
      "src": "/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
// -- ここまで --
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#bd10e0",
  "background_color": "#4a90e2"
}
```

そして，既存のアイコンファイルを削除し，新たに生成されたものをコピーします．

|既存の状態|追加しなおした状態|
|--|--|
|![スクリーンショット 2022-03-01 164014.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506931/abaeb446-ae8c-4904-8800-17bf198c606d.png)|![スクリーンショット 2022-03-01 164021.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506931/b82ccf8a-67ea-7e20-a2e9-b5794ec96aa4.png)|

## server.jsを作成
PWAを行うためには，Reactアプリをビルドして何らかの形でホスティングします．
今回は，NodeJSではおなじみのExpressを使用していきます．
参考：[Deploy ReactJs Progressive Webapp On Heroku](https://medium.com/zestgeek/deploy-reactjs-progressive-webapp-on-heroku-abb809369d5)

```bash:コンソール
npm install express
touch server.js
```

```js:server.js
const express = require('express');
const path = require('path');
const port = process.env.PORT || 3000;
const app = express();
app.use(express.static(__dirname));
app.use(express.static(path.join(__dirname, 'build')));
app.get('/*', function (req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(port);
if (port === 3000) {
  console.log(`Now hosting at "http://localhost:${port}/"`);
}
```
## ホスティング設定
`package.json`を再び書き換えます．
デフォルトのホットリローディングの利く方は`npm run local`という呼び出しに書き換えました．

```js:package.json
{
// ※ 省略
  "scripts": {
// -- 書き換え --
    "start":"node server.js",
    "local": "react-scripts start",
// -- ここまで --
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
// ※ 省略
  }
}
```

## 最終的なディレクトリツリー
```bash:ディレクトリ ツリー
qiita
├── README.md
├── build/
├── package-lock.json
├── package.json
├── public/
│   ├── favicon.ico
│   ├── icon-192x192.png
│   ├── icon-256x256.png
│   ├── icon-384x384.png
│   ├── icon-512x512.png
│   ├── index.html
│   ├── manifest.json
│   └── robots.txt
├── server.js
└──  src/
    ├── App.css
    ├── App.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── service-worker.js
    └── serviceWorkerRegistration.js
```

# PWAを動作確認
まずは，コンソールでビルドとサーバの起動を行います．

```bash:コンソール
npm run build
npm start
```

そして，http://localhost:3000/ もしくは，ご自身で設定されているアプリのパスへアクセスしてください．

私の場合以下のようになりました．

![スクリーンショット 2022-03-01 175227.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506931/7693e443-2c4c-741d-66ee-adfd05c22503.png)

この黄色で囲っている部分ですが，PWAに対応した場合，これらが追加されるかと思います(Google Chromeなら)．
PCであればここをクリックすることで，手元のPCにローカルホスト版のPWAアプリケーションがインストールされると思います.

一応，lighthouseで確認してみましょう．

![スクリーンショット 2022-03-01 181620.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506931/5740850f-cd2f-a6ad-54ff-ea0968a156fe.png)

これで，PWA対応の完了です．

## インストールして起動
インストールすると，デスクトップにアイコンが追加されると思います．

そちらを開くとこんな具合に，PWAのアプリケーションとして開くことができます．

![スクリーンショット 2022-03-01 184314.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506931/f962a293-3d1c-dfb5-0d9c-f385ef570c1b.png)

画面上部に邪魔なURL欄などなく，あたかもネイティブアプリケーションかのようにふるまってくれます．

クラウドなどにデプロイしておけば，スマホに追加することもできます．


## PWAアプリのアンインストール
一応アンインストール方法を紹介しておきます．
画面上部の三点リーダーをクリックすると，「{app-name}をアンインストール」という項目が出るので，それをクリックするだけです.

![スクリーンショット 2022-03-01 185204.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506931/6fe6bd63-3319-c42a-9a06-b2687910b2f9.png)

# おわりに
皆様はうまくいきましたでしょうか？

はじめはPWA対応する予定がなく，後からというシチュエーションは割とあるかと思います．

そんな方の一助になればと思います．

それでは:wave:

# 参考記事
- [Deploy ReactJs Progressive Webapp On Heroku](https://medium.com/zestgeek/deploy-reactjs-progressive-webapp-on-heroku-abb809369d5)
- [create-react-app と ServiceWokerで PWA を実装する](https://gg-box.com/2020_12/2020_12_05_reate-react-app_sw/)
- [マニフェストにマスカブルアイコンがない](https://web.dev/maskable-icon-audit/?utm_source=lighthouse&utm_medium=devtools)

[lh]:https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk?hl=ja
[sc]:https://www.simicart.com/manifest-generator.html/