逐步強化漸進式網頁應用程式

為新式瀏覽器建構,並像 2003 年一樣逐步強化

發布日期:2020 年 6 月 29 日

2003 年 3 月,Nick FinckSteve Champeon 提出漸進式強化的概念,震驚了網頁設計界。這項網頁設計策略強調先載入核心網頁內容,然後在內容上逐步加入更細緻且技術嚴謹的呈現和功能層。2003 年時,漸進式強化是指使用當時的現代 CSS 功能、不顯眼的 JavaScript,甚至是可縮放向量圖形。2020 年以後的漸進式強化技術,是使用新式瀏覽器功能

採用漸進增強策略,打造未來適用的無障礙網頁設計。 Finck 和 Champeon 原始簡報的標題投影片

新式 JavaScript

說到 JavaScript,最新核心 ES 2015 JavaScript 功能的瀏覽器支援情況相當良好。新標準包含 Promise、模組、類別、樣板字面值、箭頭函式、letconst、預設參數、產生器、解構指派、餘數和擴展、Map/SetWeakMap/WeakSet 等等。所有版本都支援

CanIUse 支援 ES6 功能的支援表格,顯示所有主要瀏覽器的支援情形。
ECMAScript 2015 (ES6) 瀏覽器支援表。(來源)

非同步函式是 ES 2017 的功能,也是我個人最喜歡的功能之一,可在所有主要瀏覽器中使用。asyncawait 關鍵字可讓您以更簡潔的風格編寫非同步、以 Promise 為基礎的行為,不必明確設定 Promise 鏈。

CanIUse 支援表格,顯示所有主要瀏覽器對非同步函式的支援情況。
非同步函式瀏覽器支援表格。(來源)

甚至連 ES 2020 語言新增的選用鏈結空值合併等功能,也很快就獲得支援。就核心 JavaScript 功能而言,目前已是最佳狀態。

例如:

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
Windows XP 經典的綠草背景圖片。
就核心 JavaScript 功能而言,一切都沒問題。 (Microsoft 產品螢幕截圖,已取得授權。)

範例應用程式:Fugu Greetings

GitHub這個應用程式的名稱是向 Project Fugu 🐡 致敬,這項計畫旨在賦予網頁 Android、iOS 和電腦應用程式的所有功能。如要進一步瞭解這項計畫,請前往到達網頁

Fugu Greetings 是一款繪圖應用程式,可讓你製作虛擬賀卡,並傳送給親友。這個範例會說明PWA 的核心概念。這項服務穩定可靠,且完全支援離線使用,因此即使沒有網路,你還是可以繼續使用。此外,這項服務也可安裝到裝置主畫面,並以獨立應用程式的形式與作業系統無縫整合。

Fugu Greetings PWA,內含類似 PWA 社群標誌的繪圖。
Fugu Greetings 範例應用程式。

漸進增強

說明完這點,接下來要談談漸進式強化。 MDN 網路文件詞彙表定義這個概念如下:

漸進式強化是一種設計理念,盡可能為最多使用者提供基本的重要內容和功能,同時僅為可執行所有必要程式碼的最新瀏覽器使用者,提供最佳體驗。

功能偵測通常用於判斷瀏覽器是否能處理較新的功能,而 polyfill 則通常用於透過 JavaScript 新增缺少的函式。

[…]

漸進式強化是實用的技術,可讓網頁開發人員專注於開發最佳網站,同時確保這些網站能在多個不明的使用者代理程式上運作。優雅降級與漸進式增強相關,但兩者並不相同,且通常被視為與漸進式增強相反。事實上,這兩種方法都有效,而且通常可以互補。

MDN 貢獻者

從頭開始製作每張卡片可能相當麻煩。 因此,何不提供匯入圖片的功能,讓使用者從圖片開始創作? 如果採用傳統做法,您會使用 <input type=file> 元素來達成這個目的。首先,您會建立元素、將 type 設為 'file',並將 MIME 類型新增至 accept 屬性,然後以程式輔助方式「點選」該元素並監聽變更。選取圖片後,系統會直接將圖片匯入畫布。

const importImage = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/*';
    input.addEventListener('change', () => {
      resolve(input.files[0]);
    });
    input.click();
  });
};

如果提供匯入功能,可能也需要提供匯出功能,讓使用者將電子賀卡儲存在本機。 傳統的檔案儲存方式是建立錨點連結,並使用 download 屬性和 Blob 網址做為 href。您也可以透過程式輔助方式「點選」該元素來觸發下載作業,並為了避免記憶體流失,希望不要忘記撤銷 Blob 物件網址。

const exportImage = async (blob) => {
  const a = document.createElement('a');
  a.download = 'fugu-greeting.png';
  a.href = URL.createObjectURL(blob);
  a.addEventListener('click', (e) => {
    setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
  });
  a.click();
};

但請稍等一下。在心理上,你並非「下載」而是「儲存」了卡片。瀏覽器不會顯示「儲存」對話方塊,讓您選擇檔案的儲存位置,而是直接下載賀卡,且未經使用者互動,並直接將賀卡放入「下載」資料夾。這可不妙。

如果能有更好的方法呢?如果可以開啟本機檔案、編輯檔案,然後將修改內容儲存為新檔案,或儲存回原本開啟的檔案,會怎麼樣呢?答案是肯定的。File System Access API 可讓您開啟及建立檔案和目錄,以及修改及儲存檔案和目錄。

那麼,如何進行 API 的功能偵測? File System Access API 會公開新的 window.chooseFileSystemEntries() 方法。 因此,我需要根據這個方法是否可用,有條件地載入不同的匯入和匯出模組。

const loadImportAndExport = () => {
  if ('chooseFileSystemEntries' in window) {
    Promise.all([
      import('./import_image.mjs'),
      import('./export_image.mjs'),
    ]);
  } else {
    Promise.all([
      import('./import_image_legacy.mjs'),
      import('./export_image_legacy.mjs'),
    ]);
  }
};

不過在深入探討 File System Access API 詳細資料之前,請先快速瞭解這裡的漸進式強化模式。在不支援 File System Access API 的瀏覽器上,我會載入舊版指令碼。

Safari 網頁檢查器顯示載入的舊版檔案。
Firefox 開發人員工具顯示載入的舊版檔案。

不過,在支援 API 的 Chrome 瀏覽器中,系統只會載入新指令碼。這項功能可透過動態 import() 屬性優雅地實現,所有新式瀏覽器都支援這項屬性。如先前所說,現在的環境相當有利。

Chrome 開發人員工具顯示載入的現代化檔案。
Chrome 開發人員工具的「網路」分頁。

File System Access API

現在我已解決這個問題,接下來要根據 File System Access API 看看實際的實作方式。 如要匯入圖片,我會呼叫 window.chooseFileSystemEntries(),並傳遞 accepts 屬性,指出我需要圖片檔案。系統支援副檔名和 MIME 類型。 這會產生檔案控制代碼,我可藉此呼叫 getFile() 取得實際檔案。

const importImage = async () => {
  try {
    const handle = await window.chooseFileSystemEntries({
      accepts: [
        {
          description: 'Image files',
          mimeTypes: ['image/*'],
          extensions: ['jpg', 'jpeg', 'png', 'webp', 'svg'],
        },
      ],
    });
    return handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

匯出圖片的程序幾乎相同,但這次我需要將 'save-file' 類型的參數傳遞至 chooseFileSystemEntries() 方法。從這裡我會取得檔案儲存對話方塊。 開啟檔案後,由於 'open-file' 是預設值,因此不需要這麼做。 我設定 accepts 參數的方式與先前類似,但這次僅限 PNG 圖片。 我再次取得檔案控制代碼,但這次不是取得檔案,而是呼叫 createWritable() 建立可寫入的串流。接著,我將 Blob (即我的賀卡圖片) 寫入檔案。 最後,我關閉可寫入的串流。

任何情況都可能導致失敗:磁碟空間不足、發生寫入或讀取錯誤,或是使用者取消檔案對話方塊。因此我一律會將呼叫包裝在 try...catch 陳述式中。

const exportImage = async (blob) => {
  try {
    const handle = await window.chooseFileSystemEntries({
      type: 'save-file',
      accepts: [
        {
          description: 'Image file',
          extensions: ['png'],
          mimeTypes: ['image/png'],
        },
      ],
    });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

使用 File System Access API 進行漸進式強化後,我就可以像以前一樣開啟檔案。匯入的檔案會直接繪製在畫布上。 我可以進行編輯,最後透過實際的儲存對話方塊儲存編輯內容, 並選擇檔案名稱和儲存位置。 現在檔案已可永久保存。

Fugu Greetings 應用程式,顯示開啟檔案的對話方塊。
檔案開啟對話方塊。
現在 Fugu Greetings 應用程式會顯示匯入的圖片。
匯入的圖片。
修改圖片後的 Fugu Greetings 應用程式。
將修改後的圖片儲存到新檔案。

Web Share 和 Web Share Target API

attempt-right

除了永久儲存,我也許想分享我的賀卡。 Web Share APIWeb Share Target API 就能做到這一點。行動裝置和近期的電腦作業系統都內建分享機制。

舉例來說,使用者在我的網誌上點選「分享文章」時,系統會觸發 macOS 上的 Safari 分享功能表。你可以使用 macOS 的「訊息」應用程式,與好友分享文章連結。

為達成此目的,我呼叫 navigator.share(),並在物件中傳遞選用的 titletexturl。但如果我想附加圖片呢?Web Share API 第 1 級目前尚未支援這項功能。 好消息是,Web Share Level 2 已新增檔案共用功能。

try {
  await navigator.share({
    title: 'Check out this article:',
    text: `"${document.title}" by @tomayac:`,
    url: document.querySelector('link[rel=canonical]').href,
  });
} catch (err) {
  console.warn(err.name, err.message);
}

接著說明如何搭配 Fugu Greeting Card 應用程式使用這項功能。 首先,我需要準備 data 物件,其中包含由一個 Blob 組成的 files 陣列,然後是 titletext。接著,根據最佳做法,我使用新的 navigator.canShare() 方法,這個方法會執行名稱所指的動作:判斷瀏覽器是否能在技術上分享我嘗試分享的 data 物件。如果 navigator.canShare() 告訴我資料可以共用,我就可以像之前一樣呼叫 navigator.share()。因為任何事都可能出錯,我再次使用 try...catch 區塊。

const share = async (title, text, blob) => {
  const data = {
    files: [
      new File([blob], 'fugu-greeting.png', {
        type: blob.type,
      }),
    ],
    title: title,
    text: text,
  };
  try {
    if (!(navigator.canShare(data))) {
      throw new Error("Can't share data.", data);
    }
    await navigator.share(data);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

與先前一樣,我使用漸進式增強功能。 如果 navigator 物件上同時存在 'share''canShare',我才會繼續使用動態 import() 載入 share.mjs。在僅符合其中一項條件的瀏覽器 (例如行動版 Safari) 上,我不會載入這項功能。

const loadShare = () => {
  if ('share' in navigator && 'canShare' in navigator) {
    import('./share.mjs');
  }
};

在 Fugu Greetings 中,如果我在 Android 上的 Chrome 等支援的瀏覽器中輕觸「分享」按鈕,系統會開啟內建的分享功能表。 舉例來說,我可以選擇 Gmail,然後電子郵件撰寫小工具就會彈出,並附上圖片。

作業系統層級的分享功能表,顯示可分享圖片的各種應用程式。
選擇要將檔案分享至哪個應用程式。
Gmail 的電子郵件撰寫小工具,附有圖片。
檔案會附加到 Gmail 撰寫工具中的新電子郵件。

Contact Picker API

接下來,我想談談聯絡人,也就是裝置的通訊錄或聯絡人管理應用程式。撰寫賀卡時,正確寫出對方的名字可能並不容易。舉例來說,我的朋友 Sergey 偏好以西里爾字母拼寫自己的名字。我使用德文 QWERTZ 鍵盤,完全不知道如何輸入對方的名字。聯絡人挑選器 API 可解決這個問題。 由於我已將朋友儲存在手機的聯絡人應用程式中,因此可以使用 Contacts Picker API,從網頁存取聯絡人。

首先,我需要指定要存取的資源清單。 在本例中,我只需要名稱,但如果是其他用途,我可能會需要電話號碼、電子郵件地址、個人資料相片或實際地址。接著,我會設定 options 物件,並將 multiple 設為 true,這樣就能選取多個項目。 最後,我可以呼叫 navigator.contacts.select(),傳回使用者所選聯絡人的理想屬性。

const getContacts = async () => {
  const properties = ['name'];
  const options = { multiple: true };
  try {
    return await navigator.contacts.select(properties, options);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

到目前為止,您可能已瞭解模式:只有在實際支援 API 時,我才會載入檔案。

if ('contacts' in navigator) {
  import('./contacts.mjs');
}

在 Fugu Greeting 中,當我輕觸「Contacts」按鈕並選取兩位好友「Сергей Михайлович Брин」和「劳伦斯·爱德华·"拉里"·佩奇」時,您會發現聯絡人挑選器只會顯示他們的姓名,不會顯示電子郵件地址或其他資訊 (例如電話號碼)。然後將他們的名字畫在我的賀卡上。

聯絡人挑選器顯示通訊錄中兩位聯絡人的姓名。
從地址簿中選取兩個名稱,並使用聯絡人挑選器。
先前選取的兩位聯絡人姓名,會顯示在卡片上。
這兩個名字就會畫在卡片上。

非同步剪貼簿 API

接下來是複製及貼上。 身為軟體開發人員,我們最喜歡的操作之一就是複製及貼上。 身為卡片作者,有時我也會想這麼做。我可能想將圖片貼到正在製作的賀卡中,或是複製賀卡,以便在其他地方繼續編輯。Async Clipboard API 支援文字和圖片。我將逐步說明如何為 Fugu Greetings 應用程式新增複製及貼上支援功能。

如要將內容複製到系統剪貼簿,我必須寫入該剪貼簿。 navigator.clipboard.write() 方法會將剪貼簿項目陣列做為參數。每個剪貼簿項目基本上都是一個物件,其中 Blob 是值,Blob 的類型則是鍵。

const copy = async (blob) => {
  try {
    await navigator.clipboard.write([
      new ClipboardItem({
        [blob.type]: blob,
      }),
    ]);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

如要貼上,我需要呼叫 navigator.clipboard.read(),然後對取得的剪貼簿項目進行迴圈。這是因為剪貼簿中可能有多個剪貼簿項目,且這些項目以不同形式呈現。每個剪貼簿項目都有 types 欄位,可告知我可用資源的 MIME 類型。我呼叫剪貼簿項目的 getType() 方法,並傳遞先前取得的 MIME 類型。

const paste = async () => {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      try {
        for (const type of clipboardItem.types) {
          const blob = await clipboardItem.getType(type);
          return blob;
        }
      } catch (err) {
        console.error(err.name, err.message);
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
};

現在幾乎不必多說,我只會在支援的瀏覽器上執行這項操作。

if ('clipboard' in navigator && 'write' in navigator.clipboard) {
  import('./clipboard.mjs');
}

那麼,實際運作方式為何?我在 macOS 的「預覽」應用程式中開啟圖片,並將圖片複製到剪貼簿。我點選「貼上」後,Fugu Greetings 應用程式會詢問我是否要允許應用程式查看剪貼簿中的文字和圖片。

Fugu Greetings 應用程式顯示剪貼簿權限提示。
剪貼簿權限提示。

最後,接受權限後,圖片就會貼到應用程式中。 反向操作也適用。 請將卡片複製到剪貼簿。 接著開啟「預覽」並依序點選「檔案」和「從剪貼簿新增」, 系統就會將卡片貼到新的未命名圖片中。

macOS「預覽」應用程式,顯示剛貼上的未命名圖片。
貼到 macOS「預覽」應用程式的圖片。

Badging API

另一個實用的 API 是 Badging API。 Fugu Greetings 是可安裝的 PWA,因此當然有應用程式圖示,使用者可以將圖示放在應用程式 Dock 或主畫面上。在 Fugu Greetings 中使用 API 做為筆觸計數器,是很有趣的示範方式。我已新增事件監聽器,每當發生 pointerdown 事件時,就會遞增筆觸計數器,然後設定更新後的圖示徽章。畫布每次清除時,計數器都會重設,徽章也會移除。

let strokes = 0;

canvas.addEventListener('pointerdown', () => {
  navigator.setAppBadge(++strokes);
});

clearButton.addEventListener('click', () => {
  strokes = 0;
  navigator.setAppBadge(strokes);
});

這項功能是漸進式強化功能,因此載入邏輯與平常相同。

if ('setAppBadge' in navigator) {
  import('./badge.mjs');
}

在本例中,我畫出從一到七的數字,每個數字都用一筆畫完。圖示上的徽章計數器現在顯示為 7。

在卡片上以一筆畫出 1 到 7 的數字。
使用七筆筆觸繪製 1 到 7 的數字。
Fugu Greetings 應用程式的徽章圖示,顯示數字 7。
應用程式圖示上的筆劃計數標記。

Periodic Background Sync API

想每天都以新內容展開一天嗎?Fugu Greetings 應用程式的實用功能之一,就是每天早上提供新的背景圖片,讓您製作獨一無二的問候卡。應用程式會使用 Periodic Background Sync API 達成此目的。

第一步是在服務工作人員註冊中註冊定期同步事件。這個服務會監聽名為 'image-of-the-day' 的同步標記,且間隔時間至少為一天,因此使用者每 24 小時就能取得新的背景圖片。

const registerPeriodicBackgroundSync = async () => {
  const registration = await navigator.serviceWorker.ready;
  try {
    registration.periodicSync.register('image-of-the-day-sync', {
      // An interval of one day.
      minInterval: 24 * 60 * 60 * 1000,
    });
  } catch (err) {
    console.error(err.name, err.message);
  }
};

第二步是在服務工作人員中監聽 periodicsync 事件。 如果事件標記是 'image-of-the-day',也就是先前註冊的標記,系統會使用 getImageOfTheDay() 函式擷取當天的圖片,並將結果傳播至所有用戶端,方便用戶端更新畫布和快取。

self.addEventListener('periodicsync', (syncEvent) => {
  if (syncEvent.tag === 'image-of-the-day-sync') {
    syncEvent.waitUntil(
      (async () => {
        const blob = await getImageOfTheDay();
        const clients = await self.clients.matchAll();
        clients.forEach((client) => {
          client.postMessage({
            image: blob,
          });
        });
      })()
    );
  }
});

同樣地,這項功能是漸進式強化,因此只有在瀏覽器支援 API 時,才會載入程式碼。這適用於用戶端程式碼和服務工作人員程式碼。如果使用不支援的瀏覽器,系統不會載入這兩者。 請注意,在 Service Worker 中,我使用傳統的 importScripts(),而不是動態 import() (Service Worker 環境尚不支援)。

// In the client:
const registration = await navigator.serviceWorker.ready;
if (registration && 'periodicSync' in registration) {
  import('./periodic_background_sync.mjs');
}
// In the service worker:
if ('periodicSync' in self.registration) {
  importScripts('./image_of_the_day.mjs');
}

在 Fugu Greetings 中,按下「Wallpaper」按鈕會顯示當天的問候卡片圖片,這些圖片每天都會透過 Periodic Background Sync API 更新。

按下「桌布」按鈕會顯示當日圖片。

Notification Triggers API

有時即使靈感泉湧,還是需要一點幫助才能完成製作好的卡片。這項功能是由 Notification Triggers API 啟用。 使用者可以輸入時間,系統會在該時間提醒完成電子賀卡。 屆時我會收到通知,得知卡片已可領取。

提示輸入目標時間後,應用程式會使用 showTrigger 排定通知。這可以是TimestampTrigger先前選取的目標日期。 提醒通知會在裝置上觸發,不需要網路或伺服器端。

const targetDate = promptTargetDate();
if (targetDate) {
  const registration = await navigator.serviceWorker.ready;
  registration.showNotification('Reminder', {
    tag: 'reminder',
    body: "It's time to finish your greeting card!",
    showTrigger: new TimestampTrigger(targetDate),
  });
}

如同我目前為止展示的所有內容,這項功能是漸進式強化,因此程式碼只會以條件方式載入。

if ('Notification' in window && 'showTrigger' in Notification.prototype) {
  import('./notification_triggers.mjs');
}

在 Fugu Greetings 中勾選「提醒」核取方塊後,系統會提示我何時要完成製作問候卡片。

Fugu Greetings 應用程式會顯示提示,詢問使用者何時要收到提醒,完成製作卡片。
排定本機通知,提醒完成製作卡片。

在 Fugu Greetings 中觸發排定的通知時,系統會像顯示其他通知一樣顯示通知,但如我先前所述,這不需要網路連線。

觸發的通知會顯示在 macOS 通知中心。

Wake Lock API

我也想加入 Wake Lock API。 有時你只需要盯著螢幕夠久,靈感就會降臨。最糟的情況就是螢幕關閉。Wake Lock API 可避免這種情況發生。

第一步是使用 navigator.wakelock.request method() 取得喚醒鎖定。我將字串 'screen' 傳遞給這個方法,以取得螢幕 Wake Lock。 接著,我會新增事件監聽器,以便在喚醒鎖定解除時收到通知。 舉例來說,分頁的顯示設定變更時,就會發生這種情況。如果發生這種情況,當分頁再次顯示時,我可以重新取得喚醒鎖定。

let wakeLock = null;
const requestWakeLock = async () => {
  wakeLock = await navigator.wakeLock.request('screen');
  wakeLock.addEventListener('release', () => {
    console.log('Wake Lock was released');
  });
  console.log('Wake Lock is active');
};

const handleVisibilityChange = () => {
  if (wakeLock !== null && document.visibilityState === 'visible') {
    requestWakeLock();
  }
};

document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);

是,這是漸進式強化功能,因此我只需要在瀏覽器支援 API 時載入即可。

if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
  import('./wake_lock.mjs');
}

在 Fugu Greetings 中,有一個「Insomnia」核取方塊,勾選後可讓螢幕保持喚醒狀態。

如果勾選「失眠」核取方塊,螢幕會保持喚醒狀態。
「Insomnia」核取方塊可讓應用程式保持喚醒狀態。

Idle Detection API

有時即使盯著螢幕好幾個小時,還是毫無用處,完全想不出該如何處理這張卡片。應用程式可透過 Idle Detection API 偵測使用者閒置時間。 如果使用者閒置過久,應用程式會重設為初始狀態並清除畫布。這個 API 受到通知權限的限制,因為許多閒置偵測的實際用途都與通知相關,例如只傳送通知給使用者正在使用的裝置。

確認已授予通知權限後,我會例項化閒置偵測器。我註冊的事件監聽器會監聽閒置狀態的變化,包括使用者和螢幕狀態。使用者可以處於活動或閒置狀態,螢幕可以解鎖或鎖定。如果使用者處於閒置狀態,畫布就會清除。 我為閒置偵測器設定 60 秒的門檻。

const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
  const userState = idleDetector.userState;
  const screenState = idleDetector.screenState;
  console.log(`Idle change: ${userState}, ${screenState}.`);
  if (userState === 'idle') {
    clearCanvas();
  }
});

await idleDetector.start({
  threshold: 60000,
  signal,
});

與往常一樣,我只會在瀏覽器支援時載入這段程式碼。

if ('IdleDetector' in window) {
  import('./idle_detection.mjs');
}

在 Fugu Greetings 應用程式中,如果勾選「Ephemeral」核取方塊,且使用者閒置時間過長,畫布就會清除。

使用者閒置過久後,Fugu Greetings 應用程式的畫布已清除。
如果勾選「暫時性」核取方塊,且使用者閒置時間過長,畫布就會清除。

Closing

呼,真是驚險。一個範例應用程式就包含這麼多 API。請注意,如果瀏覽器不支援某項功能,我絕不會讓使用者支付下載費用。採用漸進增強原則後,我確保只載入相關程式碼。 由於 HTTP/2 的要求成本不高,因此這個模式應該適用於許多應用程式,但如果是非常大型的應用程式,您可能需要考慮使用 Bundler。

Chrome 開發人員工具的「網路」分頁,只會顯示瀏覽器支援的程式碼檔案要求。

由於並非所有平台都支援所有功能,應用程式在不同瀏覽器上可能會略有不同,但核心功能一律存在,並會根據特定瀏覽器的功能逐步強化。即使是同一個瀏覽器,這些功能也可能會有所不同,取決於應用程式是以安裝的應用程式形式執行,還是以瀏覽器分頁的形式執行。

在 Android 版 Chrome 上執行的「Fugu Greetings」,顯示許多可用功能。
在桌機版 Safari 上執行的「Fugu Greetings」,顯示可用的功能較少。
在電腦版 Chrome 上執行的「Fugu Greetings」,顯示許多可用功能。

您可以在 GitHub 上 Fork Fugu

Chromium 團隊正努力改善進階 Fugu API,在建構應用程式時套用漸進式強化功能,可確保每個人都能獲得優質的基礎體驗,但使用支援更多 Web 平台 API 的瀏覽器時,體驗會更上一層樓。期待看到您在應用程式中運用漸進式強化功能。

特別銘謝

感謝 Christian LiebelHemanth HM 對 Fugu Greetings 的貢獻。本文由 Joe MedleyKayce Basques 審查。Jake Archibald 協助我瞭解服務工作人員環境中的動態 import() 情況。