時間:2023-07-18 12:39:02 | 來源:網(wǎng)站運(yùn)營
時間:2023-07-18 12:39:02 來源:網(wǎng)站運(yùn)營
改造你的網(wǎng)站,變身 PWA:最近有很多關(guān)于 Progressive Web Apps(PWAs)的消息,很多人都在問這是不是(移動)web 的未來。我不想陷入native app 和 PWA 的紛爭,但是有一件事是確定的 --- PWA極大的提升了移動端表現(xiàn),改善了用戶體驗。好消息是開發(fā)一個 PWA 并不難。事實(shí)上,我們可以將現(xiàn)存的網(wǎng)站進(jìn)行改進(jìn),使之成為PWA。這也是我這篇文章要講的 -- 當(dāng)你讀完這篇文章,你可以將你的網(wǎng)站改進(jìn),讓他看起來就像是一個 native web app。他可以離線工作并且擁有自己的主屏圖標(biāo)。
node ./server.js [port]
[port]是可配置的,默認(rèn)為 8888。打開 Chrome 或者其他基于Blink內(nèi)核的瀏覽器,比如 Opera 或者 Vivaldi,然后輸入鏈接 http://localhost:8888/(或者你指定的某個端口)。你也可以打開開發(fā)者工具看一下各個console信息。{ "name" : "PWA Website", "short_name" : "PWA", "description" : "An example PWA website", "start_url" : "/", "display" : "standalone", "orientation" : "any", "background_color" : "#ACE", "theme_color" : "#ACE", "icons": [ { "src" : "/images/logo/logo072.png", "sizes" : "72x72", "type" : "image/png" }, { "src" : "/images/logo/logo152.png", "sizes" : "152x152", "type" : "image/png" }, { "src" : "/images/logo/logo192.png", "sizes" : "192x192", "type" : "image/png" }, { "src" : "/images/logo/logo256.png", "sizes" : "256x256", "type" : "image/png" }, { "src" : "/images/logo/logo512.png", "sizes" : "512x512", "type" : "image/png" } ]}
在頁面的<head>中引入:<link rel="manifest" href="/manifest.json">
manifest 中主要屬性有:if ('serviceWorker' in navigator) { // register service worker navigator.serviceWorker.register('/service-worker.js');}
如果你不需要離線功能,可以簡單的創(chuàng)建一個空的 /service-worker.js文件 —— 用戶會被提示安裝你的 app。// configurationconst version = '1.0.0', CACHE = version + '::PWAsite', offlineURL = '/offline/', installFilesEssential = [ '/', '/manifest.json', '/css/styles.css', '/js/main.js', '/js/offlinepage.js', '/images/logo/logo152.png' ].concat(offlineURL), installFilesDesirable = [ '/favicon.ico', '/images/logo/logo016.png', '/images/hero/power-pv.jpg', '/images/hero/power-lo.jpg', '/images/hero/power-hi.jpg' ];
installStaticFiles()方法添加文件到緩存,這個方法用到了基于 promise的 Cache API。當(dāng)必要的文件都被緩存后才會生成返回值。// install static assetsfunction installStaticFiles() { return caches.open(CACHE) .then(cache => { // cache desirable files cache.addAll(installFilesDesirable); // cache essential files return cache.addAll(installFilesEssential); });}
最后,我們添加install的事件監(jiān)聽函數(shù)。 waitUntil方法確保所有代碼執(zhí)行完畢后,service worker 才會執(zhí)行 install。執(zhí)行 installStaticFiles()方法,然后執(zhí)行 self.skipWaiting()方法使service worker進(jìn)入 active狀態(tài)。// application installationself.addEventListener('install', event => { console.log('service worker: install'); // cache core files event.waitUntil( installStaticFiles() .then(() => self.skipWaiting()) );});
// clear old cachesfunction clearOldCaches() { return caches.keys() .then(keylist => { return Promise.all( keylist .filter(key => key !== CACHE) .map(key => caches.delete(key)) ); });}// application activatedself.addEventListener('activate', event => { console.log('service worker: activate'); // delete old caches event.waitUntil( clearOldCaches() .then(() => self.clients.claim()) );});
注意,最后的self.clients.claim()方法設(shè)置本身為active的service worker。// application fetch network dataself.addEventListener('fetch', event => { // abandon non-GET requests if (event.request.method !== 'GET') return; let url = event.request.url; event.respondWith( caches.open(CACHE) .then(cache => { return cache.match(event.request) .then(response => { if (response) { // return cached file console.log('cache fetch: ' + url); return response; } // make network request return fetch(event.request) .then(newreq => { console.log('network fetch: ' + url); if (newreq.ok) cache.put(event.request, newreq.clone()); return newreq; }) // app is offline .catch(() => offlineAsset(url)); }); }) );});
最后這個offlineAsset(url)方法通過幾個輔助函數(shù)返回一個適當(dāng)?shù)闹担?br>// is image URL?let iExt = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'].map(f => '.' + f);function isImage(url) { return iExt.reduce((ret, ext) => ret || url.endsWith(ext), false);}// return offline assetfunction offlineAsset(url) { if (isImage(url)) { // return image return new Response( '<svg role="img" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg"><title>offline</title><path d="M0 0h400v300H0z" fill="#eee" /><text x="200" y="150" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="50" fill="#ccc">offline</text></svg>', { headers: { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'no-store' }} ); } else { // return page return caches.match(offlineURL); }}
offlineAsset()方法檢查是否是一個圖片請求,如果是,那么返回一個帶有 “offline” 字樣的 SVG。如果不是,返回 offlineURL 頁面。// load script to populate offline page listif (document.getElementById('cachedpagelist') && 'caches' in window) { var scr = document.createElement('script'); scr.src = '/js/offlinepage.js'; scr.async = 1; document.head.appendChild(scr);}
/js/offlinepage.js locates the most recent cache by version name, 取到所有 URL的key的列表,移除所有無用 URL,排序所有的列表并且把他們加到 ID 為cachedpagelist的 DOM 節(jié)點(diǎn)中:// cache nameconst CACHE = '::PWAsite', offlineURL = '/offline/', list = document.getElementById('cachedpagelist');// fetch all cacheswindow.caches.keys() .then(cacheList => { // find caches by and order by most recent cacheList = cacheList .filter(cName => cName.includes(CACHE)) .sort((a, b) => a - b); // open first cache caches.open(cacheList[0]) .then(cache => { // fetch cached pages cache.keys() .then(reqList => { let frag = document.createDocumentFragment(); reqList .map(req => req.url) .filter(req => (req.endsWith('/') || req.endsWith('.html')) && !req.endsWith(offlineURL)) .sort() .forEach(req => { let li = document.createElement('li'), a = li.appendChild(document.createElement('a')); a.setAttribute('href', req); a.textContent = a.pathname; frag.appendChild(li); }); if (list) list.appendChild(frag); }); }) });
Cache-Control: max-age=31536000
頁面,CSS和 script 文件會經(jīng)常變化,所以你應(yīng)該改設(shè)置一個很短的緩存時間比如 24 小時,并在聯(lián)網(wǎng)時與服務(wù)端文件進(jìn)行驗證:Cache-Control: must-revalidate, max-age=86400
譯自 Retrofit Your Website as a Progressive Web App關(guān)鍵詞:改造
客戶&案例
營銷資訊
關(guān)于我們
微信公眾號
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。