什么是微后端?
微后端(Micro-Frontends)是一种将乙烯应用领域程序降解为较细、更分立的部份的构架商业模式。它的最终目标是使工程项目组能分立合作开发、布署和扩充应用领域程序的相同部份,而不能负面影响总体应用领域程序的灵活性和操控性。
微后端的主要特征主要包括:
模组化:应用领域程序被降解为较细的模块,每一模块能分立合作开发和布署。分立性:每一模块能采用相同的控制技术栈和架构,而不能负面影响总体应用领域程序的灵活性和操控性。软件系统:相同的模块能软件系统在一同,形成两个完备的应用领域程序。可扩充性:工程项目组能依照须要加进或删掉模块,以满足用户相同的销售业务市场需求。微后端的同时实现形式主要包括:
iframe:将相同的模块内嵌到主应用领域程序的iframe中。Web Components:采用Web Components控制技术将相同的模块PCB为自订原素。服务器端软件系统:采用后端软件系统控制技术将相同的模块软件系统到两个应用领域程序中。微后端的缺点主要包括:
提升合作开发工作效率:工程项目组能分立合作开发和布署模块,减少合作开发和布署的天数和生产成本。提升可保护性:模组化的构架使应用领域程序更更易保护和扩充。提升可F83E43Se性:相同的模块能在相同的应用领域程序中共享天然资源和宠信。提升操控性:只读取须要的模块,减少总体应用领域程序的读取天数和天然资源消耗。微后端的缺点主要包括:
复杂程度:微后端须要更多的控制技术和构架设计,减少了合作开发和保护的复杂程度。可靠性:采用iframe或Web Components时,须要考量可靠性问题,防止布吕马反击和XSS安全漏洞。微后端的应用领域
在Vue3和React工程项目中,我们仍能通过各式各样形式利用微后端:
采用webpack或Rollup等构筑辅助工具将应用领域程序降解为较细的模块,每一模块能分立合作开发和布署。采用iframe或web components控制技术将相同的模块软件系统到同一应用领域程序中。其二为:在Vue3中,能采用Vue Custom Elements应用领域程序将Vue模块PCB为Web Components;在React中,能采用React Custom Elements库将React模块PCB为Web Components。采用路由器或该事件监督机制在相同的模块间进行通讯。在Vue3中,能采用provide/inject API或EventBus来同时实现模块间的通讯;在React中,能采用React Context或Redux等状况管理工作库来同时实现模块间的通讯。采用微后端架构,例如Single-SPA或qiankun,来管理工作不同模块间的依赖关系和生命周期。这些架构提供了两个统一的入口和路由器系统,使相同的模块能分立合作开发和布署,并且能在运行时动态读取和卸载。微后端的常见应用领域场景
电商平台:将商品列表、购物车、订单管理工作等不同的模块分立合作开发和布署,通过微后端架构(例如qiankun)软件系统到两个应用领域程序中。这样能提升合作开发工作效率和可保护性,同时能依照市场需求灵活加进或删掉模块。大型企业门户网站:将相同的销售业务模块(例如人力天然资源、财务、行政、物流等)分立合作开发和布署,通过微后端架构(例如Single-SPA)软件系统到两个应用领域程序中。这样能同时实现跨工程项目组协作和灵活的销售业务拓展,同时能提升可保护性和可F83E43Se性。社交媒体平台:将用户个人主页、消息中心、动态发布等。在线教育平台:将课程列表、视频播放、在线测验等。微服务构架系统:将相同的微服务(例如用户认证、支付、推荐等)分立合作开发和布署,通过微后端架构软件系统到两个应用领域程序中。路由器系统及 Future State
我们在两个同时实现了微后端内核的产品中,正常访问两个子应用领域的页面时,可能会有这样两个链路
此时浏览器的地址可能是·https://app.alipay.com/subApp/123/detail·,如果手动刷新浏览器的话将会是很复杂的情况。
由于子应用领域都是lazy load的,刷新时主架构的天然资源会被重新读取,同时异步load子应用领域的静态天然资源。而激活子应用领域后,天然资源没有全部读取完毕,导致路由注册表中可能没有匹配子应用领域/subApp/123/detail 的规则,这时候会直接报错。
这个问题在所有 lazy load 形式读取子应用领域的方案中都会碰到,早些年前 angularjs 社区把这个问题统一称之为 Future State。
主架构配置子应用领域的路由器为 subApp: { url: /subApp/**, entry: ./subApp.js },则当浏览器的地址为 /subApp/abc 时,架构须要先读取 entry 天然资源,待 entry 天然资源读取完毕,确保子应用领域的路由器系统注册进主架构之后,再去由子应用领域的路由器系统接管 url change 该事件。同时在子应用领域路由器切出时,主架构须要触发相应的destroy该事件,子应用领域在监听到该该事件时,调用自己的卸载方法卸载应用领域,如 React 场景下 destroy = () => ReactDOM.unmountAtNode(container)。
微后端有哪些架构
基于上述对微后端总体概念和理论的阐述,目前业界已经有不少架构来帮助合作开发者轻松的软件系统微后端构架,例如下面这些:
Mooa:基于Angular的微后端服务架构Single-Spa:最早的微后端架构,兼容多种后端控制技术栈。Qiankun:基于Single-Spa,阿里系开源微后端架构。Icestark:阿里飞冰微后端架构,兼容多种后端控制技术栈。Ara Framework:由服务器端渲染延伸出的微后端架构。以qiankun为例
以下是 qiankun 提供的特性:
同时实现了子应用领域的读取,在原有 single-spa 的 JS Entry 基础上再提供了 HTML Entry
复习呵呵single-spa是怎么注册子应用领域的
singleSpa.registerApplication(
appName,
() => System.import(appName),
location => location.pathname.startsWith(appName),
);
即采用JS ENTRY 的形式接入微应用领域,也就是输出两个JS -> bootstrap -> mount -> unmount函数。
而,提供HTML ENTRY 入口来接入子应用领域的形式,更加简便
registerMicroApps([
{
name: react app, // 子应用领域名 entry: //localhost:7100, // 子应用领域 html 或网址 container: #yourContainer, // 挂载容器选择器 activeRule: /yourActiveRule, // 激活路由器},
]);
start();
然而,HTML Entry 并不是给个 HTML 的 url 就能直接接入整个子应用领域这么简单了。滞留的各式各样tag依然须要处理。所以就有了import-html-entry
import importHTML from import-html-entry;
importHTML(./subApp/index.html)
.then(res =>{
console.log(res.template); // 拿到 HTML 模板res.execScripts().then(exports => { // 执行 JS 脚本 const mobx = exports; // 下面就是拿到 JS 入口的内容,并用来做一些事 const{ observable } = mobx;
observable({
name: kuitos})
})
});
综上,读取子应用领域的伪代码如下
const {htmlText, jsText, cssText} = importHTMLEntry(https://xxxx.com)
// 创建容器const $= document.querySelector(container)
$container.innerHTML = htmlText
// 创建 style 和 js 标签const $style = createElement(style, cssText)
const $script = createElement(script, jsText)
$container.appendChild([$style, $script])
CSS和 JS 隔离
ShadowDOM :它能让两个 dom 拥有自己的“影子” DOM 树,这个 DOM 树不能在主文档中被任意访问,能拥有局部样式规则,天然同时实现了样式隔离,如上图所示,被代理的 dom节点称为 shadow host,shadow tree 中的根节点称为 shadow root。
SnapshotSandbox 快照沙箱
let sandbox = newSnapshotSandbox();
var a = 主应用领域A;
var c = 主应用领域C;
console.log(主应用领域原来的 Window:,a, c);
function beforeMounted(){
sandbox.active();
console.log(“读取子应用领域前”);
}
function beforeUnMounted(){
sandbox.inactive();
console.log(“卸载子应用领域前”);
}
function app1(){
beforeMounted();
window.a = app1A; // 修改 window.c = null; // 删掉 window.d = app1D; // 新增 console.log(“子应用领域的 Window:”,window.a, window.c, window.d);
beforeUnMounted();
}
app1();
console.log(主应用领域现在的 Window:, a, c, d);
主应用领域中声明两个变量 a 和 c,分别赋值主应用领域 A 和主应用领域 C,然后读取子应用领域之后对全局变量 a c 进行修改,并且新增 d,最后卸载时再打印 a c d。
沙箱快照的核心思想如下:在子应用领域挂载前,对当前主应用领域的全局变量保存,然后恢复之前的子应用领域环境,在子应用领域运行期间则正常 get 和 set,在卸载时保存当前变量恢复主应用领域变量,整个过程类似于中断和中断恢复。
但这里也有两个比较明显的缺点就是每次切换时须要去遍历 window,这种做法会有较大的天数耗用。在不支持 Proxy 的场景下会降级为 snapshotSandBox,如同他的名字一样。
更多的生命周期:beforeMount, afterMount, beforeUnmount, afterUnmount
这五个钩子能在主应用领域中注册子应用领域时采用。
和 single-spa 一样的是子应用领域的接入必须暴露三个生命周期(毕竟是基于 single-spa 同时实现的):
Bootstrap: 在这里做一些全局变量的初始化,比如不能在 unmount 阶段被销毁的应用领域级别的缓存。Mount:触发应用领域的渲染方法。Unmount:卸载微应用领域的应用领域实例。子应用预读取
子应用领域预读取是一种优化策略,采用 requestIdleCallback 通过天数切片的形式去读取静态天然资源,在浏览器空闲天数去执行回调函数,防止浏览器卡顿
全局状况管理工作
各个子应用领域须要和主应用领域进行通讯,以获得必要的信息,子应用领域间也可能会有少量的通讯须要,在 qiankun 中采用的是一种订阅发布商业模式的通讯方法。
全局错误处理
当运行中发生错误时,须要对其进行捕获,这里主要监听了 error 和 unhandledrejection 两个错误该事件。
window.addEventListener(error, errorHandler);
window.addEventListener(unhandledrejection, errorHandler);
布吕马反击和XSS安全漏洞的规避
安全漏洞的实例代码:
配置CORS(布吕马天然资源共享天然资源)策略:// Node.js Express示例const express = require(express);
constapp = express();
app.use((req, res, next) =>{
res.setHeader(Access-Control-Allow-Origin, http://localhost:3000); // 允许来自localhost:3000的请求 res.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE); // 允许的请求方法 res.setHeader(Access-Control-Allow-Headers, Content-Type, Authorization); // 允许的请求头next();
});
app.listen(8080, () => console.log(Server started on port 8080));
采用HTTPOnly Cookie:在设置Cookie时,将其标记为HTTPOnly,例如:
// Node.js Express示例app.use((req, res, next) =>{
res.setHeader(Set-Cookie, sessionId=123; HttpOnly); // 将sessionId标记为HTTPOnlynext();
});
对用户输入进行过滤和转义:在后端和后端对用户输入进行过滤和转义,例如:
// 后端示例:采用DOMPurify库对用户输入进行过滤import DOMPurify from dompurify;
constuserInput =<script>alert(“XSS attack!”);</script>;
constfilteredInput = DOMPurify.sanitize(userInput);
console.log(filteredInput); // 输出:alert(“XSS attack!”);// 后端示例:采用OWASP Java Encoder库对用户输入进行转义importorg.owasp.encoder.Encode;
String userInput = “<script>alert(\”XSS attack!\”);</script>”;
StringfilteredInput = Encode.forHtml(userInput);
System.out.println(filteredInput); // 输出:<script>alert("XSS attack!");</script>采用Content Security Policy(CSP):在HTTP响应头中设置CSP策略,限制页面中能读取的天然资源和脚本,例如:
// Node.js Express示例app.use((req, res, next) =>{
res.setHeader(Content-Security-Policy, “default-src self https://cdnjs.cloudflare.com”); // 允许读取来自同一域名和https://cdnjs.cloudflare.com的天然资源next();
});
采用HTTPS:采用HTTPS协议进行通讯,例如:
<!– 后端示例:采用https协议读取天然资源 –><script src=“https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js”></script>对模块进行权限控制:对相同的模块进行权限控制,限制相同的用户或角色能访问哪些模块,例如:
// 后端示例:采用Vue Router对路由器进行权限控制import { createRouter, createWebHistory } from vue-router;
constroutes = [
{ path: /, component: Home },
{ path: /dashboard, component: Dashboard, meta: { requiresAuth: true } }, // 须要登录才能访问的路由器 { path: /admin, component: Admin,meta: { requiresAdmin: true } }, // 须要管理工作员权限才能访问的路由器];
constrouter = createRouter({
history: createWebHistory(),
routes
});
router.beforeEach((to, from, next) =>{
const isAuthenticated = checkAuth(); // 检查用户是否已登录 const isAdmin = checkAdmin(); // 检查用户是否是管理工作员 if(to.meta.requiresAuth && !isAuthenticated) {
next(/login); // 如果须要登录才能访问,但用户未登录,则跳转到登录页面 } else if(to.meta.requiresAdmin && !isAdmin) {
next(/access-denied); // 如果须要管理工作员权限才能访问,但用户不是管理工作员,则跳转到访问拒绝页面 } else{
next(); // 否则允许访问}
});
总而言之,微后端这种合作开发经营理念特别适合两个部门把自己一系列的产品都集合在一同,也特别适合工程项目分模块重构等等。