背景
公司中後臺管理系統經歷幾年的迭代維護,其公共模組部分(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...