前端性能优化,Vue或Webpack项目集成 Service Worker 开启离线缓存

2,111 阅读6分钟

前端资源加载和性能优化一直是个令人头疼的问题,下面是我开发这些年的一些优化总结。注意,以下优化大部分只针对工程化的前端项目,即通过命令运行和打包的项目,比如通过vue-cli创建的vue项目。

资源加载优化,从两个方面优化:减少项目服务器带宽压力、减少用户加载资源体积
1打包项目时压缩jscss媒体等资源文件
2部分js和css可改换成CDN服务器资源,使用CDN服务加载资源可以减轻项目服务器带宽压力,项目服务器带宽不足时优化效果非常明显,切记不可盲目把资源更换为CDN资源如果第三方依赖在引入时如果是按需加载,改为使用CDN引入,则会加载所有代码,从而增加用户端需要加载的资源体积,当用户端网络不佳时,反而降低用户使用体验
例如 Element-UI,如果你大量使用了其中的组卷,则可以使用CDN引入;但如果仅仅使用了其中几个组件,以按需加载的方式引入 Element-UI,使用户查看网站时加载的资源尽可能的少,用户端加载速度反而更快
3部分插件的js或css只在个别页面用到,可以考虑异步加载这些资源,并对href进行判断,哪些页面使用了此资源就切换为同步加载,防止刷新页面报错
4使用 Service Worker 对项目静态资源进行离线加载,用户第二次进入页面直接从本地读取资源,秒开项目
代码层面优化
1、除首页外,其它vue页面和组件尽量使用懒加载方式引入。
2、小图标打包成base64到项目js文件中,减少网络请求次数,如果图标多处使用记得放到公共css文件中,减少打包后的项目体积。
3、首页引用的方法尽量放到一起,避免加载很多js小文件。
4、代码、组件能复用的,在不影响项目可读性的情况向,尽量提取成为公共方法、组件,减少项目体积。
5、防抖可节流,防止方法被频繁触发。
其它方面优化
1、升级服务器 HTTP/2,提升接口请求速度。
2、有些接口数据不经常变动,可以考虑缓存到本地,避免重复请求,提高页面响应速度。
接下来要说的就是要重点“离线缓存”,可以极大提升页面二次开启速度
离线缓存我最开始接触到的是 manifest 方案,使用的时候需要配置缓存文件清单,在html页面引入即可,当时还用node写过一个自动生成清单文件的脚本,不过现在这个方案已经被放弃了。
目前的离线缓存方案是通过 service worker 使用 fatch 拦截全局请求,并给出自己的响应,这给了完全控制项目文件的可能。包括预加载,按需加载,离线缓存,主动更新。
使用离线缓存后,网页二次开启速度基本上能控制在500ms以内,使用F5刷新都感觉不出来加载过程。

根据这些特性,我写了一个webpack plugin,直接把 Service Worker 离线缓存及更新功能集成到项目中。使用请查看文档

generate-service-worker-webpack-plugin

这个是新版本,兼容所有前端工程化项目 Vue、React、Angle 等

web-sw-pack

离线缓存方案 Service Worker + fetch

当我们使用 Service Worker 之后,用户打开浏览器进入页面,sw服务将一直在后台挂起,强制刷新不会关闭sw服务,关闭页面或浏览器,再次进入页面sw服务将自动运行。这就导致一个非常严重的问题,即应用更新,如果盲目的使用了 Service Worker 开启缓存,那么后面即使服务端去掉了这部分代码,用户端sw也在运行,需要用户进入控制台进行关闭和清除,这将导致非常严重的后果。所以在开启 Service Worker 服务之前,一定要有结束 sw 服务的控制逻辑,方便后续更新。

再讲讲使用 Service Worker 开启离线缓存并更新的思路,还有项目如果去掉打算停用Service Worker,不再进行离线缓存,用户端关闭sw服务的方式。

在使用自定义 generate-service-worker-webpack-plugin 打包之后,默认情况下,项目根目录下将生成 sw.js、sw.hash 两个文件,并向 html 页面文件中注入sw控制代码,三个文件都会保存有一个相同的 hash 值,并承担各自的功能。

image.png

index.html 负责注册Service Worker,并在每次进入页面时与sw.js通讯,并调用其内的检查更新方法。
sw.js      控制文件缓存,检查更新,并判断Service Worker是否需要销毁。
sw.hash    内含版本 hash 值,以供用户端访问判断是否版本更新。

触发检查更新方式有两种

1、由index.html触发。如果服务端项目弃用了此插件,新加载的index.html会无法触发,此时由sw.js控制。
2、由sw.js触发。拦截 fetch 时判断并检查更新,为防止重复检查更新会有一定的限制处理。

a、进入页面 index.html 先发送 postMessage 到 sw.js,sw.js 收到 message 立即触发检查更新,并刷新有效时间;(有效时间内不会再次检查更新)
b、若 sw.js 未收到 message,则在发生 fetch 请求时判断 clientId 是否变动,如果变动则触发检查更新,并刷新有效时间;(用户每次刷新窗口,clientId 都会改变)

判断是否更新

1、请求 sw.hash 比较返回结果与当前 sw.js 中的是否相同
    相同:向页面发起 postMessage,页面接收把当前页面的 hash 发送回 sw.js,再比较 hash 是否相同,
        如果相同,则离线缓存为最新版本,可用。
        如果不同则立即清除缓存并注销 sw.js;
        如果向页面发起 postMessage,页面在500ms内没有响应 sw.js,可能用户已弃用 sw.js,立即清除缓存并注销 sw.js;
        不同:说明项目已经更新,清除所有缓存,注销 sw.js,刷新页面;

2、如果 请求 sw.hash 失败
    判断用户端网络是否正常,
    如果网络正常,则说明此项目可能已经放弃使用 sw.js,立即清除缓存并注销 sw.js;
    如果网络断开连接,则直接使用离线缓存即可;

这样就解决了停用 Service Worker 和应用更新的问题,项目源码放到github上了,使用有什么问题可以找我交流。

blcyzycc/generate-service-worker-webpack-plugin: webpack打包自动生成并添加 service worker 文件的插件 (github.com)

目前已经在公司项目多次使用,一切正常,更新ok,通过上面那些优化方式,成功把公司一个老项目优化到秒开,原来需要五六秒。

大家如果想体验 Service Worker 带来的离线优化,可以先试试这个npm包尝尝鲜,再根据自己的需求修改。

由于 webpack 兼容问题,插件改为node命令模式了,新的npm包:web-sw-pack

项目案例 www.hygnqs.top ,一年一百块的新人服务器1m带宽,通过优化项目可秒开。

image.png