PWA介紹
PWA是什么
MDN解釋
PWA
(Progressive Web Apps
,漸進(jìn)式Web
應(yīng)用)運(yùn)用現(xiàn)代的Web API
以及傳統(tǒng)的漸進(jìn)式增強(qiáng)策略來創(chuàng)建跨平臺(tái)Web
應(yīng)用程序。這些應(yīng)用無處不在、功能豐富,使其具有與原生應(yīng)用相同的用戶體驗(yàn)優(yōu)勢(shì)。
- 可以提升
WebApp
體驗(yàn)的一種新方法,能給用戶原生應(yīng)用的用戶體驗(yàn) - 創(chuàng)建跨平臺(tái)
Web
桌面應(yīng)用程序
總結(jié):PWA
能做到如此,不是指某一項(xiàng)特定的技術(shù),而是在原有的WebApp
基礎(chǔ)之上通過添加Manifest
配置文件和Service Worker
以及搭配緩存使用,可以讓原有的應(yīng)用具備安裝和離線的功能
PWA優(yōu)勢(shì)
- 漸進(jìn)式
- 在原有的基礎(chǔ)上添加
PWA
功能,不影響原有應(yīng)用
- 在原有的基礎(chǔ)上添加
- 可安裝/原生體驗(yàn)
- 可以像原生
App
應(yīng)用一樣添加在桌面,點(diǎn)擊主屏幕圖標(biāo)可以實(shí)現(xiàn)啟動(dòng)動(dòng)畫以及隱藏地址欄
- 可以像原生
- 離線功能
- 在
Service Worker
和緩存api
實(shí)現(xiàn)離線緩存功能。可以做到離線訪問使用一些離線功能
- 在
- 推送
- 通過推送消息點(diǎn)擊,快速打開應(yīng)用,用戶促活
這里展示一下手機(jī)端Demo
運(yùn)行效果
PWA相關(guān)技術(shù)點(diǎn)
源碼請(qǐng)點(diǎn)擊這里下載
一個(gè)標(biāo)準(zhǔn)的PWA
應(yīng)用應(yīng)該具備以下3個(gè)特征
-
https
訪問或者localhost
Service Worker
Manifest
Manifest
manifest.json
可以使應(yīng)用具備添加桌面圖標(biāo)的能力。配置文件內(nèi)容:應(yīng)用名稱,圖標(biāo),啟動(dòng)屏,主題色,應(yīng)用樣式等等
相比傳統(tǒng)的WebApp
入口:網(wǎng)址,收藏夾,書簽;PWA
應(yīng)用入口更直接,方便快捷
- 添加桌面圖標(biāo)
- 添加啟動(dòng)動(dòng)畫,避免白屏
- 隱藏瀏覽器的默認(rèn)相關(guān)UI,看上去更像一個(gè)原生應(yīng)用
在index.html
引入manifest.json
文件
<link rel="manifest“ href="/manifest.json" />
manifest.json
常用的配置信息:
{
"name": "pwa-basic",
"short_name": "PWA基礎(chǔ)",
"start_url": "/index.html",
"icons":[
{
"src": "/images/icon128.png",
"sizes": "128x128",
"type":"image/png"
},
{
"src": "/images/icon144.png",
"sizes": "144x144",
"type":"image/png"
}
],
"description": "這個(gè)是一個(gè)PWA基礎(chǔ)的Demo",
"background_color": "skyblue",
"theme_color": "black",
"display": "standalone"
}
-
name
應(yīng)用名稱,添加時(shí)顯示的名稱,啟動(dòng)屏下方顯示的名稱 -
short_name
當(dāng)應(yīng)用名稱過長(zhǎng),桌面上顯示的名稱 -
start_url
點(diǎn)擊桌面圖標(biāo)加載的入口地址 -
icons
桌面的圖標(biāo),啟動(dòng)屏中間顯示圖標(biāo) description
-
background_color
啟動(dòng)屏的背景顏色 -
theme_color
主題顏色 -
display
-
fullscreen
全屏顯示, 所有可用的顯示區(qū)域都被使用, 并且不顯示狀態(tài)欄 -
standalone
狀態(tài)欄除外,無地址欄 -
minimal-ui
有狀態(tài)欄,有瀏覽器地址欄
-
Cache Api
Service Worker
什么是Service Worker
MDN解釋
Service workers
本質(zhì)上充當(dāng)Web
應(yīng)用程序、瀏覽器與網(wǎng)絡(luò)(可用時(shí))之間的代理服務(wù)器。這個(gè)API
旨在創(chuàng)建有效的離線體驗(yàn),它會(huì)攔截作用域下邊的網(wǎng)絡(luò)請(qǐng)求并根據(jù)網(wǎng)絡(luò)是否可用來采取適當(dāng)?shù)膭?dòng)作、更新來自服務(wù)器的的資源
Service Worker
的功能(除了具備Web Worker
功能以外)
- 安裝成功,運(yùn)行于瀏覽器后臺(tái)。可以攔截作用域范圍下所有的頁面
http
請(qǐng)求 - 利用緩存
API
可以讓離線內(nèi)容可控(由開發(fā)者控制) - 必須使用
https
。除了使用本地開發(fā)環(huán)境調(diào)試時(shí)域名可以使用localhost
Service Worker
兼容性 94%的瀏覽器都支持
Service Worker 的使用
以下簡(jiǎn)稱SW
SW
獨(dú)立于 Web 頁面的生命周期。分為5步:注冊(cè)、安裝成功(安裝失敗)、激活、運(yùn)行、銷毀。如下圖:
SW
注冊(cè)
if (window.navigator.serviceWorker) {
const serviceWorker = window.navigator.serviceWorker;
serviceWorker.register('./service-worker.js').then(registion => {
console.log('注冊(cè)成功', registion);
}).catch(error => {
console.log("注冊(cè)失敗")
})
}
SW
常見事件監(jiān)聽如下
// service worker 安裝
self.addEventListener('install', (event) => {
console.log('install 成功', event);
// 這里一般靜態(tài)資源的預(yù)緩存
});
// service worker 激活
self.addEventListener('activate', (event) => {
console.log('activate 成功', event);
// 這里建議刪除舊的緩存,所有客戶端獲取控制權(quán)
})
self.addEventListener('fetch', (event) => {
const req = event.request;
// 攔截請(qǐng)求,并利用fetch api 和 cache api把結(jié)果給到event.respondWith() 即可實(shí)現(xiàn)離線緩存
});
1、SW第一次加載
用戶首次訪問PWA
的頁面時(shí),SW
會(huì)立刻被下載并注冊(cè)。注冊(cè)結(jié)束之后,會(huì)進(jìn)行安裝SW
,執(zhí)行install
事件(會(huì)在這里做預(yù)緩存,也就是靜態(tài)文件的緩存),第二次打開就可以使用緩存的靜態(tài)資源了(這里也要看使用的緩存策略,下邊會(huì)講)
這里的靜態(tài)資源(HTML
, JS
, CSS
和 Images
)可以理解為web
應(yīng)用的外殼(應(yīng)用外殼緩存),也可以理解成首頁。因?yàn)轭A(yù)緩存一個(gè)網(wǎng)站離線工作需要的所有資源顯然是不現(xiàn)實(shí)的。
const staticFiles = [
'/',
'/index.html',
'/css/index.css',
'/js/index.js'
];
caches.open("cache_name_v1").then(cache => {
return cache.addAll(staticFiles);
}).then(self.skipWaiting)
然后會(huì)立即激活SW
,也就會(huì)觸發(fā)監(jiān)聽激活的事件,該事件會(huì)做移除舊緩存和獲取控制權(quán)的操作
caches.keys().then(keys => {
keys.forEach(key => {
if ("當(dāng)前緩存key值" !== key) {
caches.delete(key);
}
});
})
激活之后就是SW
正常運(yùn)行階段了。在運(yùn)行階段,監(jiān)聽了fetch
事件,所有的請(qǐng)求都會(huì)走到這個(gè)事件。主要做兩件事
- 使用
cache.put(request,response)
緩存一些運(yùn)行時(shí)資源,比如api
,其它文件資源 - 使用用
event.respondWith
返回?cái)r截的請(qǐng)求響應(yīng)結(jié)果,那么這個(gè)如何返回這個(gè)響應(yīng)結(jié)果就是我們開發(fā)可控的
響應(yīng)結(jié)果從原子上分為2中方式來獲取
- 網(wǎng)絡(luò)請(qǐng)求
fetch(request)
const req = event.request;
event.respondWith(
fetch(req).then((response) => {
return response;
})
);
- 直接取緩存
const req = event.request;
caches.open("緩存key").then(cache => {
return cache.match(req);
})
以上2種其實(shí)是常用緩存策略中的2個(gè)而已
NetworkOnly
-
CacheOnly
這兩種基本是不可取的,太過單一。還有一下幾種 -
CacheFirst
首先去緩存,取緩存失敗,再去請(qǐng)求 -
NetworkFirst
首先請(qǐng)求緩存,請(qǐng)求失敗,再取緩存 -
StaleWhileRevalidate
從緩存中讀取資源的同時(shí)發(fā)送網(wǎng)絡(luò)請(qǐng)求更新本地緩存
為防止緩引起問題,一般使用NetworkFirst
。優(yōu)先取最新得網(wǎng)路資源,失敗再去取緩存。這樣既可以做到離線緩存,也可以資源/數(shù)據(jù)得一致
2、SW更新
當(dāng)沒有更新的時(shí)候再次注冊(cè)SW
,不會(huì)觸發(fā)install
,activate
事件。
如果SW
有更新,和上述第一次加載路程基本相似。有2個(gè)注意事項(xiàng)
-
install
事件需要處理skipWaiting
,可以讓更新的sw
跳過等待,直接激活。防止有頁面正在使用老的SW
,導(dǎo)致新的SW
一直等待激活。 -
activate
事件需要獲取控制權(quán)clients.claim()
3、SW
銷毀
sw
一旦安裝激活成功,會(huì)一直存在,需要手動(dòng)卸載
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
})
}
PWA實(shí)操
注冊(cè)SW文件
可以使用register-service-worker
插件注冊(cè)sw.js
基本使用搞懂了,可以在項(xiàng)目中實(shí)踐一下。
對(duì)于大部分項(xiàng)目的現(xiàn)狀,使用webpack/vite
構(gòu)建工具編譯項(xiàng)目,每次發(fā)版編譯的靜態(tài)資源文件名存在變化,直接寫原生的SW
,不可能在SW
文件中每次都要手動(dòng)去修改需要緩存的靜態(tài)資源。而且編寫也比較繁瑣和復(fù)雜。所以這里需要借助輪子
PWA相關(guān)工具的使用
sw-precache sw-precache-webpack-plugin處理靜態(tài)資源緩存的工具
sw-toolbox 處理運(yùn)行時(shí)緩存的能力 運(yùn)行時(shí)緩存:動(dòng)態(tài)資源,比如服務(wù)
API
,資源圖片
sw-precache
默認(rèn)集成了 sw-toolbox
,但是兩個(gè)工具官方已經(jīng)不更新了。google
官方建議使用workbox
,workbox
是現(xiàn)在PWA
最優(yōu)的絕決方案
workbox基本使用
workbox
的緩存分為兩種,一種的precache
,一種的runtimecache
workbox
常用的包
workbox-build 自動(dòng)構(gòu)建生成sw.js
workbox-core 核心包,基本的類
workbox-expiration 配置資源過期
workbox-precaching 處理預(yù)緩存
workbox-cacheable-response 處理緩存資源相應(yīng)
workbox-strategies 緩存策略對(duì)象
precache
對(duì)應(yīng)的是在installing
階段進(jìn)行讀取緩存的操作。它讓開發(fā)人員可以確定緩存文件的時(shí)間和長(zhǎng)度,以及在不進(jìn)入網(wǎng)絡(luò)的情況下將其提供給瀏覽器,這意味著它可以用于創(chuàng)建Web離線工作的應(yīng)用。
precache api
具體使用
precache([
'/',
'/index.html',
'/css/index.css',
'/js/index.js',
])
或者
precacheAndRoute(self.__WB_MANIFEST);
第二種方式需要依賴 workbox-build
插件InjectManifest
模式下,根據(jù)配置動(dòng)態(tài)替換__WB_MANIFEST
占位符的形式
runtimecache api
具體使用如下:(運(yùn)行時(shí)的資源,比如api,其它資源等等)
registerRoute(
({ request }) => request.url === 'http://localhost:3030/api/food/getFoodList',
new NetworkFirst({
// cacheName: 'api',
plugins: [
new CacheableResponsePlugin({
statuses: [200, 0],
}),
new ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 Days
}),
],
}),
);
work-build
的使用
2種模式
-
injectManifest
需要按照workbox
規(guī)范手動(dòng)維護(hù)sw.js
文件,動(dòng)態(tài)東城預(yù)緩存的資源列表。根據(jù)sw.js
文件中__WB_MANIFEST
占位符進(jìn)行替換。這里的動(dòng)態(tài)緩存需要自己維護(hù)開發(fā),可拓展性及強(qiáng)
const { injectManifest } = require('workbox-build')
injectManifest({
swSrc: path.resolve(__dirname, './sw.js'), // 必傳
swDest: path.resolve(__dirname, './service-worker.js'),
globDirectory: path.resolve(__dirname, '../'),
globPatterns: ["**\/*.{js,css,html,ttf}"]
});
-
generateSW
不需要提供sw.js
文件,只需傳入配置,就可自動(dòng)生成sw.js
文件
const { generateSW } = require('workbox-build')
generateSW({
cacheId: 'generatesw-pwa', // 設(shè)置前綴
cleanupOutdatedCaches: true, // 刪除舊的預(yù)緩存
skipWaiting: true,
clientsClaim: true,
globDirectory: path.resolve(__dirname, '../'),
globPatterns: ["**\/*.{js,css,html,ttf}"],
sourcemap: false,
mode: 'development', // 生成sw文件的模式
swDest: path.resolve(__dirname , 'service-worker.js'), // 默認(rèn) service-worker.js
runtimeCaching: [
{
urlPattern: /^http:\/\/juheimg.oss-cn-hangzhou.aliyuncs.com/,
handler: 'NetworkFirst', // 網(wǎng)絡(luò)優(yōu)先
options: {
cacheName: 'images-cache',
expiration: {
maxEntries: 20, // 針對(duì)改類型的緩存的最大數(shù)量,超過替換
},
},
}
]
});
workbox-webpack-plugin的使用
這是分為兩種模式,和上述work-build
差不多,需要增加webpack
插件配置,需要在html-webpack-plugin
之后,否則生成的html
文件不在預(yù)緩存列表
const { GenerateSW , InjectManifest } = require('workbox-webpack-plugin')
new GenerateSW(config) 或者 new InjectManifest(config)
webpack項(xiàng)目中使用
vue項(xiàng)目
vue
新創(chuàng)建的項(xiàng)目可以自己選擇支持pwa
如下圖
如果不是,則添加@vue/cli插件即可。執(zhí)行vue add pwa
即可添加支持pwa
具體配置請(qǐng)看配置文檔
react項(xiàng)目
react
項(xiàng)目新建
使用cra
腳手架工具指定pwa
模板創(chuàng)建,如下
create-react-app my-app --template cra-template-pwa
老項(xiàng)目直接根據(jù)項(xiàng)目情況添加webpack
配置以及manifest.json
,注冊(cè)sw.js
即可
vite項(xiàng)目中使用
安裝
npm install -D vite-plugin-pwa
vite.config.js
配置
import { VitePWA } from 'vite-plugin-pwa'
export default {
plugins: [
VitePWA({
mode: 'development',
workbox: {
// 省略
}
})
]
}