最近,我们团队开源了一套沉淀了2年的前端SPA架构框架,主要是用来解决动态路由的问题。我们的思路来源于后端,采用中间件的设计模式来架构整个框架。我们的原则是让大家快速开发一个SPA单页应用,只关心业务逻辑,其他的行为都可以帮助处理掉。
其实我们的开源比较匆忙,从很多方面看还是有些不标准的,但是之后我们会严格按照标准的规范和流程走下去,给大家一份稳定的架构。对于现在开始关注我们团队的小伙伴,我表示非常的感谢。有大家的支持,我们会做的更好。
Miox到底要解决什么问题?
这个问题从我们设计的初衷来说,就是解决所有路由的问题。在业界,其实大家都普遍认可一对一的路由模式,从而生产出了很多路由体系,比如vue-router
和react-router
。他们都是非常不错的架构,从某种意义上来说,引领了前端路由的前进。很多小伙伴都是直接使用他们的全家桶来开发项目,也得到了很好的效果。
我不确定大家是否考虑过一个问题,当一个用户从登陆页面A登陆后进入B,再回到A页面的情况。一对一的静态路由其实理论上最终会将A页面显示为未登录,这是完全正确的,但是逻辑上它应该是一个登录的页面。静态路由无法区分环境变量对页面选择的影响,只能通过hook等手段来将页面内容替换掉。理论上,我们借助后端的写法应该是这样的逻辑:
route.get(‘/a‘, ctx => { if (global.logined) { ctx.render(webveiwA); } else { ctx.render(webviewB); } })
我们需要根据周边的环境变量去自动选择该渲染哪个页面,如此的逻辑才是我们想要的。所以我们会根据这样的思路来设计动态路由。在nodejs
的世界中,这种模式已经非常常见,比如express-router
与koa-router
,都是采用这种设计思路来实现动态话的路由体系。我观察了前端的发展,都没有提出在前端实现这样的逻辑。于是,我们便开始研究如何将这种思路架设在前端使用,来获得更理想的逻辑体验。
Miox从来都不是依赖任何MVX框架来实现
为什么这么说呢?原因非常简单。在公司里面,我们大概有90%的H5业务都是采用Miox来实现的。我们的技术栈其实是Vue,因为Miox对Vue的结合太过深入,所以自然有部分小伙伴认为Miox是基于Vue来开发的,也就是说Miox是依赖着Vue?其实不是,Miox并不依赖任何框架实现。我来举个列子:
我们的电脑,如果换了一个显卡,那么必须要装显卡驱动。根据不同的显卡驱动,表现也不同。如果我们将Vue当作一个显卡,而Miox当作我们的电脑,那么我们需要一份显卡驱动来让整台电脑接受这块显卡。
所以我提出一个概念,就是渲染驱动的概念。Vue仅仅是我们Miox的一个渲染引擎,用来渲染页面的,可以理解为模板。我们还需要一份驱动告诉Miox,来说明Vue的渲染是如何在Miox实现的。这部分可以从这里看明白。当然,不仅仅是Vue,我们还能够将React接入到Miox中。理论上,只要能提供对应的渲染驱动,都可以将任意的渲染引擎接入到Miox中来使用。自然的,大家的书写都将会变成那种引擎的书写方式。这就是我们的插拔式设计。在公司里面使用的时候,我们不必在意使用何种渲染引擎,Miox都可以支持,同时帮您管理好整个路由体系。
基于中间件实现
在前端,如果我们能够用中间件来拦截整个逻辑过程的话,对开发是相当有利的,不仅仅在代码层面能够提高可读性,同时可以在具体业务层面提高效率。我们用后端路由逻辑来举个例子:
当我们遇到一些API都是需要经过登录验证的时候,我们可以将
/authorize
的路由前缀都使用中间件统一处理。其他的都不走验证逻辑。
const Authorize = require(‘./auth‘); const AuthorizeApi = require(‘./auth/routes‘); route.use( ‘/authorize‘, Authorize.connect(), // Authorize.connect是统一的验证逻辑代码 AuthorizeApi.routes(), AuthorizeApi.allowedMethods() );
如此当通过/authorize
的路由都需要经过Authorize.connect()
来验证是否具有权限。这使得代码非常简洁易懂。
Miox的设计便是如此,通过这种中间件的架构,使得我们在前端获得了统一拦截处理的能力。在实际生产中,我们很多地方都用到了中间件来处理统一的校验逻辑,使得代码维护性得到很大地提升。
那么如果不使用呢?我们需要在进入页面的时候,每个页面中都要嵌入一段代码来处理权限问题,不仅仅代码量增加,而且对于之后的维护,可能会产生漏改的问题。
具体想了解中间件的小伙伴,我推荐去看下koa-router。
缓存页面得到更好的渲染性能
说到缓存,这个话题过于庞大,对于Miox的缓存机制,我只能简单介绍一下,有兴趣的小伙伴可以看下源码。
在开发过程中,特别是对于开发移动端页面,我们需要保存前一个页面滚动位置,那么我们在切换到另一个页面的时候是不能将前一个页面销毁的,原因是我们希望回到前一个页面的时候还是停留在之前滚动的位置。那么我们需要缓存这个页面来确保位置的不改动。Miox模拟了history的部分API,同时增加了一层页面堆栈。我们需要维护这层页面堆栈来确保页面的可溯性。每次我们通过一种算法来动态比较路由与页面的关系,从而从这个堆栈中选出我们想要的缓存页面。当然,没有这个对应关系的时候,我们自然是要创建的。我们基于尽最大可能限度复用页面的宗旨,来缓存这些页面与路由的关系。
可能有人要问,如果缓存过多,对于页面的切换会有性能上的影响吧?是的,过多的页面缓存也是阻碍性能的关键。这里我们进行了缓存个数的优化。在启动miox服务的时候,我们有一个配置的参数max
,一般默认为一个,当然,大家也可以自己自由设置最大个数,来保障缓存的性能问题。
历史遗留问题:history方向问题的解决方案
在History中,我们都承认无法判断出当前浏览器行为的方向性,所以无法给出我们想要的方向来自动做页面切换的效果。为了解决这个问题,我们搜集了业界的解决方案,采用sessionStorage
来模拟history堆栈,从而解决这个问题(新history的API中已经增加了history.index
动态属性来告诉我们现在位于堆栈中的位置)。我们将此方案整合到了Miox中,并且对其加强,来告诉动画引擎这次行为在浏览器中是如何表现的。自然,动画引擎就能够根据这个来自动切换页面的动画,达到自动处理的效果。
官方提供了一个简单的模块来支持动画,当然小伙伴想要自定义动画也是非常简单的,具体见这里。
独立的页面生命周期
在传统的MVX框架中提供了组件的生命周期,我们在某种意义上也认为是页面的生命周期,但是我们对比原生IOS的周期行为,还是有所欠缺的。比如说active
生命周期。这个是什么意思呢?我来举例说明:
当一个页面被推入后台,又被唤起的时候,我们根本不知道它是不是再一次被激活,我们只能知道页面又一次被进入,第二次进入的概念是需要很多代码来辅助完成的。而在传统框架中,很难触发再一次的
mounted
生命周期,因为页面已经被mounted
过了。Miox提供了这样的生命周期的定义。
// use vue.js export default Vue.extend({ mounted() { this.$on(‘webview:active‘, this.activeLife); }, methods: { activeLife() { console.log(‘我被唤起了‘); } } })
当然,我们也可以将这些生命周期直接抛到全局去,用于全局的监控。
app.on(‘webview:mounted‘, webview => { console.log(webview); })
对于前置的生命周期,我们同样提供了以下的生命周期来辅助:
- webview:enter
- webview:leave
- webview:beforeEnter
- webview:beforeLeave
这些周期能够让你很好地掌控整个过程,对于自动埋点什么的功能非常实用。
服务端渲染
目前主流的架构都支持了服务端渲染来增强SEO的能力,那么对于Miox而言,也需要支持他们的服务端渲染。考虑到Miox自身会给渲染出来的内容包裹一些代码,所以,我们需要自己实现SSR。当然,渲染引擎的SSR实现是交给自己来完成的。也就是说我们需要给他包裹一层SSR渲染。
Miox暂时支持Vue的SSR渲染,后续会逐步添加对于React的SSR渲染。还有比如百度的san.js
其实也可以接入进来实现SSR渲染。服务端渲染并不是太麻烦,如果大家能够掌握Miox的运行原理的话。
最后
对于开源,我们团队内部做了很多努力,也咨询了很多大牛,希望能够给大家创造出一份简易开发的架构来帮助大家完成业务。目前,团队后续计划如下:
- 完善SSR的文档
- 规范化Github上的git message
- 维护Github上的Issues和PR
- 单测和代码覆盖率增强
- 对路由架构更多思考来完善Miox
- 提供Pc端的演示demo
- 提供Miox实际开发场景下的开发代码示例
希望大家看到这篇文章后可以支持我们,给我们多提供一些意见和建议,让我们共同将Miox完善下去。喜欢的小伙伴,帮忙点个Star。
项目开源在 GitHub: 51nb/Miox
出处:掘金专栏-51信用卡前端团队 https://juejin.im/post/5a0eee94f265da430702d8e0