PWA(Progressive web apps,漸進式 Web 應用)html
PWA
是現代 web
開發的一個新的理念,他不依賴於某個特定的 API
,而是使用各類技術和模式來開發的 web
應用,而且同時具有 web
應用和原生應用的特性,以此來達到最佳 web
體驗的目標;node
一個應用能夠稱爲 PWA
,應該具有如下特色:webpack
Discoverable 內容能夠經過搜索引擎發現。nginx
Installable 能夠出如今設備的主屏幕。web
Linkable 你能夠簡單地經過一個URL來分享它。json
Network independent 它能夠在離線狀態或者是在網速不好的狀況下運行。瀏覽器
Progressive 它在老版本的瀏覽器仍舊可使用,在新版本的瀏覽器上可使用所有功能。緩存
Re-engageable 不管什麼時候有新的內容它均可以發送通知。安全
Responsive 它在任何具備屏幕和瀏覽器的設備上能夠正常使用——包括手機,平板電腦,筆記本,電視,冰箱,等。網絡
Safe 在你和應用之間的鏈接是安全的,能夠阻止第三方訪問你的敏感數據。
因此,判斷一個 web
應用是不是 PWA
須要看它是否同時具有原生應用的特性,好比:桌面圖標,離線緩存,消息推送等;固然,他的好處也是不少的,好比:快!真的很是快,而且離線可訪問;用戶能夠贊成添加圖標到主屏方便下次訪問;還能夠實現系統級的消息推送;總之,就是不斷的接近原生應用的體驗!
實現一個 PWA
須要的核心技術包括:Service Worker
+ Manifest.json
+ HTTPS
Service Worker
是一個註冊在指定資源和路徑下的事件驅動 worker
,所以它同其餘類型 worker
同樣不能訪問 DOM
,不容許使用同步的 API
,好比 localStorage
,可是他能攔截並修改訪問的資源請求,經過多種緩存策略來對資源進行緩存和更新;
worker
:if ('serviceWorker' in navigator) {
navigator
.serviceWorker
.register('/sw-test/sw.js', {
scope: '/sw-test/'
})
.then(function(reg) {
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
複製代碼
sw
註冊以後,瀏覽器會嘗試安裝並激活它,安裝完成以後會觸發 install
事件,爲了達到離線緩存的目的,須要使用一個新的存儲 API
- caches
,這個 API
是 sw
上的一個全局對象,他能夠用來存儲網絡請求過來的資源,與瀏覽器標準存儲不同的是,他是特定你的域的持久化緩存;
this.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v1').then(function(cache) {
return cache.addAll([
'/sw-test/app.js'
]);
})
);
});
複製代碼
任何被 sw
控制的的被請求時,都會觸發 fetch
事件,經過監聽該事件能夠控制請求的具體響應內容;
如上,安裝成功後能夠將一批指定的資源緩存起來,那麼如今就能夠攔截請求,而後將匹配到的緩存結果做爲響應,或者從新請求新版的資源,甚至能夠響應指定的內容,你攔截了,那麼你說了算!
// 響應已緩存的請求
this.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
);
});
// 響應自定義內容
const res = new Response('<p>Hello, service worker!</p>', {
headers: { 'Content-Type': 'text/html' }
});
event.respondWith(res);
// 緩存獲取失敗從新請求最新的
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
})
);
複製代碼
Service Worker
若是刷新頁面後有新版的 sw
,新版的會在後檯安裝,安裝後並不會當即生效,當沒有頁面在使用舊的版本的 sw
時,新版的就會激活並響應請求;
// 更新到 v2 版本
this.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v2').then(function(cache) {
return cache.addAll([
'/sw-test/app.js'
]);
})
);
});
複製代碼
當有了新版本,舊版本還在運行的時候,爲了不緩存數據太多佔滿磁盤空間,須要對舊的緩存進行清理;經過監聽 activate
事件,來對舊的緩存進行清理;
self.addEventListener('activate', function(event) {
var cacheWhitelist = ['v2'];
event.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (cacheWhitelist.indexOf(key) === -1) {
return caches.delete(key);
}
}));
})
);
});
複製代碼
該文件裏列出了將應用添加至桌面的全部配置信息,若是修改了該文件,已添加到桌面的應用樣式不會改變,須要從新添加到桌面:
<link rel="manifest" href="/manifest.json" />
複製代碼
例如:
{
"name": "name",
"short_name": "short_name",
"description": "description",
"icons": [
{
"src": "logo.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "logo.png",
"sizes": "512x512",
"type": "image/png"
}
],
"share_target": {
"action": "compose",
"params": {
"title": "title",
"text": "text",
"url": "url"
}
},
"start_url": "/index",
"scope": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#F3F3F3",
"theme_color": "#F3F3F3",
"related_applications": [],
"prefer_related_applications": false
}
複製代碼
https
是在 http
的基礎上對數據進行加密傳輸,涉及一次非對稱加密與一次對稱加密,即使傳輸過程當中數據被劫持,只要私鑰沒有泄露,黑客也一籌莫展,因此,數據安全性很是高,HTTPS 數據傳輸過程
letsencrypt
是一家爲全球網站免費提供 https
證書的機構,而且支持泛域名,很是值得推薦使用,大公司有錢的請無視;
certbot
是 letsencrypt
官方提供的一個用來獲取和更新 https
證書的工具,certbot.eff.org/ 在它的網站上能夠根據本身的系統及服務狀況,具體選擇如何使用,下面以 CentOS 7
和 Nginx 1.16.0
爲例,看看如何免費爲網站架上 https
;
certbot
客戶端wget https://dl.eff.org/certbot-auto
chmod a+x ./certbot-auto
./certbot-auto --help
複製代碼
nginx
這一步用來驗證域名是否可訪問,後面該工具會在配置的 root
對應目錄下建立臨時的文件,若是不能夠訪問,將無權獲取對應域名的證書;
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /path/to/www;
}
location = /.well-known/acme-challenge/ {
return 404;
}
複製代碼
別忘了重啓 nginx
:
sudo nginx -s reload
複製代碼
./certbot-auto certonly --webroot -w /path/to/www -d famanoder.com -d www.famanoder.com
複製代碼
此處 -w
爲上一步 nginx
裏配置的 root
,-d
是須要獲取證書的域名,-d
能夠屢次使用,也能夠直接生成泛域名的證書,-d *.famanoder.com
,良心之做啊,不少雲服務上不只收費貴,還不支持泛域名,一個域名花一筆錢,此處,大公司有錢的請無視!
證書生成成功後,能夠選擇是否自動續簽,由於每次生成的證書有效期爲3個月;
./certbot-auto renew --dry-run
複製代碼
經過這個命令能夠測試上一步的自動續簽是否可用;
也能夠手動更新證書:
./certbot-auto renew -v
複製代碼
或者經過命令設置自動更新:
./certbot-auto renew --quiet --no-self-upgrade
複製代碼
listen 443 ssl;
server_name .famanoder.com;
index index.html;
root /path/to/www;
ssl_certificate /etc/letsencrypt/live/famanoder.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/famanoder.org/privkey.pem;
複製代碼
可另行查閱更多的如何在 nginx
上配置 https
;
以上對 pwa
作了基本的介紹,以及闡述了一個 pwa
應用所需的基礎設施,接下來使用 offline-plugin
在項目裏實戰 pwa
;
首先,在咱們應用的入口文件中將其引入,保證當前頁面與 service worker
可以通訊;
import(/* webpackChunkName: "offline" */'offline-plugin/runtime')
.then(offline => {
offline.install({
onUpdating: () => {
console.log('SW Event:', 'onUpdating');
},
onUpdateReady: () => {
console.log('SW Event:', 'onUpdateReady');
offline.applyUpdate();
},
onUpdated: () => {
console.log('SW Event:', 'onUpdated');
// window.location.reload();
// alert('有新版可用,是否刷新?');
},
onUpdateFailed: () => {
console.log('SW Event:', 'onUpdateFailed');
}
});
});
複製代碼
該插件會根據 webpack
打包生成的文件,生成 sw.js
文件,配置 webpack
:
// webpack.config.js
const OfflinePlugin = require('offline-plugin');
module.exports = {
// ...,
plugins: [
// ...,
new OfflinePlugin({
ServiceWorker: {
events: true
}
})
]
}
複製代碼
這樣,咱們的應用就可以支持 pwa
了,可在控制檯查看相關信息;
使用 pwa
還有很是重要的一點,就是如何更新,如上所述,sw
在安裝新版本後並不會當即激活,大多數時候都須要用戶再一次刷新頁面纔會生效,固然,這個還跟具體的緩存策略有關,目前,觀察一些 pwa
網站,會發現當有新版更新後,網站會經過一個模態框來提醒用戶是否當即刷新頁面使用最新版本,該插件的 runtime
裏提供了 onUpdated
鉤子,當最新版安裝完成後會通知頁面觸發 onUpdated
,在這裏咱們能夠調用模態框組件,提醒用戶是否刷新頁面使用最新版;