XYZ變身戰術,改造你的網站,變身 PWA

 2023-10-15 阅读 27 评论 0

摘要:最近有很多關于 Progressive Web Apps(PWAs)的消息,很多人都在問這是不是(移動)web 的未來。我不想陷入native app 和 PWA 的紛爭,但是有一件事是確定的 --- PWA極大的提升了移動端表現,改善了用戶體驗。 好消息是開發一個

最近有很多關于 Progressive Web Apps(PWAs)的消息,很多人都在問這是不是(移動)web 的未來。我不想陷入native app 和 PWA 的紛爭,但是有一件事是確定的 --- PWA極大的提升了移動端表現,改善了用戶體驗。

好消息是開發一個 PWA 并不難。事實上,我們可以將現存的網站進行改進,使之成為PWA。這也是我這篇文章要講的 -- 當你讀完這篇文章,你可以將你的網站改進,讓他看起來就像是一個 native web app。他可以離線工作并且擁有自己的主屏圖標。

Progressive Web Apps 是什么?

Progressive Web Apps (下文以“PWAs”代指) 是一個令人興奮的前端技術的革新。PWAs綜合了一系列技術使你的 web app表現得就像是 native mobile app。相比于純 web 解決方案和純 native 解決方案,PWAs對于開發者和用戶有以下優點:

  1. 你只需要基于開放的 W3C 標準的 web 開發技術來開發一個app。不需要多客戶端開發。

  2. 用戶可以在安裝前就體驗你的 app。

  3. 不需要通過 AppStore 下載 app。app 會自動升級不需要用戶升級。

  4. 用戶會受到‘安裝’的提示,點擊安裝會增加一個圖標到用戶首屏。

  5. 被打開時,PWA 會展示一個有吸引力的閃屏。

  6. chrome 提供了可選選項,可以使 PWA 得到全屏體驗。

  7. 必要的文件會被本地緩存,因此會比標準的web app 響應更快(也許也會比native app響應快)

  8. 安裝及其輕量 -- 或許會有幾百 kb 的緩存數據。

  9. 網站的數據傳輸必須是 https 連接。

  10. PWAs 可以離線工作,并且在網絡恢復時可以同步最新數據。

現在還處在 PWA 的早期,但已經有 很多成功案例 。

PWA 技術目前被 Firefox,Chrome 和其他基于Blink內核的瀏覽器支持。微軟正在努力在Edge瀏覽器上實現。Apple沒有動作 although there are promising comments in the WebKit five-year plan。幸運的是,瀏覽器支持對于 PWA 似乎不太重要...

PWAs 是漸進增強的

你的app仍然可以運行在不支持 PWA 技術的瀏覽器里。用戶不能離線訪問,不過其他功能都像原來一樣沒有影響。綜合利弊得失,沒有理由不把你的 app 改進為 PWA。

不只是 Apps

Google 引領了 PWA 的一系列動作,所以大多數教程都在說如何從零開始構建一個基于 Chrome,native-looking mobile app。然而并不是只有特殊的單頁應用可以PWA化,也不需要一定遵循 material interface design guidelines。大多數網站都可以在數小時內實現 PWA 化。這包括你的 WordPress站點或者靜態站點。

示例代碼

示例代碼可以在github.com/sitepoint-e…找到。

代碼提供了一個簡單的四個頁面的網站。其中包含一些圖片,一個樣式表和一個main javascript 文件。這個網站可以運行在所有現代瀏覽器上(IE10+)。如果瀏覽器支持 PWA 技術,當離線時用戶可以瀏覽他們之前看過的頁面。

運行代碼前,確保 Node.js 已經安裝,然后再命令行里啟動服務:

node ./server.js [port]復制代碼

[port]是可配置的,默認為 8888。打開 Chrome 或者其他基于Blink內核的瀏覽器,比如 Opera 或者 Vivaldi,然后輸入鏈接 http://localhost:8888/(或者你指定的某個端口)。你也可以打開開發者工具看一下各個console信息。

瀏覽主頁,或者其他頁面,然后用以下任一方法使頁面離線:

  1. 按下 Cmd/Ctrl + C ,停止 node 服務器,或者

  2. 在開發者工具的 Network 或者 Application - Service Workers 欄里點擊 offline 選項。

重新瀏覽任意之前瀏覽過的頁面,它們仍然可以瀏覽到。瀏覽一個之前沒有看過的頁面,你會看到一個專門的離線頁面,標識“you’re offline”,還有一個你可以瀏覽的頁面列表:

連接手機

你也可以通過 USB 連接你的安卓手機來預覽示例網頁。在開發者工具中打開 Remote devices 菜單。

在左邊選擇 Settings ,點擊 Add Rule 輸入 8888 端口。你可以在你的手機上打開Chrome,打開 http://localhost:8888/。

你可以點擊瀏覽器菜單里的 “Add to Home screen”。瀏覽幾個頁面,瀏覽器會提醒你去安裝。這兩種方式都可以創建一個新的圖標在你的主屏上。瀏覽幾個頁面后關掉Chrome,斷開設備連接。你依然可以打開 PWA Website app -- 你會看到一個啟動頁,并且可以離線訪問之前你訪問過的頁面。

將你的網站改進為一個 Progressive Web App 總共有三個必要步驟:

第一步:開啟 HTTPS

由于一些顯而易見的原因,PWAs 需要 HTTPS 連接。

HTTPS 在示例代碼中并不是必須的,因為 Chrome 允許使用 localhost 或者任何 127.x.x.x 的地址來測試。你也可以在 HTTP 連接下測試你的 PWA,你需要使用 Chrome ,并且輸入以下命令行參數:

  • --user-data-dir
  • --unsafety-treat-insecure-origin-as-secure

第二步:創建一個 Web App Manifest

manifest 文件提供了一些我們網站的信息,例如 name,description 和需要在主屏使用的圖標的圖片,啟動屏的圖片等。

manifest文件是一個 JSON 格式的文件,位于你項目的根目錄。它必須用Content-Type: application/manifest+json 或者 Content-Type: application/json這樣的 HTTP 頭來請求。這個文件可以被命名為任何名字,在示例代碼中他被命名為 /manifest.json:

{"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 中主要屬性有:

  • name —— 網頁顯示給用戶的完整名稱
  • short_name —— 當空間不足以顯示全名時的網站縮寫名稱
  • description —— 關于網站的詳細描述
  • start_url —— 網頁的初始 相對 URL(比如 /
  • scope —— 導航范圍。比如,/app/的scope就限制 app 在這個文件夾里。
  • background-color —— 啟動屏和瀏覽器的背景顏色
  • theme_color —— 網站的主題顏色,一般都與背景顏色相同,它可以影響網站的顯示
  • orientation —— 首選的顯示方向:any, natural, landscape, landscape-primary, landscape-secondary, portrait, portrait-primary, 和 portrait-secondary
  • display —— 首選的顯示方式:fullscreen, standalone(看起來像是native app),minimal-ui(有簡化的瀏覽器控制選項) 和 browser(常規的瀏覽器 tab)
  • icons —— 定義了 src URL, sizestype的圖片對象數組。

MDN提供了完整的manifest屬性列表:Web App Manifest properties

在開發者工具中的 Application tab 左邊有 Manifest 選項,你可以驗證你的 manifest JSON 文件,并提供了 “Add to homescreen”。

第三步:創建一個 Service Worker

Service Worker 是攔截和響應你的網絡請求的編程接口。這是一個位于你根目錄的一個單獨的 javascript 文件。

你的 js 文件(在示例代碼中是 /js/main.js)可以檢查是否支持 Service Worker,并且注冊:

if ('serviceWorker' in navigator) {// register service workernavigator.serviceWorker.register('/service-worker.js');}復制代碼

如果你不需要離線功能,可以簡單的創建一個空的 /service-worker.js文件 —— 用戶會被提示安裝你的 app。

Service Worker 很復雜,你可以修改示例代碼來達到自己的目的。這是一個標準的 web worker,瀏覽器用一個單獨的線程來下載和執行它。它沒有調用 DOM 和其他頁面 api 的能力,但他可以攔截網絡請求,包括頁面切換,靜態資源下載,ajax請求所引起的網絡請求。

這就是需要 HTTPS 的最主要的原因。想象一下第三方代碼可以攔截來自其他網站的 service worker, 將是一個災難。

service worker 主要有三個事件: installactivatefetch

Install 事件

這個事件在app被安裝時觸發。它經常用來緩存必要的文件。緩存通過 Cache API來實現。

首先,我們來構造幾個變量:

  1. 緩存名稱(CACHE)和版本號(version)。你的應用可以有多個緩存但是只能引用一個。我們設置了版本號,這樣當我們有重大更新時,我們可以更新緩存,而忽略舊的緩存。

  2. 一個離線頁面的URL(offlineURL)。當離線時用戶試圖訪問之前未緩存的頁面時,這個頁面會呈現給用戶。

  3. 一個擁有離線功能的頁面必要文件的數組(installFilesEssential)。這個數組應該包含靜態資源,比如 CSS 和 JavaScript 文件,但我也把主頁面(/)和圖標文件寫進去了。如果主頁面可以多個URL訪問,你應該把他們都寫進去,比如//index.html。注意,offlineURL也要被寫入這個數組。

  4. 可選的,描述文件數組(installFilesDesirable)。這些文件都很會被下載,但如果下載失敗不會中止安裝。

// configuration
constversion = '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。當必要的文件都被緩存后才會生成返回值。

// install static assets
function installStaticFiles() {return caches.open(CACHE).then(cache => {// cache desirable filescache.addAll(installFilesDesirable);// cache essential filesreturn cache.addAll(installFilesEssential);});}復制代碼

最后,我們添加install的事件監聽函數。 waitUntil方法確保所有代碼執行完畢后,service worker 才會執行 install。執行 installStaticFiles()方法,然后執行 self.skipWaiting()方法使service worker進入 active狀態。

// application installation
self.addEventListener('install', event => {console.log('service worker: install');// cache core filesevent.waitUntil(installStaticFiles().then(() => self.skipWaiting()));});復制代碼

Activate 事件

當 install完成后, service worker 進入active狀態,這個事件立刻執行。你可能不需要實現這個事件監聽,但是示例代碼在這里刪除老舊的無用緩存文件:

// clear old caches
function clearOldCaches() {return caches.keys().then(keylist => {return Promise.all(keylist.filter(key => key !== CACHE).map(key => caches.delete(key)));});}// application activated
self.addEventListener('activate', event => {console.log('service worker: activate');// delete old cachesevent.waitUntil(clearOldCaches().then(() => self.clients.claim()));});復制代碼

注意,最后的self.clients.claim()方法設置本身為active的service worker。

Fetch 事件

當有網絡請求時這個事件被觸發。它調用respondWith()方法來劫持 GET 請求并返回:

  1. 緩存中的一個靜態資源。

  2. 如果 #1 失敗了,就用 Fetch API(這與 service worker 的fetch 事件沒關系)去網絡請求這個資源。然后將這個資源加入緩存。

  3. 如果 #1 和 #2 都失敗了,那就返回一個適當的值。
// application fetch network data
self.addEventListener('fetch', event => {// abandon non-GET requestsif (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 fileconsole.log('cache fetch: ' + url);return response;}// make network requestreturn 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)方法通過幾個輔助函數返回一個適當的值:

// 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 asset
function offlineAsset(url) {if (isImage(url)) {// return imagereturn 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 pagereturn caches.match(offlineURL);}}復制代碼

offlineAsset()方法檢查是否是一個圖片請求,如果是,那么返回一個帶有 “offline” 字樣的 SVG。如果不是,返回 offlineURL 頁面。

開發者工具提供了查看 Service Worker 相關信息的選項:

在開發者工具的 Cache Storage 選項列出了所有當前域內的緩存和所包含的靜態文件。當緩存更新的時候,你可以點擊左下角的刷新按鈕來更新緩存:

不出意料, Clear storage 選項可以刪除你的 service worker 和緩存:

再來一步 - 第四步:創建一個可用的離線頁面

離線頁面可以是一個靜態頁面,來說明當前用戶請求不可用。然而,我們也可以在這個頁面上列出可以訪問的頁面鏈接。

main.js中我們可以使用 Cache API 。然而API 使用promises,在不支持的瀏覽器中會引起所有javascript運行阻塞。為了避免這種情況,我們在加載另一個 /js/offlinepage.js 文件之前必須檢查離線文件列表和是否支持 Cache API 。

// load script to populate offline page list
if (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 節點中:

// cache name
constCACHE = '::PWAsite',offlineURL = '/offline/',list = document.getElementById('cachedpagelist');// fetch all caches
window.caches.keys().then(cacheList => {// find caches by and order by most recentcacheList = cacheList.filter(cName => cName.includes(CACHE)).sort((a, b) => a - b);// open first cachecaches.open(cacheList[0]).then(cache => {// fetch cached pagescache.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 => {letli = 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);});})});復制代碼

開發工具

如果你覺得 javascript 調試困難,那么 service worker 也不會很好。Chrome的開發者工具的 Application 提供了一系列調試工具。

你應該打開 隱身窗口 來測試你的 app,這樣在你關閉這個窗口之后緩存文件就不會保存下來。

最后,Lighthouse extension for Chrome 提供了很多改進 PWA 的有用信息。

PWA 陷阱

有幾點需要注意:

URL 隱藏

我們的示例代碼隱藏了 URL 欄,我不推薦這種做法,除非你有一個單 url 應用,比如一個游戲。對于多數網站,manifest 選項 display: minimal-ui 或者 display: browser是最好的選擇。

緩存太多

你可以緩存你網站的所有頁面和所有靜態文件。這對于一個小網站是可行的,但這對于上千個頁面的大型網站實際嗎?沒有人會對你網站的所有內容都感興趣,而設備的內存容量將是一個限制。即使你像示例代碼一樣只緩存訪問過的頁面和文件,緩存大小也會增長的很快。

也許你需要注意:

  • 只緩存重要的頁面,類似主頁,和最近的文章。
  • 不要緩存圖片,視頻和其他大型文件
  • 經常刪除舊的緩存文件
  • 提供一個緩存按鈕給用戶,讓用戶決定是否緩存

緩存刷新

在示例代碼中,用戶在請求網絡前先檢查該文件是否緩存。如果緩存,就使用緩存文件。這在離線情況下很棒,但也意味著在聯網情況下,用戶得到的可能不是最新數據。

靜態文件,類似于圖片和視頻等,不會經常改變的資源,做長時間緩存沒有很大的問題。你可以在HTTP 頭里設置 Cache-Control 來緩存文件使其緩存時間為一年(31,536,000 seconds):

Cache-Control: max-age=31536000復制代碼

頁面,CSS和 script 文件會經常變化,所以你應該改設置一個很短的緩存時間比如 24 小時,并在聯網時與服務端文件進行驗證:

Cache-Control: must-revalidate, max-age=86400復制代碼

譯自 Retrofit Your Website as a Progressive Web App

轉載于:https://juejin.im/post/58dba0b3a22b9d00647db6c3

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/146282.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息