1. 程式人生 > 實用技巧 >開發一個漸進式Web應用程式(PWA)前都需要了解什麼?

開發一個漸進式Web應用程式(PWA)前都需要了解什麼?

轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。

原文出處:https://dzone.com/articles/how-to-build-a-progressive-web-app-pwa-with-javasc

自蘋果推出了iPhone應用商店以來,App成為了我們生活中不可或缺的一部分,而對於實體業務也是如此,現在各行業都在推出自己的App,但有沒有人想過這樣一種場景,如果自己的潛在客戶還沒有安裝你的App亦或是即便安裝但因為客戶的手機儲存空間緊張而解除安裝掉了你的App?那有沒有使App更輕量,更易安裝的技術實現呢?答案是“有的”。

漸進式Web應用程式就是為此而生的,它同時具備了Web應用功能和以前只有在原生應用才有的功能的特點,漸進式Web應用程式通過從主螢幕上的圖示啟動,也可以根據推送通知啟動,載入時間幾乎可以忽略不計,而且除了可以線上使用外,也可以打包成可離線使用。

最重要的是,漸進式Web應用程式在手機上建立方式也很簡單,因為它們只是對你網站的增強,當有人在第一次訪問你的網站時,PWA的功能在經過你授權後就會自動為你建立在手機上。

下面, 我們會一起來看看如何來建立一個屬於自己的PWA應用。

要求

要開始學習本教程,您必須安裝以下軟體:

  • 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

通過以下地址開啟應用:http:// localhost:8080

應用的網址

有許多方法可以訪問我的本地主機:為了從遠端訪問釋出在你機器上的8080埠的地址。為此,您可以使用ngrok。請參閱:https://ngrok.com/

使用以下命令安裝ngrok:

npm install -g ngrok

在終端中執行以下命令。該命令為您生成一個可供外部訪問的URL

ngrok http 8080

然後在Chrome中的移動裝置上瀏覽至生成的網址。

PWA需要的技術元件是什麼?

PWA有三個重要的技術元件協調工作,包括:

Manifest清單檔案,Service Worker和在https下執行。

Manifest清單檔案

清單檔案是一個JSON配置檔案,其中包含了PWA的基礎資訊,例如應用的icon,Web應用程式名稱及背景顏色。如果瀏覽器檢測到網站存在PWA清單檔案,Chrome會自動出現“新增到主螢幕”按鈕。如果使用者點選同意,該圖示將被新增到主螢幕,並且將安裝PWA。

建立一個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"
}

告訴瀏覽器你應用的清單

在與index.html檔案相同的級別的目錄中建立Manifest.json檔案。清單檔案建立後,將清單檔案引用連結新增到index.html中。

<link rel=”manifest” href=”/manifest.json”>

Manifest 屬性介紹

Manifest有很多配置屬性,接下來我們會對其中的屬性做一個簡單的介紹

  • nameshort_name:指定Web應用的名稱,short_name是該應用的簡稱,當沒有足夠空間展示應用的name屬性時,系統就會使用short_name 。
  • l display:display屬性指定Web應用的顯示模式,它有四個值可供配置:fullscreen、standalone、minimal-ui和browser,但一般常用的屬性就是fullscreen和standalone。
    • fullscreen:全屏顯示
    • standalone:這種模式下開啟的應用不會出現瀏覽器的位址列,所以因此看起來更像是一個原生應用
    • minimal-uibrowser:和使用瀏覽器訪問區別不大。
  • l orientation:控制Web應用的顯示的方向及禁止手機轉屏。
  • l iconsbackground_color:icon用於指定應用圖示,background_color是應用載入完成前的背景色,通過設定這兩個屬性,可組合成應用的Splash Screen。
  • l theme_color:定義應用程式的預設主題顏色。
  • l description:設定應用的一段描述內容。

以上是pwa 清單檔案屬性的一些說明,我們通過將設定完成的清單檔案並將其放置在與index.html檔案同級的目錄中即可完成清單檔案的新增。

開啟Chrome開發者工具 – Application - Manifest,檢視新增的清單檔案是否載入完成,如果沒有下圖的資訊,我們可以通過重新啟動伺服器npm start來重新載入。

什麼是Service Worker

Service Worker(SW) 是一段JavaScript,它作為瀏覽器和網路伺服器間的代理。Service Worker可以在基於瀏覽器的 web 應用中實現如離線快取、訊息推送、靜默更新等 native 應用常見的功能,以給 web 應用提供更好更豐富的使用體驗。

另外,這個API還允許利用快取來支援離線體驗,從而使開發人員可以完全控制使用者的使用體驗  

Service Worker生命週期

對於Service Worker,基本設定的步驟如下:

  • 首先應註冊SW,如果SW已註冊,瀏覽器會根據於安裝事件自動開始安裝。
  • 安裝SW後,它將收到啟用事件。此啟用事件可用於清理SW早期版本的中使用的資源。

 

實際操作應該首先建立一個和index.html同級,名為sw.js的空檔案。然後再index.html檔案中,新增一個base標籤,如下:

<base href=”/”>

最後,在src/js/app.js中新增以下程式碼註冊SW。此程式碼將在頁面 “載入”過程中被啟用。

你可以開啟Chrome DevTools – Application - Service Worker 中檢查SW是否已經啟用。

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的原因都有什麼並如何解決:

  • 您的應用程式無法在HTTPS下執行。在開發過程中,你可以通過localhost使用SW。但如果將其部署在網站上時,則需要啟用HTTPS。
  • SW的路徑不正確。

沒有勾選Update on reload。  

Service Worker 事件

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

將以下程式碼新增到你的SW中以監聽生命週期事件(安裝和啟用):

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回撥呼叫skipWaiting()函式來觸發activate事件,並告訴Service Worker立即開始工作,而無需等待使用者瀏覽或重新載入頁面。

skipWaiting()函式強制等待中的Service Worker成為活動的Service Worker。self.skipWaiting()函式也可以和self.clients.claim()函式一起使用,以確保對底層Service Worker的更新立即生效。

在這種情況下,self-property 代表視窗物件(即你的瀏覽器視窗)。

新增到主螢幕按鈕

"新增到主螢幕按鈕" 允許使用者在其裝置上安裝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的強大之處在於其攔截HTTP請求的能力。在這一步中,我們使用這個選項來攔截HTTP請求和響應,直接從快取為使用者提供閃電般快速的響應。

Service Worker安裝期間進行預快取

當用戶第一次訪問你的網站時,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陣列。一旦呼叫開啟快取函式(caches.open),你就可以使用cache.addAll()函式來快取陣列中的檔案。通過event.waitUntil()方法使用JavaScript promise來知道安裝需要多長時間以及是否成功。

安裝事件會呼叫self.skipWaiting()直接啟用SW。如果所有檔案都已被成功快取,SW就會被安裝。但如果其中一個檔案無法下載,則安裝步驟將會失敗。在Chrome開發者工具中,你可以檢查快取(在Cache Storage中)是否被URLS_TO_PRECACHE陣列中的靜態檔案填充。

但是,如果你檢視Network選項卡,檔案仍然是通過網路獲取的。原因是雖然快取已經準備就緒了,但我們並沒有從快取中讀取引用資源。所以為了完成這部分工作,我們首先要監聽應用的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()函式檢查傳入的URL是否與當前快取中可能存在的資源匹配。如果匹配,我們就返回該快取資源,但如果該資源不存在於快取中,我們就像正常情況下一樣繼續獲取請求的資源。

在Service Worker安裝並激活後,重新整理頁面並再次檢查網路選項卡。現在,Service Worker將攔截HTTP請求,並從快取中即時載入相應的資源,而不是向伺服器發出網路請求。

現在,如果我們在網路選項卡中設定離線模式,我們的應用也依然能正常訪問。

後臺傳輸

Background Fetch API是SW的後臺功能,它允許使用者在後臺下載大檔案、視訊和音樂等資源。在獲取/傳輸過程中,你的使用者即便關閉標籤,乃至關閉整個瀏覽器,也不會清除傳輸任務。當用戶再次開啟瀏覽器後,傳輸過程將恢復。這個API也可以將傳輸的進度可以顯示給使用者,使用者可以取消或暫停這個過程。

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

以下是如何實現此類後臺傳輸的示例。

在你的index.html檔案中新增ID為“ bgFetchButton”的按鈕

<button id=”bgFetchButton”>Store assets locally</button>

然後,在載入事件處理程式中的app.js中新增用於執行後臺傳輸的程式碼

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);
         }
     }); 
...
});

上面的程式碼在以下條件下開始執行後臺傳輸:

  • 使用者點選ID為bgFetchButton的按鈕
  • SW已註冊

後臺傳輸必須在非同步函式中執行,因為傳輸過程不能阻塞使用者介面。

傳輸完成後放入快取

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’);
     }
   })() 
  );
});

這段程式碼由以下步驟組成:

  • l 當Background Fetch傳輸完成,你的SW將收到Background Fetch成功事件。
  • l 建立並開啟一個與registration.id同名的新快取。
  • l 通過registration.matchAll()獲取所有記錄並遍歷。

最後,通過Promise.all(),執行所有的承諾。  

總結

在本文中我們討論了PWA的基礎組成部分的其中兩部分:Manifest、Service Worker的基礎功能介紹,因為HTTPS之前我們已經有過一些討論了https://www.cnblogs.com/powertoolsteam/p/http2https.html

後面如果大家感興趣,我們可用一起討論一些PWA的高階功能,這些功能可以為你的應用提供呼叫硬體的能力。如果有任何問題,歡迎通過評論區留言告訴我。