网站添加 pwa 支持

也许首先有必要说明一下 pwa 是什么。

pwa 并不是某个特定的技术,而是一个 web 应用程序理念。其是通过一系列 web 技术,渐进增强式地提升 web 应用程序的体验,让 web 程序具备原生应用的特性。

pwa 式的 web 应用程序可以被安装、可以被添加到桌面、可以缓存资源以节省流量、可以推送消息等。

pwa 是一个激动人心的理念,但是其普及也困难重重。原因就不说了,很多。不过,对于个人网站而言,pwa 还是很值得尝试的,反正也没啥坏处。

我使用 pwa 的目的很简单——让网站可以添加到桌面。相比于直接发送一个快捷方式到桌面(某些手机会支持,但并不是所有),pwa 更具扩展性,体验更好,也是尝试新技术的机会。

前言结束,可以开始正文了。首先,要说明一下,vue 之类的框架在使用 webpack 构建应用的情况下是有现成的插件或者模板的。我并没有使用现成的插件或者模板,因为是既有项目改造(用的是 vue-cli2,不是 vue-cli3),所以还是自己组织结构和相关文件和配置比较好。

第一步。在项目根目录新建 pwa 文件夹,用来存放需要的文件。我这里只有简单丁四个文件:

pwa/
├── logo192.png
├── logo512.png
├── manifest.json
└── sw.js

两张图片,一张是添加到桌面使用,另一张是启动屏使用。manifets.json 是 pwa 配置文件(ps:命名随意)。

// manifest.json
{
  "name": "品词轩",
  "short_name": "品词轩",
  "description": "品词轩-在古典中寻得一方宁静",
  "icons": [{
      "src": "./logo192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "./logo512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "background_color": "#339999",
  "theme_color": "#339999",
  "display": "standalone",
  "orientation": "portrait",
  "start_url": ".",
  "scope": "/"
}

内容比较简单,基本一看就知道各字段是做什么的。这个文件的其他内容可以在 MDN 上找到。

// sw.js
var cacheStorageKey = 'poem-0.0.2'
var cacheList = []
self.addEventListener('install', e => { // install 事件,它发生在浏览器安装并注册 Service Worker 时
  // e.waitUtil 用于在安装成功之前执行一些预装逻辑
  e.waitUntil(
    caches.open(cacheStorageKey)
      .then(cache => cache.addAll(cacheList))
      .then(() => self.skipWaiting())
  )
})
self.addEventListener('fetch', function(e) {
  e.respondWith(
    caches.match(e.request).then(function(response) {
      if (response != null) {
        return response
      }
      return fetch(e.request.url)
    })
  )
})
self.addEventListener('activated', function(e) {
  e.waitUntil(
    // 获取所有cache名称
    caches.keys().then(cacheNames => {
      return Promise.all(
        // 获取所有不同于当前版本名称cache下的内容
        cacheNames.filter(cacheNames => {
          return cacheNames !== cacheStorageKey
        }).map(cacheNames => {
          return caches.delete(cacheNames)
        })
      )
    }).then(() => {
      return self.clients.claim()
    })
  )
})

sw.js 的内容基本是从网上拷贝的。这个文件会运行在 service worker 线程中,并且会拦截各种网络请求。文件中监听的 fetch 事件回调就是对请求进行的处理。我这里会尝试从缓存获取,如果缓存没有才发起请求。在一开始,我将跟路径“/”添加到了 cacheList 中。其结果是首页被缓存,导致后续发版无法更新。还好当时是本地测试,在浏览器开发工具 application 面板清除缓存并将 cacheList 置空后,问题迎刃而解。当然,这种做法并不好,实际上可以通过检测缓存版本解决。不过,算了,我只是需要将网站添加到桌面。


2021-12-24 更新:

对于只需要添加桌面图标,不需要缓存内容,我找到了更简单的版本。

const cacheName = 'kodcloud';
const staticAssets = [];
self.addEventListener('install', async e => {
});
self.addEventListener('activate', e => {
    self.clients.claim();
});
self.addEventListener('fetch', async e => {
});

如代码所示,这是可道云的代码。


四个文件准备好后,再修改一下 /index.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="manifest" href="manifest.json">
    <title>品词轩</title>
    <!-- <link rel="icon" href="favicon.ico" type="image/x-icon" /> -->
    <script>
      window.v = window.version = '4.0.1'
      // 检测浏览器是否支持SW
      if(navigator.serviceWorker != null){
        navigator.serviceWorker.register('sw.js',{scope: '/'})
        .then(function(registartion){
          //注册成功
          console.log('支持sw:',registartion.scope)
        }).catch(function (err){
          console.log('fail')
        });
      }
    </script>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

第 6 行的 <link /> 标签引用 manifest.json 文件,下面的脚本部分则是注册serviceWorker。

最后,配置 webpack,确保打包的时候会将 pwa 里的文件打包到根目录(因为作用域是根目录)。

// /build/webpack.dev.conf.js
……
new CopyWebpackPlugin([
      ……
      {
        from: path.resolve(__dirname, '../pwa'),
        to: config.build.assetsPath,
        ignore: ['.*']
      }
    ])
……

至此,大功告成。