应用程序缓存,ServiceWorker,PWA 和 开发支持 PWA 的 Sencha 应用

简介

2014年,W3C 公布了 Service Worker 的相关草案,2015 年,Chrome 支持是 Service Worker。

为了在离线和不稳定的网络环境中提供更好的体验,网站可以使用 ServiceWorker 离线缓存主要资源。 ServiceWorker 是与特定网站关联的 js 脚本,是一种 JS 工作线程,ServiceWorker 可以拦截前端网络请求,访问缓存中的文件或数据,提供给前端使用。

当有人第一次打开网站时,浏览器会安装网站的 ServiceWorker 脚本,ServiceWorker 会缓存网站的主要资源(App Shell, 应用程序的外壳)。在后续访问中,ServiceWorker 可以直接从缓存加载资源。如果用户完全脱机,ServiceWorker 仍可以加载站点,并根据需要显示缓存数据或脱机消息。

ServiceWorker 可以很好地使用 App Shell 策略,其中应用程序的主UI视图和逻辑(App Shell)被缓存,以便可以从缓存中提供它们。

有关 ServiceWorker 的背景知识,问题和调试技巧,请参阅 ServiceWorker 简介

PS: 以前浏览器有一个 HTML5 Application Cache(HTML5 应用程序缓存)功能,需要缓存的文件存在一个 *.appcache 文件中。现在这个功能在最新 safari 中已经废弃了,chrome 也出现警告,以后也会废弃。

先决条件

必须在 HTTPS 下或者 localhost 开发时,才可以使用 ServiceWorker。

注册一个 ServiceWorker 脚本

// Register service worker if supported.
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js');
}

可以再 Chrome 开发人员工具下看到注册的 ServiceWorker
Service Workers

service-worker.js 怎么来的

GoogleChromeLabs 提供了一个 sw-precache 模块(node 模块)。

此模块主要功能是,你提供一个需要缓存的静态文件列表给它,它会自动给你生成一个 service-worker.js 文件,因此您无需编写 ServiceWorker 代码。

sw-precache 还可以缓存运行时的请求,支持配置使用缓存的优先级(networkFirst/cacheFirst/fastest/cacheOnly/networkOnly)。

一般会将 sw-precache 集成到应用程序的构建过程中。

查看浏览器的应用程序缓存

可以在 Chrome 开发人员工具的 Application 标签页看到有一个 Cache Storage,里面就是当前站点的应用程序缓存。
通过 window.caches 可以对进行增删改查。
Cache Storage
可以删除缓存
PS: 该标签页下的另一个 Application Cache 就是 HTML5 Application Cache 的缓存

Progressive Web Apps

Progressive Web App, 简称 PWA,翻译成 渐进式网络应用程序,由 Google 在 2015 年提出的一个概念,被称为 Google 的小程序。

PWA 是提升 Web App 的体验的一种新方法,能给用户原生应用的体验。

PWA 的主要特点包括下面三点:

  • 可靠 - 即使在不稳定的网络环境下,也能瞬间加载并展现
  • 体验 - 快速响应,并且有平滑的动画响应用户的操作
  • 粘性 - 像设备上的原生应用,具有沉浸式的用户体验,用户可以添加到桌面

PWA 本身强调渐进式,并不要求一次性达到安全、性能和体验上的所有要求,开发者可以通过 PWA Checklist 查看现有的特征。

PWA 的实现,就是利用的 ServiceWorker。为了保证首屏的加载,PWA 可以优先保证 App Shell 的渲染,做到和 Native App 一样的体验,App Shell 是 PWA 界面展现所需的最小资源。

下面几幅图演示了将 PWA 应用添加到桌面的过程:
打开支持 PWA 的网站
菜单 - 添加到主屏幕
点击添加
桌面图标
我的 Web 应用因为没有设置图标,所以是个默认的灰色图标。

Sencha 开启 PWA

官方文档 Building Progressive Web Apps with Cmd

1、先把 HTML5 Application Cache (AppCache) 功能关闭

app.json 中的 production 块中关闭 appCachecache

PS: cache 是 sencha 缓存 app.jsapp.css 的方式(对于这2个文件,Sencha 没有用 AppCache)

"production": {
    "output": {
        "appCache": {
            "enable": false, //关闭
            "path": "cache.appcache"
        }
    },
    "loader": {
        "cache": "${build.timestamp}"
    },
    "cache": {
        "enable": false //关闭
    }
},

2、在 app.json 中添加以下 progressive 配置块

"progressive": {
    "manifest": {
        "name": "My App",
        "short_name": "My App",
        "icons": [{ // 添加 PWA 应用到桌面的图标
            "src": "resources/icon-small.png",
            "sizes": "96x96"
        }, {
            "src": "resources/icon-medium.png",
            "sizes": "192x192"
        }, {
            "src": "resources/icon-large.png",
            "sizes": "256x256"
        }],
        "theme_color": "#054059", // 主题颜色
        "background_color": "#054059",
        "display": "standalone",
        "orientation": "portrait",
        "start_url": "/index.html"
    }
}

上述代码块配置了 PWA 的一些属性。

此时,如果执行 sencha app build,Sencha Cmd 会将输出目录下的所有文件都加入 service-worker.js 中,也就是缓存所有文件。

这样会导致,如果文件很多的话(比如图片多,或者用了百度 ueditor、codemirror 等文件很多的第三方库等),用户初次打开 web 应用,会消耗很多流量,甚至界面等很久才显示。其实我们只希望缓存应用的关键资源即可。

解决 sencha build PWA 应用时缓存所有文件的问题

先如下配置

"progressive": {
    "manifest": {
        "name": "My App",
        // 省略...
        "start_url": "/index.html"
    },
    "serviceWorker": {
        "staticFileGlobs": [ // 缓存静态资源文件列表
            "index.html",
            "app.json",
            "app.js",
            "resources/MyApp-all.css",
            "resources/css/*.*", // 支持通配符
            "resources/libs/**/*.{html,js,css,json}"
        ],
        "runtimeCaching": [{ // 运行时缓存
            "urlPattern": "resources\\/ux\\/(.*)", // url 正则
            "handler": "fastest" // 可取值 networkFirst/cacheFirst/fastest/cacheOnly/networkOnly
        }]
    }
}

PS: serviceWorker 块下的配置遵循 GoogleChromeLabs 的 sw-precache 使用方式。

此时 build 后,staticFileGlobs 指定的静态文件列表并没有其效果,Sencha Cmd 还是将输出目录下的所有文件都加入了 service-worker.js 中。

我研究了半天,甚至反编译了 Sencha Cmd 的核心 sencha.jar,找到了它处理 staticFileGlobs 的逻辑,发现,不管有没有指定 staticFileGlobs,它都会将所有文件加入 service-worker.js

好在 Sencha Cmd 有一部分逻辑是 nodejs 模块,生成 service-worker.js 的逻辑在
Sencha Cmd 安装目录\6.7.0.64\js\node_modules\ext-precache 中。于是我的切入点就在这个 node 模块上。

首先,在 app.jsonprogressive.serviceWorker.staticFileGlobs 静态文件列表中增加一个 "/*truncate*/",变成下面这样:

"progressive": {
    "manifest": {
        // 省略...
    },
    "serviceWorker": {
        "staticFileGlobs": [ // 缓存静态资源文件列表
            "index.html",
            // 省略其它静态资源文件...
            "resources/libs/**/*.{html,js,css,json}"
            "/*truncate*/" // 添加一行这个
        ],
        // 省略...
    }
}

修改 Sencha Cmd 安装目录\6.7.0.64\js\node_modules\ext-precache\cli.js

// 省略以上...
config = JSON.parse(fs.readFileSync(config, 'utf8'));

// 添加代码 开始
var files = config.staticFileGlobs;
if(files) {
    config.staticFileGlobs = [];
    for(let file of files) {
        if(file == '/*truncate*/') {
            break;
        }
        else {
            config.staticFileGlobs.push(file);
        }
    }
}
// 添加代码 结束

extPrecache({ buildDir, config })
    .then(sw => console.log(`created: ${sw}`))
    .catch(e => {
        console.log(`error creating ${buildSW}`, e);
        process.exit(1);
    });

上面的逻辑是去掉 Sencha Cmd 自作主张给我们加上的所有静态文件。

这样,sencha app build 之后的 service-worker.js 就只包含我们想要 precache 的资源文件了。

检测到 Web 应用有更新时提示用户刷新

默认情况,服务器 Web 应用文件更新后,用户访问的内容会出现还是旧的情况,因为 ServiceWorker 需要在后台下载新版资源。等到用户刷新界面再次访问,才会浏览到新的内容。

我们可以让 ServiceWorker 下载完新资源后,提示用户是否刷新界面。
Application.js 中加入下面的代码:

launch: function (profile) {
    // 省略以上代码...
    this.initProductionBuild(); // 加上这句
},

// 加入以下代码
initProductionBuild() {
    const me = this;
    if (location.protocol !== 'file:' &&
        navigator.serviceWorker &&
        navigator.serviceWorker.getRegistration) {
        navigator.serviceWorker.getRegistration().then(reg => {
            reg.onupdatefound = function () {
                // The updatefound event implies that reg.installing is set; see https://w3c.github.io/ServiceWorker/#service-worker-registration-updatefound-event
                const installingWorker = reg.installing;
                installingWorker.onstatechange = function () {
                    if (installingWorker.state === 'installed' &&
                        navigator.serviceWorker.controller) {
                        // At this point, the old content will have been purged and the fresh content will
                        // have been added to the cache.
                        // It's the perfect time to display a "New content is available; please refresh."
                        // 需要删除旧版的 runtimeCaching 资源
                        window.caches.keys()
                            .then(arr => {
                                var key = arr.find(x => x.indexOf('toolbox-cache') >= 0);
                                if(key) {
                                    return window.caches.delete(key);
                                }
                            })
                            .then(r => {
                                me.onPwaUpdate();
                            })
                            .catch(err => {
                                console.error('delete cache error: ', err);
                                me.onPwaUpdate();
                            });
                    }
                };
            };
        });
    }
},

onPwaUpdate() {
    Ext.Msg.confirm('程序升级', '本应用程序已发布新版本, 是否重新加载?',
        function (choice) {
            if (choice === 'yes') {
                window.location.reload();
            }
        }
    );
    // 如果你不想提示,可以直接 window.location.reload();
}

资源加载过程

第一次打开时
第一次打开
后续打开则是 service worker 从缓存中取得:
后续则从缓存中取得

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神秘_博士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值