前端公共模組替換

記得要微笑發表於2021-10-30

背景

公司中後臺管理系統經歷幾年的迭代維護,其公共模組部分(HeaderSlider)的主題與現在UI風格不一致,需要更換。但是,該專案幾年前的老專案了,技術棧在逐步的演進,初始是Freemaker引擎搭建的前後端未分離專案,後面部分舊頁面採用React重構,新頁面採用React開發,使用iframe標籤巢狀React頁面,再後來引入了微前端的概念,使用single-spa-react改造專案中的React頁面。

賣家中心前端框架演進.png

目前,採用這三種技術棧的頁面都存在,需要全部更換。

改變前:

image-20211025134202924.png

改變後:

5b0720ad-f6f5-45c7-9d45-4202d117a411.png

Freemarker

賣家中心中後臺專案,每個頁面都有相同的部分,比如頂部、導航、底部,如果每個頁面都寫一遍,等到專案龐大時,某天需要修改這些公共模組了,你需要將所有頁面都改一遍,這真的很崩潰。但是sitemesh會讓你輕鬆應對!

什麼是sitemesh

SiteMesh is a lightweight and flexible Java web application framework that applies the Gang of Four decorator pattern to allow a clean separation of content from presentation.

SiteMesh 是一個網頁佈局和修飾的框架,利用它可以將網頁的內容和頁面結構分離,以達到頁面結構共享的目的。

Sitemesh是由一個基於Web頁面佈局、裝飾以及與現存Web應用整合的框架。它能幫助我們在由大
量頁面構成的專案中建立一致的頁面佈局和外觀,如一致的導航條,一致的banner,一致的版權,等等。
它不僅僅能處理動態的內容,如jsp,php,asp等產生的內容,它也能處理靜態的內容,如htm的內容,
使得它的內容也符合你的頁面結構的要求。甚至於它能將HTML檔案象include那樣將該檔案作為一個皮膚
的形式嵌入到別的檔案中去。所有的這些,都是GOFDecorator模式的最生動的實現。儘管它是由java語言來實現的,但它能與其他Web應用很好地整合。

image-20211027140755232.png

sitemesh如何工作?

SiteMesh 充當 Servlet 過濾器,攔截返回到 Web 瀏覽器的HTML,提取相關內容並將其合併到稱為裝飾器的模板中。過濾器將任何 html、jsp 或其他 Web 框架頁面的內容放入稱為裝飾器的預定義模板中

image-20211027140538325.png

sitemesh在賣家中心中的應用

在公司賣家中心的中後臺專案中,我們將sitemesh攔截器與Application拆成獨立的服務,分別對應著seller-center攔截服務和agentBuy應用服務,因為使用sitemeshweb容器可能有多個,將sitemesh獨立拆出來可以複用。因此,架構圖變成如下形式:

image-20211027142027697.png

賣家中心的執行流轉時序圖如下:

賣家中心執行流程 (1).png

(1)webagent閘道器服務中的路由配置如下,/agentBuy/seller/*請求會經過seller-center攔截,而seller-center服務中沒有/agentBuy/seller/*請求的controller層,會繼續匹配閘道器路由,走到agentBuy服務中

image-20211027144957601.png

image-20211027145018176.png

(2)agentBuy服務中有/agentBuy/seller/*請求的controller層,返回被裝飾的內容區域html頁面

(3)seller-center攔截返回到web瀏覽器的html,提取其中相關內容(headbody)並將其合到裝飾頁面模板中(<sitemesh:write property='head' /><sitemesh:write property='body' />

carbon.png

公共模組是怎麼注入的?

在上面模板尾部中有注入以下指令碼:

<script src="https://mstatic.cassmall.com/www/${profiles}/seller-react-frame/seller-react-frame.js"></script>

翻閱程式碼發現頁面中的Header、Slider部分並不是在上面模板中實現的,而是通過script引入seller-react-frame.js,並執行指令碼將Header DOM、Slider DOM分別注入到<div id="seller-topbar"></div><div id="seller-sidebar" class="menus"></div>

seller-react-frame.js是在seller-react-frameReact)專案中生成的,該專案中實現了HeaderSlider元件並指定掛載的元素,構建專案會生成jscss資原始檔。構建完後再執行一個指令碼,該指令碼會寫入一個自執行函式到seller-react-frame.js檔案,自執行函式的作用是將asset-manifest.json資源清單中的jscss動態注入的頁面中

// seller-react-frame專案
// 1、構建命令
"build": "cross-env PUBLIC_URL=./ react-app-rewired build && node ./scripts/build-entry-js.js",

// 2、入口檔案
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import { TopBar, SideBar } from "@casstime/seller-components";
import "@casstime/seller-components/style.scss";

ReactDOM.render(<TopBar />, document.getElementById("seller-topbar"));
ReactDOM.render(<SideBar />, document.getElementById("seller-sidebar"));

// 3、執行build-entry-js.js生成seller-react-frame.js
const fs = require("fs");

const assetManifest = require("../build/asset-manifest.json");
const entries = assetManifest.entrypoints;

fs.writeFileSync(
  "./build/seller-react-frame.js",
  `;(function loadAssets() {
  var scripts = Array.from(document.getElementsByTagName('script'));
  var targetScripts = scripts.filter(function(item) {return item.src.indexOf('seller-react-frame.js') > -1});
  var prePath = '';
  if (targetScripts.length) {
    var targetScript = targetScripts[0];
    prePath = targetScript.src.slice(0, targetScript.src.indexOf('seller-react-frame.js'));
  }
  ${JSON.stringify(entries)}.forEach(function(asset) {
    if (/\\.css$/.test(asset)) {
      var head = document.getElementsByTagName("head")[0];
      var link = document.createElement("link");
      link.rel = "stylesheet";
      link.href = prePath + asset;
      head.appendChild(link);
    }
    if (/\\.js$/.test(asset)) {
      var body = document.getElementsByTagName("body")[0];
      var script = document.createElement("script");
      script.src = prePath + asset;
      body.appendChild(script);
    }
  });
})();
`
);

因此,如果我們需要調整公共模組,只需要修改HeaderSlider元件,並重新構建、部署seller-react-frame。如果遇到快取問題拉取的不是最新seller-react-frame.js,則需要刷一下CDN

Freemarker + iframe

Freemarker + iframe跟純使用`Freemarker唯一的不同點就是內容變化的區域只有一個架子,裡面使用iframe頁面巢狀React頁面,比如:

// 內容變化區域 ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <#include "../includes/head_storemgr.ftl" >
    <#assign userLoginId = User.getUserId()>
    <link rel="stylesheet" href="${Global.getConfig("web.app.static.url")}/css/storeResolveManage.css" type="text/css">
</head>
<body id="ng-app">
        <div class="right" style="margin-top: 16px;margin-right: 16px;border: 10px solid #FFF;box-shadow: 0px 2px 8px #e4e4e4;background: #f1f1f1">
            <iframe id="iframeCon" name="iframeCon" src="/seller#/decode/decode-page" scrolling="no" allowtransparency="yes" marginwidth="0" marginheight="0" frameborder="no" border="0" onload="loadFrame();" style="width: 100%; height: 700px;"></iframe>
        </div>
    <script type="text/javascript" src="${Global.getConfig("web.app.static.url")}/js/storeResolveManage.js"></script>
</body>
</html>

其他公共模組還是由seller-react-frame.js指令碼注入的。因此,調整公共模組還是需要修改HeaderSlider元件,並重新構建、部署seller-react-frame

microfe

公司微前端專案稍微有點特殊,基座專案(microfe-seller-base)中並沒有包含公共模組,而是將公共模組也抽離成子應用。基座專案根據路由首先載入渲染公共子應用(microfe-seller-common),然後再載入渲染其他子應用,但是如何控制載入渲染子應用的順序呢?畢竟其他子應用是掛載在公共子應用裡面的。

image-20211027173420066.png

因此,調整公共模組只需要修改公共子應用(microfe-seller-common)工程,然後重新構建、部署。

參考:

https://docs.huihoo.com/sitem...

https://en.wikipedia.org/wiki...

http://www.blogjava.net/over1...

https://zhuanlan.zhihu.com/p/...

https://blog.csdn.net/xuanwug...

相關文章