背景
公司中后台管理系统经历几年的迭代维护,其公共模块部分(Header
、Slider
)的主题与现在UI
风格不一致,需要更换。但是,该项目几年前的老项目了,技术栈在逐步的演进,初始是Freemaker
引擎搭建的前后端未分离项目,后面部分旧页面采用React
重构,新页面采用React
开发,使用iframe
标签嵌套React
页面,再后来引入了微前端的概念,使用single-spa-react
改造项目中的React
页面。
目前,采用这三种技术栈的页面都存在,需要全部更换。
改变前:
改变后:
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
那样将该文件作为一个面板
的形式嵌入到别的文件中去。所有的这些,都是GOF
的Decorator
模式的最生动的实现。尽管它是由java
语言来实现的,但它能与其他Web
应用很好地集成。
sitemesh
如何工作?
SiteMesh
充当 Servlet
过滤器,拦截返回到 Web
浏览器的HTML
,提取相关内容并将其合并到称为装饰器的模板中。过滤器将任何 html、jsp
或其他 Web
框架页面的内容放入称为装饰器的预定义模板中
sitemesh
在卖家中心中的应用
在公司卖家中心的中后台项目中,我们将sitemesh
拦截器与Application
拆成独立的服务,分别对应着seller-center
拦截服务和agentBuy
应用服务,因为使用sitemesh
的web
容器可能有多个,将sitemesh
独立拆出来可以复用。因此,架构图变成如下形式:
卖家中心的执行流转时序图如下:
(1)webagent
网关服务中的路由配置如下,/agentBuy/seller/*
请求会经过seller-center
拦截,而seller-center
服务中没有/agentBuy/seller/*
请求的controller
层,会继续匹配网关路由,走到agentBuy
服务中
(2)agentBuy
服务中有/agentBuy/seller/*
请求的controller
层,返回被装饰的内容区域html
页面
(3)seller-center
拦截返回到web
浏览器的html
,提取其中相关内容(head
、body
)并将其合到装饰页面模板中(<sitemesh:write property='head' />
与<sitemesh:write property='body' />
)
公共模块是怎么注入的?
在上面模板尾部中有注入以下脚本:
<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-frame
(React
)项目中生成的,该项目中实现了Header
和Slider
组件并指定挂载的元素,构建项目会生成js
、css
资源文件。构建完后再执行一个脚本,该脚本会写入一个自执行函数到seller-react-frame.js
文件,自执行函数的作用是将asset-manifest.json
资源清单中的js
、css
动态注入的页面中
// 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);
}
});
})();
`
);
因此,如果我们需要调整公共模块,只需要修改Header
和Slider
组件,并重新构建、部署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
脚本注入的。因此,调整公共模块还是需要修改Header
和Slider
组件,并重新构建、部署seller-react-frame
。
microfe
公司微前端项目稍微有点特殊,基座项目(microfe-seller-base
)中并没有包含公共模块,而是将公共模块也抽离成子应用。基座项目根据路由首先加载渲染公共子应用(microfe-seller-common
),然后再加载渲染其他子应用,但是如何控制加载渲染子应用的顺序呢?毕竟其他子应用是挂载在公共子应用里面的。
因此,调整公共模块只需要修改公共子应用(microfe-seller-common
)工程,然后重新构建、部署。
参考:
https://docs.huihoo.com/sitem...
https://en.wikipedia.org/wiki...
http://www.blogjava.net/over1...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。