也许首先有必要说明一下 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: ['.*'] } ]) ……
至此,大功告成。