摘要:和的關系不是一項技術,也不是一個框架,我們可以把她理解為一種模式,一種通過應用一些技術將在安全性能和體驗等方面帶來漸進式的提升的一種的模式。這里需要注意的是,首次注冊線程的頁面需要再次加載才會受其控制。
1. PWA和Service Worker的關系
PWA (Progressive Web Apps) 不是一項技術,也不是一個框架,我們可以把她理解為一種模式,一種通過應用一些技術將 Web App 在安全、性能和體驗等方面帶來漸進式的提升的一種 Web App的模式。對于 webview 來說,Service Worker 是一個獨立于js主線程的一種 Web Worker 線程, 一個獨立于主線程的 Context,但是面向開發(fā)者來說 Service Worker 的形態(tài)其實就是一個需要開發(fā)者自己維護的文件,我們假設這個文件叫做 sw.js。通過 service worker 我們可以代理 webview 的請求相當于是一個正向代理的線程,fiddler也是干這些事情),在特定路徑注冊 service worker 后,可以攔截并處理該路徑下所有的網(wǎng)絡請求,進而實現(xiàn)頁面資源的可編程式緩存,在弱網(wǎng)和無網(wǎng)情況下帶來流暢的產(chǎn)品體驗,所以 service worker 可以看做是實現(xiàn)pwa模式的一項技術實現(xiàn)。
2. service worker簡介
注意事項
service worker 是一種JS工作線程,無法直接訪問DOM, 該線程通過postMessage接口消息形式來與其控制的頁面進行通信;
service worker 廣泛使用了Promise,這些在接下來代碼示例中將會看到;
目前并不是所有主流瀏覽器支持 service worker, 可以通過 navigator && navigator.serviceWorker 來進行特性探測;
在開發(fā)過程中,可以通過 localhost 使用服務工作線程,如若上線部署,必須要通過https來訪問注冊服務工作線程的頁面,但有種場景是我們的測試環(huán)境可能并不支持https,這時就要通過更改host文件將localhost指向測試環(huán)境ip來巧妙繞過該問題(例如:192.168.22.144 localhost);
生命周期
service worker的生命周期完全獨立于網(wǎng)頁,要為網(wǎng)站安裝服務工作線程,我們需要在頁面業(yè)務js代碼中注冊,瀏覽器從指定路徑下載并解析服務工作線程腳本進而瀏覽器將會在后臺啟動安裝步驟,在安裝過程中,我們通常會緩存靜態(tài)資源,如果所有文件都成功緩存,那么服務工程線程就安裝完畢,如果任何文件下載失敗或緩存失敗,那么安裝步驟將會失敗,當然也不會被激活。安裝后就進入激活步驟,這里是管理舊緩存的絕佳機會(后面代碼示例中將會介紹原因),激活后service worker將開始對其作用域內(nèi)的所有頁面實施控制。這里需要注意的是,首次注冊 service worker 線程的頁面需要再次加載才會受其控制。在成功安裝完成并處于激活狀態(tài)之前,服務工程線程不會收到fetch和push事件;
工作流程
注冊
這里需要注意的是register方法注冊服務工作線程文件的位置,該path就是默認的 serviceworker 的作用域,例如注冊path為/a/b/service-worker.js,則默認scope為/a/b/,當然也可以通過傳入{scope: "/a/b/c/"}來指定自己的scope,但這里要特別注意的是,傳入的scope參數(shù)一定是在默認作用域范圍內(nèi)再自定義(例如/a/b/c/),反之自定義為/d/e/就不行;
通俗來講,上面提到的scope就是 service worker 能夠控制和發(fā)揮作用的范圍;
注意注冊是在自己的業(yè)務代碼中進行,后面會有具體通過插件來實現(xiàn)注冊的代碼示例;
if(navigator && navigator.serviceWorker) { navigator.serviceWorker.register("/service-worker.js").then(function (registration) { console.log(registration) }).catch(function (err) { console.log(err) }) }
安裝
下面代碼就是前面注冊的service-worker.js文件內(nèi)容;
我們通過install事件來定義安裝步驟,通過緩存名稱調用caches.open(), 之后再調用cache.addAll()并傳入具體緩存文件清單數(shù)組,這是一個Promise鏈式event.waitUntil()方法帶有Promise參數(shù)并使用它來判斷花費耗時以及安裝是否成功;
正如前面提到,安裝過程中如果所有清單中文件成功緩存,則安裝結束,否則安裝過程視為失敗,所以在實踐中我們盡可能緩存核心資源以避免服務工作線程未能安裝;
var cacheVersion = "test_2017122608"; // 安裝服務工作線程 self.addEventListener("install", function(event){ // 需要緩存的資源 var cacheFiles = [ "/dist/index.html", "/dist/js/index_async_bundle.js" ]; console.log("service worker: run into install"); event.waitUntil(caches.open(cacheVersion).then(function(cache) { return cache.addAll(cacheFiles); })); });
?激活
在某個時間點服務工程線程需要更新(例如:service-worker.js文件發(fā)生更改并上線),用戶訪問頁面時瀏覽器會嘗試在后臺重新下載service-worker.js,如果服務工程線程文件與當前所用文件存在字節(jié)差異,則將其視為“新服務工作線程”;
新服務工作線程將會啟動,且將會觸發(fā) install 事件;
此時舊的服務工作線程仍將控制著當前頁面,因此新服務工作線程將會進入waiting狀態(tài);
當網(wǎng)站當前頁面關閉時,舊服務工作線程將會終止,新服務工作線程將會取得控權;
新服務工作線程取得控制權后,將會觸發(fā) activate 事件;
監(jiān)聽 activate 事件的回調函數(shù)中常見的任務是管理緩存,前面我也提到過這是管理舊緩存的絕佳時機,因為如果在安裝步驟中清理了舊緩存,由于舊的服務工作線程仍舊控制著頁面,將無法從緩存中提取文件,但是在 activate 時舊服務工作線程已經(jīng)終止了頁面控制權,所在在這里清理舊緩存再合適不過;
// 新的service worker線程被激活(其實和離線包一樣存在"二次生效"的機理) self.addEventListener("activate", function (event) { console.log("service worker: run into activate"); event.waitUntil(caches.keys().then(function (cacheNames) { return Promise.all(cacheNames.map(function (cacheName) { // 注意這里cacheVersion也可以是一個數(shù)組 if(cacheName !== cacheVersion){ console.log("service worker: clear cache" + cacheName); return caches.delete(cacheName); } })); })); });
監(jiān)聽
這里通過監(jiān)聽fetch事件來代理響應,進而實現(xiàn)自定義前端資源緩存;
在event.respondWith()中我們傳入來自caches.match()的一個promise,此方法攔截請求并從服務工作線程所創(chuàng)建的任何緩存中查找緩存結果,如若發(fā)現(xiàn)匹配的響應則返回緩存的值,否則,將會調用fetch以代理發(fā)出網(wǎng)絡請求,并將從網(wǎng)絡中檢索的數(shù)據(jù)作為結果返回;
如果希望連續(xù)性緩存新的請求,則注意注釋的代碼部分,其通過cache.put來將請求的響應添加到緩存來實現(xiàn);
在fetch請求中添加對then()的回調,獲得響應后執(zhí)行檢查,并clone響應,注意這樣處理的原因是該響應是stream,主體只能使用一次,我們需要返回能被瀏覽器使用的響應,還要傳遞到緩存以供使用,因此需要克隆一份副本;
// 攔截請求并響應 self.addEventListener("fetch", function (event) { console.log("service worker: run into fetch"); event.respondWith(caches.match(event.request).then(function (response) { // 發(fā)現(xiàn)匹配的響應緩存 if(response){ console.log("service worker 匹配并讀取緩存:" + event.request.url); return response; } console.log("沒有匹配上:" + event.request.url); return fetch(event.request); /*var fetchRequest = event.request.clone(); return fetch(fetchRequest).then(function(response){ if(!response || response.status !== 200 || response.type !== "basic"){ return response; } var responseToCache = response.clone(); caches.open(cacheVersion).then(function (cache) { console.log(cache); cache.put(fetchRequest, responseToCache); }); return response; });*/ })); });
3. 前端資源緩存演進
利用webview自身的http緩存機制。這里往往需要服務器運維同事配合,對于前端來講不夠靈活且緩存粒度太粗,而且在http協(xié)議在不同版本下緩存機制有一定的差異(例如1.0版本中If-Modified-Since、Last-Modified、expires, 1.1版本中對緩存進行了優(yōu)化,添加If-None-Match、Etag、cache-control等;
離線包策略,其大致原理是通過將靜態(tài)資源打包至離線管理平臺(自行開發(fā)),在app啟動時從離線管理平臺拉取資源包并存放于本地,后續(xù)終端將會攔截url請求并基于約定規(guī)則將請求代理到本地文件系統(tǒng),進而加快靜態(tài)資源的訪問以及為cdn減壓,該方案的缺陷在于需要離線資源管理平臺和終端的配合,牽扯資源過多,但其優(yōu)點是不存在兼容性問題;
h5離線緩存manifest,其實質就是一個緩存清單文件(xx.manifest),然后在html標簽設置manifest屬性為xx.manifest,該緩存方案也存在“二次更新”的問題,該方案需要注意的問題是xx.manifest文件自身不要被webview緩存,且manifest文件cache部分不能使用通配符,必須手動指定,不過好在可以通過構建工具來解決,主流瀏覽器對該方案支持度也不錯。與service worker相對,其業(yè)務JS代碼無法感知緩存更新的時機,所以service worker方案更具有想象空間;
service worker 通過一個獨立JS線程來實現(xiàn)資源的可編程式緩存;
4. 項目如何快速接入service worker
在接入前有兩個問題擺在我們面前,service worker可以幫助我們解決資源緩存問題,有緩存就必須要有更新的機制,service-worker.js本身也會被瀏覽器緩存,后續(xù)產(chǎn)品迭代過程中如何解決該文件自身的更新問題,否則其他資源的緩存更新也就無從談起(舊的服務工作線程將一直控制頁面),無可厚非每次構建部署時service-worker.js需要攜帶版本號(例如?v=201801021721),當然也可以在服務器運維層控制該文件的cache-control: no-cache從而規(guī)避瀏覽器緩存問題,但這樣太麻煩;
我們是在業(yè)務代碼中通過register的方式引入service-worker.js, 那問題就變?yōu)槿绾卧谧苑展ぷ骶€程的位置引入版本號呢,我們可以通過sw-register-webpack-plugin來解決該問題,其思路是將服務工作線程的注冊放在一個多帶帶的文件中(sw-register.js),然后自動在頁面入口(例如index.html)寫入一段JS腳本來動態(tài)加載sw-register.js文件,這里sw-register.js的加載路徑是帶有實時時間戳的,而生成的sw-register.js文件內(nèi)容中注冊service-worker.js的位置自動攜帶構建版本號參數(shù)(默認是當前構建時間),該插件配置如下(基于webpack構建的項目):
let SwRegisterWebpackPlugin = require("sw-register-webpack-plugin") ... plugins: [ new SwRegisterWebpackPlugin({ filePath: path.resolve(__dirname, "../src/sw-register.js") }) ]
構建后html新增部分如圖:
構建后生成的sw-register.js文件變化如圖:
這樣處理后,sw-register.js文件就不會被瀏覽器緩存,也即每次刷新會多一次sw-register.js的文件請求,由于它只是用來做注冊的工作,體量不會太大,可以接受,關鍵是前端可以自行控制
已緩存資源文件如何更新呢?上述插件只是解決了service-worker.js文件本身的更新的問題(保證每次構建部署后會新啟一個服務工作線程),但對于service-worker.js文件中定義的cacheFiles而言,當我們修改了已緩存文件后如何來更新緩存呢,我的項目是基于vue.js + webpack,打包后的JS文件是[name].[hash].[ext]格式,從前面的介紹可知資源的緩存也是基于url(作為key)來的,不可能每次構建后都手動去調整service-worker.js文件內(nèi)容中cacheFiles的路徑值吧,應該是將構建后的文件名(包括路徑)直接放到service-worker.js內(nèi)容中,看到這里你應該想到了有webpack插件已經(jīng)幫我們做好了,那就是sw-precache-webpack-plugin,該插件會自動在dist目錄下生成service-worker.js文件,供給service worker運行,也就是說service-worker.js文件本身不需要我們手動添加了,但問題是我們?nèi)绾巫远x需要緩存的文件呢,該插件的配置參數(shù)會告訴你,我的項目該插件配置如下:
// 生成service-worker.js和配置緩存清單 new SwPrecacheWebpackPlugin({ cacheId: "attendance-mobile-cache", filename: "service-worker.js", minify: true, dontCacheBustUrlsMatching: false, staticFileGlobs: [ "dist/static/js/manifest.**.*", "dist/static/js/vendor.**.*", "dist/static/js/app.**.*" ], stripPrefix: "dist/" })
由上可知,我們能夠通過正則來匹配需要緩存的文件,這里特別要注意的是stripPrefix參數(shù)的使用,我們配置的緩存文件路徑是項目中的路徑,但對于部署線上而言,我們可能需要過濾前綴的部分路徑(我的項目線上部署文件根目錄下就是static等,所以需要過濾dist路徑),最終該插件生成的service-worker.js文件如圖所示(僅截取緩存文件清單部分代碼)
4. 調試service worker
通過上述兩個插件,我們的service-worker接入工作基本完成,那接下來就是驗證服務工作線程運行是否ok,通過chrome devTools(Application項)我們可以很方面的查看當前服務工作線程的運行情況和已緩存了哪些文件,具體如何查看這里不再介紹;
當首次運行 service worker 時我們會發(fā)現(xiàn)要緩存的文件還是走正常的網(wǎng)絡請求,cache storage 下也看不到我們的緩存項,因為服務工程線程也存在“二次生效”的機制(即使需要緩存的資源延遲加載),具體如下圖所示:
通過刷新訪問我們可以看到,service worker 緩存文件已經(jīng)生效,在network面板下自定義的緩存文件size項都顯示為“from ServiceWorker”, 耗時也明顯很低。在cache storage下面也可以看到已經(jīng)緩存的文件列表,具體如下圖所示:
接下來我們更新service-worker.js文件來看下新服務工作線程如何工作,正如前面所講新服務工作線程將會啟動安裝,但由于舊服務工作線程控制著頁面,所以新服務工作線程將進入waiting狀態(tài),當當前打開的頁面關閉時,舊服務工作線程將會被終止,新服務工作線程會得的控制權并觸發(fā)activate事件,在開發(fā)過程中我們需要通過Chrome Devtools的skipWaiting或者勾選Updated on reload來強制激活新服務工作線程,具體如下圖所示:
在開發(fā)過程中我們可以通過上述來了解新服務工作線程的更新流程,但在實際項目中我們可以通過self.skipWaiting()跳過等待過程安裝后直接激活,一般我們在install事件中調用,具體可參見sw-precache-webpack-plugin生成的service-worker源代碼。這會導致新服務工作線程將當前活動的工作線程逐出,skipWaiting()意味著新服務工作線程可能會控制使用較舊工作線程加載的頁面,也就是頁面獲取的部分數(shù)據(jù)由舊工作線程處理,而新服務工作線程處理后來獲取的數(shù)據(jù),如果有問題就不要使用skipWaiting();
手動清理service worker緩存后刷新頁面,在 Network 面板中,我們會看到本應緩存文件的一組初始請求。之后是前面帶有齒輪圖標的第二輪請求,這些請求似乎要獲取相同的資源,“齒輪”圖標代表這些請求來自服務工作線程,如果不unregsiter該服務工作線程,我們會發(fā)現(xiàn)即使多次刷新頁面,Network 面板依然如此,其實也就是說資源沒有再次緩存(因為服務工作線程已經(jīng)安裝且控制當前頁面,刷新操作不會重新觸發(fā)install事件,也就不會再次添加資源到緩存,除非unregister或者更新service-worker.js文件),具體如下圖所示:
5. 異?;貪L(注銷)
某些場景下如果service worker使用出現(xiàn)異常,比如不同頁面間 service worker 控制的scope存在“重疊污染”的問題,那么我們就需要緊急回滾(撤銷)當前 service worker,在開發(fā)環(huán)境很好解決,我們依然可以通過Chrome Devtools來進行unregister, 那么在線上環(huán)境已經(jīng)有服務工作線程在運行的情況下呢,我們需要在新上線版本的service worker注冊前將被污染或者異常的service worker注銷掉,具體代碼如下:
if (navigator.serviceWorker) { navigator.serviceWorker.getRegistrations().then(function (registrations) { for (var item of registrations) { if (item.scope === "http://localhost/attendance-mobile/dist/") { item.unregister(); } } // 注銷掉污染 Service Worker 之后再重新注冊... }); }
備注:文中部分內(nèi)容摘選自Google開發(fā)者文檔
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90660.html
摘要:和的關系不是一項技術,也不是一個框架,我們可以把她理解為一種模式,一種通過應用一些技術將在安全性能和體驗等方面帶來漸進式的提升的一種的模式。這里需要注意的是,首次注冊線程的頁面需要再次加載才會受其控制。 1. PWA和Service Worker的關系 PWA (Progressive Web Apps) 不是一項技術,也不是一個框架,我們可以把她理解為一種模式,一種通過應用一些技...
摘要:前端每周清單專注前端領域內(nèi)容,以對外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點分為新聞熱點開發(fā)教程工程實踐深度閱讀開源項目巔峰人生等欄目。 前端每周清單專注前端領域內(nèi)容,以對外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點;分為新聞熱點、開發(fā)教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目。歡迎關注【前端之巔】微信公眾號(ID:frontshow),及時獲取前端每周清單;本文則是對于...
摘要:我喜歡移動,而且也是那些堅持使用技術構建移動應用程序的人之一。我們準備做這樣的一個漸進式應用是典型的旨在提高用戶離線體驗的應用。當我們開始構建應用時,你就能理解上面的場景了。的作用范圍是針對相對路徑的。最佳的做法是在應用的入口。 我喜歡移動app,而且也是那些堅持使用Web技術構建移動應用程序的人之一。 經(jīng)過技術的不斷迭代(可能還有一些其它的東西),移動體驗設計愈來愈平易近人,給予用戶...
摘要:另外,單頁應用因為數(shù)據(jù)前置到了前端,不利于搜索引擎的抓取。所以我們需要對自己的單頁應用進行一些優(yōu)化。 前言 最近秋招之余空出時間來按自己的興趣動手做了一個項目,一個基于vue-cli3.0, vue,typescript的移動端pwa,現(xiàn)在趁熱打鐵,將這個項目從開發(fā)到部署整個過程記錄下來,并將從這個項目中學習到的東西分享出來,如果大家有什么意見或補充也可以在評論區(qū)提出。先介紹一下這個項...
閱讀 1354·2021-09-10 10:51
閱讀 2829·2019-08-30 15:54
閱讀 3367·2019-08-29 17:11
閱讀 925·2019-08-29 16:44
閱讀 1390·2019-08-29 13:47
閱讀 1085·2019-08-29 13:47
閱讀 1484·2019-08-29 12:23
閱讀 1037·2019-08-28 18:18