摘要:最后用把緩存的路徑憑證信息存在中。緩存策略現(xiàn)在來(lái)看看提供的緩存策略,主要有這幾種。自定義緩存配置回到在緩存策略里提到的,講講和緩存策略的參數(shù)。
PWA之Workbox緩存策略分析作者:陳達(dá)孚
香港中文大學(xué)研究生,《移動(dòng)Web前端高效開(kāi)發(fā)實(shí)戰(zhàn)》作者之一,《前端開(kāi)發(fā)者指南2017》譯者之一,在中國(guó)前端開(kāi)發(fā)者大會(huì),中生代技術(shù)大會(huì)等技術(shù)會(huì)議發(fā)表過(guò)主題演講, 專注于新技術(shù)的調(diào)研和使用.
本文為原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明作者及出處
precache(預(yù)緩存)本文主要分析通過(guò)workbox(基于1.x和2.x版本,未來(lái)3.x版本會(huì)有新的結(jié)構(gòu))生成Service-Worker的緩存策略,workbox是GoogleChrome團(tuán)隊(duì)對(duì)原來(lái)sw-precache和sw-toolbox的封裝,并且提供了Webpack和Gulp插件方便開(kāi)發(fā)者快速生成sw.js文件。
首先看一下 workbox 提供的 Webpack 插件 workboxPlugin 的三個(gè)最主要參數(shù):
globDirectory
staticFileGlobs
swDest
其中 globDirectory 和 staticFileGlobs 會(huì)決定需要緩存的靜態(tài)文件,這兩個(gè)參數(shù)也存在默認(rèn)值,插件會(huì)從compilation參數(shù)中獲取開(kāi)發(fā)者在 Webpack 配置的 output.path 作為 globDirectory 的默認(rèn)值,staticFileGlobs 的默認(rèn)配置是 html,js,css 文件,如果需要緩存一些界面必須的圖片,這個(gè)地方需要自己配置。
之后 Webpack 插件會(huì)將配置作為參數(shù)傳遞給 workbox-build 模塊,workbox-build 模塊中會(huì)根據(jù) globDirectory 和 staticFileGlobs 讀取文件生成一份配置信息,交給 precache 處理。需要注意的是,precache里不要存太多的文件,workbox-build 對(duì)文件會(huì)有一個(gè)過(guò)濾, 該模塊會(huì)讀取利用 node 的 fs 模塊讀取文件,如果文件大于2M則不會(huì)加入配置中(可以通過(guò)配置 maximumFileSize 修改),同時(shí)會(huì)根據(jù)文件的 buffer 生成一個(gè) hash 值,也就是說(shuō)就算開(kāi)發(fā)者不改變文件名,只要文件內(nèi)容修改了,也會(huì)生成一個(gè)新的配置內(nèi)容,讓瀏覽器更新緩存。
那么說(shuō)了那么多,precache 到底干了什么,看一下生成的sw文件:
const fileManifest = [ { "url": "main.js", "revision": "0e438282dc400829497725a6931f66e3" }, { "url": "main.css", "revision": "02ba19bb320adb687e08dded3e71408d" } ]; const workboxSW = new self.WorkboxSW(); workboxSW.precache(fileManifest);
那還是需要看一下 precache 的代碼:
precache(revisionedFiles) { this._revisionedCacheManager.addToCacheList({ revisionedFiles, }) }
是的,workbox會(huì)提供一個(gè)對(duì)象 revisionedCacheManager 來(lái)管理所有的緩存,先不管里面具體怎么處理的,往下看有個(gè) registerInstallActivateEvents。
_registerInstallActivateEvents(skipWating, clientsClaim) { self.addEventListener("install", (event) => { const cachedUrls = this._revisionedCacheManager.getCachedUrls(); event.waitUntil( this._revisionedCacheManager.install().then(() => { if (skipWaiting) { return self.skipWaiting(); } }) ) }
這里可以看出,所有的 precache 都會(huì)在 service worker 的 install 事件中完成。event.waitUntil 會(huì)根據(jù)內(nèi)部promise的結(jié)果來(lái)確定安裝是否完成。如果安裝失敗,則會(huì)舍棄這個(gè)ServiceWorker。
現(xiàn)在看一下 _revisionedCacheManager.install 里干了什么,首先 revisionedFiles 會(huì)被放在一個(gè) Map 中,當(dāng)然這個(gè) revisionedFiles 是已經(jīng)被處理過(guò)了, 在經(jīng)過(guò) addToCacheList -> _addEntries -> _parseEntry 的過(guò)程后,會(huì)返回:
{ entryID, revision, request: new Request(url), cacheBust }
entryID 不主動(dòng)傳入可以視為用戶傳入的url,將用來(lái)作為IndexDB中的key存儲(chǔ)revision,而request則用來(lái)提供給之后的fetch請(qǐng)求,cacheBust默認(rèn)為true,功能等會(huì)再分析。
Map 的set 過(guò)程在 _addEntries 的 _addEntryToInstallList 函數(shù)中,這里只需注意因?yàn)?fileManifest 中不能存放具有相同 url (或者說(shuō)entryID)的值,不然會(huì)被警告。
現(xiàn)在回來(lái)看install,install是一個(gè)async函數(shù),返回一個(gè)包含一系列Promise請(qǐng)求的Promise.all,符合waitUntil的要求。每一個(gè)需要緩存的文件會(huì)到 cacheEntry 函數(shù)中處理:
async _cacheEntry(precacheEntry) { const isCached = await this._isAlreadyCached(precacheEntry); const precacheDetails = { url: precacheEntry.request.url, revision: precacheEntry.revision, wasUpdated: !isCached, }; if (isCached) { return precacheDetails; } try { await this._requestWrapper.fetchAndCache({ request: precacheEntry.getNetworkRequest(), waitOnCache: true, cacheKey: precacheEntry.request, cleanRedirects: true, }); await this._onEntryCached(precacheEntry); return precacheDetails; } catch (err) { throw new WorkboxError("request-not-cached", { url: precacheEntry.request.url, error: err, }); } }
對(duì)于每一個(gè)請(qǐng)求會(huì)去通過(guò) _isAlreadyCached 方法訪問(wèn)indexDB 得知是否被緩存過(guò)。這里可能有讀者會(huì)疑惑,我們不是不能在 fileManifest 中不允許存儲(chǔ)同樣的url,為什么還要查是否緩存過(guò),這是因?yàn)楫?dāng)你sw文件更新后,原來(lái)的緩存還是存在的,它們或許持有相同的url,如果它們的revision也相同,就不用獲取了。
在 _cacheEntry 內(nèi)部,還有兩個(gè)異步操作,一個(gè)是通過(guò)包裝后的 requestWrapper 的 fetchAndCache 請(qǐng)求并緩存數(shù)據(jù),一個(gè)是通過(guò) _onEntryCached 方法更新indexDB,可以看到雖然catch了錯(cuò)誤,但依舊會(huì)throw出來(lái),意味著任何一個(gè)precache的文件請(qǐng)求失敗,都會(huì)終止此次install。
這里另一個(gè)需要注意的地方是 _requestWrapper.fetchAndCache,所有請(qǐng)求最后都會(huì)在 requestWrapper中處理,這里調(diào)用的實(shí)例方法是 fetchAndCache ,說(shuō)明這次請(qǐng)求會(huì)涉及到網(wǎng)絡(luò)請(qǐng)求和緩存處理兩部分。在發(fā)出請(qǐng)求后,首先會(huì)判斷請(qǐng)求結(jié)果是否需要加入緩存中:
const effectiveCacheableResponsePlugin = this._userSpecifiedCachableResponsePlugin || cacheResponsePlugin || this.getDefaultCacheableResponsePlugin();
如果沒(méi)有插件配置,會(huì)使用 getDefaultCacheableResponsePlugin() 來(lái)取得默認(rèn)配置,即緩存返回狀態(tài)為200的請(qǐng)求。
在上面的代碼中可以看到在 precache 環(huán)境下,會(huì)有兩個(gè)參數(shù)為 true, 一個(gè)是 waitOnCache,另一個(gè)是cleanRedirects。waitOnCache保證在需要緩存的情況下返回網(wǎng)絡(luò)結(jié)果時(shí)必須完成緩存的處理,cleanRedirects則會(huì)重新包裝一下請(qǐng)求重定向的結(jié)果。
最后用_onEntryCached把緩存的路徑憑證信息存在indexDB中。
在activate階段,會(huì)對(duì)precache在cache里的內(nèi)容進(jìn)行clean,因?yàn)榍懊嬷蛔隽烁拢绻切碌膒recache沒(méi)有的資源地址,在這里會(huì)刪除。
所以 precache 就是在 service-worker 的 install 事件下完成一次對(duì)配置資源的網(wǎng)絡(luò)請(qǐng)求,并在請(qǐng)求結(jié)果返回時(shí)完成對(duì)結(jié)果的緩存。
runtimecache(運(yùn)行時(shí)緩存)在了解 runtimecache 前,先看下 workbox-sw 的實(shí)例化過(guò)程中比較重要的部分:
this._runtimeCacheName = getDefaultCacheName({cacheId}); this._revisionedCacheManager = new RevisionedCacheManager({ cacheId, plugins, }); this._strategies = new Strategies({ cacheId, }); this._router = new Router( this._revisionedCacheManager.getCacheName(), handleFetch ); this._registerInstallActivateEvents(skipWaiting, clientsClaim); this._registerDefaultRoutes(ignoreUrlParametersMatching, directoryIndex);
所以看出 workbox-sw 實(shí)例化的過(guò)程主要有生成緩存對(duì)應(yīng)空間名,緩存空間,掛載緩存策略,掛載路由方法(用于處理對(duì)應(yīng)路徑的緩存策略),注冊(cè)安裝激活方法,注冊(cè)默認(rèn)路由。
precache 對(duì)應(yīng)的就是 runtimecache,runtimecache 顧名思義就是處理所有運(yùn)行時(shí)的緩存,runtimecache 往往應(yīng)對(duì)著各種類型的資源,對(duì)于不同類型的資源往往也有不同的緩存策略,所以在 workbox 中使用 runtimecache 需要調(diào)用方法,workbox.router.registerRoute 也是說(shuō)明 runtimecache 需要路由層面的細(xì)致劃分。
看到最后一步的 _registerDefaultRoutes ,看一下其中的代碼,可以發(fā)現(xiàn) workbox 有一個(gè)最基本的cache,這個(gè) cache 其實(shí)處理的就是前面的 precache,這個(gè) cache 遵從著 cacheFirst 原則:
const cacheFirstHandler = this.strategies.cacheFirst({ cacheName: this._revisionedCacheManager.getCacheName(), plugins, excludeCacheId: true, }); const capture = ({url}) => { url.hash = ""; const cachedUrls = this._revisionedCacheManager.getCachedUrls(); if (cachedUrls.indexOf(url.href) !== -1) { return true; } let strippedUrl = this._removeIgnoreUrlParams(url.href, ignoreUrlParametersMatching); if (cachedUrls.indexOf(strippedUrl.href) !== -1) { return true; } if (directoryIndex && strippedUrl.pathname.endsWith("/")) { strippedUrl.pathname += directoryIndex; return cachedUrls.indexOf(strippedUrl.href) !== -1; } return false; }; this._precacheRouter.registerRoute(capture, cacheFirstHandler);
簡(jiǎn)單的說(shuō),如果你一個(gè)路徑能直接在 precache 中可以找到,或者在去除了部分查詢參數(shù)后符合,或者去處部分查詢參數(shù)添加后綴后符合,就會(huì)直接返回緩存,至于請(qǐng)求過(guò)來(lái)怎么處理的,稍后再看。
我們可以這么認(rèn)為 precache 就是添加了 cache,至于真實(shí)請(qǐng)求時(shí)如何處理還是和 runtimecache 在一個(gè)地方處理,現(xiàn)在看來(lái),在 workbox 初始化的時(shí)候就有了第一個(gè) router.registerRoute(),之后的就需要手動(dòng)注冊(cè)了。
在寫自己注冊(cè)的策略之前,考慮下,注冊(cè)了 route 后,又怎么處理呢?在實(shí)例化 Router 的時(shí)候,我們就會(huì)添加一個(gè) self.addEventListener("fetch", (event) => {...}),除非你手動(dòng)傳入一個(gè)handleFetch參數(shù)為false。
在注冊(cè)路由的時(shí)候,registerRoute(capture, handler, method)在類中接受一個(gè)捕獲條件和一個(gè)句柄函數(shù),這個(gè)捕獲條件可以是字符串,正則表達(dá)式或者是直接的Route對(duì)象,當(dāng)然最終都會(huì)變成 Route 對(duì)象(分別通過(guò) ExpressRoute 和 RegExpRoute),Route對(duì)象包含匹配,處理方法,和方法(默認(rèn)為 GET)。然后在注冊(cè)時(shí)會(huì)使用一個(gè) Map,以每個(gè)使用到的方法為 Key,值為包含所有Route對(duì)象的數(shù)組,在遍歷時(shí)也只會(huì)遍歷相應(yīng)方法的值。所以你也可以給不同的方法定義同樣的捕獲路徑。
這里使用了 unshift 操作,所以每個(gè)新的配置會(huì)被壓入堆棧的頂部,在遍歷時(shí)則會(huì)被優(yōu)先遍歷到。因?yàn)?workbox 實(shí)例化是在 registerRoute 之前,所以默認(rèn)配置優(yōu)先級(jí)最低,配置后面的注冊(cè)會(huì)優(yōu)先于前面的。
所以最終在頁(yè)面上,你的每次請(qǐng)求都會(huì)被監(jiān)聽(tīng),到相應(yīng)的請(qǐng)求方法數(shù)組里找有沒(méi)有匹配的,如果沒(méi)有匹配的話,也可以使用 setDefaultHandler,setDefaultHandler不是前面的 _registerDefaultRoutes,它需要開(kāi)發(fā)者自己定義,并決定策略,如果定義了,所有沒(méi)被匹配的請(qǐng)求就會(huì)被這個(gè)策略處理。請(qǐng)求還支持設(shè)置在,在請(qǐng)求被匹配卻沒(méi)有正確被方法處理情況下的錯(cuò)誤處理,最終 event 會(huì)用處理方法(策略)處理這個(gè)請(qǐng)求,否則就正常請(qǐng)求。這些請(qǐng)求就是 workbox下的 runtimecache。
緩存策略現(xiàn)在來(lái)看看 Workbox 提供的緩存策略,主要有這幾種:cache-first,cache-only,network-first,network-only,stale-while-revalidate。
在前面看到,實(shí)例化的時(shí)候會(huì)給 workbox 掛載一個(gè) Strategies 的實(shí)例。提供上面一系列的緩存策略,但在實(shí)際調(diào)用中,使用的是 _getCachingMechanism,然后把整個(gè)策略類放到一參中,二參則提供了配置項(xiàng),在每個(gè)策略類中都有 handle 方法的實(shí)現(xiàn),最終也會(huì)調(diào)用 handle方法。那既然如此還搞個(gè) _getCachingMechanism干嘛,直接返回策略類就得了,這個(gè)等下看。
先看下各個(gè)策略,這里就簡(jiǎn)單說(shuō)下,可以參考離線指南,雖然會(huì)有一點(diǎn)不一樣。
第一個(gè) Cache-First, 它的 handle 方法:
const cachedResponse = await this.requestWrapper.match({ request: event.request, }); return cachedResponse || await this.requestWrapper.fetchAndCache({ request: event.request, waitOnCache: this.waitOnCache, });
Cache-First策略會(huì)在有緩存的時(shí)候返回緩存,沒(méi)有緩存才會(huì)去請(qǐng)求并且把請(qǐng)求結(jié)果緩存,這也是我們對(duì)于precache的策略。
然后是 Cache-only,它只會(huì)去緩存里拿數(shù)據(jù),沒(méi)有就失敗了。
network-first 是一個(gè)比較復(fù)雜的策略,它接受 networkTimeoutSeconds 參數(shù),如果沒(méi)有傳這個(gè)參數(shù),請(qǐng)求將會(huì)發(fā)出,成功的話就返回結(jié)果添加到緩存中,如果失敗則返回立即緩存。這種網(wǎng)絡(luò)回退到緩存的方式雖然利于那些頻繁更新的資源,但是在網(wǎng)絡(luò)情況比較差的情況(無(wú)網(wǎng)會(huì)直接返回緩存)下,等待會(huì)比較久,這時(shí)候 networkTimeoutSeconds 就提供了作用,如果設(shè)置了,會(huì)生成一個(gè)setTimeout后被resolve的緩存調(diào)用,再把它和請(qǐng)求放倒一個(gè) Promise.race 中,那么請(qǐng)求超時(shí)后就會(huì)返回緩存。
network-only,也比較簡(jiǎn)單,只請(qǐng)求,不讀寫緩存。
最后提供的策略是 StaleWhileRevalidate,這種策略比較接近 cache-first,代碼如下:
const fetchAndCacheResponse = this.requestWrapper.fetchAndCache({ request: event.request, waitOnCache: this.waitOnCache, cacheResponsePlugin: this._cacheablePlugin, }).catch(() => Response.error()); const cachedResponse = await this.requestWrapper.match({ request: event.request, }); return cachedResponse || await fetchAndCacheResponse;
他們的區(qū)別在于就算有緩存,它仍然會(huì)發(fā)出請(qǐng)求,請(qǐng)求的結(jié)果會(huì)用來(lái)更新緩存,也就是說(shuō)你的下一次訪問(wèn)的如果時(shí)間足夠請(qǐng)求返回的話,你就能拿到最新的數(shù)據(jù)了。
可以看到離線指南中還提供了緩存然后訪問(wèn)網(wǎng)絡(luò)再更新頁(yè)面的方法,但這種需要配合主進(jìn)程代碼的修改,WorkBox 沒(méi)有提供這種模式。
自定義緩存配置回到在緩存策略里提到的,講講 _getCachingMechanism和緩存策略的參數(shù)。默認(rèn)支持5個(gè)參數(shù):"cacheExpiration", "broadcastCacheUpdate", "cacheableResponse", "cacheName", "plugins",(當(dāng)然你會(huì)發(fā)現(xiàn)還有幾個(gè)參數(shù)不在這里處理,比如你可以傳一個(gè)自定義的 requestWrapper, 前面提到的 waitOnCache 和 NetworkFirst 支持的 networkTimeoutSeconds),先看一個(gè)完整的示例:
const workboxSW = new WorkboxSW(); const cacheFirstStrategy = workboxSW.strategies.cacheFirst({ cacheName: "example-cache", cacheExpiration: { maxEntries: 10, maxAgeSeconds: 7 * 24 * 60 * 60 }, broadcastCacheUpdate: { channelName: "example-channel-name" }, cacheableResponse: { stses: [0, 200, 404], headers: { "Example-Header-1": "Header-Value-1", "Example-Header-2": "Header-Value-2" } } plugins: [ // Additional Plugins ] });
大致可以認(rèn)定的是 cacheExpiration 會(huì)用來(lái)處理緩存失效,cacheName 決定了 cache 的索引名,cacheableResponse 則決定了什么請(qǐng)求返回可以被緩存。
那么插件到底是怎么被處理,現(xiàn)在可以看_getCachingMechanism函數(shù)了,_getCachingMechanism函數(shù)處理了什么,它其實(shí)就是把 cacheExpiration,broadcastCacheUpdate,cacheabelResponse里的參數(shù)找到對(duì)應(yīng)方法,傳入?yún)?shù)實(shí)例化,然后掛在在封裝后的wrapperOptions的plugins參數(shù)里,但是只是實(shí)例化了有什么用呢?這里有關(guān)鍵的一步:
options.requestWrapper = new RequestWrapper(wrapperOptions);
所以最終這些插件還是會(huì)在 RequestWrapper 里處理,這里的一些操作是我們之前沒(méi)有提到的,來(lái)看下 RequestWrapper 里怎么處理的。
看下 RequestWrapper 的構(gòu)造函數(shù),取其中涉及到 plugins 的部分:
constructor({cacheName, cacheId, plugins, fetchOptions, matchOptions} = {}) { this.plugins = new Map(); if (plugins) { isArrayOfType({plugins}, "object"); plugins.forEach((plugin) => { for (let callbackName of pluginCallbacks) { if (typeof plugin[callbackName] === "function") { if (!this.plugins.has(callbackName)) { this.plugins.set(callbackName, []); } else if (callbackName === "cacheWillUpdate") { throw ErrorFactory.createError( "multiple-cache-will-update-plugins"); } else if (callbackName === "cachedResponseWillBeUsed") { throw ErrorFactory.createError( "multiple-cached-response-will-be-used-plugins"); } this.plugins.get(callbackName).push(plugin); } } }); } }
plugins是一個(gè)Map,默認(rèn)支持以下幾種Key:cacheDidUpdate, cacheWillUpdate, fetchDidFail, requestWillFetch, cachedResponseWillBeUsed??梢岳斫鉃?requestWrapper 提供了一些hooks或者生命周期,而插件就是在 hook 上進(jìn)行一些處理。
這里舉個(gè)緩存失效的例子看看怎么處理:
首先我們需要實(shí)例化CacheExpirationPlugin,CacheExpirationPlugin沒(méi)有構(gòu)造函數(shù),實(shí)例化的是CacheExpiration,然后在this上添加maxEntries,maxAgeSeconds。所有的 hook 方法實(shí)現(xiàn)都放在了 CacheExpirationPlugin,提供了兩個(gè) hook: cachedResponseWillBeUsed 和 cacheDidUpdate,cachedResponseWillBeUsed 會(huì)在 RequestWrapper的match中執(zhí)行,cacheDidUpdate 在 fetchAndCache中 執(zhí)行。
這里可以看出,每個(gè)plugin其實(shí)就是對(duì)hook或者生命周期調(diào)用的具體實(shí)現(xiàn),在把response扔到cache里之后,調(diào)用了插件的cacheDidUpdate方法,看下CacheExpirationPlugin中的cacheDidUpdate:
async cacheDidUpdate({cacheName, newResponse, url, now} = {}) { isType({cacheName}, "string"); isInstance({newResponse}, Response); if (typeof now === "undefined") { now = Date.now(); } await this.updateTimestamp({cacheName, url, now}); await this.expireEntries({cacheName, now}); }
那么關(guān)鍵就是更新時(shí)間戳和失效條數(shù),如果設(shè)置了更新時(shí)間戳?xí)趺礃幽?,在?qǐng)求的時(shí)候,runtimecache也會(huì)添加到IndexedDB,值存入的是一個(gè)對(duì)象,包含了一個(gè)url和時(shí)間戳。
這個(gè)時(shí)間戳怎么生效,CacheExpirationPlugin提供了另外一個(gè)方法,cachedResponseWillBeUsed:
cachedResponseWillBeUsed({cachedResponse, cachedResponse, now} = {}) { if (this.isResponseFresh({cachedResponse, now})) { return cachedResponse; } return null; }
RequestWrapper中的match方法會(huì)默認(rèn)從cache里取,取到的是當(dāng)時(shí)的完整 response, 在cache的 response 里的 headers 里取到 date,然后把當(dāng)時(shí)的date加上 maxAgeSecond 和 現(xiàn)在的時(shí)間比, 如果小于了就返回 false,那么自然會(huì)去發(fā)起請(qǐng)求了。
CacheableResponsePlugin用來(lái)控制 fetchAndCache 里的 cacheable,它設(shè)置了一個(gè) cacheWillUpdate,可以設(shè)置哪些 http status 或者 headers 的 response 要緩存,做到更精細(xì)的緩存操作。
如何配置我的緩存離線指南已經(jīng)提供了一些緩存方式,在 workbox 中,可以大致認(rèn)為,有一些資源會(huì)直接影響整個(gè)應(yīng)用的框架能否顯示的(開(kāi)發(fā)應(yīng)用的 JS,CSS 和部分圖片)可以做 precache,這些資源一般不存在“異步”的加載,它們?nèi)绻伙@示整個(gè)頁(yè)面無(wú)法正常加載。
那他們的更新策略也很簡(jiǎn)單,一般這些資源的更新需要發(fā)版,而在這里用更新sw文件更新。
對(duì)于大部分無(wú)狀態(tài)(注意無(wú)狀態(tài))數(shù)據(jù)請(qǐng)求,推薦StaleWhileRevalidate方式或者緩存回退,在某些后端數(shù)據(jù)變化比較快的情況下,添加失效時(shí)間也是可以的,對(duì)于其它(業(yè)務(wù)圖片)需求,cache-first比較適用。
最后需要討論的是頁(yè)面和有狀態(tài)的請(qǐng)求,頁(yè)面是一個(gè)比較復(fù)雜的情況,頁(yè)面如果是純靜態(tài)的,那么可以放入precache。但要注意,如果我們的頁(yè)面不是打包工具生成的,頁(yè)面文件很可能不在dist目錄下,那么怎么追蹤變化呢,這里推薦一種方式,我們的頁(yè)面往往有一個(gè)模版,和一個(gè)json串配置hash變量,那么你可以添加這種模式:
templatedUrls: { path: [ ".../xxx.html", ".../xxx.json" ] }
如果沒(méi)有json,就需要關(guān)聯(lián)所有可能影響生成頁(yè)面的數(shù)據(jù)了,那么這些文件的變化都會(huì)改變最后生成的sw文件。
如果你在頁(yè)面上有一些動(dòng)態(tài)信息(比如用戶信息等等),那就比較麻煩了,推薦使用 network-first 配合一個(gè)合適的失敗時(shí)間,畢竟大家都不希望用戶登錄了另一個(gè)賬號(hào),顯示的還是上一個(gè)賬號(hào),這同樣適用于那些使用cookie(有狀態(tài))的請(qǐng)求,這些請(qǐng)求也推薦你添加失效策略,和失敗狀態(tài)。
永遠(yuǎn)記住你的目標(biāo),讓用戶能夠更快的看到頁(yè)面,但不要給用戶一個(gè)錯(cuò)誤的頁(yè)面。
總結(jié)在目前的網(wǎng)絡(luò)環(huán)境下,service worker 的推送服務(wù)并不能得到很好的利用,所以使用 service worker 很大程度就是利用其強(qiáng)大的緩存能力給用戶在弱網(wǎng)和無(wú)網(wǎng)環(huán)境的優(yōu)化,甚至可以通過(guò)判斷網(wǎng)絡(luò)環(huán)境進(jìn)行一些預(yù)下載,豐富頁(yè)面的交互。但是一個(gè)錯(cuò)誤的緩存策略可能會(huì)使用戶得不到最新的內(nèi)容,每一個(gè)致力于使用 service worker 或者 PWA 的開(kāi)發(fā)者都需要了解其緩存的處理。Google 提供了一系列的工具能夠快速生成優(yōu)質(zhì)的sw文件,但是配套文檔過(guò)分簡(jiǎn)單和無(wú)本地化讓這些配置如同一個(gè)黑盒,使開(kāi)發(fā)者很難確定正確的配置方案。希望能夠閱讀本文,解決讀者這方面的困惑。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/90270.html
摘要:另外,單頁(yè)應(yīng)用因?yàn)閿?shù)據(jù)前置到了前端,不利于搜索引擎的抓取。所以我們需要對(duì)自己的單頁(yè)應(yīng)用進(jìn)行一些優(yōu)化。 前言 最近秋招之余空出時(shí)間來(lái)按自己的興趣動(dòng)手做了一個(gè)項(xiàng)目,一個(gè)基于vue-cli3.0, vue,typescript的移動(dòng)端pwa,現(xiàn)在趁熱打鐵,將這個(gè)項(xiàng)目從開(kāi)發(fā)到部署整個(gè)過(guò)程記錄下來(lái),并將從這個(gè)項(xiàng)目中學(xué)習(xí)到的東西分享出來(lái),如果大家有什么意見(jiàn)或補(bǔ)充也可以在評(píng)論區(qū)提出。先介紹一下這個(gè)項(xiàng)...
摘要:類似于我們熟知的,可以脫離主線程,處理一些臟累活,干完后通過(guò)向主線程匯報(bào)工作結(jié)果。所以,也是脫離主線程的存在,與不同的是,具有持久化的能力。什么是 PWA Progressive Web App, 簡(jiǎn)稱 PWA,是「漸進(jìn)式」提升 Web App 體驗(yàn)的一種新方法,能給用戶類似原生應(yīng)用的體驗(yàn)。 「高可靠,高性能,優(yōu)體驗(yàn)」是 PWA 慣用的形容詞,他的另外一個(gè)優(yōu)點(diǎn)就是「漸進(jìn)式」,開(kāi)發(fā)者可以對(duì)照 ...
摘要:業(yè)界動(dòng)態(tài)發(fā)布版本,同時(shí)發(fā)布了版本以及首個(gè)穩(wěn)定版本的。程序人生如何用人類的方式進(jìn)行二關(guān)于如何在中進(jìn)行良好的溝通,避免陷入一些潛在的陷阱。技術(shù)周刊由小組出品,匯聚一周好文章,周刊原文。 業(yè)界動(dòng)態(tài) Angular 5.1 & More Now Available Angular發(fā)布5.1版本,同時(shí)發(fā)布了Angular CLI 1.6版本以及首個(gè)穩(wěn)定版本的Angular Material。CL...
摘要:另一部分屬于進(jìn)程,它重新在后臺(tái)起了一個(gè)進(jìn)程,它和應(yīng)用的主進(jìn)程互不影響,可以同時(shí)執(zhí)行。其中一般作為應(yīng)用程序?yàn)g覽器和網(wǎng)絡(luò)如果可用之間的代理服務(wù)。他們還將允許訪問(wèn)推送通知和后臺(tái)同步。然后開(kāi)始在進(jìn)程中通過(guò)事件,來(lái)監(jiān)聽(tīng)請(qǐng)求,并對(duì)請(qǐng)求和響應(yīng)進(jìn)行緩存。 前言:我們的應(yīng)用可以分為兩部分,一部分是屬于主進(jìn)程的(包括js(同步,異步),以及dom渲染等等),在一個(gè)時(shí)刻點(diǎn),只能執(zhí)行一個(gè),要么先去渲染dom,...
摘要:實(shí)際上是指的為簡(jiǎn)化開(kāi)發(fā)而開(kāi)源的第三方庫(kù)。首先安裝依賴然后再配置文件中啟用就完成了使用采坑官網(wǎng)上線后發(fā)現(xiàn),啟用后不能播放視頻了。把當(dāng)成了失敗請(qǐng)求,導(dǎo)致請(qǐng)求視頻文件失敗。 PWA(Progressive Web App)是前端的大趨勢(shì),它能極大的加快前端頁(yè)面的加載速度,得到近乎原生 app 的展示效果(其實(shí)難說(shuō))。PWA 其實(shí)是多種前端技術(shù)的組合,其中最重要的一個(gè)技術(shù)就是 service ...
閱讀 1420·2021-11-22 15:11
閱讀 2848·2019-08-30 14:16
閱讀 2766·2019-08-29 15:21
閱讀 2924·2019-08-29 15:11
閱讀 2463·2019-08-29 13:19
閱讀 2995·2019-08-29 12:25
閱讀 428·2019-08-29 12:21
閱讀 2841·2019-08-29 11:03