React + vite によるPWAアプリ

# はじめに
12月に社内で技術交流会が開催されました。
私も参加し、15分程度発表をさせて頂きました。
しかし、準備した発表資料は半分ほどしか発表できず、日の目を浴びることがなかった部分が出てしまいました。
・
・
・
というのも、、、
当初、本記事のタイトルの通り`React + vite によるPWAアプリ`について発表しようと思っていました。
せっせとサンプルアプリ構築を済ませ、発表資料を作成していたのですが、、、
若手であったり、PWAを知らない人もいるだろうな思い。
`PWA`とは何なのかを説明する資料を準備しました。
結構まじめに`PWA`について資料を準備したせいで、発表は`PWA`についての説明までしか出来ませんでした。
サンプルアプリについての説明は当日は触り程度しか実施しませんでした。
・
・
・
ですので、日の目を浴びることが出来なかった本記事のタイトルとなっている`React + vite によるPWAアプリ`について記載しようと思います。
また、`npx create-react-app my-app`で作成するReact環境とは違いデフォルトでマニュフェストファイルが作成されず、`pwa-vite-plugin`パッケージを利用する必要があった為、備忘録としても記載したいと思います。

ちなみに、ちょうどアドベントカレンダー期間だったのもあり、、、
急遽探して、参加させていただいました！！
本記事は [React Advent Calendar 2023](https://qiita.com/advent-calendar/2023/react)の22日目となります！！

# 本題
というわけで、本題のReact + viteによるPWAアプリを作成していきます。

## 作成したアプリ
10秒間で何回タップできるか？というアプリです。
アプリ自体は以下のGithubサンプル(PWA Nightコミュニティーで紹介されていました)を真似して作成しました。
参考にさせて頂いた[サンプル(Github)](https://github.com/KanDai/tap10_pwa)です。
こちらを`React+vite`で置き換えました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222088/ecb049e8-a03b-db8a-05fe-108836724514.png" width="30%"　>


## 環境
開発エディター： `Visual Studio　Code`
node:　`LTS(※18+、20+以上のバージョンが必要です。)`
npm:　`LTS`
PWAアプリ構築に必要なnodemodule:　`vite-plugin-pwa`
公開用Webpage: `Github pagesを利用`

## React+vite の環境作成
環境作成については以下を参考にしました。
[viteでReact×TypeScript環境を爆速で作る最小版](https://qiita.com/teradonburi/items/fcdd900adb069811bfda)
今回はPWAのサンプルアプリ作成が趣旨なので、`ESLint, Prettier導入
`、`CSSライブラリ導入`はしていません。
また、viteの公式ドキュメントは[こちら](https://ja.vitejs.dev/guide/)。

初回に作成したReact+vite環境で、不要なファイルについては以下の対応を実施
・index.cssは不要なので読み込まれないように設定
・初期のApp.tsxのコードは作り替えます
・初回で作成した環境でpublicディレクトに存在していた不要な画像、svgファイル削除


## 10秒間のサンプルアプリを実装
App.tsxに実装し、該当のデザインはApp.cssに作成。
ここは、PWAの趣旨とは違うので以下のGithubを確認ください。
[ソースコード](https://github.com/njc50047/pwa-sample-pj)

## PWAの実装
### アイコンの作成
私はmac端末でしたので`KeyNote`で適当にアイコンの元画像を作成しました。
作成した画像を、[PWA Manifest Generator](https://www.simicart.com/manifest-generator.html/)等のサービスを利用してアイコンを作成していきます。
※その他サービスでは[pwabuilder](https://www.pwabuilder.com/imageGenerator)があります。
アイコンは以下のサイズを準備します。
- 180×180 // ファイル名の例：apple-touch-icon.png
- 192×192 // ファイル名の例：pwa-192×192.png
- 256×256 // ファイル名の例：pwa-256×256.png
- 384×384 // ファイル名の例：pwa-384×384.png
- 512×512 // ファイル名の例：pwa-512×512.png
- favicon.ico

apple-touch-icon.png は作成済のロゴのsvgファイルからInkscapeにて180×180サイズで作ります。
基本デバイスごとで上記のサイズを準備する必要がありますが、今回私は「apple-touch-icon.png」「pwa-192×192.png」「pwa-512×512.png」のみ準備しました。
作成したアイコンについてはpublicディレクトに配置します。

### vite-plugin-pwaのインストール
vite にはManifestの設定やServiceWorkerの登録するためのプラグインが準備されています。
以下でパッケージをインストールをします。
```
npm install vite-plugin-pwa -D
```
また、インストールが終わったら、`vite.config.ts`を編集します。
`vite.config.ts`の編集でManifestの設定や、デフォルトのServiceWorkerの設定ができます。
それぞれの設定はこの後の`Manifest`、`ServiceWorkerの登録`で説明します。

### Manifest
では、`vite.config.ts`を編集してManifestを設定します。
buildした時に、Mnifestのjsonファイルを`vite.config.ts`の設定を元に作成してくれます。
また、index.htmlにも追加してくれます。
```typescript:vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import {  VitePWA } from 'vite-plugin-pwa' // 追加

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    // ★★★以下を追加★★★
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico', 'apple-touch-icon.png'],
      manifest: {
        name: 'PWA Sample Apps',
        short_name: 'PWAApps', 
        description: 'PWAサンプルアプリ',
        theme_color: '#ffffff',
        icons: [
          {
            src: 'pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png'
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'any'
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable'
          }
        ]
      }
    })
    // ★★★ここまで！！★★★
  ],
})
```

### ServiceWorkerの登録
ServiceWorkerは直接スクリプトを書くと大変です。
ですので、基本は[Workbox](https://developer.chrome.com/docs/workbox/what-is-workbox?hl=ja)を利用します。
インストールした`vite-plugin-pwa`にはWorkboxを内部的に入っているので、こちらのプラグインで登録をしていきます。
今回は、`injectRegister`を`auto`で設定しデフォルトのServiceWorkerを登録します。
`vite.config.ts`に以下を追加します。

```typescript: vite.config.ts
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    VitePWA({ 
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico', 'apple-touch-icon.png'],
      injectRegister: 'auto', // 追加
      manifest: {
       ...省略
      }
    })
  ],
})
```

autoの設定をすると、自動でServiceWorkerのjsファイルがbuildをした段階で作成されます。
また、手動で設定したい場合は、injectRegisterに`inline`等を設定することで可能です。
他には`script`等の設定があります。
詳しくは[ServiceWorkerの登録](https://vite-pwa-org.netlify.app/guide/register-service-worker.html#register-service-worker)を参照。
そして、以下のindex.htmlに`<script>`を追加することで手動登録ができます。
```html:index.html
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
    <!-- PWA -->
    <script>
      if ('serviceWorker' in navigator) {
        window.addEventListener('load', () => {
          navigator.serviceWorker
            .register('./serviceWorker.js')
            .then((reg) => console.log('サービスワーカーの登録成功', reg.scope))
            .catch((err) => console.log('サービスワーカーの登録失敗', err));
        });
      }
    </script>
  </body>
```
そして、ここでindex.htmlで設定している`'./serviceWorker.js'`をpubulicディレクトに作成をします。
作成するServiceWorkerの処理の例は以下となります。
```javascript:serviceWorker.js
const CACHE_NAME = 'version-1';
const urlsToCache = ['index.html', 'offline.html'];

// Install SW
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log('Opened cache');
      return cache.addAll(urlsToCache);
    })
  );
});

// Listen for requests
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then(() => {
      return fetch(event.request).catch(() => caches.match('offline.html'));
    })
  );
});

// Activate the SW
self.addEventListener('activate', (event) => {
  const casheWhitelist = [];
  casheWhitelist.push(CACHE_NAME);

  event.waitUntil(
    caches.keys().then((casheNames) =>
      Promise.all(
        casheNames.map((casheName) => {
          if (!casheWhitelist.includes(casheName)) {
            return cashes.delete(casheName);
          }
        })
      )
    )
  );
});
```

ただ今回はinjectRegisterは`auto`で設定とします。`injectRegister: 'auto'`の追加だけOKです。
また、`vite-plugin-pwa`によるプリキャッシュの設定も可能みたいです。
・[プリキャッシュの設定](https://vite-pwa-org.netlify.app/guide/service-worker-precache.html)
以上でPWAアプリの作成は完了となります。

## 余談
今回はPush通知機能も作成していました。
作成したアプリは以下です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222088/82370453-2a40-3766-3445-57517f40c3af.png)

### 簡単な実装説明
Push通知については[OneSignal](https://onesignal.com/)を利用して作成しました。
通知機能は独自実装だと難しいですが、こちらのサービスを利用すると簡単に始めれます。
また、SDKをダウンロードして、数行のコードを記載するだけでOKです。

OneSignalのパッケージも準備されています。
```
 npm install -D react-onesignal
```
### 実装資料
※Github pagesは`https://アカウント.github.io/プロジェクト名`で公開されます。
OneSignalはドメイン名までの登録となるので、別で準備が必要です。
私は、別途AWS S3 + CloudFrontで実装しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222088/24a1db4e-a650-81a4-6e4d-4778e2045365.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222088/df509232-54a1-a013-b8ca-4425b2e03ed4.png)


# 終わり
今後のPWAとしてはWebで様々なAPIが増えて行きます。現在も日々進化しています。
特に`ProjectFugu`という取り組みが行われており、ネイティブアプリとのギャップが徐々に埋まっていくと思われます。
現在の`ProjectFugu`の開発状況は[Fugu API Tracker](https://fugu-tracker.web.app/#)で管理されています。
また、近年XR技術が話題になっています。
今年はMeta Quest3が発売され、来年にはApple Vision Proが発売されます。
XRにより、今後あらたなWeb体験が増えていく思われます。
わたし個人としてPWAがどのようにアプローチしていくのかとても楽しみです。