サービスワーカー。 使用説明書





モバイルデバイスからのインターネットアクセスの数は、毎年2〜4%増加しています。 コミュニケーションの質はこのペースに追いついていません。 その結果、ユーザーがダウンロードできない場合でも、最高のWebアプリケーションであってもひどいエクスペリエンスを提供します。



問題は、リソースキャッシュとネットワーククエリの結果を管理するための優れたメカニズムがまだないことです。 私の記事では、Service Worker(SW)がこの問題を解決する方法を説明したいと思います。 レシピの形式で説明します-タスクと要件に基づいて、望ましい結果を得るためにどの要素とどの割合で混合するかを説明します。



SWが登場する前に、オフラインモードでの作業の問題は別のAPI-AppCacheによって解決されました。 ただし、AppCacheの落とし穴とともに、単一ページアプリケーションではうまく機能しますが、複数ページのサイトにはあまり適していません。 SWは、これらの問題を回避するように設計されています。



サービスワーカーとは何ですか?



まず、これはブラウザがページとは別にバックグラウンドで実行されるスクリプトであり、Webページやユーザーの操作を必要としない機能の扉を開きます。 今日、彼らはプッシュ通知やバックグラウンド同期などの機能を実行しますが、将来、SWは他のものをサポートします。 主な機能は、プログラムによる応答キャッシュの管理を含む、ネットワーク要求をインターセプトおよび処理する機能です。



次に、SWはワーカーコンテキストで起動されるため、DOMにアクセスできず、アプリケーションを制御するメインJavaScriptスレッドとは別のスレッドで動作するため、ブロックしません。 完全に非同期になるように設計されているため、SWで同期API(XHRおよびLocalStorage)を使用することはできません。



第三に、セキュリティ上の理由から、SWはHTTPSでのみ動作します。見知らぬ人にネットワーク要求を変更する権限を与えることは非常に危険だからです。



何をキャッシュする必要がありますか?



アプリケーションの応答性を高めるために、すべての静的ファイルをキャッシュする必要があります。





この状況にLocalStorageを使用できないのはなぜですか?



すべてが非常に簡単です。 LocalStorageは同期APIであり、5MBの制限があり、行のみを保存できます。



エディ・オスマニによる記事「 プログレッシブWebアプリのオフラインストレージ 」によると、SWはこれによりうまく機能しています。非同期で、リクエストのプロキシです。





私はすでにService Workerが好きです。 使い方は?



以下に、SWを作成してレスポンシブで理解可能なアプリケーションを作成するためのレシピについて説明します。



サービスワーカーの準備



独自のソフトウェアを作成するには、次のものが必要です。





あなたがする必要があるのは、sw.jsファイルが登録されるindex.htmlのindex.jsを接続することです



//  ,     Service Worker API. if ('serviceWorker' in navigator) { //      . navigator.serviceWorker.register('./sw.js') .then(() => navigator.serviceWorker.ready.then((worker) => { worker.sync.register('syncdata'); })) .catch((err) => console.log(err)); }
      
      





sw.jsファイルでは、SWが応答する基本的なイベントを決定するだけです。



 self.addEventListener('install', (event) => { console.log(''); }); self.addEventListener('activate', (event) => { console.log(''); }); self.addEventListener('fetch', (event) => { console.log('   '); });
      
      





SWのライフサイクルの詳細については、 この記事をご覧ください。



レシピNo. 1-ネットワークまたはキャッシュ



メディアコンテンツが多いメディアサイトに適しています。 写真や動画は長期間提供できますが、私たちの仕事はそれらをキャッシュすることです。 サーバーへの後続のリクエストでは、キャッシュからデータを送信します。 データがすでに無関係である可能性があることを意味します。ここでの主なことは、ユーザーがファイルのダウンロードを待つのを防ぐことです。





解決策



このオプションは、コンテンツのダウンロード速度が優先される場合に適していますが、最も関連性の高いデータを表示したいと思います。



作業のメカニズムは次のとおりです。たとえば、400ミリ秒など、時間制限のあるリソースに対する要求があります。この時間内にデータを受信しなかった場合、キャッシュから送信します。



このレシピのSWは、最も関連性の高いコンテンツをネットワークから取得しようとしますが、リクエストに時間がかかりすぎると、データがキャッシュから取得されます。 この問題は、リクエストのタイムアウトを設定することで解決できます。



 const CACHE = 'network-or-cache-v1'; const timeout = 400; //         (). self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE).then((cache) => cache.addAll([ '/img/background' ]) )); }); //   fetch,    ,   ,    timeout. self.addEventListener('fetch', (event) => { event.respondWith(fromNetwork(event.request, timeout) .catch((err) => { console.log(`Error: ${err.message()}`); return fromCache(event.request); })); }); // - . function fromNetwork(request, timeout) { return new Promise((fulfill, reject) => { var timeoutId = setTimeout(reject, timeout); fetch(request).then((response) => { clearTimeout(timeoutId); fulfill(response); }, reject); }); } function fromCache(request) { //     (CacheStorage API),    . //  ,       Promise  ,    `undefined` return caches.open(CACHE).then((cache) => cache.match(request).then((matching) => matching || Promise.reject('no-match') )); }
      
      





レシピ2-キャッシュのみ



ランディングページの理想的なレシピ。そのタスクは、ユーザーに製品をデモンストレーションし、それによってサイトに注意を向けることです。 この場合、接続が不十分なコンテンツの読み込みが遅いことは単に受け入れられないため、このレシピの優先順位は、すべての接続でキャッシュからデータを返すことです。 例外は、キャッシュの最初の要求とフラッシュです。 この方法のマイナス点は、コンテンツを変更すると、キャッシュが無効になった後にユーザーが変更されることです。 デフォルトでは、SWはインストールの24時間後に再登録します。





解決策



SWを登録すると、すべての静的リソースをキャッシュするだけです。 リソースへの後続のアクセスでは、SWは常にキャッシュからのデータで応答します。



 const CACHE = 'cache-only-v1'; //         (). self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE).then((cache) => { return cache.addAll([ '/img/background' ]); }) ); }); //     ( fetch),     . self.addEventListener('fetch', (event) => event.respondWith(fromCache(event.request)); ); function fromCache(request) { return caches.open(CACHE).then((cache) => cache.match(request) .then((matching) => matching || Promise.reject('no-match')) ); }
      
      





レシピ番号3-キャッシュと更新



このレシピは、レシピ番号2にはなかったデータの関連性の問題を解決します。

つまり、更新されたコンテンツを取得しますが、次のページがロードされるまで遅延が発生します。





解決策



前のバージョンと同様に、このレシピでは、SWは最初にキャッシュから応答して迅速な応答を提供しますが、同時にネットワークからキャッシュデータを更新します。



 const CACHE = 'cache-and-update-v1'; //         (). self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE).then((cache) => cache.addAll(['/img/background'])) ); }); //   fetch,   ,         self.addEventListener('fetch', function(event) { //   `respondWith()`,        . event.respondWith(fromCache(event.request)); // `waitUntil()` ,     worker'a     . event.waitUntil(update(event.request)); }); function fromCache(request) { return caches.open(CACHE).then((cache) => cache.match(request).then((matching) => matching || Promise.reject('no-match') )); } function update(request) { return caches.open(CACHE).then((cache) => fetch(request).then((response) => cache.put(request, response) ) ); }
      
      





レシピ4-キャッシュ、更新、更新



レシピ番号3の拡張。 このソリューションでは、コンテンツをバックグラウンドで更新しますが、ページ上のデータが変更されたことをいつでもユーザーに伝えることができます。 例は、コンテンツがバックグラウンドで編集されるアプリケーションの作成です。 そのため、ニュースサイトで記事を読んで、ページのデータが更新され、最新の情報が表示されたという通知を受け取ります。









解決策



このレシピを使用すると、SWはキャッシュから応答して、迅速な応答を提供したり、ネットワークからキャッシュ内のデータを更新したりできます。 要求が成功すると、ユーザーインターフェイスは自動的に更新されるか、UIコントロールを介して更新されます。



キャッシュの内容を使用しますが、同時に、キャッシュエントリを更新する要求を実行し、新しいデータをUIに通知します。



 const CACHE = 'cache-update-and-refresh-v1'; //         (). self.addEventListener('install', (event) => { event.waitUntil( caches .open(CACHE) .then((cache) => cache.addAll(['/img/background'])) ); }); //               . self.addEventListener('fetch', (event) => { //     ,  `respondWith()`  `waitUntil()` event.respondWith(fromCache(event.request)); event.waitUntil( update(event.request) //  ,   ""      . .then(refresh) ); }); function fromCache(request) { return caches.open(CACHE).then((cache) => cache.match(request).then((matching) => matching || Promise.reject('no-match') )); } function update(request) { return caches.open(CACHE).then((cache) => fetch(request).then((response) => cache.put(request, response.clone()).then(() => response) ) ); } //       . function refresh(response) { return self.clients.matchAll().then((clients) => { clients.forEach((client) => { //   ETag    // https://en.wikipedia.org/wiki/HTTP_ETag const message = { type: 'refresh', url: response.url, eTag: response.headers.get('ETag') }; //     . client.postMessage(JSON.stringify(message)); }); }); }
      
      





レシピ5-埋め込みフォールバック



デフォルトのブラウザからオフラインのメッセージが表示されると問題が発生します。 私はこれを問題と呼んでいます:









この状況での最善の解決策は、オフラインキャッシュのカスタムフラグメントをユーザーに表示することです。 SWの助けを借りて、アプリケーションがオフラインであり、その機能が一定の期間制限されていることを示す事前に準備された回答を準備できます。





解決策



リソース(ネットワークとキャッシュ)へのアクセスがない場合、フォールバックデータを提供する必要があります。

データは事前​​に準備され、SWが利用できる静的リソースとして配置されます。



 const CACHE = 'offline-fallback-v1'; //         (). self.addEventListener('install', (event) => { event.waitUntil( caches .open(CACHE) .then((cache) => cache.addAll(['/img/background'])) // `skipWaiting()` ,      SW //    ,    . .then(() => self.skipWaiting()) ); }); self.addEventListener('activate', (event) => { // `self.clients.claim()`  SW      , //     `skipWaiting()`,   `fallback`    . event.waitUntil(self.clients.claim()); }); self.addEventListener('fetch', function(event) { //      . //     ,   `Embedded fallback`. event.respondWith(networkOrCache(event.request) .catch(() => useFallback())); }); function networkOrCache(request) { return fetch(request) .then((response) => response.ok ? response : fromCache(request)) .catch(() => fromCache(request)); } //  Fallback     . const FALLBACK = '<div>\n' + ' <div>App Title</div>\n' + ' <div>you are offline</div>\n' + ' <img src="/svg/or/base64/of/your/dinosaur" alt="dinosaur"/>\n' + '</div>'; //    , .      . function useFallback() { return Promise.resolve(new Response(FALLBACK, { headers: { 'Content-Type': 'text/html; charset=utf-8' }})); } function fromCache(request) { return caches.open(CACHE).then((cache) => cache.match(request).then((matching) => matching || Promise.reject('no-match') )); }
      
      





おわりに



上記では、アプリケーションにSWを使用するための基本的なレシピを検討しました。

それらはより複雑になるにつれて説明されます。 単純なランディングページがある場合-ワイルドに移動する必要はありません。キャッシュのみまたはネットワークまたはキャッシュを使用してください。 より複雑なアプリケーションの場合は、残りのレシピを使用します。



この記事は、SW APIに関する一連の記事の出発点となることを目的としています。 このトピックがどれほど興味深く有用であるかを理解したいと思います。 コメントや提案を待っています。



All Articles