基於 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 爲主,因此這方面還須要團隊有時間才能支持。

傳送們

相關文章
相關標籤/搜索