PWA簡單入門教程
什麼是PWA
MDN官網對PWA有很明確的定義
Progressive web apps (PWAs) take traditional web sites/applications — with all of the advantages the web brings — and add a number of features that give them many of the user experience advantages of native apps.
複製程式碼
簡單地翻譯過來就是:PWA像傳統的web app一樣,但是它更優秀。
PWA是Google推出的技術,你可以在這裡找到更為詳細的資料。或者前往MDN檢視文件。
PWA準備知識
PWA是全新的內容,它不僅僅是全新的API那麼簡單,更為重要的是,它引入了一系列全新的標準和語法作為基礎。在學習PWA之前,你需要保證你已經熟練使用以下的內容:
- ES6標準語法
- Promise標準,這是最為重要的知識點,如果你還不熟或者沒聽說過,那麼你得好好思考一下了
- fetch,全新的獲取資源的API,它包括Request、Response、Header和Stream
- WebWorker,JavaScript解決單執行緒的方案
- Cache API(快取API)
PWA的很容易犯錯的地方
PWA線上上部署的時候,請確保是在HTTPS下面,而非HTTP。當然,為了便於開發,瀏覽器支援localhost
上面部署。
PWA完成快取後,很多時候你會發現程式碼無法變動,或者沒有按照預期的那樣自動更新worker,這時候不妨在清除快取試試。
PWA並非支援所有瀏覽器,事實上,很少瀏覽器預設支援PWA。這方面Chrome和FireFox做得比較好,因此本文采用Chrome作為開發工具。
一些準備
首先,讓我們先來一些簡單的準備工作。PWA需要一個伺服器,我們使用Koa簡單地搭建一個伺服器。
首先,建立開發目錄,並初始化一些檔案:
mkdir pwa-test
cd pwa-test
touch index.html
touch index.js
touch sw.js
// 伺服器指令碼
touch server.js
// 初始化nodejs專案目錄,一路回車
npm init
複製程式碼
接著安裝一些開發使用的包
npm install koa koa-static --save-dev
//或者
yarn add koa koa-static --dev
複製程式碼
接著,我們在server.js
裡面寫入下面這段簡單的程式碼:
const Koa = require(`koa`);
const Static = require(`koa-static`);
const path = require(`path`);
const app = new Koa();
const staticPath = `./`;
app.use(Static(path.resolve(__dirname, staticPath)));
app.listen(8080);
複製程式碼
然後在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>PWA TEST</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
複製程式碼
開始PWA之旅
service worker
PWA最重要的一個部分,service worker
。它和傳統的Worker相似但又不同。操作Service Worker
的方法很簡單,只需要簡單的register一下。我們接下來介紹一下具體使用。
首先是檢測是否支援service worker
:
// index.js
if (`serviceWorker` in navigator) {
navigator.serviceWorker.register(`/sw.js`).then(reg => {
console.log(`service worker registed!`);
}).reject(err => {
console.log(`Opooos, something wrong happend!`);
})
}
window.onload = function() {
document.body.append(`PWA!`)
}
複製程式碼
這段程式碼使用了Promise,當你register一個檔案的時候,它會返回一個Promise。值得注意的是,register接收的檔案並非相對於當前檔案所在路勁的路勁,而是根路徑的相對路徑。換句話來說,如果你的pwa應用在https://www.example.com/pwa
上執行,那麼你register的sw.js
並非是./sw.js
,而應該是/pwa/sw.js
。
現在我們已經註冊了一個service Worker,但是它還沒有任何內容。接著我們在sw.js寫入以下程式碼:
// sw.js
self.addEventListener(`install`, function (e) {
e.waitUntil(
caches.open(`v1`).then(cache => {
return cache.addAll([
`/index.js`,
`/index.html`,
`/`
]);
})
);
});
self.addEventListener(`fetch`, function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
// 檢測是否已經快取過
if (response) {
return response;
}
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function (response) {
// 檢測請求是否有效
if (!response || response.status !== 200 || response.type !== `basic`) {
return response;
}
var responseToCache = response.clone();
caches.open(`v1`)
.then(function (cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
複製程式碼
self
在Worker裡面相當於Global
,這裡我們註冊了兩個事件:install
和fetch
。這兩個事件分別對應的是service Worker
安裝以及下載檔案的時候時候呼叫。
首先是install
,當你register一個檔案之後,install會被呼叫,它意味著這個檔案要被安裝到service Worker中去了。install
事件的event
裡面有個waitUntil
的函式,它接收一個Promise作為引數。它保證了在傳入的Promise執行完之後才完成安裝。
在waitUntil
裡面,我們使用了caches
。這是一個全域性變數,我們使用open
開啟一個快取,我們假設這個快取庫叫做v1
,如果沒有,它會自動建立。
caches.open(`v1`)
同樣返回一個Promise,它的回撥函式接收一個cache
,也就是對應的快取庫。接著,我們使用addAll
新增了/index.js
和/index.html
這兩個檔案。記住,cache上的操作應該返回,不然waitUntil接收不到什麼時候完成安裝的指令。
另一個事件fetch
是做什麼用的呢?它會’攔截‘網頁的fetch請求。這樣一來,我們就可以攔截網頁的部分或者全部fetch
請求,然後看看這些請求所請求的檔案在我們的快取裡有沒有,有的話就直接從快取裡拿,不用下載了。這也是PWA最重要的功能之一。
self.addEventListener(`fetch`, function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
if (response) {
return response;
}
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function (response) {
if (!response || response.status !== 200 || response.type !== `basic`) {
return response;
}
var responseToCache = response.clone();
caches.open(`v1`)
.then(function (cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
複製程式碼
首先,當產生fetch請求時,fetch
事件被呼叫。fetch
事件的event
裡面同樣有一個特殊的屬性,那就是request
。和nodejs裡面的那個request
類似,它代表了一個請求。
我們首先在快取裡查詢這個request
以前存過沒有,呼叫了match
函式,它返回一個Promise,這個Promise成功時呼叫一個response
作為引數的回撥函式。如果在快取裡找到了請求對應的檔案,那麼response
不為undefined
,那麼直接返回就行了。
但是問題來了,我們怎麼告訴主執行緒:“這個檔案下載過,我的cache裡面有,直接來拿就行了”。event
裡面還有個方法,是event.respondWith
的方法。它接受一個Promise作為引數,這個Promise的成功回撥應該是一個response
。
如果沒有找到,那麼就需要從伺服器下載下來,當然,我們同樣也希望把這個檔案加入快取,免得每次都下載。
我們使用fetch
而非ajax
的重要原因是,fetch
天然的支援Request
和Response
,並且是使用的Promise。這和service Worker
是一套的。
fetch
接收一個request
作為請求,並且在回撥函式裡面返回這個請求的response
。
值得注意的是,request
和response
都是流,和nodejs的Stream
類似。因此如果我們直接把event.request
傳給fetch
,那麼request
就不能複用了。於是我們複製一個request
來請求,這樣就能複用了。當請求成功的時候(fetch的請求成功並不等於是得到了資料,和ajax不同,只有當出現錯誤導致這次請求失敗的時候,才不是成功的。其餘情況,就算請求到的是404,也算成功),我們判斷下response有沒有收到東西,並且是200
成功的,另外不能是跨域
獲得的(也就是response.type == `basic`
)。
好了,現在從伺服器得到了這個響應了,我們只需要把它加入我們的快取裡面就行了。這裡我們呼叫了cache.put
來快取這個請求的響應。
最後,一定不要忘記,返回這個response!並且為了複用,返回的應該是clone後的!因為event.respondWith
需要告訴主執行緒:“這個請求我們已經拿到了(不管是從快取中拿到的還是從伺服器拿到的),你接受這個響應就行了!”
萬事俱備
我們現在都準備好了,讓我們看看能不能執行。開啟終端,執行伺服器指令碼
nodejs ./server.js
複製程式碼
開啟localhost:8080
,不出意外你應該能看見PWA!
。
現在,我們停止伺服器指令碼,再次重新整理頁面。頁面上應該同樣會顯示PWA!
。
一些問題
-
怎麼更新service Worker呢?
很簡單,你只需要更改sw.js就行了,它會在下次聯網時主動檢測並進行比對,如果不同,那麼會重新安裝。
-
怎麼更新快取後的指令碼或檔案呢?
這需要你自己手動的檢測了,你可以開發一個檢測更新的介面,然後手動的再次請求並更新部分檔案
-
有沒有什麼成功的案例可以借鑑呢?
有,用chrome開啟Vue中文官網,會提示你加入桌面,同意之後,你就會發現pwa的神奇之處了。
-
我的PWA怎麼手機上不支援?
你需要安裝chrome或者Firefox,並且授予它一些基本的許可權,比如允許建立桌面圖示
-
看起來pwa沒有什麼啊,不就是允許離線訪問了嗎?
No!pwa可不止這些內容哦!pwa甚至可以呼叫一些原生APP才可以呼叫的介面,也可以像原生的APP那樣推送訊息哦!
-
太棒了,我怎麼繼續學習呢!