基于 Vue SSR 的微架构在 FOLLOWME5.0 实践

2020年5月22日FOLLOWME5.0的第一个版本终于上线了,这也是公司内部基于 Genesis 上线的第二个项目。首页是老项目经历了最原始的那种 Vue SSR,后来在年初的时候,迁移到了 Nuxt.js 下,再到如今迁移到了 Genesis,可谓是一波三折。html

首次实践


在2019年的上半年,咱们在和 APP 混合开发的项目中,首次实践了模块化,它拥有了独立的 API、路由、状态和页面,而且是按需进行初始化的,可是它并非完美的,它是基于路由的微模块,而且状态也是按需注入到全局的状态中的管理前端

无路由微模块

2019年的8月份,我开始介入到 web 端首页的开发,首先面对的就是基于 Vuex 的状态管理,全部的状态所有注入到全局的状态,交集到一块儿,随着业务的迭代,已经分不清楚哪些是须要的,哪些是能够被删除的,随着业务规模的膨胀,愈来愈难以维护。为了保证无入侵性,基于Tms.js抽象出来了一个支持微模块的状态库,这时候咱们的微模块拥有了独立的 API、状态和页面。


咱们5.0左导航就被抽离成一个独立运行的微模块,一个微模块由多个组件组合而成,它有本身内部状态管理。vue

大跃进

在2019年年末的时候,得知咱们要升级5.0的版本,网站也将会大幅度的重构,在基于已有的微模块开发理念下,咱们但愿可以更近一步,解决一些以往项目架构的弊端。 node

5.0以前,咱们有一个公共的导航栏,不论是 CSR、仍是 SSR 的项目都须要它,每次导航栏发生变动以后,都须要从新打包十几个项目发布,这极大的消耗了咱们的身心体力,我但愿能作到一个页面能够由不一样的 SSR 服务聚合而成,经过 API 的形式提供给另一个服务使用。ios

这个大胆的想法诞生后,咱们深刻的研究了一下 Nuxt.js,指望它能够作到咱们的需求,通过一番调研后,确认没法知足个人需求后,最终仍是选择了造轮子。git

Genesis

参考了一些社区 SSR 框架的实现,基本上都是封装好的框架,灵活性较低,而咱们指望它是一个简单的 SSR 库,当成一个工具函数来使用,以便于可以支撑多实例运行。github

在这里的理念下, 它没有像 Nuxt.js nuxt.config.js 这样直接读取一个配置开始运行,给你集成了各类各样的功能,它仅仅一个基础到不能再基础的渲染工具函数web

import { SSR } from '@fmfe/genesis-core';

const ssr = new SSR();

const renderer = ssr.createRenderer();

renderer.render({ url: '/' });

固然了,在实际的业务中,咱们还须要建立一个 HTTP 服务将咱们的内容返回给用户。typescript

若是要作到微服务,而且能被不一样的服务之间调用,首先就须要服务的自身具有将渲染结果输出 JSON 的能力,而后第三方服务读取渲染结果,输出到 HTML 中npm

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 组件访问
});

5.0的服务拆分


根据 UI 的呈现效果,咱们将左导航和内容区拆分红不一样的服务,其中内容区由于开发团队的不一样、业务的不一样,又拆分红不一样的服务。
第一个版本咱们拆分了左导航的 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 为主,因此这方面还须要团队有时间才能支持。

传送们

相关文章
相关标签/搜索