動態

詳情 返回 返回

使用Workbox創建PWA應用 - 動態 詳情

前言

最近公司項目迭代逐漸放緩,下班時間逐漸變早,所以本着漸進增加的理念,在下班後,將公司項目進行了一下PWA改造

為何要改造成PWA

  1. 用户需求。我們的用户有許多電腦小白,不想記網址,又不會使用瀏覽器的收藏功能。以前使用的同類軟件都有桌面版,有一種覺得桌面版比網頁版可靠,使用簡單的錯覺,曾多次在釘釘售後羣裏反映,如何將網頁保存至桌面,方便他下次直接在桌面打開
  2. PWA是漸進式的,如果用户的瀏覽器不支持ServiceWorker等構建PWA所需的API,並不會對其造成使用上的影響,並且通過埋點平台獲知,我們用户Chrome瀏覽器數量佔到80%左右
  3. 離線緩存,可安裝,可攔截fetch等功能,對我個人有一定的吸引力,希望學習使用

開始改造

為了快速改造成PWA, 我這裏選擇使用了谷歌推出的PWA工具庫workbox, 並且結合webpack創建serviceWorker文件

安裝依賴

npm install --save-dev workbox-webpack-plugin
npm install --save workbox-core workbox-routing workbox-strategies workbox-precaching workbox-expiration workbox-cacheable-response

workbox-webpack-plugin裏提供了兩種插件,GenerateSW以及InjectManifest

GenerateSW

GenerateSW插件可以通過配置直接編譯生成對應的serviceWorker文件,不需要我們直接編寫serviceWorker文件。使用方式大致如下:

import { InjectManifest } from 'workbox-webpack-plugin';

new GenerateSW({
    skipWaiting: true,
    clientsClaim: true,
    mode: 'development',
    runtimeCaching: [
      {
        urlPattern: /^https?\:\/\/.+?\.alicdn.com\/.+$/,
        handler: 'StaleWhileRevalidate'
      },
    ],
});

通過GenerateSW編譯生成serviceWorker文件雖然簡單,但不夠靈活,所以實際上我使用了另一個InjectManifestPlugin插件

InjectManifest

InjectManifest主要做了兩件事

  1. 將webpack編譯生成的資源文件清單,以變量self.__WB_MANIFEST的形式注入到我們提供的serviceWorker模板文件中
  2. 編譯我們提供的模板文件,生成目標serviceWorker文件

使用方式大致如下:

const { InjectManifest } = require('workbox-webpack-plugin');
new InjectManifest({
    swSrc: path.resolve('src/sw.js'),
    swDest: path.resolve(BUILD_DEST, 'sw.js'),
}),

編寫serviceWorker模板文件

預緩存靜態資源

預緩存會在serviceWork激活後,立即請求並緩存所有預緩存清單中的文件, 之後下載請求同一資源時,會使用緩存優先策略,優先使用已經預緩存的資源

workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);

路由請求緩存

  1. 使用NavigationRoute來緩存html文件
registerRoute(
  new NavigationRoute(
    new NetworkFirst({
      cacheName: 'navigation-cache',
      plugins: [
        new CacheableResponsePlugin({
          statuses: [200]
        }),
        new ExpirationPlugin({
          maxEntries: 300,
          maxAgeSeconds: 7 * 24 * 60 * 60,
        }),
      ],
    }),
  ),
);
  1. 緩存本地靜態資源文件
registerRoute(
  /\.(css|js|png|jpg|jpeg|svg|webp)$/,
  new CacheFirst({
    cacheName: 'static-cache',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [200]
      }),
      new ExpirationPlugin({
        maxEntries: 300,
        maxAgeSeconds: 7 * 24 * 60 * 60,
      }),
    ],
  }),
);
  1. 緩存cdn中的靜態資源文件
registerRoute(
  /^https?\:\/\/.*?\.alicdn.com\/.+?\.(css|js|png|jpg|jpeg|svg|gif|webp)$/,
  new CacheFirst({
    cacheName: 'alicdn-cache',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [200]
      }),
      new ExpirationPlugin({
        maxEntries: 300,
        maxAgeSeconds: 7 * 24 * 60 * 60,
      }),
    ],
  }),
);

這裏有一個需要注意的點,alicdn靜態資源與我司網頁域名不是同域名,存在跨域,當請求靜態資源的時候,會返回不透明響應(opaque response); 當我們使用Cache-First策略緩存不透明響應時,workbox會提示我們不要使用這個策略來緩存不透明響應,因為不透明響應對JavaScript來説是一個黑盒,無法獲取到正確的status code, headers, body, 所以我們緩存中的資源是不可靠的;並且當我們緩存不透明響應時,緩存所佔有的空間遠大於實際資源的大小,容易造成DOMException: Quota exceeded. 所以需要處理下不透明響應的緩存

不透明響應變成透明響應

既然不透明響應會造成問題,那隻要把不透明響應變成透明響應,那就應該沒問題了。
經過查看,我發現alicdn的響應頭會返回access-control-allow-origin: *, 後端是支持cors跨域資源共享的。既然如此,只要當我們請求靜態資源的時候,讓請求走cors應該就可以了。於是,我嘗試在其中一個img標籤中,啓用cors

<img crossorigin="anonymous" />

不透明響應成功變成透明響應。但如果給所有<img /><script /><link />標籤添加crossorigin, 這工作量也太大了。有沒有統一處理的方法呢?有。可以通過攔截fetch請求來統一處理, 在使用workbox的場景下,可以通過設置緩存策略類中fetchOptions來實現

registerRoute(
  /^https?\:\/\/.*?\.alicdn.com\/.+?\.(css|js|png|jpg|jpeg|svg|gif|webp)$/,
  new CacheFirst({
    cacheName: 'alicdn-cache',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [200]
      }),
      new ExpirationPlugin({
        maxEntries: 300,
        maxAgeSeconds: 7 * 24 * 60 * 60,
      }),
    ],
    // 添加如下fetch options
    fetchOptions: {
        mode: 'cors',
        credentials: 'omit',
    },
  }),
);

創建manifest.json文件

通過manifest配置文件,可以指定pwa應用的圖標,初始頁面,背景色,主題色,顯示模式等內容

// manifest.json
{   
    "name": "xxx",
    "short_name": "xxx",
    "icons": [
        {
            "src": "/static/images/favicon@144x144.png",
            "sizes": "144x144",
            "type": "image/png"
        }
    ],
    "start_url": "/index.html",
    "display": "standalone",
    "background_color": "#000",
    "theme_color": "#000"
}
<link rel="manifest" href="/manifest.json">

結語

最後,我們的PWA應用改造就完成了。PWA技術是一系列技術的集合,這裏,我只用到了serviceWorker, manifest,push/notification等沒有涉及到,如果日後有這個必要,再增加相應功能

延伸擴展

什麼是不透明響應(opaque response)

簡單的説,不透明響應就是當我們使用fetch,並且設置no-cors,來請求跨域資源時獲取到的響應

fetch('https://www.baidu.com/img/flexible/logo/pc/result@2.png', {
  mode: 'no-cors'
}).then(response => {
  return console.log(response)
}).catch(error => {
  return console.log(error)
});

打印的結果為

Response {
  body: null
  bodyUsed: false
  headers: {},
  ok: false
  redirected: false
  status: 0
  statusText: ""
  type: "opaque"
  url: ""
}

從Response中,我們可以發現不透明響應

  1. status為0,而非200等http status code
  2. statusText為空
  3. headers也為空
  4. body也為空

總之,我們(JavaScript)獲取不到這個Response中的內容

Add a new 評論

Some HTML is okay.