一、原生app優缺點css
a、體驗好、下載到手機上入口方便html
b、開發成本高(ios和安卓)前端
c、軟件上線須要審覈android
d、版本更新須要將新版本上傳到不一樣的應用商店ios
e、使用前需下載git
二、web網頁優缺點github
a、開發成本低、網站更新時上傳最新的資源到服務器便可、手機自帶瀏覽器打開便可web
b、體驗比原生app差chrome
c、入口不便捷express
d、無網無相應,不具有離線能力
e、無app的消息推送
三、PWA是什麼?
PWA是一個新的前端技術,全稱:Progressive Web App,這是一個漸進式的網頁應用程序。結合了一系列現代web技術,在網頁應用中實現和原生應用相近的用戶體驗。
PWA的三個關鍵詞:
Reliable(可靠的):當用戶從手機屏幕啓動時,無需考慮網絡狀態,能夠馬上加載出PWA
Fast(快速的):加載速度快
Engaging(可參與的):PWA能夠添加在用戶的主屏幕上,無需從應用商店裏下載,他們經過網絡應用程序Manifest file提供相似於APP的使用體驗(android上可設置全屏顯示,因爲Safari支持度的問題ios不能夠),能夠進行「推送通知」
小小總結:
a、解決的問題:
1>可添加至主屏幕
2>實現離線緩存功能
3>實現消息推送
b、優點:幾乎瞬間加載,但安全且富有彈性
c、核心:manifest文件清單、Service Workers
四、Manifest
做用:
a、可以將你瀏覽的網頁添加到你的手機屏幕上
b、在 Android 上可以全屏啓動,不顯示地址欄 ( 因爲 Iphone 手機的瀏覽器是 Safari ,因此不支持)
c、控制屏幕 橫屏 / 豎屏 展現
d、定義啓動畫面
e、能夠設置你的應用啓動是從主屏幕啓動仍是從 URL 啓動
f、能夠設置你添加屏幕上的應用程序圖標、名字、圖標大小
示例:
index.html
<head> <title>Minimal PWA</title> <meta name="viewport" content="width=device-width, user-scalable=no" /> <link rel="manifest" href="manifest.json" /> <link rel="stylesheet" type="text/css" href="main.css"> <link rel="icon" href="/e.png" type="image/png" /> </head>
manifest.json
{ "name": "Minimal PWA", // 必填 顯示的插件名稱 "short_name": "PWA Demo", // 可選 在APP launcher和新的tab頁顯示,若是沒有設置,則使用name "description": "The app that helps you understand PWA", //用於描述應用 "display": "standalone", // 定義開發人員對Web應用程序的首選顯示模式。standalone模式會有單獨的 "start_url": "/", // 應用啓動時的url "theme_color": "#313131", // 桌面圖標的背景色 "background_color": "#313131", // 爲web應用程序預約義的背景顏色。在啓動web應用程序和加載應用程序的內容之間建立了一個平滑的過渡。 "icons": [ // 桌面圖標,是一個數組 { "src": "icon/lowres.webp", "sizes": "48x48", // 以空格分隔的圖片尺寸 "type": "image/webp" // 幫助userAgent快速排除不支持的類型 }, { "src": "icon/lowres", "sizes": "48x48" }, { "src": "icon/hd_hi.ico", "sizes": "72x72 96x96 128x128 256x256" }, { "src": "icon/hd_hi.svg", "sizes": "72x72" } ] }
另附:
Manifest參考文檔:https://developer.mozilla.org/zh-CN/docs/Web/Manifest
能夠打開網站https://developers.google.cn/web/showcase/2015/chrome-dev-summit查看添加至主屏幕的動圖。
五、Service Worker
SW 是什麼呢?這個是離線緩存文件。咱們 PWA 技術使用的就是它!SW 是瀏覽器在後臺獨立於網頁運行的腳本,它打開了通向不須要網頁或用戶交互的功能的大門,由於使用了它,纔會有的那個 Reliable 特性吧,SW 做用於 瀏覽器於服務器之間,至關於一個代理服務器。
功能(仍是比較逆天的)
生命週期:
當前已無激活狀態的 worker 、 SW腳本中的 self.skipWaiting()方法被調用 ( ps: self 是 SW 中做用於全局的對象,這個方法根據英文翻譯過來也能明白什麼意思啦,跳過等待狀態 )、用戶已關閉 SW 做用域下的全部頁面,從而釋放了當前處於激活狀態的 worker、超出指定時間,從而釋放當前處於激活狀態的 worker
若是上個圖很差理解,能夠看這個,把它的生命週期當作紅綠燈:
當用戶首次導航至 URL 時,服務器會返回響應的網頁
六、實現離線緩存
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello Caching World!</title> </head> <body> <!-- Image --> <img src="/images/hello.png" /> <!-- JavaScript --> <script async src="/js/script.js"></script> <script> // 註冊 service worker if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then(function (registration) { // 註冊成功 console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function (err) { // 註冊失敗 :( console.log('ServiceWorker registration failed: ', err); }); } </script> </body> </html>
注意:
注:Service Worker 的註冊路徑決定了其 scope 默認做用頁面的範圍。
若是 service-worker.js 是在 /sw/ 頁面路徑下,這使得該 Service Worker 默認只會收到 頁面/sw/ 路徑下的 fetch 事件。
若是存放在網站的根路徑下,則將會收到該網站的全部 fetch 事件。
若是但願改變它的做用域,可在第二個參數設置 scope 範圍。示例中將其改成了根目錄,即對整個站點生效。
service-worker.js
var cacheName = 'helloWorld'; // 緩存的名稱
// install 事件,它發生在瀏覽器安裝並註冊 Service Worker 時
self.addEventListener('install', event => {
/* event.waitUtil 用於在安裝成功以前執行一些預裝邏輯
可是建議只作一些輕量級和很是重要資源的緩存,減小安裝失敗的機率
安裝成功後 ServiceWorker 狀態會從 installing 變爲 installed */
event.waitUntil(
caches.open(cacheName)
.then(cache => cache.addAll([ // 若是全部的文件都成功緩存了,便會安裝完成。若是任何文件下載失敗了,那麼安裝過程也會隨之失敗。
'/js/script.js',
'/images/hello.png'
]))
);
});
/**
爲 fetch 事件添加一個事件監聽器。接下來,使用 caches.match() 函數來檢查傳入的請求 URL 是否匹配當前緩存中存在的任何內容。若是存在的話,返回緩存的資源。
若是資源並不存在於緩存當中,經過網絡來獲取資源,並將獲取到的資源添加到緩存中。
*/
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
if (response) {
return response;
}
var requestToCache = event.request.clone(); //
return fetch(requestToCache).then(
function (response) {
if (!response || response.status !== 200) {
return response;
}
var responseToCache = response.clone();
caches.open(cacheName)
.then(function (cache) {
cache.put(requestToCache, responseToCache);
});
return response;
})
);
});
注意:
注:爲何用request.clone()和response.clone()
須要這麼作是由於request和response是一個流,它只能消耗一次。由於咱們已經經過緩存消耗了一次,而後發起 HTTP 請求還要再消耗一次,因此咱們須要在此時克隆請求
Clone the request—a request is a stream and can only be consumed once.
七、消息推送
前兩步:
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Progressive Times</title> <link rel="manifest" href="/manifest.json"> </head> <body> <script> var endpoint; var key; var authSecret; var vapidPublicKey = 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY'; // 方法很複雜,可是能夠不用具體看,知識用來轉化vapidPublicKey用 function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/\-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js').then(function (registration) { return registration.pushManager.getSubscription() .then(function (subscription) { if (subscription) { return; } return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) }) .then(function (subscription) { var rawKey = subscription.getKey ? subscription.getKey('p256dh') : ''; key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : ''; var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : ''; authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : ''; endpoint = subscription.endpoint; return fetch('./register', { method: 'post', headers: new Headers({ 'content-type': 'application/json' }), body: JSON.stringify({ endpoint: subscription.endpoint, key: key, authSecret: authSecret, }), }); }); }); }).catch(function (err) { // 註冊失敗 :( console.log('ServiceWorker registration failed: ', err); }); } </script> </body> </html>
步驟三:
app.js
const webpush = require('web-push'); const express = require('express'); var bodyParser = require('body-parser'); const app = express(); webpush.setVapidDetails( 'mailto:contact@deanhume.com', 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY', 'p6YVD7t8HkABoez1CvVJ5bl7BnEdKUu5bSyVjyxMBh0' ); app.post('/register', function (req, res) { var endpoint = req.body.endpoint; saveRegistrationDetails(endpoint, key, authSecret); const pushSubscription = { endpoint: req.body.endpoint, keys: { auth: req.body.authSecret, p256dh: req.body.key } }; var body = 'Thank you for registering'; var iconUrl = 'https://example.com/images/homescreen.png'; // 發送 Web 推送消息 webpush.sendNotification(pushSubscription, JSON.stringify({ msg: body, url: 'http://localhost:3111/', icon: iconUrl })) .then(result => res.sendStatus(201)) .catch(err => { console.log(err); }); }); app.listen(3111, function () { console.log('Web push app listening on port 3111!') });
service worker監聽push事件,將通知詳情推送給用戶
service-worker.js
self.addEventListener('push', function (event) { // 檢查服務端是否發來了任何有效載荷數據 var payload = event.data ? JSON.parse(event.data.text()) : 'no payload'; var title = 'Progressive Times'; event.waitUntil( // 使用提供的信息來顯示 Web 推送通知 self.registration.showNotification(title, { body: payload.msg, url: payload.url, icon: payload.icon }) ); });
八、PWA小demo
準備:
建立一個關於PWA項目的文件夾
文件夾內準備一張圖
一個index.html文件
一個main.css文件
一個manifest.json文件
一個sw.js文件
之後更新把,寫崩了,我再尋思尋思
分割線,可愛的我來更新小栗子了
css文件夾裏有一個style.css
images裏有一個logo.jpg
具體看代碼:
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Hello PWA</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <img src="images/logo.jpg"> <script> if ('serviceWorker' in navigator) { // 瀏覽器支持SW navigator.serviceWorker.register('sw.js').then(function (registration) { console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function (err) { console.log('ServiceWorker registration failed: ', err); }); } </script> </body> </html>
個人style.css是空的哈哈哈
sw.js
var cacheName = 'hello-pwa'; self.addEventListener('install', event => { event.waitUntil( caches.open(cacheName) .then(cache => cache.addAll( [ '/', // 這個必定要包含整個目錄,否則沒法離線瀏覽 './images/logo.jpg', './index.html', './css/style.css' ] )).then(() => self.skipWaiting()) ); }); self.addEventListener('fetch', function (event) { event.respondWith( caches.match(event.request) .then(function (response) { if (response) { return response; } return fetch(event.request); }) ); });
接下來經過 http-server 和 ngrok(https)進行調試查看
在當前文件下
安裝 http-server
npm install http-server -g
安裝 ngrok,下載解壓便可
在項目目錄下執行以下命令:
http-server -c-1 // -c-1 會關閉緩存
再開啓另一個終端在 ngrok 文件的目錄下執行以下命令:
./ngrok http 8080 // http-server 默認開啓8080端口
運行:(我端口8080被佔了,因此這裏是8081)
查看application
查看緩存部分
第一次加載進來緩存沒有東西,須要刷新一下頁面。
https://github.com/yangTwo100/PWA_search_demo
以上。