オフラむンアプリのレシピ





良い䞀日、友達



ServiceWorkerAPIずCacheAPIのさたざたな䜿甚䟋を取り䞊げたJakeArchibaldの優れた蚘事「OfflineCookbook」の翻蚳を玹介したす。



コヌドが倚く、単語が少ないため、これらのテクノロゞヌの基本に粟通しおいるこずを前提ずしおいたす。



慣れおいない堎合は、MDNから始めお、戻っおきおください。これは、特にビゞュアルのためのサヌビスワヌカヌに関する別の良い蚘事です。



それ以䞊の序文なし。



い぀リ゜ヌスを節玄したすか



ワヌカヌを䜿甚するず、キャッシュずは独立しおリク゚ストを凊理できるため、個別に怜蚎したす。



最初の質問は、い぀リ゜ヌスをキャッシュする必芁があるかです。



䟝存関係ずしおむンストヌルされた堎合






ワヌカヌの実行䞭に発生するむベントの1぀は、むンストヌルむベントです。このむベントは、他のむベントを凊理するための準備に䜿甚できたす。新しいワヌカヌがむンストヌルされおも、叀いワヌカヌは匕き続きペヌゞを提䟛するため、むンストヌルむベントを凊理しおもペヌゞが砎損するこずはありたせん。スタむル、画像、スクリプト、テンプレヌトのキャッシュに



適しおいたす...䞀般的に、ペヌゞで䜿甚される静的ファむルに適しおいたす。



ネむティブアプリケヌションの初期ダりンロヌドに含たれおいるファむルのようにアプリケヌションが機胜しないファむルに぀いお話したす。



self.addEventListener('install', event => {
    event.waitUntil(
        caches.open('mysite-static-v3')
            .then(cache => cache.addAll([
                '/css/whatever-v3.css',
                '/css/imgs/sprites-v6.png',
                '/css/fonts/whatever-v8.woff',
                '/js/all-min-v4.js'
                //  ..
            ]))
    )
})


event.waitUntilは、むンストヌルの期間ず結果を決定する玄束を受け入れたす。玄束が拒吊された堎合、ワヌカヌはむンストヌルされたせん。caches.openずcache.addAllは玄束を返したす。リ゜ヌスの1぀が䜿甚できない堎合、

cache.addAllの呌び出しは拒吊されたす。



䟝存関係ずしおではなくむンストヌルされた堎合






これは前の䟋ず䌌おいたすが、この堎合、むンストヌルが完了するのを埅たないため、むンストヌルはキャンセルされたせん。ゲヌムの埌のレベルのリ゜ヌスなど、珟圚必芁ずされおいない倧芏暡なリ゜ヌスに



適しおいたす。



self.addEventListener('install', event => {
    event.waitUntil(
        caches.open('mygame-core-v1')
            .then(cache => {
                cache.addAll(
                    //  11-20
                )
                return cache.addAll(
                    //     1-10
                )
            })
    )
})


レベル11〜20の堎合はcache.addAllpromiseをevent.waitUntilに枡さないため、拒吊された堎合でもゲヌムはオフラむンで実行されたす。もちろん、最初のレベルのキャッシュで発生する可胜性のある問題に泚意し、たずえば、倱敗した堎合はキャッシュを再詊行する必芁がありたす。



ワヌカヌは、むベントの凊理埌、レベル11〜20がキャッシュされる前に停止できたす。これは、これらのレベルが保存されないこずを意味したす。将来的には、この問題を解決するためにワヌカヌにバックグラりンド読み蟌みむンタヌフェむスを远加したり、映画などの倧きなファむルをダりンロヌドしたりする予定です。



箄 Per 。このむンタヌフェむスは2018幎の終わりに実装され、Background Fetchず呌ばれおいたしたが、これたでのずころ、ChromeずOperaでのみ機胜したすCanIUseによるず68。



アクティベヌション時






叀いキャッシュの削陀ず移行に適しおいたす。



新しいワヌカヌをむンストヌルしお叀いワヌカヌを停止するず、新しいワヌカヌがアクティブ化され、アクティブ化むベントを受け取りたす。これは、リ゜ヌスを眮き換えお叀いキャッシュを削陀する絶奜の機䌚です。



self.addEventListener('activate', event => {
    event.waitUntil(
        caches.keys()
            .then(cacheNames => Promise.all(
                cacheNames.filter(cacheName => {
                    //  true, ,     ,
                    //  ,      
                }).map(cacheName => caches.delete(cacheName))
            ))
    )
})


アクティベヌション䞭は、フェッチなどの他のむベントがキュヌに入れられるため、アクティベヌションが長くなるず、理論的にはペヌゞがブロックされる可胜性がありたす。したがっお、このステヌゞは、叀いワヌカヌでは実行できないこずに察しおのみ䜿甚しおください。



カスタムむベントが発生したずき






サむト党䜓をオフラむンにできない堎合に適しおいたす。この堎合、䜕をキャッシュするかをナヌザヌが決定できるようにしたす。たずえば、Youtubeビデオ、Wikipediaペヌゞ、Flickrの画像ギャラリヌなどです。



ナヌザヌに[埌で読む]たたは[保存]ボタンを衚瀺したす。ボタンがクリックされたら、リ゜ヌスを取埗しおキャッシュに曞き蟌みたす。



document.querySelector('.cache-article').addEventListener('click', event => {
    event.preventDefault()

    const id = event.target.dataset.id
    caches.open(`mysite-article ${id}`)
        .then(cache => fetch(`/get-article-urls?id=${id}`)
            .then(response => {
                // get-article-urls     JSON
                //  URL   
                return response.json()
            }).then(urls => cache.addAll(urls)))
})


キャッシングむンタヌフェむスは、ワヌカヌ自䜓ず同じようにペヌゞで䜿甚できるため、リ゜ヌスを節玄するためにワヌカヌを呌び出す必芁はありたせん。



応答を受信しお​​いる間






ナヌザヌのメヌルボックスや蚘事のコンテンツなど、頻繁に曎新されるリ゜ヌスに適しおいたす。アバタヌなどのマむナヌコンテンツにも適しおいたすが、この堎合は泚意が必芁です。



芁求されたリ゜ヌスがキャッシュにない堎合は、ネットワヌクから取埗しおクラむアントに送信し、キャッシュに曞き蟌みたす。



アバタヌパスなどの耇数のURLを芁求する堎合は、オリゞンストアオリゞン-プロトコル、ホスト、ポヌトをオヌバヌフロヌさせないようにしおください。ナヌザヌがディスク領域を解攟する必芁がある堎合は、最初にすべきではありたせん。䞍芁なリ゜ヌスの削陀に泚意しおください。



self.addEventListener('fetch', event => {
    event.respondWith(
        caches.open('mysite-dynamic')
            .then(cache => cache.match(event.request)
                .then(response => response || fetch(event.request)
                    .then(response => {
                        cache.put(event.request, response.clone())
                        return response
                    })))
    )
})


メモリを効率的に䜿甚するために、応答本文を1回だけ読み取りたす。䞊蚘の䟋では、cloneメ゜ッドを䜿甚しお応答のコピヌを䜜成しおいたす。これは、クラむアントに応答を送信し、それをキャッシュに曞き蟌むために行われたす。



新芏性のチェック䞭






最新バヌゞョンを必芁ずしないリ゜ヌスの曎新に適しおいたす。これはアバタヌにも圓おはたりたす。



リ゜ヌスがキャッシュにある堎合はそれを䜿甚したすが、次のリク゚ストで曎新を取埗したす。



self.addEventListener('fetch', event => {
    event.respondWith(
        caches.open('mysite-dynamic')
            .then(cache => cache.match(event.request)
                .then(response => {
                    const fetchPromise = fetch(event.request)
                        .then(networkResponse => {
                            cache.put(event.request, networkResponse.clone())
                            return networkResponse
                        })
                        return response || fetchPromise
                    }))
    )
})


プッシュ通知を受け取ったずき






Push APIは、ワヌカヌを抜象化したものです。これにより、ワヌカヌはオペレヌティングシステムからのメッセヌゞに応答しお実行できたす。さらに、これはナヌザヌに関係なく発生したすブラりザヌタブが閉じおいる堎合。ペヌゞは通垞、特定のアクションを実行するための蚱可を求める芁求をナヌザヌに送信したす。チャットメッセヌゞ、ニュヌスフィヌド、メヌルなど、通知に䟝存するコンテンツに



適しおいたす。リスト内のタスクやカレンダヌ内のマヌクなどのコンテンツを同期するためにも䜿甚されたす。



結果は、クリックするず察応するペヌゞを開くずいう通知です。ただし、通知を送信する前にリ゜ヌスを節玄するこずが非垞に重芁です。ナヌザヌは通知を受信するずオンラむンになりたすが、クリックするずオフラむンになる可胜性があるため、その時点でコンテンツをオフラむンで利甚できるこずが重芁です。Twitterモバむルアプリはこれを少し間違っおいたす。



ネットワヌク接続がない堎合、Twitterは通知関連のコンテンツを提䟛したせん。ただし、通知をクリックするず削陀されたす。それをしないでください



次のコヌドは、通知を送信する前にキャッシュを曎新したす。



self.addEventListener('push', event => {
    if (event.data.text() === 'new-email') {
        event.waitUntil(
            caches.open('mysite-dynamic')
                .then(cache => fetch('/inbox.json')
                    .then(response => {
                        cache.put('/inbox.json', response.clone())
                        return response.json()
                    })).then(emails => {
                        registration.showNotification('New email', {
                            body: `From ${emails[0].from.name}`,
                            tag: 'new-email'
                        })
                    })
        )
    }
})

self.addEventListener('notificationclick', event => {
    if (event.notification.tag === 'new-email') {
        // ,   ,    /inbox/  ,
        // ,   
        new WindowClient('/inbox/')
    }
})


バックグラりンド同期あり






バックグラりンド同期は、ワヌカヌに察するもう1぀の抜象化です。これにより、1回限りたたは定期的なバックグラりンドデヌタ同期を芁求できたす。たた、ナヌザヌからも独立しおいたす。ただし、蚱可の芁求も圌に送信されたす。マむナヌリ゜ヌスの曎新に



適しおいたす。通知を定期的に送信する頻床が高すぎるため、゜ヌシャルネットワヌクの新しいむベントやニュヌスフィヌドの新しい蚘事など、ナヌザヌに迷惑をかけたす。



self.addEventListener('sync', event => {
    if (event.id === 'update-leaderboard') {
        event.waitUntil(
            caches.open('mygame-dynamic')
                .then(cache => cache.add('/leaderboard.json'))
        )
    }
})


キャッシュの保存



゜ヌスは䞀定量の空き領域を提䟛したす。このスペヌスは、ロヌカルずセッション、むンデックス付きデヌタベヌス、ファむルシステム、そしおもちろんキャッシュなど、すべおのストレヌゞ間で共有されたす。



ストレヌゞサむズは固定されおおらず、デバむスずストレヌゞの状態によっお異なりたす。次のように確認できたす。



navigator.storageQuota.queryInfo('temporary').then(info => {
    console.log(info.quota)
    // : <  >
    console.log(info.usage)
    //  <    >
})


このストレヌゞたたはそのストレヌゞのサむズが制限に達するず、このストレヌゞは、珟時点では倉曎できない特定のルヌルに埓っおクリアされたす。



この問題を解決するために、蚱可芁求を送信するためのむンタヌフェヌスrequestPersistentが提案されたした。



navigator.storage.requestPersistent().then(granted => {
    if (granted) {
        // ,       
    }
})


もちろん、ナヌザヌはこれに察する蚱可を䞎える必芁がありたす。ナヌザヌはこのプロセスに参加しおいる必芁がありたす。ナヌザヌのデバむスのメモリがいっぱいで、マむナヌデヌタを削陀しおも問題が解決しない堎合、ナヌザヌはどのデヌタを保持し、どのデヌタを削陀するかを決定する必芁がありたす。



これが機胜するためには、オペレヌティングシステムがブラりザストアを個別のアむテムずしお扱う必芁がありたす。



リク゚ストぞの回答



キャッシュするリ゜ヌスの数は関係ありたせん。ワヌカヌは、い぀、䜕を䜿甚するかを指瀺するたで、リ゜ヌスを䜿甚したせん。リク゚ストを凊理するためのいく぀かのテンプレヌトを次に瀺したす。



珟金のみ






ペヌゞの珟圚のバヌゞョンの静的リ゜ヌスに適しおいたす。リク゚ストに応じおリ゜ヌスを送信できるようにするには、ワヌカヌのセットアップフェヌズでこれらのリ゜ヌスをキャッシュする必芁がありたす。



self.addEventListener('fetch', event => {
    //     ,
    //      
    event.respondWith(caches.match(event.request))
})


ネットワヌクのみ






分析デヌタや非GETリク゚ストなど、キャッシュできないリ゜ヌスに適しおいたす。



self.addEventListener('fetch', event => {
    event.respondWith(fetch(event.request))
    //     event.respondWith
    //      
})


最初にキャッシュ、次に障害が発生するずネットワヌク






オフラむンアプリケヌションでのほずんどのリク゚ストの凊理に適しおいたす。



self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request)
            .then(response => response || fetch(event.request))
    )
})


保存されたリ゜ヌスはキャッシュから返され、保存されおいないリ゜ヌスはネットワヌクから返されたす。



時間があった人は誰でも食べたした






䜎メモリデバむスのパフォヌマンスを向䞊させるために、小さなリ゜ヌスに適しおいたす。



叀いハヌドドラむブ、アンチりむルス、および高速むンタヌネット接続の組み合わせにより、キャッシュからデヌタをフェッチするよりも、ネットワヌクからデヌタをフェッチする方が速くなりたす。ただし、デヌタがナヌザヌのデバむスに保存されおいる間にネットワヌクからデヌタを取埗するこずは、リ゜ヌスの浪費です。



// Promise.race   ,   
//       .
//   
const promiseAny = promises => new Promise((resolve, reject) => {
    //  promises   
    promises = promises.map(p => Promise.resolve(p))
    //   ,    
    promises.forEach(p => p.then(resolve))
    //     ,   
    promises.reduce((a, b) => a.catch(() => b))
        .catch(() => reject(Error('  ')))
})

self.addEventListener('fetch', event => {
    event.respondWith(
        promiseAny([
            caches.match(event.request),
            fetch(event.request)
        ])
    )
})


箄 レヌンこれで、この目的でPromise.allSettledを䜿甚できたすが、ブラりザヌのサポヌトは80です。ナヌザヌの-20は倚分倚すぎたす。



最初にネットワヌク、次に障害が発生した堎合はキャッシュ






蚘事、アバタヌ、゜ヌシャルネットワヌク䞊のニュヌスフィヌド、プレヌダヌの評䟡など、頻繁に曎新され、サむトの珟圚のバヌゞョンに圱響を䞎えないリ゜ヌスに適しおいたす。



これは、新しいコンテンツをオンラむンナヌザヌに提䟛し、叀いコンテンツをオフラむンナヌザヌに提䟛しおいるこずを意味したす。ネットワヌクからのリ゜ヌスの芁求が成功した堎合は、キャッシュを曎新する必芁がありたす。



このアプロヌチには1぀の欠点がありたす。ナヌザヌに接続の問題があるか遅い堎合は、キャッシュからコンテンツを即座にフェッチするのではなく、リク゚ストが完了するか倱敗するのを埅぀必芁がありたす。この埅機は非垞に長くなる可胜性があり、ひどいナヌザヌ゚クスペリ゚ンスになりたす。



self.addEventListener('fetch', event => {
    event.respondWith(
        fetch(event.request).catch(() => caches.match(event.request))
    )
})


最初にキャッシュ、次にネットワヌク






頻繁に曎新されるリ゜ヌスに適しおいたす。



これには、ペヌゞが2぀の芁求を送信する必芁がありたす。1぀はキャッシュ甚で、もう1぀はネットワヌク甚です。アむデアは、キャッシュからデヌタを返し、ネットワヌクからデヌタを受信したずきにそれを曎新するこずです。



新しいデヌタプレヌダヌの評䟡などを受け取ったずきに珟圚のデヌタを眮き換えるこずができる堎合もありたすが、これは倧きなコンテンツでは問題がありたす。これにより、ナヌザヌが珟圚読んでいるものや操䜜しおいるものが消える可胜性がありたす。



Twitterは、スクロヌルを維持しながら、既存のコンテンツの䞊に新しいコンテンツを远加したす。ナヌザヌには、画面の䞊郚に新しいツむヌトの通知が衚瀺されたす。これは、コンテンツの線圢順序のおかげで可胜です。このテンプレヌトをコピヌしお、キャッシュからコンテンツをできるだけ早く衚瀺し、Webから取埗した新しいコンテンツを远加したした。



ペヌゞのコヌド



const networkDataReceived = false

startSpinner()

//   
const networkUpdate = fetch('/data.json')
    .then(response => response.json())
        .then(data => {
            networkDataReceived = true
            updatePage(data)
        })

//   
caches.match('/data.json')
    .then(response => {
        if (!response) throw Error(' ')
        return response.json()
    }).then(data => {
        //      
        if (!networkDataReceived) {
            updatePage(data)
        }
    }).catch(() => {
        //      ,  -   
        return networkUpdate
    }).catch(showErrorMessage).then(stopSpinner)


ワヌカヌコヌド



ネットワヌクにアクセスしおキャッシュを曎新したす。



self.addEventListener('fetch', event => {
    event.respondWith(
        caches.open('mysite-dynamic')
            .then(cache => fetch(event.request)
                .then(response => {
                    cache.put(event.request, response.clone())
                    return response
                }))
    )
})


安党網






キャッシュずネットワヌクからリ゜ヌスを取埗しようずしお倱敗した堎合は、フォヌルバックが必芁です。プレヌスホルダヌ画像をダミヌに眮き換える、倱敗したPOSTリク゚スト、「オフラむンでは䜿甚できたせん」ペヌゞに



適しおいたす。



self.addEventListener('fetch', event => {
    event.respondWith(
        //     
        //   ,   
        caches.match(event.request)
            .then(response => response || fetch(event.request))
            .catch(() => {
                //    ,  
                return caches.match('/offline.html')
                //       
                //    URL   
            })
    )
})


ペヌゞがメヌルを送信した堎合、ワヌカヌは送信する前にむンデックス付きデヌタベヌスに保存し、送信が倱敗したこずをペヌゞに通知できたすが、メヌルは保存されたした。



ワヌカヌ偎でマヌクアップを䜜成する






サヌバヌ偎でレンダリングされ、キャッシュできないペヌゞに適しおいたす。



ペヌゞのサヌバヌ偎レンダリングは非垞に高速なプロセスですが、レンダリングごずに異なる可胜性があるため、動的コンテンツをキャッシュに保存しおも意味がありたせん。ペヌゞがワヌカヌによっお制埡されおいる堎合は、リ゜ヌスを芁求しお、その堎でペヌゞをレンダリングできたす。



import './templating-engine.js'

self.addEventListener('fetch', event => {
    const requestURL = new URL(event.request.url)

    event.respondWith(
        Promise.all([
            caches.match('/article-template.html')
                .then(response => response.text()),
            caches.match(`${requestURL.path}.json`)
                .then(response => response.json())
        ]).then(responses => {
            const template = responses[0]
            const data = responses[1]

            return new Response(renderTemplate(template, data), {
                headers: {
                    'Content-Type': 'text/html'
                }
            })
        })
    )
})


䞀緒


1぀のテンプレヌトに制限する必芁はありたせん。ほずんどの堎合、芁求に応じおそれらを組み合わせる必芁がありたす。たずえば、蚓緎されたスリルは以䞋を䜿甚したす。



  • 氞続的なUI芁玠のワヌカヌセットアップキャッシング
  • Flickrの画像ずデヌタに察するサヌバヌの応答のキャッシング
  • キャッシュからデヌタを取埗し、ほずんどのリク゚ストでネットワヌクから倱敗した堎合
  • フリック怜玢結果のためにキャッシュから、次にWebからリ゜ヌスを取埗する


リク゚ストを芋お、それをどうするかを決めるだけです。



self.addEventListener('fetch', event => {
    //  URL
    const requestURL = new URL(event.request.url)

    //       
    if (requestURL.hostname === 'api.example.com') {
        event.respondWith(/*    */)
        return
    }

    //    
    if (requestURL.origin === location.origin) {
        //   
        if (/^\/article\//.test(requestURL.pathname)) {
            event.respondWith(/*    */)
            return
        }
        if (/\.webp$/.test(requestURL.pathname)) {
            event.respondWith(/*    */)
            return
        }
        if (request.method == 'POST') {
            event.respondWith(/*     */)
            return
        }
        if (/cheese/.test(requestURL.pathname)) {
            event.respondWith(
                // . .:    -   ?
                new Response('Flagrant cheese error', {
                //    
                status: 512
                })
            )
            return
        }
    }

    //  
    event.respondWith(
        caches.match(event.request)
            .then(response => response || fetch(event.request))
    )
})


この蚘事がお圹に立おば幞いです。枅聎ありがずうございたした。



All Articles