背景
为了首屏渲染更快,让用户更快看到内容,网易易盾开启了移动端官网同构建设,即一套代码两端运行, 客户端代码直接运行在服务端, 无需编写冗余的渲染视图。
改造前,官网项目的技术栈较大众,以 egg.js 为后端框架,Nunjuck 为模板,前端采用 jQuery。
本次则采用Vue 作为前端框架进行了部分改进,团队成员的技术储备也基本上建立在 Vue 的技术体系上,从以下3方面出发,重整技术选型,全面改善易盾的官网生态。
统一技术栈:Nunjuck 模板很强大,但和我们习惯用的 Vue 语法还是有些不同。而 jQuery 属于旧时代的产物,数据驱动视图的思想已经深入人心。
用户体验:同构后的前端能兼顾 SEO 和 SPA 的交互体验。
开发体验:更易和现代的开发构建工具集成,如 webpack、ESLint、SASS 等。
技术选型
方案一:
Vue SSR 官方指南 https://ssr.vuejs.org/zh/
项目地址:
https://github.com/vuejs/vue-hackernews-2.0/
优缺点分析:
1.自由度最高,不依赖 node 框架和 vue 相关任何生态
2.需要的工作量最大,主要是 vue-ssr 通用逻辑 & 前端构建配置的实现
3.可复用目前基于 eggjs 的后端逻辑
方案二:nuxt.js
优缺点分析:
1.完善的解决方案,提供了许多开箱即用的功能,能大大减少很多配置和通用逻辑实现
2.不依赖 node 框架,主要关注的是应用的 UI 渲染
3.需要完成 egg.js 后端 api 接口的逻辑迁移
方案三:egg.js 和 nuxt.js 结合
以 egg.js 为主,nuxt.js 降级为 egg.js 一个中间件。这种方案可行得益于 nuxt.js 不依赖 node web 框架。
1.复用目前 node 端逻辑,仅对视图做迁移
2.利用 nuxt.js 提供的开箱即用的特性,无需关注 vue-ssr 通用逻辑实现和前端复杂的构建配置
3.工作量之一需要完成 eggjs 和 nuxt.js 的结合,提供一个 eggjs 中间件
方案三既能利用了 nuxt.js 的能力,且无需对目前 node 端逻辑做改动,很好的满足了我们的需求。基于该方案的流程图如下:
落地过程
【egg-nuxt-render 插件的实现】
插件中间件的实现关注点:
1.请求未命中 egg 路由时会返回 404,中间件处理 404 请求,修改 status 为 200,后续交于 nuxt.js 处理。如果 nuxt 路由中也未命中,由 nuxt 提供 404 页面。
2.传递 egg ctx,使得 nuxt 中能获取到 egg 的上下文。
3.调用 nuxt.render,即完成将当次请求交于 nuxt.js 处理。
【项目目录】
至此,我们可以这样设计我们的目录结构。仅需在 eggjs 根目录下新增 client 目录,来放置 nuxt.js 的所需的各种目录(每个目录的含义可参考 nuxt 官方文档)。nuxt.config.js 为 nuxt 的配置文件,会被 egg-nuxt-render 插件读取。
【页面迁移】
到了这一步,我们就可以开始迁移我们的页面了,即 Nunjuck 模板重构为 vue 模板。删除原先 egg router 中的页面路由定义,然后在 client/pages 里添加对应的页面.vue。这一步是体力活,也是本次同构最费时间的工作。看到这里有人可能会疑惑,这种方案需要要求所有的页面一次性重构完成,如果项目很庞大,没办法一次性重构完或者或者一次性重构完风险极高的情况下,是不是就很难实施了?考虑到这种合理的情况,设计了另一个兼容方案,给大家提供一种思路。
【兼容方案】
因为我们团队的项目规模可控,故采用一次性重构完所有页面。
方案流程图如下:
注意:本方案未经线上测试,仅供参考
这种方案的核心想法是先渲染 Nunjuck 模板,然后通过 ctx 将渲染结果传递到 nuxt 的 vue 模板完成最终渲染。主要可以关注以下几点:
1.保留 eggjs 页面路由的定义,调用 htmlRender 将 Nunjuck 模板转化成 html 字符串,并且保存到ctx.__HTML_RENDER__。此时需要将 status 置为 404。
2.由于 status 为 404,会执行 egg-nuxt-render 中间件,进入 nuxt 路由。但此时 nuxt 中并没有该路由(因为该页面未重构),所以我们将其指到同一个组件,即 NjkWraper.vue。
3.NjkWraper.vue 负责渲染 __HTML_RENDER__ 内容。__HTML_RENDER__ 应仅包含每个页面的主体部分,不包含页面通用部分(如导航、底部等),此部分由 vue 实现。
4.由于所有的路由都会命中 NjkWraper.vue,会导致真实的 404 请求丢失。所以我们还需要做一步判断该请求是不是真实的 404 请求。如果是,则需要重定向到 404 页面。这一步可在 vue router 守卫中实现。
至此,我们只要先将页面通用部分,如导航、底部等,通过 vue 重构,具体页面后面可以慢慢迁移。
兼容方案存在的弊端:
页面间跳转必须刷新页面。因为最终输出到浏览器的页面中会包含 vue 组件 和 Nunjuck 渲染出来的内容,其中 Nunjuck 内容中可能存在 jquery、regular 等各种库,重复引入恐有问题。
所有页面重构完后,兼容代码需要删除。
总结
本次的同构实践本质上是对已有项目的重构,考虑了系统稳定性和重构成本,故未对 egg.js 主框架做调整。当开启一个新的项目时,直接采用 nuxt.js 提供的完整的解决方案会更加纯粹。(作者: 业务安全专家拾叁)