Github Pages 是 Github 提供的一個網站託管服務,能夠用於部署我的博客或者項目主頁,使用的是 jekyll 框架做爲文件解析轉換爲網頁靜態文件的載體。PWA(Progressive Web Apps) 是近幾年 Google 提出的概念,致力於使用原生 Web 技術快速打造可靠的、媲美原生應用體驗的 Web App。使用 Github Pages
搭建我的博客是很是快捷,方便以及免費的可靠的方式,再配合以 PWA 技術加強,能使咱們的博客像一個應用那樣被訪問,加強用戶粘度。css
PWA
並無特指某種技術或者工具,而是指使用一系列最新的 Web 技術,但同時保證應用在不支持新特性的瀏覽器中的使用也不受影響的方法論,能夠選擇性只使用其中幾項技術而沒必要使用全套技術,這就是漸進式(Progressive)
的意義所在。大多數 FEer 應該都聽過這個詞語,從大咖 Google 在大力推行開始這個技術迅速在前端圈裏掀起了熱潮,可是若是追尋這個概念最先的提出者,應該要追述到由 Alex Russell 大神的寫的 Progressive Web Apps: Escaping Tabs Without Losing Our Soul,核心就是要在保留 Web 靈魂的基礎上對網頁進行加強。html
PWA 的帶來的提高主要有:前端
Web靈魂
其中一點就是指這個開放性。目前開發 PWA 應用可使用到的技術有:ios
本篇幅不一一詳細介紹各項技術的概念與使用,有興趣可自行了解,本文說明如何在 Github Pages 中一步一步引入 PWA 中的各個特性。git
下面例子使用的是 Github Pages 默認使用的 jekyll
引擎,詳情能夠參考這裏。github
提及 PWA 不得不首先提起 Service Worker
,其餘特性的功效或多或少都依賴於首先啓用了該功能。咱們主要在Service Worder的三個生命週期期間(事件)install
、activate
和fetch
裏搞事情:web
Service Worker
和通常的腳本代碼不同,它的全部代碼須要單獨放在一個文件中,而後經過指定的接口註冊到頁面裏。chrome
假設如今有一個 service-worker.js
在項目根目錄下,咱們在根目錄下的 index.html
里加入如下代碼:json
<script> // 註冊 service worker if (navigator.serviceWorker) { navigator.serviceWorker.register('/service-worker.js', {scope: '/'}) } </script>
複製代碼
首先判斷當前環境是否支持 Service Worder,記住咱們的核心是 Progressive
!register
方法的第二個參數 scope 用於指定Worker 可控制的範圍(經過 URL 判斷),舉個🌰:若是 scope 設爲 /sub/
,那麼網頁中全部到/other/
或/other/foo
的請求都沒法在 Worker 的 fetch
事件中攔截。默認值範圍和 Service Worker 文件路徑相同。api
註冊完成後,Service Worker
開始執行,首先會接收到一次install事件,咱們能夠在install事件回調中進行獲取資源,而後放入緩存中的操做:
假設咱們的博客須要使用 main.js
和 main.css
兩個文件,分別放置在 js
目錄和 css
目錄下,Service Worker 能夠這麼寫:
const CACHE_NAME = 'xlaoyu_blog_1.0.0';
const URLS = [ // Add URL you want to cache in this list.
// '/', // If you have separate JS/CSS files, add path to those files here
'/index.html',
'/css/main.css',
'/js/main.js'
];
// Cache resources
self.addEventListener('install', function (e) {
e.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
console.log('installing cache : ' + CACHE_NAME)
return cache.addAll(URLS);
}).then(_ => {
return self.skipWaiting();
});
);
});
複製代碼
e.waitUntil
- 表示等待傳入的 Promise 完成以後才把安裝狀態標記爲完成
caches.open(chche_name)
- 打開一個緩存對象。一個域名下能夠有多個緩存對象
cache.addAll(urls)
- 根據傳入的 URL 在後臺自動請求獲取資源,而後以 URL 爲 key 資源內容爲 value 存入上一條打開的 cache 對象中。
self.skipWaiting()
- 直接跳過 waiting
階段,下面會詳細講解。
這個事件使得咱們的 Service Worker 有能力對指定範圍內的頁面發出的全部請求進行濾或者替換。
// Respond with cached resources
self.addEventListener('fetch', function (e) {
e.respondWith(
caches.match(e.request).then(function (request) {
if (request) {
// 若是緩存存在,直接返回緩存
console.log('responding with cache : ' + e.request.url);
return request;
} else {
// 緩存不存在,發起請求獲取資源返回
console.log('file is not cached, fetching : ' + e.request.url);
return fetch(e.request);
}
});
);
});
複製代碼
這個階段咱們能夠在這裏進行舊或者再也不使用的緩存的清理工做。
知足如下兩個條件之一,纔會進入此階段:
skipWaiting
跳過 waiting 階段若是 Service Worker 文件的內容有改動,當訪問網站頁面時瀏覽器獲取了新的文件,它會認爲有更新,因而會安裝新的文件並觸發 install 事件。可是此時已經處於激活狀態的舊的 Service Worker 還在運行,新的 Service Worker 完成安裝後會進入 waiting 狀態。直到全部已打開的頁面都關閉,舊的 Service Worker 自動中止,新的 Service Worker 纔會在接下來打開的頁面裏生效。
// Delete outdated caches
self.addEventListener('activate', function (e) {
e.waitUntil(
caches.keys().then(function (keyList) {
// `keyList` contains all cache names under your username.github.io
// filter out ones that has this app prefix to create white list
// 以 app_prefix 開頭這裏會返回0,會被過濾掉
// 因此 cacheWhitelist 只包含當前腳本最新的key或者其餘腳本添加的 cache
var cacheWhitelist = keyList.filter(function (key) {
return key.indexOf(APP_PREFIX);
});
// add current cache name to white list
cacheWhitelist.push(CACHE_NAME);
return Promise.all(keyList.map(function (key, i) {
if (cacheWhitelist.indexOf(key) === -1) {
console.log('deleting cache : ' + keyList[i] )
return caches.delete(keyList[i])
}
}));
}).then(function () {
// 更新客戶端
clients.claim();
})
);
});
複製代碼
clients.claim()
- 使頁面馬上使用新的 Worker。通常狀況下,新的 Service Worker 須要在頁面從新打開後才生效,經過結合 skipWaiting
和此方法的組合拳,能使新 Worker 當即生效。
至此,一個簡單的 Service Worker 流程已經走完,
上面咱們模擬了最簡單的一個頁面使用 Service Worker 是如何操做的,很是簡單快捷,可是仔細想一想,在複雜的場景下事情就沒那麼簡單了,咱們須要考慮幾個問題:
Service-Worker.js
文件的變化,而後進行對應的操做,可是 Service Worker 若是沒變化,它是沒法檢測出被緩存的文件是否有改變而讀取最新的文件的。其實不管是否使用 Service Worker 都會有這個問題,在傳統場景下最多見的解決方案就是hash化文件名;這個方法也能使用在這裏,不過結合第一點,顯然不可能人手維護;sw-precache - 經過掃描指定的靜態文件目錄,計算文件hash而後生成service worker 文件的工具,能有效解決上述兩個問題。打開方式參考官方文檔便可,這裏列一下我使用的配置:
const prefix = '_site';
module.exports = {
staticFileGlobs: [
'!_site/assets/**/**.*',
'!_site/service-worker.js',
prefix + '/**/**.html', // 全部頁面,文章頁面的html(必須包含)
prefix + '/js/*.js', // 全部 js 文件
prefix + '/css/*.css', // 全部 css 文件
prefix + '/images/**/**.*', // 我的用於存放博客相關圖片的文件夾,正常狀況是沒有的
prefix + '/favicon.ico',
prefix + '/**/*.json',
],
stripPrefix: prefix
}
複製代碼
有幾點須要說明:
_site
目錄?_site
目錄。_site/assets
? 由於本地會生成這個目錄,可是通過測試在我發佈到 github 上後,正式環境下並不會生成這個目錄,因此若是不排除此目錄的話 Service Worker 會嘗試去緩存這目錄下的文件,致使加載報錯而後整個 Worker 都失效,這是咱們不肯看到的。實際效果:
在聯網狀態下訪問 www.xlaoyu.info,而後把網絡斷開,在頁面進行操做(非外鏈轉跳),能夠看到在斷網時交互並不受影響。
App Manifest 是一項提高 Web 應用移動端能力的技術。就是讓咱們的網頁能被添加到主屏幕中,擁有和原生應用幾乎一致的表現。
首先,咱們在頁面 head
區域添加引入 manifest 文件的信息:
<!-- APP Manifest -->
<link rel="manifest" href="/manifest.json">
複製代碼
下面是個人 manifest.json
文件配置
{
"scope": "/",
"name": "xlaoyu-blog",
"short_name": "xlaoyu-blog",
"start_url": "/?from=homescreen",
"display": "standalone",
"description": "路漫漫其修遠兮,吾將上下而求索",
"dir": "ltr",
"lang": "cn",
"orientation": "portrait",
"theme_color": "#70B7FD",
"background_color": "#fff",
"icons": [{
"src": "images/icon-48x48.png",
"sizes": "48x48",
"type": "image/png"
}, {
"src": "images/apple-touch-icon-57×57.png",
"sizes": "57x57",
"type": "image/png"
}, {
"src": "images/apple-touch-icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
}, {
"src": "images/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
}]
}
複製代碼
icons 怎麼配置能夠看 這裏。
注意,添加到桌面的 Web 頁面,須要先在聯網狀態下打開一次桌面的版本,才能實現離線訪問,添加後若是一次都沒打開過,斷網以後這個「APP」仍是沒法使用的。
因爲在 Github Pages 中不太可能須要用到推送等功能,這些屬於真正的應用才須要的功能,因此這裏不贅述。
其實在大多數我的blog或者網頁的場景下,是否支持離線訪問,是否能添加到桌面模擬原生應用並無那麼的重要,能留住用戶吸引別人來訪問的核心需求是文章的內容和質量,此次嘗試也只是做爲練手目的。
以上只是 PWA 的其中一小點應用場景,結合這麼多技術 + 非凡的創意必定會催生出更多使人驚喜的特性和功能。也許如今不是前端最好的時代,可是必定是愈來愈精彩的時代!
以上內容若有錯漏,或者有其餘見解,請留言共同探討。
參考文章:
版權聲明:原創文章,如需轉載,請註明出處「本文首發於xlaoyu.info」