FlaskでPWAを作るための最小実装

PWAとはProgressive Web Appsのことで、アプリストアを通さずに端末のホーム画面にインストールすることができます。

Flaskで作ったサイトをPWA化しよう調べていたところ、ReactやVueの実装例ばかりだったので、FlaskでPWA化するための方法をまとめました。


以下のようなシンプルなサイトをPWA化していきます。
```app.py
from flask import Flask, render_template

app = Flask(__name__)
@app.route('/')
def index():
    return render_template('index.html')
if __name__ == '__main__':
    app.run()
```

```index.html
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<html>
  <body>
    <h1>Hello World!</h1>
  </body>
</html>
```

### manifest.jsonの作成
ルートディレクトリにmanifest.jsonを作成します。このファイルで、PWAに関する基本的な情報を定義します。
```maniest.json
{
  "name": "PWAサンプル",
  "short_name": "サンプル",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "icons": [
    {
      "src": "/static/icons/icon_144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "/static/icons/icon_192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/static/icons/icon_512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}
```
項目名から何となく意味は分かると思いますが、詳細を確認したい方は以下のドキュメントをご参考ください。
https://developer.mozilla.org/ja/docs/Web/Manifest

また、アイコンはこちらのサイトを使うと、様々なサイズの画像を一括で生成できて便利です。
https://app-manifest.firebaseapp.com/


### サービスワーカーの作成
次にサービスワーカーを追加しましょう。そもそもサービスワーカーとは何ぞやですが、MDN Web Docsによると、
> サービスワーカーは、基本的にウェブアプリケーション、ブラウザー、そして（もし繋がっていれば）ネットワークの間に介在するプロキシサーバーのように振る舞います。これは、よりよいオフラインの操作性を可能にするように意図されており、ネットワークのリクエストに介在してネットワークの使用可否の状況に基づいて適切な対応を取ったり、サーバー上にある資産を更新したりします。また、プッシュ通知やバックグラウンド同期の API 群へのアクセスもできるようになります。

だそうです。ちょっと難しい。
中身を見てイメージをつかみましょう。
sw.jsというファイルをルートディレクトリに作成し、以下のコードを記述してください。

```sw.js
self.addEventListener('install', function(e) {
  console.log("[Service Worker] Install");
});
self.addEventListener('fetch', function(e) {
  console.log("[Service Worker] Fetched resource " + e.request.url);
});
```

installのイベントリスナーで、インストール時に行いたい処理を記述できます。キャッシュの初期化やオフラインで使用するためのキャッシュ追加などに便利です。
また、fetchのイベントリスナーで、HTTP 要求がアプリから発生したときの処理を記述できます。
他にもプッシュ通知などの実装をこのファイルに書けます。
今回は特に何も行わず、がわだけ用意しておきます。

### 既存ファイルの修正
Flaskではルートディレクトにファイルを配置しただけでは外部から見えないため、app.pyを次のように変更します。

```app.py
from flask import Flask, render_template, send_file

app = Flask(__name__)
@app.route('/')
def index():
    return render_template('index.html')

@app.route('/manifest.json')
def serve_manifest():
    return send_file('manifest.json', mimetype='application/manifest+json')

@app.route('/sw.js')
def serve_sw():
    return send_file('sw.js', mimetype='application/javascript')
    
if __name__ == '__main__':
    app.run()@app.route('/sw.js')
```


index.htmlのヘッダーにも、manifest.jsonとsw.jsに関する記述を追加しましょう。
```index.html
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="{{url_for('static', filename='icons/icon_144x144.png')}}" type="image/png">
  <link rel="icon" href="{{url_for('static', filename='icons/icon_192x192.png')}}" type="image/png">
  <link rel="icon" href="{{url_for('static', filename='icons/icon_512x512.png')}}" type="image/png">
  <link rel="apple-touch-icon" href="{{url_for('static', filename='icons/icon_144x144.png')}}" type="image/png">
  <link rel="apple-touch-icon" href="{{url_for('static', filename='icons/icon_192x192.png')}}" type="image/png">
  <link rel="apple-touch-icon" href="{{url_for('static', filename='icons/icon_512x512.png')}}" type="image/png">
  <link rel="manifest" href="/manifest.json">
  <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', function() {
        navigator.serviceWorker.register("/sw.js").then(function(registration) {
          console.log('ServiceWorkerが正常に登録されました: ', registration.scope);
        }, function(err) {
          console.log('ServiceWorkerの登録に失敗しました: ', err);
        });
      });
    }
  </script>
</head>

<html>
  <body>
    <h1>Hello World!</h1>
  </body>
</html>

```

### テスト
Flaskを実行し、chromeのようなPWAをサポートしているブラウザでアクセスしてください。
URLの横にインストールマークが出ているのでそこからインストールできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254014/8172b11f-9006-a364-abfe-f52d4ee96068.png)

Androidの場合、右上の三点リーダーをタップすると「アプリをインストール」という項目が出るので、そこからインストールしてください。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254014/a9457559-3501-486d-8822-6e830cf757a3.png">


iPhoneの場合、safariでページを開き、シェアボタンから「ホーム画面に追加」できるみたいです。（手持ちにiPhoneがないので確認できていませんが）


なお、ローカルではなくサーバーで公開する場合は、HTTPSで接続しないと、インストールボタンが表示されないので注意してください。


### 参考文献
https://medium.com/@tristan_4694/how-to-create-a-progressive-web-app-pwa-using-flask-f227d5854c49