首屏

问题背景

首先说说为什么要做这个,主要是三个原因:

  • 公司目前还有大约30%甚至更多的页面需要套模版,为了SEO也好,项目历史原因也罢,这些套模版的页面需要前端写模版,然后后端写php模版去渲染html,这种模式沟通成本巨大,删减html,加个id,类名都需要跟后端沟通,定位问题也比较困难。

  • seo需求,这也是为什么不可以把这些页面简单的迁移到spa上,前端组也一直在把模版项目的部分页面能迁移的都迁移到spa项目上,用vue来实现,但是spa的模式,牺牲了seo,所以部分页面还不适合迁移到spa上

  • 首屏时间,第一次打开页面,可能有2m的静态资源需要加载,加载完静态资源,还需要请求接口,等接口返回之后才能显示页面,其实这个等待时间是比较难优化。而且app上的各个页面几乎都是打开一个新的weiview,每次都有这个加载过程,白屏时间不太可控。

所以我觉得做这个东西是有必要的

方案挑选

说完为什么,再说一下做了哪些选择上的思考,基本上需要SEO需求的方案离不开这几种:

  • 让前端写php模版,这种方式的学习成本我是觉得不高,但是弊端很明显,前端侵入后端项目,两端还是耦合的,并没有解决沟通问题,所以不考虑。
  • 前端写nodejs,使用ejs,pug这样的模版去编写模版渲染,前端自己写路由,写控制器等,这样产出的多页面比较传统的渲染模型,也是有点问题的,组件化困难,依赖dom操作来做的页面交互最大的问题是组件复用,可能是需要在js里面拼接html append body其实代码很丑陋的。
  • SSR+SPA,基于第二种方案想解决的问题,我们希望是有一种方式解决组件化,工程化,也希望到浏览器端体验也比较好是个spa,最好跟写前端项目一样的体验。

方案选定

定方案为ssr,因为前端主要技术栈是vue,所以选用vue来做server side render。

  • 首先说前端选型:

    • vue模版语法,前端成员都可以无缝切到这个项目,上手成本几乎为0
    • 这个方案一开始对性能有所担忧,看了一些性能分析文章,跟方案二最优模版pug性能相差百分之二三十,我觉得在目前的业务场景下,这种性能差距是可以被接受的。
  • 后端的选型:

    • nodejs的不二选择 koa,koa@2是一个基于async await的服务端框架,简单概括他的能力,就是他是一个包裹了后续所有中间件的装饰器模型,在目前nodejs lts已经原生支持async function的情况下,我想不到不用koa的理由。
    • koa简单,说明他需要更多的底层支撑,egg是阿里开发的基于koa的标榜着企业级的应用框架,选egg还有一个比较大的原因就是因为它的进程管理模块,因为js是单线程的,跑在服务端各个在应用启动的时候,master进程会起n个worker进程来协调,master进程只负责协调进程,脏活累活都由worker来做,可能其中一个worker出现在异常流程挂了,master会自动起一个新的worker代替这个进程。除此之外,egg还拓展koa的上下文,为了持续开发做的一些约定,插件机制,发布机制,官方插件等等,他奉行的是「约定优于配置」的理念,可能做起来束手束脚,但是长期看,这是一个正确的方向。

因为node的局限做了静态资源cdn,因为静态资源需要根据环境区分,所以做了在服务端打包。

请求过程

那看下一个请求过程是怎么样的,
请求的入口是nodejs这块,进来时访问缓存,目前缓存是三分钟,两千条缓存开始丢弃旧缓存,维护一个必须的key值列表,按照一定的排序重新拼接请求链接,验证是否有缓存。
如果没有缓存,那就会根据路由匹配页面组件,判断页面组件上是否有preFetch方法,在这里向后端请求数据,等待执行完成,这里说一下preFetch必须return一个promise,执行完的时候store上已经是有数据的了,带着数据渲染vue模版,产出一段html,一些js,css的头尾加载是在编译阶段已经引入的,过一层gzip然后返回响应体
浏览器端接收到请求,加载的阶段首屏已经渲染完成,在这个阶段做一些状态同步之类的事情,然后就是常规的vue项目初始化的过程,用户在页面的点击等操作,也直接请求后端接口。

会话状态和缓存

cookie: 开始的时候实现过从浏览器端请求携带cookie到渲染层,再从渲染层携带cookie请求后端。但有几个问题,跟用户有关的模版渲染出来很难复用,或者说复用率比较低,每个用户对应一个缓存可能意义不大,缓存时间内,用户可能没机会访问到这份缓存。后面压测数据来看,跟用户相关的渲染只放在浏览器端来做是正确的。如果没有缓存,在qc环境下TPS只有二三十,每个用户渲染一样的数据加上有效缓存TPS能达到800+。
所以,所有跟用户有关的请求全都放在浏览器端发起,能想到的场景就是普通的页面展示,一般是用户头像这些,直接一个公共接口就可以了,store的公共模块也定义了。再有就是列表上可能有一些用户已关注未关注的状态,这些后端需要拆分接口给我们,一份列表数据,一份用户关注的id,再前端跟列表数据匹配一下就可以了。其他场景基本都是全部和用户有关的页面。这些页面不需要seo,不必在服务端发起请求,直接不定义prefetch,只在浏览器端做渲染就好。

页面代码

页面代码要怎么写呢?
跟spa不同的是,需要在页面组件上注册dataStore说明是要注册哪个store module,还有pretch方法,一般返回直接返回一个dispatch action就可以。为了减少重复代码,我写了一个简单的包装方法,这样就完成一个简单的页面组件。

未来

  • 业务上

    • 套模版页面有修改,立即将页面迁移到node项目上
    • 一二级页面有改版也进行迁移,咨询首页,学院首页
  • 技术上

    • 这个项目没有等到vue3发布,vue3支持类申明组件,更好的服务端渲染性能,到时候如果允许会尝试升级
    • 公共模块,目前的公共模块比较散乱,在这个项目上要重新整理好,希望多和ui沟通,让项目有更多可以重复使用的模块。并且,希望大家区分好公共和业务组件的区别,不要在公共组件做任何程度的业务入侵
    • 测试模块,虽然现在已经接入了阿里日志,但是其实等发现问题可能这个异常已经发生很多次了,所以还是需要测试代码来保障,至少是一些页面空白需要排查掉,node项目稳定之后,服务端代码几乎不需要修改,到时我会写一个测试模块,每次提测都跑一跑,把一些已知的问题在开发阶段找出来,并修复掉。
    • 这次使用的是easywebpack,用起来坑还是比较多的,而且它又是webpack封装起来的,出现一些问题不知道怎么处理。目前还是能用的,有空把这份构建改到原生的webpack4来。

尾屏