我喜歡移動app,並且也是那些堅持使用Web技術構建移動應用程序的人之一。javascript
通過技術的不斷迭代(可能還有一些其它的東西),移動體驗設計越來越平易近人,給予用戶更好的體驗。css
而今天,咱們就要介紹一個新技術--漸進式 web 應用程序。在理解這個概念並本身嘗試了一下以後,我以爲沒有必要再作 hybrid 應用了。html
咱們準備作這樣的一個demo:
java
漸進式 Web 應用是典型的旨在提升用戶離線體驗的 Web 應用。它解決了這樣的問題:怎麼才能不顯示相似下面的離線錯誤?
jquery
事實上,PWA 不只解決了離線錯誤,還在恢復鏈接的時候將用戶與內容鏈接起來。移動設備是漸進式 web 應用的主要使用場景。讓我來告訴你爲何?web
用戶打開電腦(在家、學校或者辦公室)chrome
檢查是否連上網絡,沒有則手動鏈接shell
打開 web 應用api
拿出手機promise
默認手機已經鏈接上網絡
直接打開 app
如上,用戶對待兩種場景的處理方式是不同的。移動端用戶不必定有很好的網絡鏈接,有的甚至沒有。在這樣的場景下,開發商須要作的就是保持用戶對產品的好感,在其網絡恢復時與其互動。若是信號不好,開發商須要經過一些手段保持用戶的耐心,不至於在請求過程當中用戶直接關閉 web 應用。
當咱們開始構建 PWA 應用時,你就能理解上面的場景了。
PWA 背後的原理是 service workers。若是想讓用戶在離線場景下依然保持打開 web 頁面,你須要在用戶打開 web 應用而且有網絡鏈接時作一些「後臺任務」,這個「後臺任務」會蒐集 web 頁面最近一次運行須要的一些資源,以備離線時使用。
這就好像每一年秋收儲備糧食,以備冬天不時之需同樣,不斷循環。
PWA 中的 service worker,能夠類比成春天的播種的農民。下面是 MDN 對 service workers 的描述:
Service worker 是一個註冊在指定源和路徑下的事件驅動 worker。它採用 JavaScript 控制關聯的頁面或者網站,攔截並修改訪問和資源請求,細粒度地緩存資源。你能夠徹底控制應用在特定情形(最多見的情形是網絡不可用)下的表現。
簡而言之,service worker 就是一些在後臺運行邏輯的 worker。它沒有權限操做 DOM,可是能夠調用其它的 API (例如 IndexDB 以及 Fetch API)。
開始以前請牢記:
service workers 只能在 HTTPS 協議下生效(或者 Localhost)。
service workers 被設計成異步的,不能使用 XHR (但你可使用 Fetch)或者 LocalStorage。
service workers 的做用範圍是針對相對路徑的。所以,demo/sw.js
只能相對於 demo
起做用,demo/first/sw.js
相對於 first
。
若是你能利用 service workers 存儲離線使用所需的文件,那你就沒有必要開發移動 app 了。若是你的 web 應用對移動用戶進行了優化,而且幾乎不須要調用移動端的硬件功能,那麼你應該嘗試一下 PWA。
我花了一些時間看飛行模式下一些移動 app 的表現。我將它們分紅三類:
例子: Coinbase
Coinbase 就是一直停留在 loading 的這個頁面。它甚至讓我懷疑這樣的 app 爲啥要存在,由於這個頁面簡直跟 web 展現如出一轍。Coinbase 不是財經類 app,無需實時展現信息,所以,PWA 可能只適用應用於其 App Shell。
App Shell 是指不包含動態內容的一部分應用程序。例如導航菜單、側邊欄、背景、logo 等等。
例子:Uber
Uber 給用戶展現了一些信息(經過 App Shell 以及地圖),而且告知用戶不能操做是因爲他網絡中斷了。Uber是一個很高頻的 app,這樣的交互展現對於他們的應用場景頗有意義。
例子: Medium
Medium在離線狀態下展現緩存的數據,一些離線展現在這個分類裏面的 app(例如,Instagram)還會提示用戶離線了,因此,就不要對這個分類裏面的 app 指望再搞了。
個人想法是,若是 PWA(或者 service workers)技術成熟而且被大規模應用的話,爲何不節省掉:
前往應用商店
下載並不經常使用的 app
呢?
當咱們接下來談到 Web Manifest 時,你就意識到只要給你的 web 應用新增一個桌面 icon,web 應用就能夠經過點擊這個 icon 實現啓動了。
一些公司已經在 PWA 方面作的比較好了,你能夠在這個網址上面找到這些公司:pwa.rocks
咱們已經介紹了足夠多的理論知識了。這是一個手把手的教程,來吧,讓咱們動起手來。首先,按照下面的結構來建立一個新的項目:
|--pwa-demo |----css |----fonts |----images |----js |----index.html |----service-worker.js
下載 Materialize 這個 UI 庫,用裏面 CSS
、Fonts
、js 文件
分別替換項目裏面的文件夾。
打開 index.html
文件,引入一些資源:
<!-- ./index.html --> <!DOCTYPE html> <html> <head> <!--Import Google Icon Font--> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <!--Import materialize.css--> <link type="text/css" rel="stylesheet" href="css/materialize.min.css" media="screen,projection"/> <link type="text/css" rel="stylesheet" href="css/app.css"> <!--Let browser know website is optimized for mobile--> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> </head> <body> Body coming soon <!-- Scripts --> <script type="text/javascript" src="js/jquery-2.1.1.min.js"></script> <script type="text/javascript" src="js/materialize.min.js"></script> <script type="text/javascript" src="js/app.js"></script> </body> </html>
咱們已經引入了下載好的文件,還須要本身在相應的目錄建立一下 app.css
以及 app.js
這兩個文件。
越早在瀏覽器註冊,Service Worker 就能越早的開始工做。最佳的作法是在應用的入口。在這個項目中,咱們能夠在 app.js
註冊一個新的 worker:
(function(){ if ('serviceWorker' in navigator) { navigator.serviceWorker .register('/service-worker.js') .then(function() { console.log('Service Worker Registered'); }); } })()
在作其餘操做以前,咱們首先須要檢測一下瀏覽器對於 Service Worker 的兼容性。若是支持,那咱們就能夠利用 register
這個方法來註冊這個 worker,這個方法告知了 service worker 文件的路徑。註冊函數返回一個 promise ,你能夠在這個 promise 裏面判斷註冊是否成功。
在開始構建 PWA 以前,你須要理解 Service Worker 的生命週期:
這一階段主要是讓 worker 在瀏覽器給定的做用域掛載。因爲這是生命週期的第一步,最好在這一步緩存各類資源:
// ./service-worker.js var cacheName = 'PWADemo-v1'; var filesToCache = [ '/index.html', '/css/app.css', '/js/app.js', /* ...and other assets (jQuery, Materialize, fonts, etc) */ ]; self.addEventListener('install', function(e) { console.log('[ServiceWorker] Install'); e.waitUntil( caches.open(cacheName).then(function(cache) { console.log('[ServiceWorker] Caching app shell'); return cache.addAll(filesToCache); }) ); });
caches.open
和 cache.addAll
都是異步操做.service worker 在這些操做完成以前可能會中斷,e.waitUntil
用來等待 promise 的狀態變成 resolved 或者 rejected。
當緩存開關被打開時,咱們嘗試利用 addAll
來新增緩存。
請記住,只要有一個文件緩存失敗,service worker 就沒法被正確掛載。
當 worker 掛載完成,其效果並不會當即展現出來,除非前一個 service worker 銷燬而且該 web 應用被從新訪問。假設咱們掛載了另外一個不一樣 cacheName 的 service worker:
// ./service-worker.js var cacheName = 'PWADemo-v2'; var filesToCache = [ //... ]; self.addEventListener('install', function(e) { console.log('[ServiceWorker] Install'); //... });
當這個新的 service worker 建立以後,新的緩存 PWADemo-v2
也被建立,這時候 PWADemo-v1
仍然存在。當觸發 Activate 時,咱們能夠刪除 PWADemo-v1
,使其「讓位」於 PWADemo-v2
:
// ./service-worker.js self.addEventListener('activate', function(e) { console.log('[ServiceWorker] Activate'); e.waitUntil( caches.keys().then(function(keyList) { return Promise.all(keyList.map(function(key) { if (key !== cacheName) { console.log('[ServiceWorker] Removing old cache', key); return caches.delete(key); } })); }) ); });
咱們檢查全部的 cache 名稱,若是發現不是正在使用的 cache,那麼將其直接刪除。
Fetch 不是一個必需的生命週期,但它提供了攔截請求資源的方法。當發送請求時,首先會觸發這樣的事件:
// ./service-worker.js self.addEventListener('fetch', function(e) { console.log('[ServiceWorker] Fetch', e.request.url); e.respondWith( caches.match(e.request).then(function(response) { return response || fetch(e.request); }) ); });
若是資源已經被緩存了,咱們返回瀏覽器緩存的版本。若是沒有,那麼咱們調用 fetch api 去發送 HTTP 請求該資源。
因爲 service workers 的工做方式,特別是進行緩存時,不是很容易進行 debugger 調試。幸運的是,chrome 的 dev tools 提供了助力。跟着下面的步驟,調試咱們剛註冊的 service worker:
打開 chrome dev tools
點擊 Application 這一選項,打開 service worker 分區:
你能夠查看到 status 是綠色的,這就代表你的 service worker 成功了:
你能夠打開 "Update on reload" 去強制更新 service worker,不用關閉全部已存在的 session:
右擊 "Cache Storage",而後點擊刷新去查看緩存。根據名稱點擊你所設置的cache,而後你就會看到緩存裏面的各個項:
你已經瞭解了必備的知識點,PWA 的概念對你來講已經不陌生了。接下來,咱們將要討論 PWA 的緩存策略。咱們將瞭解如何使用 IndexDB 來保存數據而不是 localStorage。