3

背景

公司中后台管理系统经历几年的迭代维护,其公共模块部分(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...


记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。


引用和评论

0 条评论