Webpackとワークボックスを使用してPWAを構築する

公開: 2022-03-10
クイックサマリー↬このチュートリアルは、オフラインで動作しないアプリを、オフラインで動作し、更新可能なアイコンを表示するPWAに変換するのに役立ちます。 ワークボックスを使用してアセットをプリキャッシュする方法、動的キャッシュを処理する方法、およびPWAの更新を処理する方法を学習します。 フォローして、これらのテクニックをWebサイトに適用する方法を確認してください。

プログレッシブウェブアプリ(PWA)は、最新のテクノロジーを使用してウェブ上でアプリのようなエクスペリエンスを提供するサイトです。 これは、「Webアプリマニフェスト」、「サービスワーカー」などの新しいテクノロジーの総称です。 これらのテクノロジーを組み合わせることで、Webサイトで高速で魅力的なユーザーエクスペリエンスを提供できます。

この記事は、既存の1ページのWebサイトにサービスワーカーを追加するためのステップバイステップのチュートリアルです。 Service Workerを使用すると、Webサイトをオフラインで機能させると同時に、サイトの更新をユーザーに通知できます。 これはWebpackにバンドルされている小さなプロジェクトに基づいているため、Workbox Webpackプラグイン(Workbox v4)を使用することに注意してください。

ツールを使用してServiceWorkerを生成することは、キャッシュを効率的に管理できるため、推奨されるアプローチです。 このチュートリアルでは、Workbox(Service Workerコードの生成を容易にするライブラリのセット)を使用して、ServiceWorkerを生成します。

プロジェクトに応じて、3つの異なる方法でWorkboxを使用できます。

  1. ワークボックスを任意のアプリケーションに統合できるコマンドラインインターフェイスを利用できます。
  2. Node.jsモジュールを使用すると、ワークボックスをgulpやgruntなどの任意のノードビルドツールに統合できます。
  3. Webpackで構築されたプロジェクトと簡単に統合できるWebpackプラグインが利用可能です。

Webpackはモジュールバンドラーです。 簡単にするために、JavaScriptの依存関係を管理するツールと考えることができます。 これにより、ライブラリからJavaScriptコードをインポートし、JavaScriptを1つまたは複数のファイルにバンドルできます。

ジャンプした後もっと! 以下を読み続けてください↓

開始するには、コンピューターに次のリポジトリのクローンを作成します。

 git clone [email protected]:jadjoubran/workbox-tutorial-v4.git cd workbox-tutorial-v4 npm install npm run dev

次に、 https://localhost:8080/に移動します。 このチュートリアル全体で使用する通貨アプリを確認できるはずです。

この記事で作成している通貨アプリのスクリーンショット。
「通貨」は、ユーロ(€)通貨に対する通貨の換算手数料を一覧表示するPWAです。 (大プレビュー)

アプリシェルから始める

アプリケーションシェル(または「アプリシェル」)は、ネイティブアプリから着想を得たパターンです。 アプリをよりネイティブな外観にするのに役立ちます。 これは、データのないレイアウトと構造をアプリに提供するだけです。これは、Webアプリの読み込みエクスペリエンスを向上させることを目的とした移行画面です。

ネイティブアプリのアプリシェルの例を次に示します。

Google Inbox App Shell
Google Inbox App Shell:メールがアプリシェルに読み込まれる数ミリ秒前。 (大プレビュー)
Twitterネイティブアプリシェル
Android上のTwitterのネイティブアプリ:ナビゲーションバー、タブ、ローダーを表示するアプリシェル。 (大プレビュー)

PWAのアプリシェルの例は次のとおりです。

TwitterのPWAのアプリシェル
TwitterのPWAのアプリシェル(大プレビュー)
フリップカートのPWAのアプリシェル
FlipkartのPWAのアプリシェル(大プレビュー)

ユーザーは、空白の画面を軽蔑するため、アプリシェルの読み込みエクスペリエンスを好みます。 空白の画面は、ユーザーにWebサイトがロードされていないことを感じさせます。 それは彼らにウェブサイトが立ち往生しているように感じさせます。

アプリシェルは、ナビゲーションバー、タブバー、リクエストしたコンテンツが読み込まれていることを示すローダーなど、アプリのナビゲーション構造をできるだけ早くペイントしようとします。

では、どのようにしてアプリシェルを構築しますか?

アプリのシェルパターンは、最初にレンダリングされるHTML、CSS、JavaScriptの読み込みを優先します。 つまり、これらのリソースを完全に優先する必要があるため、これらのアセットをインライン化する必要があります。 したがって、アプリシェルを構築するには、アプリシェルを担当するHTML、CSS、JavaScriptをインライン化する必要があります。 もちろん、すべてをインライン化するのではなく、合計で約30〜40KBの制限内にとどまる必要があります。

インライン化されたアプリシェルはindex.htmlで確認できます。 index.htmlファイルをチェックアウトすることでソースコードを検査でき、開発ツールで<main>要素を削除することでブラウザでプレビューできます。

ナビゲーションバーとローダーを備えた通貨PWAアプリシェル
この記事で構築しているAppShell。 (大プレビュー)

オフラインで動作しますか?

オフラインになることをシミュレートしましょう! DevToolsを開き、[ネットワーク]タブに移動して、[オフライン]チェックボックスをオンにします。 ページをリロードすると、ブラウザのオフラインページが表示されます。

ブラウザのオフラインエラーページ
ホームページへのリクエストは失敗したので、結果として明らかにこれがわかります。 (大プレビュー)

これは、インターネットがオフラインであるため、 /への最初の要求( index.htmlファイルをロードします)が失敗するためです。 その要求の失敗から回復する唯一の方法は、サービスワーカーを配置することです。

サービスワーカーなしでリクエストを視覚化してみましょう。

クライアントのブラウザからインターネットに送信されるネットワークリクエストのアニメーション。
ネットワーク要求は、ブラウザからインターネットに行き来します。 (flaticon.comのアイコン)(大きなプレビュー)

サービスワーカーはプログラム可能なネットワークプロキシです。つまり、サービスワーカーはWebページとインターネットの間に位置します。 これにより、着信および発信ネットワーク要求を制御できます。

ServiceWorkerによってインターセプトされたネットワーク要求のアニメーション。
ネットワーク要求は、ServiceWorkerによってインターセプトされます。 (flaticon.comのアイコン)(大きなプレビュー)

これは、この失敗したリクエストをキャッシュに再ルーティングできるため、有益です(キャッシュにコンテンツがあると仮定します)。

Service Workerによってインターセプトされ、キャッシュにリダイレクトされるネットワーク要求のアニメーション。
ネットワーク要求は、キャッシュにすでに存在する場合、キャッシュにリダイレクトされます。 (flaticon.comのアイコン)(大きなプレビュー)

Service Workerも一種のWebWorkerです。つまり、メインページとは別に実行され、 windowまたはdocumentオブジェクトにアクセスできません。

AppShellをプリキャッシュする

アプリをオフラインで動作させるために、アプリシェルをプリキャッシュすることから始めます。

それでは、WebpackWorkboxプラグインをインストールすることから始めましょう。

 npm install --save-dev workbox-webpack-plugin

次に、 index.jsファイルを開き、サービスワーカーを登録します。

 if ("serviceWorker" in navigator){ window.addEventListener("load", () => { navigator.serviceWorker.register("/sw.js"); }) }

次に、 webpack.config.jsファイルを開き、Workboxwebpackプラグインを構成しましょう。

 //add at the top const WorkboxWebpackPlugin = require("workbox-webpack-plugin"); //add inside the plugins array: plugins: [ … , new WorkboxWebpackPlugin.InjectManifest({ swSrc: "./src/src-sw.js", swDest: "sw.js" }) ]

これにより、。 / src /src-sw.jsファイルをベースとして使用するようにWorkboxに指示されます。 生成されたファイルはsw.jsと呼ばれ、 distフォルダーにあります。

次に、ルートレベルで./src/src-sw.jsファイルを作成し、その中に次のように記述します。

 workbox.precaching.precacheAndRoute(self.__precacheManifest);

self.__precacheManifest変数は、ワークボックスによって動的に生成されるファイルからインポートされます。

これで、 npm run buildを使用してコードをビルドする準備が整い、Workboxはdistフォルダー内に2つのファイルを生成します。

  • precache-manifest.66cf63077c7e4a70ba741ee9e6a8da29.js
  • sw.js

sw.jsは、CDNおよびprecache-manifest。[chunkhash] .jsからワークボックスをインポートします。

 //precache-manifest.[chunkhash].js file self.__precacheManifest = (self.__precacheManifest || []).concat([ "revision": "ba8f7488757693a5a5b1e712ac29cc28", "url": "index.html" }, "url": "main.49467c51ac5e0cb2b58e.js" ]);

precacheマニフェストには、webpackによって処理され、最終的にdistフォルダーに格納されるファイルの名前が一覧表示されます。 これらのファイルを使用して、ブラウザにプリキャッシュします。 これは、Webサイトが最初にロードされ、Service Workerを登録するときに、次回使用できるようにこれらのアセットをキャッシュすることを意味します。

また、一部のエントリには「リビジョン」があり、他のエントリにはないことに注意してください。 これは、ファイル名のチャンクハッシュからリビジョンを推測できる場合があるためです。 たとえば、ファイル名main.49467c51ac5e0cb2b58e.jsを詳しく見てみましょう。 ファイル名にリビジョンがあります。これはchunkhash49467c51ac5e0cb2b58eです

これにより、Workboxはファイルがいつ変更されたかを認識できるため、新しいバージョンのService Workerを公開するたびにすべてのキャッシュをダンプするのではなく、変更されたファイルのみをクリーンアップまたは更新します。

ページを初めてロードするときに、ServiceWorkerがインストールされます。 あなたはDevToolsでそれを見ることができます。 最初にsw.jsファイルが要求され、次に他のすべてのファイルが要求されます。 それらは歯車のアイコンではっきりとマークされています。

ServiceWorkerがインストールされたときの[DevToolsネットワーク]タブのスクリーンショット。
️アイコンでマークされたリクエストは、サービスワーカーによって開始されたリクエストです。 (大プレビュー)

そのため、Workboxは初期化され、precache-manifestにあるすべてのファイルをprecacheします。 .mapファイルやアプリシェルの一部ではないファイルなど、 precache-manifestファイルに不要なファイルがないことを再確認することが重要です。

[ネットワーク]タブでは、ServiceWorkerからのリクエストを確認できます。 そして今、あなたがオフラインにしようとすると、アプリシェルはすでに事前にキャッシュされているので、オフラインでも機能します!

API呼び出しが失敗したことを示す[開発ツールネットワーク]タブのスクリーンショット。
オフラインになると、API呼び出しは失敗します。 (大プレビュー)

動的ルートのキャッシュ

オフラインにすると、アプリシェルは機能しますが、データは機能しないことに気づきましたか? これは、これらのAPI呼び出しが事前にキャッシュされたアプリシェルの一部ではないためです。 インターネットに接続されていない場合、これらのリクエストは失敗し、ユーザーは通貨情報を見ることができなくなります。

ただし、これらのリクエストはAPIからの値であるため、事前にキャッシュすることはできません。 さらに、複数のページが作成され始めた場合、すべてのAPIリクエストを一度にキャッシュする必要はありません。 代わりに、ユーザーがそのページにアクセスしたときにそれらをキャッシュする必要があります。

これらを「動的データ」と呼びます。 多くの場合、API呼び出しや、ユーザーがWebサイトで特定のアクションを実行したとき(たとえば、新しいページを参照したとき)に要求される画像やその他のアセットが含まれます。

Workboxのルーティングモジュールを使用してこれらをキャッシュできます。 方法は次のとおりです。

 //add in src/src-sw.js workbox.routing.registerRoute( /https:\/\/api\.exchangeratesapi\.io\/latest/, new workbox.strategies.NetworkFirst({ cacheName: "currencies", plugins: [ new workbox.expiration.Plugin({ maxAgeSeconds: 10 * 60 // 10 minutes }) ] }) );

これにより、URLhttps https://api.exchangeratesapi.io/latestに一致するリクエストURLの動的キャッシュが設定されます。

ここで使用したキャッシング戦略はNetworkFirstと呼ばれます。 よく使用されるものは他に2つあります。

  1. CacheFirst
  2. StaleWhileRevalidate

CacheFirstは、最初にキャッシュ内でそれを探します。 見つからない場合は、ネットワークから取得します。 StaleWhileRevalidateは、ネットワークとキャッシュに同時に移動します。 キャッシュの応答をページに返します(バックグラウンドで)。新しいネットワーク応答を使用して、次に使用されるときにキャッシュを更新します。

私たちのユースケースでは、頻繁に変化する為替レートを扱っているため、 NetworkFirstを使用する必要がありました。 ただし、ユーザーがオフラインになった場合、少なくとも10分前のレートを表示できます。そのため、 maxAgeSeconds10 * 60秒に設定して有効期限プラグインを使用しました。

アプリの更新を管理する

ユーザーがページをロードするたびに、Service Workerが既にインストールされて実行されている場合でも、ブラウザーはnavigator.serviceWorker.registerコードを実行します。 これにより、ブラウザは新しいバージョンのServiceWorkerがあるかどうかを検出できます。 ブラウザは、ファイルが変更されていないことに気付くと、登録呼び出しをスキップします。 そのファイルが変更されると、ブラウザは新しいバージョンのService Workerがあることを認識し、現在実行中のServiceWorkerと並行して新しいServiceWorkerをインストールします

ただし、同時にアクティブ化できるService Workerは1つだけであるため、 installed/waitingフェーズで一時停止します。

サービスワーカーのライフサイクル:解析、インストール/待機、アクティブ化、および冗長
サービスワーカーの簡素化されたライフサイクル(大プレビュー)

以前のServiceWorkerによって制御されていたすべてのブラウザーウィンドウが閉じられた場合にのみ、新しいServiceWorkerが安全にアクティブ化されます。

また、 skipWaiting() (または、 selfはサービスワーカーのグローバル実行コンテキストであるため、 self.skipWaiting() ))を呼び出すことにより、手動で制御することもできます。 ただし、ほとんどの場合、最新の更新を取得するかどうかをユーザーに尋ねた後でのみ、これを行う必要があります。

ありがたいことに、 workbox-windowはこれを達成するのに役立ちます。 これは、Workbox v4で導入された新しいウィンドウライブラリであり、ウィンドウ側の一般的なタスクを簡素化することを目的としています。

次のようにインストールすることから始めましょう。

 npm install workbox-window

次に、ファイルindex.jsの先頭にあるWorkboxをインポートします。

 import { Workbox } from "workbox-window";

次に、登録コードを以下に置き換えます。

 if ("serviceWorker" in navigator) { window.addEventListener("load", () => { const wb = new Workbox("/sw.js"); wb.register(); }); }

次に、IDを持つ更新ボタンを見つけますapp-update workbox-waitingイベントをリッスンします。

 //add before the wb.register() const updateButton = document.querySelector("#app-update"); // Fires when the registered service worker has installed but is waiting to activate. wb.addEventListener("waiting", event => { updateButton.classList.add("show"); updateButton.addEventListener("click", () => { // Set up a listener that will reload the page as soon as the previously waiting service worker has taken control. wb.addEventListener("controlling", event => { window.location.reload(); }); // Send a message telling the service worker to skip waiting. // This will trigger the `controlling` event handler above. wb.messageSW({ type: "SKIP_WAITING" }); }); });

このコードは、新しい更新がある場合(つまり、Service Workerが待機状態の場合)に更新ボタンを表示し、 SKIP_WAITINGメッセージをServiceWorkerに送信します。

service Workerファイルを更新し、 skipWaitingを呼び出すようにSKIP_WAITINGイベントを処理する必要があります。

 //add in src-sw.js addEventListener("message", event => { if (event.data && event.data.type === "SKIP_WAITING") { skipWaiting(); });

次に、 npm run devを実行してから、ページをリロードします。 コードに移動し、ナビゲーションバーのタイトルを「Navbarv2」に更新します。 ページを再度リロードすると、更新アイコンが表示されるはずです。

まとめ

現在、当社のWebサイトはオフラインで機能し、ユーザーに新しい更新について通知することができます。 ただし、PWAを構築する際の最も重要な要素は、ユーザーエクスペリエンスであることに注意してください。 ユーザーが使いやすいエクスペリエンスの構築に常に焦点を合わせます。 私たちは開発者として、テクノロジーに興奮しすぎて、ユーザーのことを忘れてしまう傾向があります。

これをさらに一歩進めたい場合は、ユーザーがサイトをホーム画面に追加できるようにするWebアプリマニフェストを追加できます。 また、Workboxについて詳しく知りたい場合は、WorkboxのWebサイトで公式ドキュメントを見つけることができます。

SmashingMagの詳細

  • モバイルアプリまたはPWAでより多くのお金を稼ぐことができますか?
  • プログレッシブWebアプリケーションの広範なガイド
  • ネイティブとPWA:挑戦者ではなく、選択肢!
  • Angular6を使用したPWAの構築