2020 年 5 月 22 日FOLLOWME5.0的第一个版本终于上线了,这也是公司内部基于 Genesis
上线的第二个项目。首页是老项目经历了最原始的那种 Vue SSR,后来在年初的时候,迁移到了 Nuxt.js 下,再到现在迁移到了 Genesis
,可谓是一波三折。
2019 年的 8 月份,我开始介入到 web 端首页的开发,首先面对的就是基于 Vuex 的状态管理,所有的状态全部注入到全局的状态,交集到一起,随着业务的迭代,已经分不清楚哪些是需要的,哪些是可以被删除的,随着业务规模的膨胀,越来越难以维护。为了保证无入侵性,基于Tms.js抽象出来了一个支持微模块的状态库,这时候我们的微模块拥有了独立的 API 、状态和页面。
在 2019 年年底的时候,得知我们要升级 5.0 的版本,网站也将会大幅度的重构,在基于已有的微模块开发理念下,我们希望能够更近一步,解决一些以往项目架构的弊端。
5.0 之前,我们有一个公共的导航栏,不管是 CSR 、还是 SSR 的项目都需要它,每次导航栏发生变更之后,都需要重新打包十几个项目发布,这极大的消耗了我们的身心体力,我希望能做到一个页面可以由不同的 SSR 服务聚合而成,通过 API 的形式提供给另外一个服务使用。
这个大胆的想法诞生后,我们深入的研究了一下 Nuxt.js ,期望它可以做到我们的需求,经过一番调研后,确认无法满足我的需求后,最终还是选择了造轮子。
参考了一些社区 SSR 框架的实现,基本上都是封装好的框架,灵活性较低,而我们期望它是一个简单的 SSR 库,当成一个工具函数来使用,以便于能够支撑多实例运行。
在这里的理念下, 它没有像 Nuxt.js nuxt.config.js
这样直接读取一个配置开始运行,给你集成了各种各样的功能,它仅仅一个基础到不能再基础的渲染工具函数
import { SSR } from '@fmfe/genesis-core';
const ssr = new SSR();
const renderer = ssr.createRenderer();
renderer.render({ url: '/' });
当然了,在实际的业务中,我们还需要创建一个 HTTP 服务将我们的内容返回给用户。
如果要做到微服务,并且能被不同的服务之间调用,首先就需要服务的自身具备将渲染结果输出 JSON
的能力,然后第三方服务读取渲染结果,输出到 HTML 中
renderer.render({ url: '/', mode: 'ssr-json' });
我们巧妙的使用了 Vue 的 Renderer
选项 template,传入一个函数,执行完成后返回了一个 JSON 的渲染结果
const template: any = async (
strHtml: string,
ctx: Genesis.RenderContext
): Promise<Genesis.RenderData> => {
const html = strHtml.replace(
/^(<[A-z]([A-z]|[0-9])+)/,
`$1 ${this._createRootNodeAttr(ctx)}`
);
const vueCtx: any = ctx;
const resource = vueCtx.getPreloadFiles().map(
(item): Genesis.RenderContextResource => {
return {
file: `${this.ssr.publicPath}${item.file}`,
extension: item.extension
};
}
);
const { data } = ctx;
if (html === '<!---->') {
data.html += `<div ${this._createRootNodeAttr(ctx)}></div>`;
} else {
data.html += html;
}
data.script += vueCtx.renderScripts();
data.style += vueCtx.renderStyles();
data.resource = [...data.resource, ...resource];
(ctx as any)._subs.forEach((fn: Function) => fn(ctx));
(ctx as any)._subs = [];
return ctx.data;
};
考虑到不是所有的项目,都需要远程调用,所以远程调用组件,我们是以独立的包提供的。在拿到 SSR JSON 的渲染结果后,远程组件将会帮我们负责嵌入到该服务的页面中去。
<template>
<remote-view :fetch="fetch" />
</template>
fetch
是一个异步的回调函数,你可以通过使用 axios 库来发送请求将 SSR 渲染的结果返回给 remote-view
组件。
renderer.render({ url: '/', mode: 'ssr-json' }).then((r) => {
// 编写一个接口, 将 r.data 提供给 remote-view 组件访问
});
node-ssr-general
服务,首页和通知的 node-ssr-home
服务,以及信号的 node-ssr-signal
服务,每一个服务都是独立部署、独立开发、由不同的人进行维护,只是最终由 node-ssr-general
服务进行聚合。
未来我们的产品还将会进一步迭代,越来越多的服务都将会被集成进去这个大应用中。
最初的 SSR 服务,尝试过使用 RPC 和 HTTP 获取接口数据进行渲染,后面经过权衡后,在 5.0 我们统一采用了在服务器配置 HOST 的方式,配置一个内网的域名,提供给 SSR 服务通过 HTTP 请求来获取数据
为了首屏可以更快的呈现给用户,所以在服务端远程组件走的是 SSR 的渲染,在客户端路由切换的时候,走 CSR 渲染,所以在首页
切换到信号
栏目时,会有一些加载过程明显的白屏。
后续可以通过 Service Worker 对所有服务的静态资源和 CSR 渲染时的 HTML 进行预加载,减少加载该服务内容时,出现白屏的几率
从理论上,Genesis
也可以同时做到 React 的支持,输出同样标准的 JSON 渲染结果、同样标准的应用创建和销毁逻辑。只是目前我们团队都是以 Vue 为主,所以这方面还需要团队有时间才能支持。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.