Open xinglie opened 3 years ago
本文不讨论manifest,servicework等技术,但本文给出的方案不与manifest,servicework等冲突,可以一起使用,从多维度给web加速
开发阶段我们通常根据自己的需要,把前端文件进行拆分,以进行更好的逻辑组合。 上线时,我们拆分的这些文件,有些打包工具会默认合并,比如webpack,而某些打包工具不会合并,比如magix-composer
我们的这些资源js、css等文件,到底是合并还是拆分需要具体到应用场景,不过从整体看,pc更适合拆分的方式,而移动更适合合并的方式,本文也不再深入讨论为什么pc适合拆分,移动适合合并。
因个人长期从事pc端的开发,因此接下来我们讨论的场景基于pc端,资源不合并的情况下。
在pc端因为资源不合并,我们从任意一个入口进入时,即打开某个页面,比如首页、报表或列表等,可以最小化的加载所需要资源,让页面展示更快。
当我们从某个页面进入时,需要先加载资源,再请求接口,该过程会有一个稍长的loading过程
当我们从某个页面切换到其它页面时,仍然是加载所需要资源,再进行接口请求,最后才能渲染显示页面,该过程的loading由于某些资源在之前页面可能加载过,会比之前稍快一些,但仍旧较长,我们能否做到点击页面后就能立即显示,避免loading?
我们默认并不会对所有资源进行打包,或分功能打包,开发时什么样,上线后仍然保持什么样的拆分粒度。 但我们可以使用离线编译工具对整个项目中的文件进行扫描处理,生成一份资源清单,比如这样
//app/files.js export default [ "~mp/app/components/xy-btn", "~mp/app/dragdrop", "~mp/app/inner", "~mp/app/keyboard", "~mp/app/test/z-inner", "~mp/galleries/mx-table/index" ]
因为该文件位置固定,我们在页面空闲时,可以获取到该文件。然后使用加载器,逐一加载清单中的资源,这样在用户切换到未来某个页面时,该页面所需要的资源已经加载好了,只需要等待接口数据的请求即可。
目前大多数的web都是使用http的方式与后端交互,如果需要实时性更高的方案,可以参考我的另一篇:[基于websocket的前后端分离](https://github.com/xinglie/xinglie.github.io/issues/71) 对于http接口,我们不能缓存,绝大多数接口都需要每次请求后端来拿到最新的数据。但我们可以换另一种方式进行缓存
核心思路为:普通的请求过程是 前端展示loading->请求后端接口->获取到数据->隐藏loading-> 展示界面 升级思路为:无缓存的情况 前端请求接口-> 数据层无缓存-> 展示loading -> 获取数据 -> 缓存数据 -> 隐藏loading -> 展示界面 有缓存的情况 前端请求接口-> 数据层有缓存-> 展示界面 -> 获取数据 -> 缓存数据 -> 新数据与旧数据不同 -> 展示界面
我们可以看到,在首次场景下,因为数据层为空,我们依然需要展示loading,毕竟首次未拿到数据的场景下,展示空状态的界面也不合适。但在后续有缓存的场景下,我们可以直出界面,后台发送一个请求,如果返回的数据与界面展示的不同,则再重新更新一下界面即可。
想象场景如下: 某个列表,用户首次进入时,展示loading并请求缓存数据,后续再进入时,因为缓存中有列表数据,可以直接展示,即使这个列表数据在其它页面中有添加或删除,因为我们后台会有一个请求再去获取一次数据,因此在返回后依然可以更新界面,确保数据一致性。
实践代码
// datasource-http.ts 实现一个文件管理器,用于在web中管理远程服务器上的文件import Magix from 'magix5'; let { mix, config, toUrl, Event, Cache } = Magix; let lastKey; let dataCache = new Cache(); let request = async (part, params) => {//使用fetch发送请求 let options = { method: 'get', credentials: 'include', headers: { 'Accept': 'application/json', 'Content-type': 'application/x-www-form-urlencoded;charset=utf-8', } } as RequestInit; let url = config<string>('server'); return await fetch(toUrl(url + part, params), options); }; let send = async (key, cached, entity, action, path) => { let r = await request(action, { path }); let text = await r.text(); let json = JSON.parse(text); if (cached) { if (lastKey == key && text != cached['@:{text}']) {//如果数据与缓存中的不同,则更新缓存,并派发事件 dataCache.set(key, { '@:{text}': text, '@:{json}': json }); entity.fire('@:{event#fv.list.path}'); } } else { dataCache.set(key, { '@:{text}': text, '@:{json}': json }); return json; } }; export default mix({ async '@:{send}'({ action, path }) { let key = action + '~' + path; let cached = dataCache.get(key); lastKey = key; let sendPromise = send(key, cached, this, action, path); if (cached) {//如果有缓存,直接返回更新界面 return cached['@:{json}']; } else {//等待请求完成 return await sendPromise; } } }, Event);
在某个view中使用
import Magix from 'magix5'; import DataSource from './ds'; let { View } = Magix; export default View.extend({ tmpl: '@:./index.html', init() { let update = this.render.bind(this); DataSource.on('@:{event#fv.list.path}', update); }, async render() { let r = await DataSource['@:{send}']({ action: 'fv/list.json', path: '/abc/def' }); console.log(r); this.digest({files:r}); } })
加入缓存后,从原来的请求等待->渲染界面到 直接渲染界面->请求数据。实现了依赖的反转,避免了loading。
以iCloud为例,我们在pc端放入了新的文件,在移动端打开app时,app会直接先展示之前的文件与文件夹,然后再同步数据,如果有变化则显示新的文件和文件夹,native应用并不有像我们之前先展示loading,请求数据返回后再展示。
之前的方案使用内存进行缓存,如果用户刷新页面,则每次都需要先展示loading,如果我们能够使用indexDB或localStorage进行数据缓存,这样在indexDB或localStorage有数据的情况下,刷新页面也不需展示loading,更类似native应用了。
资源的分与合
开发阶段我们通常根据自己的需要,把前端文件进行拆分,以进行更好的逻辑组合。 上线时,我们拆分的这些文件,有些打包工具会默认合并,比如webpack,而某些打包工具不会合并,比如magix-composer
我们的这些资源js、css等文件,到底是合并还是拆分需要具体到应用场景,不过从整体看,pc更适合拆分的方式,而移动更适合合并的方式,本文也不再深入讨论为什么pc适合拆分,移动适合合并。
因个人长期从事pc端的开发,因此接下来我们讨论的场景基于pc端,资源不合并的情况下。
loading场景
在pc端因为资源不合并,我们从任意一个入口进入时,即打开某个页面,比如首页、报表或列表等,可以最小化的加载所需要资源,让页面展示更快。
当我们从某个页面进入时,需要先加载资源,再请求接口,该过程会有一个稍长的loading过程
当我们从某个页面切换到其它页面时,仍然是加载所需要资源,再进行接口请求,最后才能渲染显示页面,该过程的loading由于某些资源在之前页面可能加载过,会比之前稍快一些,但仍旧较长,我们能否做到点击页面后就能立即显示,避免loading?
资源加速
我们默认并不会对所有资源进行打包,或分功能打包,开发时什么样,上线后仍然保持什么样的拆分粒度。 但我们可以使用离线编译工具对整个项目中的文件进行扫描处理,生成一份资源清单,比如这样
因为该文件位置固定,我们在页面空闲时,可以获取到该文件。然后使用加载器,逐一加载清单中的资源,这样在用户切换到未来某个页面时,该页面所需要的资源已经加载好了,只需要等待接口数据的请求即可。
接口加速
核心思路为:普通的请求过程是 前端展示loading->请求后端接口->获取到数据->隐藏loading-> 展示界面 升级思路为:无缓存的情况 前端请求接口-> 数据层无缓存-> 展示loading -> 获取数据 -> 缓存数据 -> 隐藏loading -> 展示界面 有缓存的情况 前端请求接口-> 数据层有缓存-> 展示界面 -> 获取数据 -> 缓存数据 -> 新数据与旧数据不同 -> 展示界面
我们可以看到,在首次场景下,因为数据层为空,我们依然需要展示loading,毕竟首次未拿到数据的场景下,展示空状态的界面也不合适。但在后续有缓存的场景下,我们可以直出界面,后台发送一个请求,如果返回的数据与界面展示的不同,则再重新更新一下界面即可。
想象场景如下: 某个列表,用户首次进入时,展示loading并请求缓存数据,后续再进入时,因为缓存中有列表数据,可以直接展示,即使这个列表数据在其它页面中有添加或删除,因为我们后台会有一个请求再去获取一次数据,因此在返回后依然可以更新界面,确保数据一致性。
实践代码
在某个view中使用
加入缓存后,从原来的请求等待->渲染界面到 直接渲染界面->请求数据。实现了依赖的反转,避免了loading。
同类natvie应用
以iCloud为例,我们在pc端放入了新的文件,在移动端打开app时,app会直接先展示之前的文件与文件夹,然后再同步数据,如果有变化则显示新的文件和文件夹,native应用并不有像我们之前先展示loading,请求数据返回后再展示。
使用indexDB或localStorage增强缓存
之前的方案使用内存进行缓存,如果用户刷新页面,则每次都需要先展示loading,如果我们能够使用indexDB或localStorage进行数据缓存,这样在indexDB或localStorage有数据的情况下,刷新页面也不需展示loading,更类似native应用了。