如何開發(fā)一個漸進(jìn)式Web應(yīng)用程序PWA
概述
自蘋果推出了iPhone應(yīng)用商店以來,App成為了我們生活中不可或缺的一部分,而對于實體業(yè)務(wù)也是如此,現(xiàn)在各行業(yè)都在推出自己的App,但有沒有人想過這樣一種場景,如果自己的潛在客戶還沒有安裝你的App亦或是即便安裝但因為客戶的手機(jī)存儲空間緊張而卸載掉了你的App?那有沒有使App更輕量,更易安裝的技術(shù)實現(xiàn)呢?答案是“有的”。
漸進(jìn)式Web應(yīng)用程序就是為此而生的,它同時具備了Web應(yīng)用功能和以前只有在原生應(yīng)用才有的功能的特點,漸進(jìn)式Web應(yīng)用程序通過從主屏幕上的圖標(biāo)啟動,也可以根據(jù)推送通知啟動,加載時間幾乎可以忽略不計,而且除了可以在線使用外,也可以打包成可離線使用。
最重要的是,漸進(jìn)式Web應(yīng)用程序在手機(jī)上創(chuàng)建方式也很簡單,因為它們只是對你網(wǎng)站的增強(qiáng),當(dāng)有人在第一次訪問你的網(wǎng)站時,PWA的功能在經(jīng)過你授權(quán)后就會自動為你創(chuàng)建在手機(jī)上。
下面, 我們會一起來看看如何來創(chuàng)建一個屬于自己的PWA應(yīng)用。
要求
要開始學(xué)習(xí)本教程,您必須安裝以下軟件:
- node 8.9 版本及以上 (https: // nodejs.org/en/download/). Yarn(https://yarnpkg.com) Git.
作為本教程初始的工程,你可以clone以下Github庫:
git clone https://github.com/petereijgermans11/progressive-web-app
然后,到以下目錄中
cd pwa-article / pwa-app-manifest-init
通過以下命令安裝依賴并啟動工程
npm i && npm start
通過以下地址打開應(yīng)用:http:// localhost:8080

應(yīng)用的網(wǎng)址
有許多方法可以訪問我的本地主機(jī):為了從遠(yuǎn)程訪問發(fā)布在你機(jī)器上的8080端口的地址。為此,您可以使用ngrok。請參閱:https://ngrok.com/
使用以下命令安裝ngrok:
npm install -g ngrok
在終端中運(yùn)行以下命令。該命令為您生成一個可供外部訪問的URL。
ngrok http 8080
然后在Chrome中的移動設(shè)備上瀏覽至生成的網(wǎng)址。
PWA需要的技術(shù)組件是什么?
PWA有三個重要的技術(shù)組件協(xié)調(diào)工作,包括:
Manifest清單文件,Service Worker和在https下運(yùn)行。

Manifest清單文件
清單文件是一個JSON配置文件,其中包含了PWA的基礎(chǔ)信息,例如應(yīng)用的icon,Web應(yīng)用程序名稱及背景顏色。如果瀏覽器檢測到網(wǎng)站存在PWA清單文件,Chrome會自動出現(xiàn)“添加到主屏幕”按鈕。如果用戶點擊同意,該圖標(biāo)將被添加到主屏幕,并且將安裝PWA。

創(chuàng)建一個Manifest.json
PWA的Manifest.json文件如下所示:
JSON格式
{
"name": "Progressive Selfies",
"short_name": "PWA Selfies",
"icons": [
{
"src": "/src/images/icons/app-icon-192x192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/src/images/icons/app-icon-512x512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/index.html",
"scope": ".",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#3f51b5"
}
告訴瀏覽器你應(yīng)用的清單
在與index.html文件相同的級別的目錄中創(chuàng)建Manifest.json文件。清單文件創(chuàng)建后,將清單文件引用鏈接添加到index.html中。
<link rel=”manifest” href=”/manifest.json”>
Manifest 屬性介紹
Manifest有很多配置屬性,接下來我們會對其中的屬性做一個簡單的介紹
- name、short_name:指定Web應(yīng)用的名稱,short_name是該應(yīng)用的簡稱,當(dāng)沒有足夠空間展示應(yīng)用的name屬性時,系統(tǒng)就會使用short_name 。
- display:display屬性指定Web應(yīng)用的顯示模式,它有四個值可供配置:fullscreen、standalone、minimal-ui和browser,但一般常用的屬性就是fullscreen和standalone。 fullscreen:全屏顯示 standalone:這種模式下打開的應(yīng)用不會出現(xiàn)瀏覽器的地址欄,所以因此看起來更像是一個原生應(yīng)用 minimal-ui、browser:和使用瀏覽器訪問區(qū)別不大。
- orientation:控制Web應(yīng)用的顯示的方向及禁止手機(jī)轉(zhuǎn)屏。
- icons、background_color:icon用于指定應(yīng)用圖標(biāo),background_color是應(yīng)用加載完成前的背景色,通過設(shè)置這兩個屬性,可組合成應(yīng)用的Splash Screen。
- theme_color:定義應(yīng)用程序的默認(rèn)主題顏色。
- description:設(shè)置應(yīng)用的一段描述內(nèi)容。
以上是pwa 清單文件屬性的一些說明,我們通過將設(shè)置完成的清單文件并將其放置在與index.html文件同級的目錄中即可完成清單文件的添加。
打開Chrome開發(fā)者工具 – Application - Manifest,查看添加的清單文件是否加載完成,如果沒有下圖的信息,我們可以通過重新啟動服務(wù)器npm start來重新加載。

什么是Service Worker?
Service Worker(SW) 是一段JavaScript,它作為瀏覽器和網(wǎng)絡(luò)服務(wù)器間的代理。Service Worker可以在基于瀏覽器的 web 應(yīng)用中實現(xiàn)如離線緩存、消息推送、靜默更新等 native 應(yīng)用常見的功能,以給 web 應(yīng)用提供更好更豐富的使用體驗。
另外,這個API還允許利用緩存來支持離線體驗,從而使開發(fā)人員可以完全控制用戶的使用體驗

Service Worker生命周期
對于Service Worker,基本設(shè)置的步驟如下:
- 首先應(yīng)注冊SW,如果SW已注冊,瀏覽器會根據(jù)于安裝事件自動開始安裝。
- 安裝SW后,它將收到激活事件。此激活事件可用于清理SW早期版本的中使用的資源。

實際操作應(yīng)該首先創(chuàng)建一個和index.html同級,名為sw.js的空文件。然后再index.html文件中,添加一個base標(biāo)簽,如下:
<base href="/" rel="external nofollow" >
最后,在src/js/app.js中添加以下代碼注冊SW。此代碼將在頁面 “加載”過程中被激活。
你可以打開Chrome DevTools – Application - Service Worker 中檢查SW是否已經(jīng)啟用。
window.addEventListener('load', () => {
const base = document.querySelector('base');
let baseUrl = base && base.href || '';
if (!baseUrl.endsWith('/')) {
baseUrl = `${baseUrl}/`;
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(`${baseUrl}sw.js`)
.then( registration => {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
.catch(err => {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
}
});
以上這段代碼主要的作用是檢查SW的API在window對象的navigator屬性中是否可用。window對象代表瀏覽器窗口。如果SW在navigator中可用,則在頁面加載時立即注冊SW。
雖然注冊一個SW很簡單,但在有些情況下我們依然會遇到無法注冊Service Worker的問題,我們來簡單看看無法注冊SW的原因都有什么并如何解決:
- 您的應(yīng)用程序無法在HTTPS下運(yùn)行。在開發(fā)過程中,你可以通過localhost使用SW。但如果將其部署在網(wǎng)站上時,則需要啟用HTTPS。
- SW的路徑不正確。
沒有勾選Update on reload?!?/p>

Service Worker 事件
除了install和activate事件外,其他事件還有message、fetch、sync和push事件。

將以下代碼添加到你的SW中以監(jiān)聽生命周期事件(安裝和激活):
self.addEventListener('install', event => {
console.log('[Service Worker] Installing Service Worker ...', event);
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', event => {
console.log('[Service Worker] Activating Service Worker ...', event);
return self.clients.claim();
});
install回調(diào)調(diào)用skipWaiting()函數(shù)來觸發(fā)activate事件,并告訴Service Worker立即開始工作,而無需等待用戶瀏覽或重新加載頁面。
skipWaiting()函數(shù)強(qiáng)制等待中的Service Worker成為活動的Service Worker。self.skipWaiting()函數(shù)也可以和self.clients.claim()函數(shù)一起使用,以確保對底層Service Worker的更新立即生效。
在這種情況下,self-property 代表窗口對象(即你的瀏覽器窗口)。
添加到主屏幕按鈕
"添加到主屏幕按鈕" 允許用戶在其設(shè)備上安裝PWA。為了真正用這個按鈕安裝PWA,你必須在SW中定義一個fetch事件處理程序。讓我們在sw.js中解決這個問題。
self.addEventListener('fetch', event => {
console.log('[Service Worker] Fetching something ....', event);
// This fixes a weird bug in Chrome when you open the Developer Tools
if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') {
return;
}
event.respondWith(fetch(event.request));
});
Service Worker緩存
Service Worker的強(qiáng)大之處在于其攔截HTTP請求的能力。在這一步中,我們使用這個選項來攔截HTTP請求和響應(yīng),直接從緩存為用戶提供閃電般快速的響應(yīng)。
在Service Worker安裝期間進(jìn)行預(yù)緩存
當(dāng)用戶第一次訪問你的網(wǎng)站時,SW會開始自行安裝。在這個安裝階段,你可以將PWA使用的所有頁面、腳本和樣式文件下載并緩存起來,以下是完成這項工作的sw.js文件代碼:
const CACHE_STATIC_NAME = 'static';
const URLS_TO_PRECACHE = [
'/',
'index.html',
'src/js/app.js',
'src/js/feed.js',
'src/lib/material.min.js',
'src/css/app.css',
'src/css/feed.css',
'src/images/main-image.jpg',
'https://fonts.googleapis.com/css?family=Roboto:400,700',
'https://fonts.googleapis.com/icon?family=Material+Icons',
];
self.addEventListener('install', event => {
console.log('[Service Worker] Installing Service Worker ...', event);
event.waitUntil(
caches.open(CACHE_STATIC_NAME)
.then(cache => {
console.log('[Service Worker] Precaching App Shell');
cache.addAll(URLS_TO_PRECACHE);
})
.then(() => {
console.log('[ServiceWorker] Skip waiting on install');
return self.skipWaiting();
})
);
});
這段代碼使用安裝事件,并在安裝階段添加了一個URLS_TO_PRECACHE數(shù)組。一旦調(diào)用開啟緩存函數(shù)(caches.open),你就可以使用cache.addAll()函數(shù)來緩存數(shù)組中的文件。通過event.waitUntil()方法使用JavaScript promise來知道安裝需要多長時間以及是否成功。
安裝事件會調(diào)用self.skipWaiting()直接激活SW。如果所有文件都已被成功緩存,SW就會被安裝。但如果其中一個文件無法下載,則安裝步驟將會失敗。在Chrome開發(fā)者工具中,你可以檢查緩存(在Cache Storage中)是否被URLS_TO_PRECACHE數(shù)組中的靜態(tài)文件填充。

但是,如果你查看Network選項卡,文件仍然是通過網(wǎng)絡(luò)獲取的。原因是雖然緩存已經(jīng)準(zhǔn)備就緒了,但我們并沒有從緩存中讀取引用資源。所以為了完成這部分工作,我們首先要監(jiān)聽?wèi)?yīng)用的fetch事件,然后攔截并從緩存中獲取資源,讓我們看看下面的代碼吧:
self.addEventListener('fetch', event => {
console.log('[Service Worker] Fetching something ....', event);
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
console.log(response);
return response;
}
return fetch(event.request);
})
);
});
我們使用caches.match()函數(shù)檢查傳入的URL是否與當(dāng)前緩存中可能存在的資源匹配。如果匹配,我們就返回該緩存資源,但如果該資源不存在于緩存中,我們就像正常情況下一樣繼續(xù)獲取請求的資源。
在Service Worker安裝并激活后,刷新頁面并再次檢查網(wǎng)絡(luò)選項卡?,F(xiàn)在,Service Worker將攔截HTTP請求,并從緩存中即時加載相應(yīng)的資源,而不是向服務(wù)器發(fā)出網(wǎng)絡(luò)請求。
現(xiàn)在,如果我們在網(wǎng)絡(luò)選項卡中設(shè)置離線模式,我們的應(yīng)用也依然能正常訪問。
后臺傳輸
Background Fetch API是SW的后臺功能,它允許用戶在后臺下載大文件、視頻和音樂等資源。在獲取/傳輸過程中,你的用戶即便關(guān)閉標(biāo)簽,乃至關(guān)閉整個瀏覽器,也不會清除傳輸任務(wù)。當(dāng)用戶再次打開瀏覽器后,傳輸過程將恢復(fù)。這個API也可以將傳輸?shù)倪M(jìn)度可以顯示給用戶,用戶可以取消或暫停這個過程。

默認(rèn)情況下,后臺傳輸功能是不可用的,你必須通過url(chrome://flags/#enable-experimental-web-platform-features)允許chrome的“Experimental Web Platform features”選項

以下是如何實現(xiàn)此類后臺傳輸?shù)氖纠?/p>
在你的index.html文件中添加ID為“ bgFetchButton”的按鈕
<button id="bgFetchButton">Store assets locally</button>
然后,在加載事件處理程序中的app.js中添加用于執(zhí)行后臺傳輸?shù)拇a
window.addEventListener(‘load', () => {
...
bgFetchButton = document.querySelector(‘#bgFetchButton');
bgFetchButton.addEventListener(‘click', async event => {
try {
const registration = await navigator.serviceWorker.ready;
registration.backgroundFetch.fetch(‘my-fetch', [new Request(`${baseUrl}src/images/main-image-lg.jpg`)]);
} catch (err) {
console.error(err);
}
});
...
});
上面的代碼在以下條件下開始執(zhí)行后臺傳輸:
- 用戶點擊ID為bgFetchButton的按鈕
- SW已注冊
后臺傳輸必須在異步函數(shù)中執(zhí)行,因為傳輸過程不能阻塞用戶界面。
傳輸完成后放入緩存
self.addEventListener(‘backgroundfetchsuccess', event => {
console.log(‘[Service Worker]: Background Fetch Success', event.registration); event.waitUntil(
(async function() {
try {
// Iterating the records to populate the cache
const cache = await caches.open(event.registration.id); const records = await event.registration.matchAll(); const promises = records.map(async record => {
const response = await record.responseReady;
await cache.put(record.request, response);
});
await Promise.all(promises);
} catch (err) {
console.log(‘[Service Worker]: Caching error');
}
})()
);
});
這段代碼由以下步驟組成:
- 當(dāng)Background Fetch傳輸完成,你的SW將收到Background Fetch成功事件。
- 創(chuàng)建并打開一個與registration.id同名的新緩存。
- 通過registration.matchAll()獲取所有記錄并遍歷。
最后,通過Promise.all(),執(zhí)行所有的承諾。

總結(jié)
在本文中我們討論了PWA的基礎(chǔ)組成部分的其中兩部分:Manifest、Service Worker的基礎(chǔ)功能介紹,因為HTTPS之前我們已經(jīng)有過一些討論了https://www.cnblogs.com/powertoolsteam/p/http2https.html
以上就是如何開發(fā)一個漸進(jìn)式Web應(yīng)用程序PWA的詳細(xì)內(nèi)容,更多關(guān)于開發(fā)一個漸進(jìn)式Web應(yīng)用程序的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
node實現(xiàn)socket鏈接與GPRS進(jìn)行通信的方法
這篇文章主要介紹了node實現(xiàn)socket鏈接與GPRS進(jìn)行通信的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05
node+koa實現(xiàn)數(shù)據(jù)mock接口的方法
本篇文章主要介紹了node+koa實現(xiàn)數(shù)據(jù)mock接口的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09
使用Typescript和ES模塊發(fā)布Node模塊的方法
這篇文章主要介紹了使用Typescript和ES模塊發(fā)布Node模塊的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
Node.js?搭建后端服務(wù)器內(nèi)置模塊(?http+url+querystring?的使用)
這篇文章主要介紹了Node.js搭建后端服務(wù)器內(nèi)置模塊(http+url+querystring的使用),文章圍繞主題展開詳細(xì)的內(nèi)容戒殺,具有一定的參考價值,需要的朋友可以參考一下2022-09-09
Node.js(v16.13.2版本)安裝及環(huán)境配置的圖文教程
本文主要介紹了Node.js(v16.13.2版本)安裝及環(huán)境配置的圖文教程,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05
前端node Session和JWT鑒權(quán)登錄示例詳解
關(guān)于前端鑒權(quán)登錄是比較常見的需求了,本文將從服務(wù)端渲染和前后端分離的不同角度下演示鑒權(quán),為大家介紹前端node Session和JWT鑒權(quán)登錄示例詳解2022-07-07

