初めてのプログレッシブ ウェブアプリ

最終更新日: 2019 年 4 月 30 日

ウェブアプリ、プログレッシブ ウェブアプリの特長は何ですか?

プログレッシブ ウェブアプリは、ウェブから直接構築、配信される、デスクトップおよびモバイルにインストール可能なアプリのようなエクスペリエンスを提供します。高速、高信頼性のウェブアプリです。最も重要なのは、任意のブラウザで動作するウェブアプリである点です。すでにウェブアプリを作成している場合は、プログレッシブ ウェブアプリの構築をすでに進めていることになります。

高速、高信頼性

すべてのウェブ エクスペリエンスが高速でなければならず、これはプログレッシブ ウェブアプリに特に当てはまります。「高速」とは、有意義なコンテンツを画面に表示し、インタラクティブなエクスペリエンスを提供するのに要する時間のことを指します。

しかも、安定した高速の処理を心がけてください。パフォーマンスの信頼性をいかに高めればよいかストレスを感じることは難しいでしょう。ネイティブ アプリを初めて読み込むのは大変です。アプリストアと膨大なダウンロード数によって制御されていますが、アプリをインストールするポイントに到達すると、先行投資はすべてのアプリ開始時で平均化され、これらの起動はどれも遅延にかかる時間が変化しません。どのアプリケーションも前回の実行と同じくらい高速で、変動はありません。プログレッシブ ウェブアプリは、ユーザーがあらゆるインストール エクスペリエンスで期待できるこの信頼できるパフォーマンスを実現する必要があります。

インストール可能

プログレッシブ ウェブアプリはブラウザタブで実行できますが、インストールすることもできます。サイトをブックマークするとショートカットが追加されますが、インストールされているプログレッシブ ウェブアプリ(PWA)は、他のすべてのインストール済みアプリと同じように表示されます。他のアプリが起動するのと同じ場所から起動します。カスタマイズされたスプラッシュ画面、アイコンなど、リリース エクスペリエンスを制御できます。アドレスバーやその他のブラウザ UI のないアプリ ウィンドウでアプリとして実行されます。他のすべてのインストール済みアプリと同様に、タスク スイッチャーの最上位アプリです。

インストール可能な PWA は高速で信頼性が高いことを必ず覚えておいてください。PWA をインストールするユーザーは、どのようなネットワーク接続を使用していてもアプリが動作することを期待しています。インストール済みのすべてのアプリで満たす必要があるベースラインの期待値です。

モバイルとパソコン

レスポンシブ デザインの手法を使用すると、PWA はモバイルとパソコンの両方で機能し、プラットフォーム間で単一のコードベースを使用します。ネイティブ アプリの作成を検討している場合は、PWA のメリットについてご覧ください。

作成するアプリの概要

この Codelab では、PWA 手法を使用して天気情報ウェブアプリを作成します。作成するアプリの機能は次のとおりです。

  • パソコンまたはモバイルでレスポンシブ デザインを使用しましょう。
  • 高速化するには、Service Worker を使用して、実行に必要なアプリリソース(HTML、CSS、JavaScript、画像)を事前キャッシュし、ランタイムに気象データをキャッシュに保存して、パフォーマンスを向上させます。
  • インストール可能であること(ウェブアプリ マニフェストと beforeinstallprompt イベントを使用してインストール可能であることをユーザーに通知します)

ラボの内容

  • ウェブアプリ マニフェストを作成して追加する方法
  • シンプルなオフライン環境を提供する方法
  • 完全なオフライン エクスペリエンスを提供する方法
  • アプリをインストール可能にする方法

この Codelab では、プログレッシブ ウェブアプリに焦点を当てます。関連のない概念やコードブロックについては詳しく触れず、コードはコピーして貼るだけの状態で提供されています。

必要なもの

  • Chrome の最新バージョン(74 以降)。PWA はすべてのブラウザで動作するため、Chrome DevTools のいくつかの機能を使用して、ブラウザレベルで何が起きているかをより深く理解し、それを使ってインストール エクスペリエンスをテストします。
  • HTML、CSS、JavaScript、Chrome DevTools に関する知識。

Dark Sky API のキーを取得する

気象データはDark Sky APIから取得されています。使用するには、API キーをリクエストする必要があります。使いやすく、ビジネス以外のプロジェクトでは無料です。

API キーを登録する

API キーが適切に機能していることを確認する

API キーが正常に機能していることをテストするには、DarkSky API に HTTP リクエストを送信します。以下の URL を更新して、DARKSKY_API_KEY を API キーに置き換えます。問題がなければ、ニューヨークの最新の天気予報が表示されます。

https://api.darksky.net/forecast/DARKSKY_API_KEY/40.7720232,-73.9732319

コードを取得する

このプロジェクトで必要となるすべてのものは Git リポジトリに用意されています。まず、コードを取得してお好みの開発環境で開く必要があります。この Codelab では、Glitch を使用することをおすすめします。

強く推奨: Glitch を使用してリポジトリをインポートする

この Codelab では Glitch を使用することをおすすめします。

  1. 新しいブラウザタブを開き、https://glitch.com にアクセスします。
  2. アカウントをお持ちでない場合は、登録する必要があります。
  3. [新しいプロジェクト]、[Git リポジトリからクローンを作成] の順にクリックします。
  4. https://github.com/googlecodelabs/your-first-pwapp.git のクローンを作成して、[OK] をクリックします。
  5. リポジトリが読み込まれたら、.env ファイルを編集して、DarkSky API キーで更新します。
  6. [Show] ボタンをクリックし、[In a New Window] を選択して、PWA の動作を確認します。

代替方法: コードとダウンロード、ローカルでの作業

コードをダウンロードしてローカルで動作させる場合は、最新バージョンの Node.js とコードエディタを準備しておく必要があります。

ソースコードをダウンロード

  1. ダウンロードした zip ファイルを解凍します。
  2. npm install を実行して、サーバーの実行に必要な依存関係をインストールします。
  3. server.js を編集して、DarkSky API キーを設定します。
  4. node server.js を実行して、ポート 8000 でサーバーを起動します。
  5. ブラウザタブを開いて http://localhost:8000 にアクセスします。

出発点

出発点は、この Codelab 用に設計された基本的な天気アプリです。コードは、この Codelab のコンセプトを示すために簡略化されており、エラー処理はほとんどありません。本番環境のアプリでこのコードを再利用する場合は、すべてのエラーを処理し、すべてのコードをテストしてください。

次の方法をお試しください

  1. 右下の青い + ボタンを使って新しい都市を追加します。
  2. 右上の更新ボタンを使用してデータを更新します。
  3. 都市名を削除するには、都市カードの右上にある x を使用します。
  4. Chrome DevTools のデバイス切り替えツールバーを使用して、パソコンとモバイルでの動作を確認します。
  5. Chrome DevTools の [Network] パネルを使用して、オフラインになったときの動作を確認します。
  6. Chrome DevTools の [Network] パネルを使用して、ネットワークを低速 3G にスロットリングした場合の動作を確認します。
  7. server.jsFORECAST_DELAY の値を変更して、予測サーバーに遅延を追加します

Lighthouse による監査

Lighthouse は、サイトやページの品質向上に役立つ使いやすいツールです。Lighthouse は、パフォーマンス、ユーザー補助、プログレッシブ ウェブアプリなどの監査を実施します。各監査には、その監査が重要である理由と問題の解決方法を説明するリファレンス ドキュメントがあります。

Lighthouse を使用して天気アプリを監査し、変更を確認します。

Lighthouse を実行

  1. 新しいタブでプロジェクトを開きます。
  2. Chrome DevTools を開き、[Audits] パネルに切り替えます。すべての監査タイプを有効にします。
  3. [監査を実行] をクリックします。しばらくすると、Lighthouse からページにレポートが表示されます。

プログレッシブ ウェブアプリ監査

ここでは、プログレッシブ ウェブアプリ監査の結果を中心に説明します。

注目すべき赤い点はたくさんあります。

  • ❗FAILED: 現在のページは、オフライン時は 200 で応答しません。
  • ❗FAILED: start_url がオフライン時は 200 で応答しません。
  • ❗FAILED: ページと start_url. を制御する Service Worker が登録されていません
  • ❗FAILED: ウェブアプリ マニフェストがインストール可能要件を満たしていません。
  • ❗FAILED: カスタム スプラッシュ画面が設定されていません。
  • ❗FAILED: アドレスバーのテーマの色を設定しません。

これらの問題の一部を修正して始めましょう。

このセクションを終了した時点で、天気アプリは以下の監査に合格するようになります。

  • ウェブアプリ マニフェストがインストール可能となる要件を満たしていません。
  • カスタム スプラッシュ画面が設定されていません。
  • アドレスバーのテーマの色を設定しません。

ウェブアプリ マニフェストを作成する

ウェブアプリ マニフェストは、シンプルな JSON ファイルです。デベロッパーはこのファイルから、アプリがユーザーにどのように表示されるかを制御できます。

ウェブアプリ マニフェストを使用すると、次のことができます。

  • アプリをスタンドアロン ウィンドウ(display)で開くようにブラウザを設定します。
  • アプリの初回起動時に開くページを定義します(start_url)。
  • ドックまたはアプリ ランチャー(short_nameicons)でアプリの外観を定義します。
  • スプラッシュ画面(nameiconscolors)を作成する。
  • ウィンドウを横表示または縦表示(orientation)で開くようブラウザに指示します。
  • 他にも多くの機能があります。

プロジェクトに public/manifest.json という名前のファイルを作成します。次の内容をコピーして貼り付けます。

public/manifest.json

{
  "name": "Weather",
  "short_name": "Weather",
  "icons": [{
    "src": "/images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    }, {
      "src": "/images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#3E4EB8",
  "theme_color": "#2F3BA2"
}

マニフェストは、さまざまな画面サイズを対象としたアイコンの配列をサポートします。この Codelab では、iOS との統合に役立つその他のツールをいくつか紹介します。

次に、アプリの各ページに <link rel="manifest"... を追加して、マニフェストについてブラウザに伝える必要があります。index.html ファイルの <head> 要素に次の行を追加します。

public/index.html

<!-- CODELAB: Add link rel manifest -->
<link rel="manifest" href="/manifest.json">

DevTools の迂回

DevTools を使用すると、manifest.json ファイルをすばやく簡単にチェックできます。[Application] パネルの [Manifest] ペインを開きます。マニフェスト情報が正しく追加されていれば、そのペインが解析され、人間が理解できる形式でこのペインに表示されます。

iOS メタタグとアイコンを追加する

iOS の Safari はウェブアプリ マニフェストはまだサポートされていないため従来の meta タグindex.html ファイルの <head> に追加する必要があります。

public/index.html

<!-- CODELAB: Add iOS meta tags and icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Weather PWA">
<link rel="apple-touch-icon" href="/images/icons/icon-152x152.png">

参考: Lighthouse の簡単な修正

Lighthouse の監査では、簡単に修正できる項目がほかにもいくつか用意されており、当面の間にそうした問題に対処します。

メタ ディスクリプションを設定する

Lighthouse によると、SEO の監査では「ドキュメントにメタ ディスクリプションはない」と記載されています。説明は Google 検索結果に表示されます。独自の質の高い説明により、検索ユーザーにとって関連性が高まり、検索トラフィックの増加につながります。

説明を追加するには、次の <head> タグをドキュメントの <head> に追加します。

public/index.html

<!-- CODELAB: Add description here -->
<meta name="description" content="A sample weather app">

アドレスバーのテーマの色を設定する

PWA 監査で、Lighthouse はアプリに「アドレスバーのテーマ色を設定しない」と記録しています。ブラウザのアドレスバーをブランドの色に合わせてテーマ設定すると、より没入感のあるユーザー エクスペリエンスを実現できます。

モバイルでテーマ色を設定するには、ドキュメントの <head> に次の meta タグを追加します。

public/index.html

<!-- CODELAB: Add meta theme-color -->
<meta name="theme-color" content="#2F3BA2" />

Lighthouse による変更の確認

[Audits] ペインの左上にある + 記号をクリックして Lighthouse を再度実行し、変更内容を確認します。

SEO 監査

  • ✅ 合格: ドキュメントにメタ ディスクリプションが含まれる。

プログレッシブ ウェブアプリ監査

  • ❗FAILED: 現在のページは、オフライン時は 200 で応答しません。
  • ❗FAILED: start_url がオフライン時は 200 で応答しません。
  • ❗FAILED: ページと start_url. を制御する Service Worker が登録されていません
  • ✅ 合格: ウェブアプリ マニフェストがインストール可能性の要件を満たしている。
  • ✅ 合格: カスタム スプラッシュ画面に設定されている。
  • ✅ 合格: アドレスバーのテーマの色を設定する。

ユーザーは、アプリをインストールすれば必ずベースライン エクスペリエンスを利用できると期待します。インストール可能なウェブアプリで Chrome のオフライン恐竜ゲームが表示されないようにすることが重要な理由です。オフライン環境としては、シンプルなオフライン ページから、データをキャッシュに保存した読み取り専用環境、ネットワーク接続が回復したときに自動的に同期されるオフライン機能などがあります。

このセクションでは、シンプルなオフライン ページを天気アプリに追加します。オフライン時にユーザーがアプリを読み込もうとすると、ブラウザで表示される一般的なオフライン ページではなく、カスタムページが表示されます。このセクションを終了した時点で、天気アプリは以下の監査に合格するようになります。

  • 現在のページは、オフライン時は 200 で応答しません。
  • start_url は、オフライン時に 200 で応答しません。
  • ページと start_url. を制御する Service Worker は登録されません

次のセクションでは、カスタムのオフライン ページを完全なオフライン エクスペリエンスに置き換えます。これによってオフラインでのエクスペリエンスは向上しますが、さらに重要な点は、パフォーマンスが大幅に向上することです。ほとんどのアセット(HTML、CSS、JavaScript)がローカルに保存され、配信されるため、ネットワークがボトルネックになる可能性があるからです。

Service Worker の活用

Service Worker についてよく知らない場合は、Service Worker の概要で、Service Worker でできることやライフサイクルの仕組みなどの基本事項をつかむことができます。

Service Worker を通じて提供される機能は、段階的に拡張されるものとみなされ、ブラウザでサポートされている場合にのみ追加されます。たとえば、Service Worker のアプリシェルとデータをキャッシュに保存し、ネットワークが使用できないときでも利用できるようにします。Service Worker がサポートされていない場合、オフライン コードは呼び出されず、ユーザーには基本的な操作が提供されます。機能検出を使用して漸進型の拡張を提供する場合、オーバーヘッドはほとんどなく、この機能をサポートしていない古いブラウザで動作させることはできません。

Service Worker を登録する

まず、Service Worker を登録します。index.html ファイルに次のコードを追加します。

public/index.html

// CODELAB: Register service worker.
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
        .then((reg) => {
          console.log('Service worker registered.', reg);
        });
  });
}

このコードは、Service Worker API が使用可能かどうかを確認します。使用可能である場合は、ページが読み込まれる/service-worker.js の Service Worker が登録されます。

Service Worker は、/scripts/ ディレクトリからではなく、ルート ディレクトリから提供されます。これは、Service Worker の scope を設定する最も簡単な方法です。Service Worker の scope によって、Service Worker が制御するファイル、つまり Service Worker がリクエストをインターセプトするパスが決まります。デフォルトの scope は、Service Worker ファイルの場所で、下記のすべてのディレクトリにあります。したがって、service-worker.js がルート ディレクトリにある場合、Service Worker はこのドメインのすべてのウェブページからのリクエストを制御します。

オフラインページを事前キャッシュする

まず、キャッシュに保存する内容を Service Worker に指示する必要があります。ネットワーク接続がないときは常に表示される、シンプルなオフライン ページpublic/offline.html)をすでに作成しています。

service-worker.js で、'/offline.html',FILES_TO_CACHE 配列に追加すると、最終結果は次のようになります。

public/service-worker.js

// CODELAB: Add list of files to cache here.
const FILES_TO_CACHE = [
  '/offline.html',
];

次に、install イベントに次のコードを追加して、オフライン ページを事前キャッシュするよう Service Worker に指示する必要があります。

public/service-worker.js

// CODELAB: Precache static resources here.
evt.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log('[ServiceWorker] Pre-caching offline page');
      return cache.addAll(FILES_TO_CACHE);
    })
);

これで、install イベントが caches.open() でキャッシュを開き、キャッシュ名を指定できるようになりました。キャッシュ名を指定すると、ファイルのバージョン管理や、キャッシュされたリソースからのデータの分離が可能になり、一方を簡単に更新でき、他方に影響を与えません。

キャッシュが開いたら、cache.addAll() を呼び出します。これは URL のリストを取得し、サーバーから URL を取得して、レスポンスをキャッシュに追加します。個々のリクエストのいずれかが失敗すると、cache.addAll() が失敗します。つまり、インストール手順が成功した場合、キャッシュは一貫した状態になることが保証されます。ただし、なんらかの理由で失敗した場合は、Service Worker が次回起動したときに自動的に再試行されます。

DevTools の迂回

DevTools を使用して Service Worker について理解し、デバッグする方法をご紹介します。ページを再読み込みする前に、DevTools を開き、[Application] パネルの [Service Workers] ペインに移動します。次のようになります。

このような空白ページが表示された場合、現在開いているページに登録済み Service Worker がないことを意味します。

ページを再読み込みします。[Service Workers] ペインは次のようになります。

次のような情報が表示されている場合、そのページでは Service Worker が稼働しています。

[ステータス] ラベルの横には数字が表示されます(この場合は 34251)。Service Worker を使用しているので、この数には注意してください。これにより、Service Worker がアップデートされているかどうかを簡単に確認できます。

古いオフライン ページをクリーンアップする

ここでは、activate イベントを使用してキャッシュ内の古いデータをクリーンアップします。このコードにより、App Shell ファイルのいずれかが変更されるたびに、Service Worker がキャッシュを更新します。これが機能するには、Service Worker ファイルの先頭にある CACHE_NAME 変数の値を増やす必要があります。

activate イベントに次のコードを追加します。

public/service-worker.js

// CODELAB: Remove previous cached data from disk.
evt.waitUntil(
    caches.keys().then((keyList) => {
      return Promise.all(keyList.map((key) => {
        if (key !== CACHE_NAME) {
          console.log('[ServiceWorker] Removing old cache', key);
          return caches.delete(key);
        }
      }));
    })
);

DevTools の迂回

[Service Workers] ペインを開き、ページを更新します。新しい Service Worker がインストールされ、ステータス番号が増えます。

install イベントは self.skipWaiting() で終了し、activate イベントは self.clients.claim() で終了するため、更新された Service Worker はすぐに制御します。それらがなければ、タブが開いている限り、古い Service Worker が引き続きページを制御します。

失敗したネットワーク リクエストを処理する

最後に、fetch イベントを処理する必要があります。ここでは、「キャッシュにフォールバックするネットワーク」戦略を使用します。Service Worker はまず、ネットワークからリソースを取得します。失敗した場合、Service Worker はキャッシュからオフライン ページを返します。

public/service-worker.js

// CODELAB: Add fetch event handler here.
if (evt.request.mode !== 'navigate') {
  // Not a page navigation, bail.
  return;
}
evt.respondWith(
    fetch(evt.request)
        .catch(() => {
          return caches.open(CACHE_NAME)
              .then((cache) => {
                return cache.match('offline.html');
              });
        })
);

fetch ハンドラで処理する必要があるのはページ ナビゲーションのみです。そのため、他のリクエストをハンドラからダンプして、ブラウザで通常どおり処理できます。ただし、リクエスト .modenavigate の場合は、fetch を使用してネットワークからアイテムを取得してみます。失敗した場合、catch ハンドラが caches.open(CACHE_NAME) でキャッシュを開き、cache.match('offline.html') を使用して事前にキャッシュされたオフライン ページを取得します。その後、evt.respondWith() を使用して結果がブラウザに渡されます。

DevTools の迂回

すべてが想定どおりに動作することを確認しましょう。[Service Workers] ペインを開いて、ページを更新します。新しい Service Worker がインストールされ、ステータス番号が増えます。

何がキャッシュされたかも確認できます。DevTools の [Application] パネルの [Cache Storage] ペインに移動します。[キャッシュ ストレージ] を右クリックし、[キャッシュを更新] を選択して、セクションを展開します。左側に静的キャッシュの名前が表示されます。キャッシュ名をクリックして、キャッシュされたすべてのファイルを表示します。

次に、オフライン モードをテストします。DevTools の [Application] パネルの [Service Workers] ペインに戻り、[Offline] チェックボックスをオンにします。確認後、[Network] パネルタブの横に黄色の小さな警告アイコンが表示されます。このアイコンは、オフラインであることを示しています。

ページを再読み込みすると...Chrome のオフライン ディノではなく、オフラインの panda を使用できます。

Service Worker のテストのヒント

Service Worker のデバッグは困難な場合があります。また、キャッシュに関連する場合、想定どおりにキャッシュが更新されないと、さらに悪化する可能性があります。標準的な Service Worker のライフサイクルとコードのバグの間にはすぐに不満を感じることがあります。禁止事項

DevTools を使用する

[Application] パネルの [Service Workers] ペインにチェックボックスがあり、操作を大幅に簡略化できます。

  • オフライン - オンにすると、オフラインの状態をシミュレートし、リクエストをネットワークに送信できなくなります。
  • 再読み込み時に更新 - オンにすると、最新の Service Worker がインストールされ、すぐにインストールされて有効になります。
  • ネットワークをバイパスする - オンにすると、リクエストは Service Worker をバイパスし、ネットワークに直接送信されます。

最初から始める

キャッシュ データの読み込みや、予定どおりに更新されない場合もあります。保存されているデータ(localStorage、indexedDB データ、キャッシュ ファイル)をすべて消去して Service Worker を削除するには、[アプリケーション] パネルの [ストレージを消去] ペインを使用します。また、シークレット ウィンドウで作業することもできます。

その他のヒント:

  • Service Worker の登録が解除されると、Service Worker はブラウザ ウィンドウが閉じられるまでリストに残ります。
  • アプリに対して複数のウィンドウが開いている場合、すべてのウィンドウが再読み込みされ、最新の Service Worker に更新されるまで、新しい Service Worker は有効になりません。
  • Service Worker を登録解除してもキャッシュはクリアされません。
  • Service Worker が存在し、新しい Service Worker が登録されている場合、すぐに制御しない限り、ページが再読み込みされるまで新しい Service Worker は制御しません。

Lighthouse による変更の確認

もう一度 Lighthouse を実行して、変更を確認します。変更を確認する前に、[オフライン] チェックボックスをオフにしてください。

SEO 監査

  • ✅ 合格: ドキュメントにメタ ディスクリプションが含まれる。

プログレッシブ ウェブアプリ監査

  • ✅ 合格: 現在のページがオフラインのときに 200 を返す。
  • ✅ 合格: オフラインの場合、start_url は 200 を返す。
  • ✅ 合格: ページと start_url. を制御する Service Worker を登録する
  • ✅ 合格: ウェブアプリ マニフェストがインストール可能性の要件を満たしている。
  • ✅ 合格: カスタム スプラッシュ画面に設定されている。
  • ✅ 合格: アドレスバーのテーマの色を設定する。

スマートフォンを機内モードにしてから、お気に入りのアプリを実行してみてください。ほとんどの場合、かなり安定したオフライン体験が提供されます。ユーザーは、自分のアプリに堅牢なエクスペリエンスを期待します。ウェブの世界も変わらないはずです。プログレッシブ ウェブアプリは、オフライン時の主要なシナリオとして設計する必要があります。

Service Worker のライフサイクル

Service Worker のライフサイクルは最も複雑な部分です。やろうとしていることとメリットがわからないのなら、あなたと戦っているように感じることができます。仕組みがわかれば、ウェブのネイティブ パターンとネイティブ パターンを組み合わせて、シームレスで控えめなアップデートをユーザーに配信できます。

イベントinstall

Service Worker が最初に取得するイベントは install です。これは、ワーカーが実行されるとすぐにトリガーされ、Service Worker ごとに 1 回だけ呼び出されます。Service Worker スクリプトを変更すると、ブラウザでは別の Service Worker と認識され、独自の install イベントが取得されます。

通常、install イベントは、アプリの実行に必要なものをすべてキャッシュに保存するために使用されます。

イベントactivate

Service Worker は起動するたびに activate イベントを受け取ります。activate イベントの主な目的は、Service Worker の動作を構成し、以前の実行によって残っているリソース(古いキャッシュなど)をクリーンアップして、Service Worker がネットワーク リクエストを処理できるようにすることです(例: 以下に説明する fetch イベント)。

イベントfetch

フェッチ イベントを使用すると、Service Worker はネットワーク リクエストをインターセプトしてリクエストを処理できます。ネットワークから取得してリソースを取得する、独自のキャッシュから pull する、カスタム レスポンスを生成するなど、さまざまなオプションを使用できます。オフライン クックブックで、さまざまな戦略について確認する。

Service Worker の更新

ブラウザは、ページが読み込まれるたびに新しいバージョンの Service Worker が存在するかどうかを確認します。新しいバージョンが見つかった場合は、新しいバージョンがダウンロードされてバックグラウンドでインストールされますが、有効化されていません。新しいバージョンの Service Worker は、古い Service Worker を使用するページが開くまで待機状態になります。古い Service Worker を使用しているすべてのウィンドウを閉じると、新しい Service Worker が有効になり、制御できるようになります。詳細については、Service Worker のライフサイクル ドキュメントのService Worker の更新セクションをご覧ください。

適切なキャッシュ戦略の選択

適切なキャッシュ戦略の選択は、キャッシュしようとしているリソースの種類と、後でアクセスする必要がある方法によって異なります。ウェザーアプリでは、キャッシュに保存する必要があるリソースを、事前キャッシュに保存するリソースと実行時にキャッシュに保存するデータの 2 つのカテゴリに分割します。

静的リソースのキャッシュ

リソースのキャッシュ保存は、ユーザーがデスクトップ モバイルアプリまたはモバイルアプリをインストールした場合とよく似ています。アプリの実行に必要な主要リソースをデバイスにインストールまたはキャッシュし、ネットワーク接続の有無にかかわらず後で読み込むことができるようにします。

このアプリの場合、Service Worker がインストールされている場合にすべての静的リソースを事前キャッシュするため、アプリの実行に必要なすべてがユーザーのデバイスに保存されます。アプリの読み込みを高速化するには、キャッシュ ファースト戦略を使用します。これは、ネットワークに移動してリソースを取得する代わりに、ローカル キャッシュから取得します。このリソースを利用できない場合にのみ、ネットワークから取得しようとします。

ローカル キャッシュから pull すると、ネットワークの変動がなくなります。ユーザーがどのようなネットワーク(Wi-Fi、5G、3G、2G など)を使用していても、実行する必要がある主要なリソースはすぐに利用できます。

アプリデータのキャッシュ

再検証は、特定の種類のデータに理想的で、アプリに適した手法です。できるだけ早く画面でデータを取得し、ネットワークが最新のデータを返すと更新されます。「再実行の際の再検証」とは、2 つの非同期リクエスト(1 つはキャッシュ、もう 1 つはネットワーク)を開始する必要があることを意味します。

通常の状況下では、キャッシュ データはほぼ即時に返され、使用可能な最新のデータがアプリに提供されます。ネットワーク リクエストが返されると、アプリはネットワークの最新データを使用して更新されます。

このアプリの場合、これはネットワークよりも優れたユーザー エクスペリエンスを実現し、キャッシュ 戦略にフォールバックします。なぜなら、画面上で何かが表示されるまでネットワーク リクエストがタイムアウトするまで、ユーザーは待つ必要がないためです。最初は古いデータが表示されることがありますが、ネットワーク リクエストが返されたら、アプリは最新のデータで更新されます。

アプリのロジックを更新する

前述のように、アプリはキャッシュとネットワークに対して 2 つの非同期リクエストを開始する必要があります。アプリは window で使用可能な caches オブジェクトを使用してキャッシュにアクセスし、最新のデータを取得します。これは、段階的に拡張を行う優れた例です。caches オブジェクトがすべてのブラウザで利用できるとは限りません。オブジェクトを使用できない場合でも、ネットワーク リクエストは機能します。

getForecastFromCache() 関数を更新して、caches オブジェクトがグローバル window オブジェクトで使用可能かどうかを確認し、利用できる場合はキャッシュにデータをリクエストします。

public/scripts/app.js

// CODELAB: Add code to get weather forecast from the caches object.
if (!('caches' in window)) {
  return null;
}
const url = `${window.location.origin}/forecast/${coords}`;
return caches.match(url)
    .then((response) => {
      if (response) {
        return response.json();
      }
      return null;
    })
    .catch((err) => {
      console.error('Error getting data from cache', err);
      return null;
    });

次に、updateData() を 2 回呼び出すように変更する必要があります。1 つは getForecastFromNetwork() への呼び出しで、ネットワークから 1 つ、getForecastFromCache() では最新のキャッシュされた予測を取得します。

public/scripts/app.js

// CODELAB: Add code to call getForecastFromCache.
getForecastFromCache(location.geo)
    .then((forecast) => {
      renderForecast(card, forecast);
    });

これで、天気情報アプリはキャッシュに対して 1 つと fetch を介して 1 つのデータに対する 2 つの非同期リクエストを行うようになりました。キャッシュ内にデータが存在する場合は、非常に高速に(数十ミリ秒)返され、レンダリングされます。その後、fetch が応答すると、カードはウェザー API から直接最新のデータで更新されます。

キャッシュ リクエストと fetch リクエストの両方で、予測カードを更新するための呼び出しが行われます。最新のデータが表示されているかどうかをアプリで確認するには、どうすればよいですか?これは、renderForecast() の次のコードで処理されます。

public/scripts/app.js

// If the data on the element is newer, skip the update.
if (lastUpdated >= data.currently.time) {
  return;
}

カードが更新されるたびに、アプリはデータのタイムスタンプをカードの非表示属性に保存します。カードにすでに存在するタイムスタンプが関数に渡されたデータよりも新しい場合、アプリは無効になります。

アプリリソースの事前キャッシュ

Service Worker で DATA_CACHE_NAME を追加して、アプリケーションのデータを App Shell から分離できるようにします。App Shell が更新され、古いキャッシュが消去されても、データは手つかずで保持され、非常に高速な読み込みが可能です。データ形式が今後変更された場合に備えて、App Shell とコンテンツの同期を維持する方法が必要になります。

public/service-worker.js

// CODELAB: Update cache names any time any of the cached files change.
const CACHE_NAME = 'static-cache-v2';
const DATA_CACHE_NAME = 'data-cache-v1';

CACHE_NAME も忘れずに更新してください。静的リソースもすべて変更されます。

アプリをオフラインで動作させるには、必要なすべてのリソースをあらかじめキャッシュする必要があります。パフォーマンスの向上にも役立ちます。ネットワークからすべてのリソースを取得する代わりに、ローカル キャッシュからすべてのリソースを読み込めるため、ネットワークが不安定になります。

FILES_TO_CACHE 配列をファイルのリストで更新します。

public/service-worker.js

// CODELAB: Add list of files to cache here.
const FILES_TO_CACHE = [
  '/',
  '/index.html',
  '/scripts/app.js',
  '/scripts/install.js',
  '/scripts/luxon-1.11.4.js',
  '/styles/inline.css',
  '/images/add.svg',
  '/images/clear-day.svg',
  '/images/clear-night.svg',
  '/images/cloudy.svg',
  '/images/fog.svg',
  '/images/hail.svg',
  '/images/install.svg',
  '/images/partly-cloudy-day.svg',
  '/images/partly-cloudy-night.svg',
  '/images/rain.svg',
  '/images/refresh.svg',
  '/images/sleet.svg',
  '/images/snow.svg',
  '/images/thunderstorm.svg',
  '/images/tornado.svg',
  '/images/wind.svg',
];

キャッシュするファイルのリストを手動で生成するため、ファイルを更新するたびに CACHE_NAME を更新する必要があります。アプリにオフライン作業に必要なすべてのリソースが含まれており、オフライン ページが再び表示されないことから、キャッシュ ファイルの一覧から offline.html を削除できました。

Activate イベント ハンドラを更新する

activate イベントでデータが誤って削除されないように、service-worker.jsactivate イベントで if (key !== CACHE_NAME) { を次のように置き換えます。

public/service-worker.js

if (key !== CACHE_NAME && key !== DATA_CACHE_NAME) {

取得イベント ハンドラを更新する

後で簡単にアクセスできるよう、Weather API に対するリクエストをインターセプトして応答をキャッシュに保存するように、Service Worker を変更する必要があります。最新でない戦略では、ネットワーク レスポンスが「信頼できる情報源」となっており、常に最新の情報が得られると想定しています。ネットワークの最新のキャッシュ データがすでに取得されているため、ネットワークがエラーになった場合は失敗します。

fetch イベント ハンドラを更新して、データ API へのリクエストを他のリクエストとは別に処理します。

public/service-worker.js

// CODELAB: Add fetch event handler here.
if (evt.request.url.includes('/forecast/')) {
  console.log('[Service Worker] Fetch (data)', evt.request.url);
  evt.respondWith(
      caches.open(DATA_CACHE_NAME).then((cache) => {
        return fetch(evt.request)
            .then((response) => {
              // If the response was good, clone it and store it in the cache.
              if (response.status === 200) {
                cache.put(evt.request.url, response.clone());
              }
              return response;
            }).catch((err) => {
              // Network request failed, try to get it from the cache.
              return cache.match(evt.request);
            });
      }));
  return;
}
evt.respondWith(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.match(evt.request)
          .then((response) => {
            return response || fetch(evt.request);
          });
    })
);

コードはリクエストをインターセプトし、天気予報かどうかを確認します。存在する場合は、fetch を使用してリクエストを行います。レスポンスが返されたら、キャッシュを開いてレスポンスのクローンを作成し、キャッシュに保存して、元のリクエスト元にレスポンスを返します。

Service Worker に、ナビゲーションだけでなくすべての画像(スクリプト、CSS ファイルなど)を処理させるため、evt.request.mode !== 'navigate' チェックを削除する必要があります。チェックインを終了すると、Service Worker のキャッシュから HTML のみが配信されます。それ以外は、ネットワークからリクエストされます。

お試しください

これでアプリが完全にオフラインになりました。ページを更新して、最新の Service Worker がインストールされていることを確認します。次に、いくつかの都市を保存し、アプリの更新ボタンを押して、最新の天気データを取得します。

次に、DevTools の [Application] パネルの [Cache Storage] ペインに移動します。セクションを展開すると、左側に静的キャッシュとデータ キャッシュの名前が表示されます。データ キャッシュを開くと、それぞれの都市に保存されているデータが表示されます。

[Service Workers] ペインに切り替えて、[オフライン] チェックボックスをオンにします。ページを再読み込みしてから、オフラインになってからページを再読み込みしてみてください。

高速のネットワークを使用して低速な接続で天気予報がどのように更新されるのかを確認するには、server.jsFORECAST_DELAY プロパティを 5000 に設定します。predict API へのリクエストはすべて 5,000 ミリ秒遅れます

Lighthouse による変更の確認

Lighthouse を再度実行することもおすすめします。

SEO 監査

  • ✅ 合格: ドキュメントにメタ ディスクリプションが含まれる。

プログレッシブ ウェブアプリ監査

  • ✅ 合格: 現在のページがオフラインのときに 200 を返す。
  • ✅ 合格: オフラインの場合、start_url は 200 を返す。
  • ✅ 合格: ページと start_url. を制御する Service Worker を登録する
  • ✅ 合格: ウェブアプリ マニフェストがインストール可能性の要件を満たしている。
  • ✅ 合格: カスタム スプラッシュ画面に設定されている。
  • ✅ 合格: アドレスバーのテーマの色を設定する。

プログレッシブ ウェブアプリは、インストールされると外観と動作が、他のすべてのインストール済みアプリと同じように機能します。他のアプリが起動する場合と同じ場所から起動します。アドレスバーやその他のブラウザ UI がないアプリで動作します。他のすべてのインストール済みアプリと同様に、タスク スイッチャーの最上位アプリです。

プログレッシブ ウェブアプリは、Chrome のその他メニューを使用してインストールできます。また、アプリのインストールを促すボタンなどの UI コンポーネントをユーザーに提供することもできます。

Lighthouse による監査

ユーザーがプログレッシブ ウェブアプリをインストールできるようにするには、アプリが特定の条件を満たしている必要があります。最も簡単な方法は、インストールが可能かどうか、Lighthouse を使用することです。

この Codelab を実施済みの場合、PWA はすでに次の基準を満たしている必要があります。

install.js を index.html に追加する

まず、install.jsindex.html ファイルに追加します。

public/index.html

<!-- CODELAB: Add the install script here -->
<script src="/scripts/install.js"></script>

beforeinstallprompt イベントをリッスンする

ホーム画面への追加条件が満たされると、Chrome から beforeinstallprompt イベントが発行されます。このイベントは、アプリをインストール可能であることを示すためにインストールを促すことができます。以下のコードを追加して、beforeinstallprompt イベントをリッスンします。

public/scripts/install.js

// CODELAB: Add event listener for beforeinstallprompt event
window.addEventListener('beforeinstallprompt', saveBeforeInstallPromptEvent);

予定を保存してインストール ボタンを表示する

saveBeforeInstallPromptEvent 関数で、beforeinstallprompt イベントへの参照を保存し、後で prompt() を呼び出して UI を更新してインストール ボタンを表示できるようにします。

public/scripts/install.js

// CODELAB: Add code to save event & show the install button.
deferredInstallPrompt = evt;
installButton.removeAttribute('hidden');

メッセージを表示してボタンを非表示にする

ユーザーがインストール ボタンをクリックしたときに、保存された beforeinstallprompt イベントで .prompt() を呼び出す必要があります。また、.prompt() は保存されたイベントごとに 1 回しか呼び出せないので、インストール ボタンも非表示にする必要があります。

public/scripts/install.js

// CODELAB: Add code show install prompt & hide the install button.
deferredInstallPrompt.prompt();
// Hide the install button, it can't be called twice.
evt.srcElement.setAttribute('hidden', true);

.prompt() を呼び出すと、ユーザーにモーダル ダイアログが表示され、ホーム画面にアプリを追加するよう求められます。

結果を記録する

保存された beforeinstallprompt イベントの userChoice プロパティから返される Promise をリッスンすることで、ユーザーがインストール ダイアログに応答したかどうかを確認できます。Promise は、プロンプトが表示されてユーザーが応答した後で、outcome プロパティを含むオブジェクトを返します。

public/scripts/install.js

// CODELAB: Log user response to prompt.
deferredInstallPrompt.userChoice
    .then((choice) => {
      if (choice.outcome === 'accepted') {
        console.log('User accepted the A2HS prompt', choice);
      } else {
        console.log('User dismissed the A2HS prompt', choice);
      }
      deferredInstallPrompt = null;
    });

userChoice については 1 つのコメントがあります。仕様では、プロパティとして定義されており、期待される関数ではありません。

すべてのインストール イベントをログに記録する

ユーザーは、アプリのインストール時に追加した UI のほか、Chrome のその他メニューなどの方法で PWA をインストールすることもできます。これらのイベントを追跡するには、アプリのインストール イベントをリッスンします。

public/scripts/install.js

// CODELAB: Add event listener for appinstalled event
window.addEventListener('appinstalled', logAppInstalled);

次に、logAppInstalled 関数を更新する必要があります。この Codelab では単に console.log を使用しますが、本番環境アプリでは、おそらく分析ソフトウェアを使用してイベントとしてログに記録することをおすすめします。

public/scripts/install.js

// CODELAB: Add code to log the event
console.log('Weather App was installed.', evt);

Service Worker を更新する

すでにキャッシュされているファイルに変更を加えたため、service-worker.js ファイルの CACHE_NAME を更新するのを忘れないでください。DevTools の [Application] パネルの [Service Workers] ペインで [Bypass for network] チェックボックスをオンにすると、機能しますが、実際の環境では役に立ちません。

お試しください

インストールのステップを見てみましょう。安全を確保するには、DevTools の [Application] パネルの [Clear site data] ボタンを使用して、すべてを消去し、最初から正常に始まるようにします。以前にこのアプリをインストールしていた場合は、必ずアンインストールしてください。アンインストールしないと、インストール アイコンが再び表示されません。

インストール ボタンが表示されていることを確認する

まず、インストール アイコンが正しく表示されることを確認します。必ずモバイルとパソコンの両方でお試しください。

  1. 新しい Chrome タブで URL を開きます。
  2. Chrome のその他メニュー(アドレスバーの横)を開きます。
    ▢ メニューに [天気情報のインストール] と表示されていることを確認します。
  3. 右上の更新ボタンで天気データを更新して、ユーザー エンゲージメントのヒューリスティックに確実に対応できるようにします。
    ▢ インストール アイコンがアプリのヘッダーに表示されていることを確認します。

インストール ボタンが機能することを確認する

次に、すべてが正しくインストールされ、イベントが適切に呼び出されることを確認します。この設定はパソコンとモバイルのどちらからでも行えます。モバイルでこれをテストする場合は、リモート デバッグを使用して、コンソールに何が記録されているかを確認してください。

  1. Chrome を開き、新しいブラウザタブで天気予報アプリに移動します。
  2. DevTools を開き、[Console] パネルに切り替えます。
  3. 右上にあるインストール ボタンをクリックします。
    ▢ インストール ボタンが消えることを確認する
    ▢ インストール モーダル ダイアログが表示されることを確認する。
  4. [キャンセル] をクリックします。
    ユーザーが A2HS プロンプトを閉じたことをコンソールの出力に表示します。
    ▢ インストール ボタンが再度表示されることを確認します。
  5. インストール ボタンを再度クリックし、モーダル ダイアログの [インストール] ボタンをクリックします。
    ユーザーが A2HS プロンプトを承認したことを示すコンソールの表示
  6. Weather PWA を起動します。
    ▢ アプリがスタンドアロン アプリとして(パソコンのアプリ ウィンドウまたはモバイルの全画面表示で)開くことを確認する。

.

iOS が適切にインストールされていることを確認する

iOS でも動作を確認してみましょう。iOS デバイスをお使いの場合は iOS を使用できます。Mac の場合は Xcode で利用できる iOS シミュレータをお試しください。

  1. Safari を開き、新しいブラウザタブで [Weather PWA] に移動します。
  2. 共有ボタン をクリックします。
  3. 右にスクロールして、[ホーム画面に追加] ボタンをクリックします。
    ▢ タイトル、URL、アイコンが正しいことを確認します。
  4. [追加
    ] をクリック
    ▢ アプリのアイコンがホーム画面に追加されていることを確認します。
  5. ホーム画面から天気情報 PWA を起動します。
    ▢ アプリが全画面表示されることを確認します。

参考: アプリがホーム画面から起動されたかどうかを検出する

display-mode メディアクエリを使用すると、アプリの起動方法に応じてスタイルを適用したり、JavaScript で起動方法を判断したりすることができます。

@media all and (display-mode: standalone) {
  body {
    background-color: yellow;
  }
}

JavaScript で display-mode のメディアクエリを調べて、スタンドアロンで実行されているかどうかを確認できます。

参考: PWA のアンインストール

beforeinstallevent は、アプリがすでにインストールされている場合は起動しないことに注意してください。開発中に、アプリを複数回インストールおよびアンインストールして、すべてが想定どおりに機能することを確認できます。

Android

Android では、PWA は他のインストール済みアプリがアンインストールされるのと同じ方法でアンインストールされます。

  1. アプリドロワーを開きます。
  2. 下にスクロールして天気アイコンを見つけます。
  3. アプリアイコンを画面上部にドラッグします。
  4. [アンインストール] を選択します。

Chrome OS

ChromeOS では、PWA はランチャーの検索ボックスから簡単にアンインストールできます。

  1. ランチャーを開きます。
  2. 検索ボックスに「天気」と入力すると、天気予報に関する検索結果が表示されます。
  3. 天気情報 PWA を右クリック(alt クリック)します。
  4. [Chrome から削除] をクリックします。

macOS と Windows

Mac と Windows では、Chrome から PWA をアンインストールできます。

  1. 新しいブラウザタブで、chrome://apps を開きます。
  2. 天気情報 PWA を右クリック(alt クリック)します。
  3. [Chrome から削除] をクリックします。

または、インストールした PWA を開き、右上のその他メニューをクリックして [Weather PWA をアンインストールする] を選択します。

これで、初めてのプログレッシブ ウェブアプリの作成が完了しました。

ウェブアプリ マニフェストを追加してインストールできるようにし、Service Worker を追加して、PWA が常に高速で信頼性が高いことを確認しました。DevTools を使用してアプリを監査する方法と、そのツールを使用してユーザー エクスペリエンスを向上させる方法を学びました。

ここまで、ウェブアプリをプログレッシブ ウェブアプリにするために必要となる主要な手順について学習しました。

次のステップ

以下の Codelab をご覧ください。

参考資料

リファレンス ドキュメント