前几天重构之后, Lighthouse 中有一个评分让我念念不忘: Progressive web App .
PWA 不算一个新话题了, 所以概念性的东西和 API 我就不多做介绍, 下面简单介绍一个无干预更新的缓存方案, 整体代码量在一百行以内, 如果你也想在不 "大动干戈" 的情况下对站点或者 Web App 进行性能提升的话, 可以了解一下.
浏览器客户端代码
说到 PWA , 我们能直接想到的, 无非是 增强缓存 和 推送能力 . 而这两个能力, 都是 ServiceWorker API 实现的.(添加桌面图标这个需求, 我不需要, 就不介绍了, 感兴趣可以自行搜索)
我在之前的重构文章中有简单聊过访客数据, 其中有一部分访客使用的客户端并不支持 Service Worker , 所以在使用它的时候, 需要使用 能力探测 的方式引入, 比如:
- try {
- ('serviceWorker' in navigator) && navigator.serviceWorker.register('/sw.js');
- } catch (error) {
- console.error(error);
- }
当然, 构建压缩之后, 你得到的结果应该是 drop console 的最简代码. 不过, 如果你不确定你的运行环境是否有问题, 可以使用下面带有调试日志的版本.
- try {
- if ('serviceWorker' in navigator) {
- navigator.serviceWorker.register('/sw.min.js').then(function(registration) {
- console.log('ServiceWorker registration successful with scope:', registration.scope);
- }).catch(function(err) {
- console.error('ServiceWorker registration failed:', err);
- });
- }
- } catch (error) {
- console.error(error);
- }
ServiceWorker 客户端代码
上面介绍浏览器客户端代码的时候, 有引入一个外部脚本依赖 sw.JS .
在分享代码之前, 有做过 PWA 相关项目或者了解的同学, 可能或多或少会在 增强缓存 这个地方被坑到, 比如: 缓存无法更新, 缓存内容过多无法写入.
缓存无法更新有一个简单有效的解决方案: 定时切换缓存使用的 Store . 如果再引入 当前时间 这个因素, 可以保障缓存使用的 Store 不存在资源争抢的问题.
结合站点内容特点, 配合定期清理缓存的脚本, 可以将缓存数量控制在一定的范围之内.
这里提供一个小思路, 对服务端资源进行二次缓存的时候, 可以设定一个最大缓存时间的策略, 而这里有两个方案:
TTL TTL
我个人选择第二个方案, 牺牲一定的缓存复用, 但是有效降低资源之间的版本管理的复杂度.
而需要缓存的资源一般分为两类:
短时间缓存: 页面或者页面片段
相对长时间缓存: 图片等媒体资源, 或者有一定跨页面通用能力的脚本和样式资源
这里以 10 分钟 (调试模式单位替换为秒) 为一个时间段, 为短时间缓存的资源进行缓存. 设定星期数为其他资源进行缓存周期.
这里我们不必过分处理 install 和 active 事件, 只需要统一在 fetch 事件中进行缓存更新和清理即可, 比如下面这样:
- function weekId() {
- var now = (new Date());
- var dt = new Date(now.getFullYear(), 0, 1);
- return Math.ceil((((now - dt) / 86400000) + dt.getDay() + 1) / 7);
- }
- var isDevMode = false;
- var CACHE_LONG_TTL = 'WEEK_' + weekId();
- var CACHE_TINY_TTL = 'TINY_';
- function cleanCache(whiteList) {
- return caches.keys().then(function(buckets) {
- return Promise.all(buckets.filter(function(bucket) {
- return whiteList.indexOf(bucket) === -1;
- }).map(function(bucket) {
- return caches.delete(bucket);
- }));
- });
- }
- self.addEventListener('activate', function(event) {
- event.waitUntil(cleanCache([CACHE_LONG_TTL]));
- });
- self.addEventListener('fetch', function(event) {
- var url = new URL(event.request.url);
- if (url.protocol.toLowerCase() !== 'https:') return;
- var now = new Date;
- var CACHE_INTERVAL = Math.floor((isDevMode ? now.getSeconds() : now.getMinutes()) / 10);
- var CACHE_KEY = CACHE_TINY_TTL + CACHE_INTERVAL;
- if (url.pathname.endsWith('/') ||
- url.pathname.endsWith('.html') ||
- url.pathname.endsWith('.md') ||
- url.pathname.endsWith('/feed/') ||
- url.pathname.endsWith('/feed') ||
- url.pathname.endsWith('sw.js')) {
- cleanCache([CACHE_LONG_TTL, CACHE_KEY]);
- event.respondWith(caches.open(CACHE_KEY).then(function(cache) {
- return cache.match(url).then(function(response) {
- if (response) return response;
- return fetch(event.request).then(function(response) {
- cache.put(event.request, response.clone());
- return response;
- });
- });
- }));
- } else {
- event.respondWith(caches.open(CACHE_LONG_TTL).then(function(cache) {
- return cache.match(event.request).then(function(response) {
- if (response) return response;
- return fetch(event.request).then(function(response) {
- cache.put(event.request, response.clone());
- return response;
- });
- });
- }));
- }
- });
如果你的访客有不少是 Chrome v50 和 Firefox v46 以下的客户端, 那么你可能还需要引入下面的 Polyfill :
当然, 在引入的时候, 建议还是进行压缩, 进一步提高页面性能(哪怕它是独立于客户端脚本另外的异步进程).
当一切都完成之后, 如果顺利的话, 你的网站的将支持有限时间 (本文是 10min) 的离线访问, 以及重复访问时更好的响应能力.
另外, 你也不需要担心 sw.JS 被缓存后, 这个站点变成 "完全离线". 因为 sw.JS 按照规范可以保证它的最长存在生命周期是 1 天, 也就是说, 未来你的策略更新最多延迟 1 天.
最后
再次跑分, 发现 Lighthouse 已经全面绿色评价了, 不过前文中我提过, 我不太需要把网站变为纯粹的 PWA 应用, 没有去设定 mainfest , 所以, 依旧有 4 点改进建议.
用户不能够 "安装" Web App.
没有定义启动屏幕(仿客户端体验).
没有定义顶栏的主题色.
viewport 没有优化.
这些后面再说吧.
- EOF
来源: http://www.tuicool.com/articles/fqY7naq