🐶

これからはじめる PWA 2021

2021/01/19に公開

📝 まえがき

こんにちは犬です。
この記事をPWA初学者へ送ります。参考になれば幸いです。

📝 PWA とは

https://web.dev/progressive-web-apps/

PWAは Google が提唱した Web アプリケーションです。

  • Progressive (プレグレッシブ)
  • Web(ウェブ)
  • App(アップ)

これらを略して「PWA(プログレッシブウェブアプリ)」といいます。
プログレッシブウェブアプリは、「機能」、「信頼性」、「インストール可能性」
上記3つの軸を考慮し設計された Web アプリケーションです。
PWAとは何かをざっくりと説明すると、ネイティブアプリと Webの良いとこどりしたもので、
より良いUXを提供できるWebアプリケーションを目指しています。

上図は、「ネイティブアプリ」と「Webアプリ」、「PWA」の3つのアプリケーションを CapabilityReach で図に表したもので、オレンジ色がネイティブアプリで、青色が Web です。
縦軸のCapabilityは 「機能(できること)」 を表しており、横軸のReachは 「どれだけの人に届くか」 という指標です。見ての通り、ネイティブアプリはできることが多く、Webに比べるとリーチが弱いです。Webはリーチが強く、できることが限られています。一方、PWAはこの二つの指標をどちらも満たす可能性を表しています。

Web ではネイティブアプリの持つ一部の機能は、まだ実現できないところにありますが、
近年の Web アプリケーションはこれまで以上に多機能になっており、Web の期待は年々高まっています。

📝 どういうものが PWA?

PWA のチェックリストを Google が公開しています。
https://developers.google.com/web/progressive-web-apps/checklist

チェックリストは CoreOptimal の2段階に分けられています。
Coreがベースラインで、Optimalはより最適化されたラインと捉えてください。
Coreで定義されているラインを満たすことがPWAとしての第一歩となります。

CoreOptimal の定義はざっくり以下のような内容です。

Core / ベースライン

サイズや入力タイプに関係なく、すべてのユーザーがアプリをインストールして使用できること

  • 高速に起動し、高速を維持していること
  • どのブラウザでも動作すること
  • あらゆる画面サイズに対応していること
  • オフライン時に専用のオフライン画面が表示されること
  • デバイスにPWAをインストールできること

Optimal / より最適化されたライン

Webの良い点を生かしつつ、ネイティブアプリのような優れた体験を提供できること

  • オフラインでも優れた体験を提供できること
  • アクセシビリティの要件を満たしていること
  • 検索して見つけることができること
  • あらゆる入力タイプに対応していること
  • Push通知のような許可設定が正しく設定されていること(UXの概念)
  • アプリケーションを最新,健全な状態に保つこと

チェックリストは、以前と比べて微妙にアップデートされているようなので一度目を通しておくと良いでしょう。

📝 Lighthouse での PWA チェック

PWA のテストは Chrome Developer Tools に組み込まれている Lighthouse パネルのProgressive Web App の項目でもチェックできます。PWA の仕様に正しく沿ったサイトかどうかという観点で評価されます。


Lighthouse の PWAレポートは、3カテゴリに分かれています。
Fast and reliableInstallablePWA Optimized

カテゴリをクリアすると与えられるバッジが存在しており、
すべての監査に合格すると、フルPWAバッジが表示されます。(一番右 PWA✅)

フルPWAバッジは比較的簡単に獲得可能なので是非トライしてみてください!

- Fast and reliable

高速かつ不安定なネットワーク下でも動作信頼性が高いこと

  • モバイルネットワークでもページの読み込みが十分速いこと
  • オフライン時にもステータスコード 200 で応答すること
  • start_url(開始URL) がオフライン時にステータスコード 200 で応答すること

- Installable

ホーム画面にインストール可能であること

  • HTTPS で配信しているか
  • Service Worker が正しく設定できているか
  • manifest がインストール可能な要件を満たしているか

- PWA Optimized

最適化されていること

  • HTTP から HTTPS にリダイレクトされるか
  • スプラッシュ画面が正しく設定できているか
  • アドレスバーにテーマカラーが正しく設定できているか
  • コンテンツのサイズがビューポートに対して正しく設定できているか
  • <meta name="viewport"> タグに width, scale の初期値が指定されているか
  • JavaScriptが無効になっていても、一部のコンテンツを表示できるか
  • apple-touch-icon が正しく設定できているか
  • マスク可能なアイコンをサポートしているか

- Lighthouse がチェックできないもの

以下は PWAのベースラインとして列挙されていますが、Lighthouse ではチェックできません。
開発者が手動でチェックしてください。

  • サイトがクロスブラウザで動作すること
  • ページ遷移時にネットワークでブロックされているように感じないこと
  • すべてのページに一意のURLが存在すること

📝 PWAを実装する

細かいことは気にせずに、まずは最低限の PWA を実装してみましょう。
ここからは実際に作りながら解説します。
Node.js や npm/yarn などの環境が整っているのを前提としますので、ご了承ください🙇‍♀️
また、今回 例として GitHub, Netlify を使用しています。
今回作成するサンプルサイトは以下です。
オフライン対応、A2HS (Add To Home Screen) が表示される非常にシンプルな機能を持ったサイトになります。

GitHub

https://github.com/mi-upto/pwa-simple

PWA サンプルサイト

https://pwa-simple-site.netlify.app/

👩‍💻 PWA とするサイトの雛形をつくろう

まずは元となる HTML と JavaScript が必要です。
ここは楽をして、ある程度環境の整ったこちらのリポジトリをフォークして利用しましょう。

https://github.com/wbkd/webpack-starter

$ npm i
$ npm start

手順通り実行するとプロジェクトがビルドされ、開発サーバが立ち上がります。
http://localhost:8080/ にアクセスして以下の画面が表示されていればOKです。

👩‍💻 サイトを公開してみよう

PWA は HTTPS 環境で公開されている必要があります。
今回は手軽・簡単・無料で HTTPS でサイトを公開できる Netlify というサービスを使います。
Netlify についての説明は割愛します。知らない方は Web 上に有益な記事がたくさん存在しているので是非一度目を通してみてください。そんなに難しくないはずです。

アカウント登録後、 New site from Git より公開するリポジトリを選択します。
https://app.netlify.com/start


フォークしたリポジトリを選択します。
著者はリポジトリ名を webpack-starter から pwa-simple に変更してフォークしています。

Publish directory を build/ に変更して Deploy site ボタンをクリックします。

ビルドが始まります。

Your site is deployed と表示されていれば、デプロイが完了です。
左上に表示されている緑色のリンクをクリックして、URLにアクセスしてみましょう。
URLは以下のような形で自動生成されているはずです。
https://ランダムな文字列.netlify.app/

今後は、main ブランチに push したものが、自動的にビルド -> 本番へデプロイされるようになります。
Netlify の初期設定で main ブランチ push をフックに本番へ作業内容が反映されるようになっているためです。こちらのデプロイ設定と、URLの自動生成されたランダム文字列は変更可能ですので自由にカスタマイズしてみてください。

👩‍💻 Add To Homescreen を実装する

Add To Homescreen とは?

Add To Homescreen は文字通りホーム画面に Web サイト(ウェブアプリ)を追加する機能を指します。ユーザが Web サイトを「インストール」して、ネイティブにインストールされたアプリであるかのように使用できる機能のことです。以降、ユーザはホーム画面に追加されたアイコンをタップするだけで、最適化された Web サイトを起動できるようになります。

Add To Homescreen は A2HS と略されて書かれることもあります。

A2HS は、 iOS safari , webview を除いたほとんどのモバイルブラウザが対応しています。また、一部の Chromium ベースのデスクトップブラウザも Ominibox での対応が完了しています。

以下のスクリーンショットが、A2HS のそれぞれの見た目になっています。

Ominibox Mini-infobar A2HS Dialog
Google Chrome Google Chrome Canary
(※アイコンが異なる)
Android Chrome のみ確認可

A2HS Mini-infobar を実装手順

  1. manifest を設定する
  2. Service Worker を導入する
  3. デプロイ

1. manifest を設定する

マニフェスト(manifest) とは?

ウェブアプリをダウンロードしたり、ユーザーにネイティブアプリと同じように見せるために必要なの情報を記載した設定(ファイル)のこと。PWA のマニフェストには、表示名、ホーム画面での表示名、アイコン、背景色、画面の表示モードなどを記載します。

マニフェストの展開

マニフェストは、HTML文書の <head> 内 <link> 要素で設定したファイルを参照します。
W3Cは .webmanifest の拡張子を推奨していますが、 .json でも問題ありません。

<link rel="manifest" href="/public/manifest.json">

index.html のサンプル
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="apple-touch-icon" sizes="180x180" href="/public/images/icons/apple-touch-icon.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/public/images/icons/favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/public/images/icons/favicon-16x16.png">
  <link rel="manifest" href="/public/manifest.json">
  <link rel="mask-icon" href="/public/images/icons/safari-pinned-tab.svg" color="#2b5797">
  <meta name="msapplication-TileColor" content="#2b5797">
  <meta name="msapplication-config" content="images/icons/browserconfig.xml">
  <meta name="theme-color" content="#f3f3f3">
  <title>pwa simple site</title>
</head>
<body>
  <h1>Hello PWA</h1>
  <p>sample site.</p>
  <img src="/public/images/icons/icon-512x512.png" alt="app icon">
</body>
</html>

manifest.json のサンプル
{
  "name": "pwa-simple",
  "short_name": "pwa-simple",
  "theme_color": "#fff",
  "background_color": "#fff",
  "display": "standalone",
  "orientation": "any",
  "scope": "/",
  "start_url": "/",
  "icons": [
    {
      "src": "images/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "images/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "images/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ]
}

| 項目 | 説明 |
|:-----------|:------------|:------------|
| name | 表示名 |
| short_name |ホーム画面での表示名|
| theme_color |ブラウザによって背景色になる (#000 ~ #fff)|
| background_color |スプラッシュスクリーンでの背景色(#000 ~ #fff)|
| display |画面の表示(UI)方法(fullscreen, standalone, minimal-ui, browser)|
| orientation |画面の向き (any, landscape, portrait, ...)|
| scope |対応ページを制限できる
ユーザがスコープ外に移動すると、通常のWebページに戻る |
| start_url | ホーム画面でのアイコンをタップした際に表示させる開始URL |
| icons |サイズごとに表示するアイコンを指定する|
マニフェストの各項目に対する詳しい説明は以下を参照してください。

https://developer.mozilla.org/en-US/docs/Web/Manifest#browser_compatibility

マニフェストはもちろん自分で書いても良いですが、ジェネレータサービスが存在するのでそちらを活用するとミス無く作成できるのでオススメです。512x512 サイズの画像をジェネレータにアップロードして、マニフェスト用のアセットを取得してください。

著者は Web App Manifest Generator と Favicon Generator 両方を使用し、足りない部分は自分で補って対応しています。(何かオススメの良いサービスや方法があれば教えてくださいー! 🙇‍♀️)

マニフェスト項目 icons の purpose は、比較的新しく追加された項目になるので、ジェネレータでカバー出来ていない所が多いです。purpose にはアイコンの用途/種別を記入します。スペースで区切りで複数の値を指定することができます。この項目を埋めることでマスクアイコンをサポートすることができます。lighthouse の PWA Optimized 項目にもあるので、漏れなく対応しておくと良いでしょう。

👩‍💻 マニフェストを生成してプロジェクトへ追加しよう

512x512 の画像がない方は、著者がテキトーに作ったこちらのアイコンを使ってください

1. Web App Manifest Generator へ 512x512画像をアップロードして zip ファイルを取得

2. manifest.json を プロジェクトの /public/ 直下へ移動 ( /public/manifest.json )

3. アイコンを プロジェクトの /public/ 直下へ移動 ( /public/images/icons/icon-*.png )

4. Favicon Generator へ 512x512画像をアップロードし、お好みで設定をぽちぽちする


5. 手順4 で生成された result 画面から favicon_package.zip を取得し解凍

6. 手順5 で生成したアセットを全てプロジェクトの /public/images/icons/ 直下へ移動

接頭辞 android-chrome のpng と、site.webmanifestは使わないので削除してください

7. 手順4 で生成された result 画面から <head> 周りの HTML をコピーし、index.html の head 部分へ貼り付ける


マニフェストの参照先を手順2で指定したパスへ変更します。
before: <link rel="manifest" href="/public/images/icons/site.webmanifest">
after: <link rel="manifest" href="/public/manifest.json">
また、その他貼り付けたHTMLのそれぞれの参照先(href, content)が 手順3のパスを正しく参照できているかあわせて確認してください。

8. ターミナルで npm start し、この状態で開発環境を起動

9. http://localhost:8080/ へアクセスし、ファビコンが設定されていることを確認

10. Developer Tools > Application > Manifest を参照し、 アイコンが設定されていることを確認

11. Developer Tools > Lighthouse > Generate report で、現状のPWA対応でエラーになっている個所を確認


オフライン対応周りは まだ対応していないので、気にしなくて大丈夫です。
上記の場合、maskable icon の設定がジェネレータで出来なかった部分なので、手で追記します。

12. manifest.json を開き、 icons にそれぞれ "purpose": "any maskable" key valueを追記

{
  "src": "images/icons/icon-512x512.png",
  "sizes": "512x512",
  "type": "image/png",
  "purpose": "any maskable"
}

再度 Lighthouse を実行し、maskable icon 対応がクリアできれいればOKです!

お疲れさまでした!
マニフェスト対応が全て完了しました。
引き続き Service Worker を導入して、オフライン対応をおこなってみましょう。

マニフェスト対応したここまでの作業は下記に置いていますので、参考にしてみてください。
https://github.com/mi-upto/pwa-simple/tree/01_manifest

👩‍💻 Service Worker を導入する

Service Worker とは?

これまでネイティブアプリでしか実現できなかった「オフライン動作」、「プッシュ通知」、「Webアプリを開かなくても通信できるバックグラウンドシンク」など、Service Worker はこれらの機能を提供する基盤技術です。
Service Worker はHTTPリクエスト時に、任意の処理を行えるようにするのが主な役割です。オフライン時には、ブラウザに保存しておいたキャッシュをレスポンスとして返すことでオフライン動作が実現できます。

Workbox とは?

Service Worker を超簡単に扱うことができるライブラリです。
複雑な Service Worker のコードが、Workbox (Workbox API) を通して数行のコードで実現できます。また、Workbox CLI を利用することで、ポチポチエンター!でオフライン時にキャッシュされたリソースを返す設定の入ったシンプルな Service Worker が自動生成されます。

今回は Workbox CLI を使ってオフライン対応を行います。 Workbox CLI について詳しく知りたい方は下記を参照してください。

https://developers.google.com/web/tools/workbox/modules/workbox-cli

1. Workbox CLIを導入する

$ npm i -D workbox-cli

2. Workbox を設定する

$ npx workbox wizard

上記のコマンドを実行します。
対話形式により workbox-config.js を作成することができます。

今回の場合、プロジェクトのルートは build 先になるので build/ を選択します。
※ 選択肢に build/ ディレクトリが表示されていない場合は、先に npm run build を行ってください。
今回はサンプルのため、それ以降の設問に対しては、全てエンターで答えます。

上記で workbox の設定が完了しました。 プロジェクトのルートに workbox-config.js が生成されているはずです。この生成された workbox-config.js を元に Service Worker を生成します。

3. 手元で Service Worker を生成してみる

$ npx workbox generateSW workbox-config.js

build/ ディレクトリに sw.js が生成されました!(めっちゃ簡単✌️)
後はこの sw.js を読み込むようにするだけです。

4. 手順3で生成した sw.js を読み込むようにする

workbox-window モジュールをインストールして、サービスワーカーの登録と更新プロセスを簡素化できるようにします。

$ npm i workbox-window

/src/scripts/index.js を以下に変更します。

import { Workbox } from 'workbox-window';
import '../styles/index.scss';

// 本番環境のみ Service Worker を読み込むよう対応
if (process.env.NODE_ENV === 'production') {
  const wb = new Workbox('/sw.js');
  wb.register();
}

5. ビルドしたものでサーバが立ち上がるようにする

手順4で Service Worker は本番環境のみ読み込まれるよう対応しました。
そのため、現在の npm start コマンドでは手順3~4で読み込むようにした sw.jsは読み込まれることはありません。

動作確認用に本番ビルドした状態で開発サーバが立ち上がるよう、開発環境を追加します。

$ npm i -D express

/scripts/local-server/index.js を作成し、上記の用件を満たすよう中身を以下のようにします。

const express = require('express') ;
const path = require('path');

const buildDir = path.join(process.cwd(), 'build');

const app = express();

app.use(express.static(buildDir));

app.listen(3000, () => {
  console.log('started server on http://localhost:3000');
});

あわせて npm script も修正しましょう。
package.json の scripts を以下のように修正します。

{
  "name": "webpack-starter",
  "version": "1.0.0",
  "description": "A light foundation for your next frontend project based on webpack.",
  "scripts": {
    "lint": "npm run lint:styles; npm run lint:scripts",
    "lint:styles": "stylelint src",
    "lint:scripts": "eslint src",
    "dev": "webpack serve --config webpack/webpack.config.dev.js",
    "build": "npm run build:app && npm run build:sw",
    "build:app": "cross-env NODE_ENV=production webpack --config webpack/webpack.config.prod.js",
    "build:sw": "workbox generateSW workbox-config.js",
    "start": "npm run build && node scripts/local-server/index.js"
  },
  ...略
}
コマンド名 説明
dev 従来の npm start だった処理
build アプリケーションの本番ビルドと、sw.js の生成
build:appbuid:sw の実行)
build:app アプリケーションの本番ビルド
build:sw sw.js の生成
start アプリケーションの本番ビルドと、sw.js の生成したもので開発環境を立ち上げる
手順3 で手動で行っていた sw.js の生成も npm script で実行できるようここで対応しています。

6. ビルドした状態で開発環境を立ち上げてみよう

$ npm strat


http://localhost:3000 にアクセスしてみましょう


この状態で Lighthouse を実行してみましょう。 Does not redirect HTTP traffic to HTTPS 対応以外緑色になっているはずです。

6. オフラインでも動作するのか確認する

次はオフライン時にも問題無く動作することを確認してみましょう。
Chrome Developer Tools > Application > Service Workers > ✅ offline
(もしくは Chrome Developer Tools > Network > Offline でもOK)

offline にチェックを入れると Network タブに⚠️が表示されます。これはネットワークのデバッグ中でオフラインになっている状況を表しています。
この状態でリロードしても問題なくページが表示されているはずです 🎉✨

オフライン対応してあるページの場合 オフライン未対応のページの場合

ここまでの作業は下記に置いていますので、参考にしてみてください。
https://github.com/mi-upto/pwa-simple/tree/02_workbox

👩‍💻 本番で確認してみよう

上記手順通り作業を行い、且つ Netlify を活用している方は、 main ブランチに作業内容を push して本番へデプロイしてみましょう。デプロイが完了したら自分の Netlify URL にアクセスしてみてください。
ちなみに著者のURLは以下になります。https://pwa-simple-site.netlify.app/

アクセスしてみて Lighthouse を実行してみましょう。
すべての監査に合格しているはずなので、フルPWAバッジが表示されているはずです👍


(※ 上記は HTML 等を少しカスタマイズしたものをデプロイしています。)

また Android 端末をお持ちの方は、 Android Chrome ブラウザでアクセスしてみてください。次のスクリーンショットのように画面下部に mini-infobar が表示されていれば成功です!
更新して mini-infobar が表示されなくなってしまった場合は、右上の「︙」メニューの「ホーム画面に追加」から A2HS Dialog を表示できます。

Ominibox Mini-infobar A2HS Dialog
Google Chrome Android Chrome Android Chrome, safari

ホーム画面に追加して、アプリ一覧に現れたアイコンをタップして起動してみましょう。起動するとブラウザ特有の UI も消えて、スプラッシュ画面が表示された後、今回作成した画面が表示されるかと思います。
また、アプリのタスクリストも普段使っているブラウザとは別に単独のアプリケーションとして存在しているはずです。もちろんアプリケーションのアンインストールもネイティブアプリと同様です。

📝 [備考] Service Worker について

Service Worker はオリジン単位でインストールされます。そのため localhost:3000 のようなオリジンが重複してしまう可能性のある開発環境では問題になってしまうことがあります。
また、オフライン時にはキャッシュを活用するため、正しい知識を持ってキャッシュを設定する必要があります。Google Developers のオフラインブックには、キャッシュ戦略について解説されています。オフライン対応する際はぜひ一読しておくと良いでしょう。

https://web.dev/offline-cookbook/

最近では各種フレームワークが、そのフレームワークに適した Service Worker を作るためのライブラリを用意していることも少なくありません。基本的に使っているフレームワークが提供しているライブラリがある場合は、そちらを使う方が実装コストも低く、失敗も少ないためそちらを使うようにしましょう。

📝 さいごに

今回は難しい個所/説明を割愛して、まずは触って動かしてみよう!をコンセプトに「PWA とは?」「PWAとはどういうものなのか?」を解説していきました。
この記事をベースに更なるステップアップを目指して、キャッシュ戦略やWebプッシュ通知など実現できる一歩になると幸いです。

ありがとうございました。
    明けましておめでとうございます。2021年もよろしくお願いいたします! 🎍(遅)

📝 参考 ( special thx )

Discussion