Open LiuL0703 opened 2 years ago
众所周知,微前端是一种架构方式,与后端的微服务可以说是一脉相承。其核心思想就是"分而治之"。简单来说就是讲某单体应用解耦拆分为若干个子应用,这些子应用可以独立运行、开发、部署、和维护、且互不影响。且各个子应用可以选择不同的技术栈开发等等。目前在项目中有所尝试,下面内容就以实际业务为基础的微前端实践
说之前先来分析一下为什么需要微前端?
背景分析: 项目中的子应用越来越多,其中部分功能越来越复杂,加之系统开发中碰到的问题越来越多
开发过程中的痛点:
可以实现微前端的方案有很多,比较常见的一些微前端实现方案比如:iframe、Module Federation、动态路由等
iframe : 实现简单;可同时挂载多个应用;天然支持隔离,但是存在路由不同步,刷新页面状态丢失,通信困难只能通过postMessage等方式比较麻烦。
动态路由: 常见的实现方式比如single-spa 、qiankun等,通过统一维护所有应用的注册加载,挂载和卸载,针对不同技术栈的应用做聚合。但是通讯难,接入成本高。
Module Federation: 依赖webpack5,存在加载第三方依赖、版本控制等问题,会增加试错成本。
考虑到若选用上述方式,则会对项目本身带来较大影响,并且改造成本太高。 结合项目现状和改造成本,决定通过借鉴动态路由的思想实现通过以React为基座的特定动态路由的方案。对于动态路由的方式来说,关键部分就是注册、加载、挂载与卸载,由于我们的技术栈都是React,而且react-router自身就具备对组件挂载和卸载的能力。利用这一点优势,将所有子应用作为一个component,将挂载卸载的工作交由react-router来完成,也不用每次切换路由都重新初始化整个子应用,用户体验和单页应用保持一致。
todo
基座:从配置文件中读取配置信息,在路由注册层注册信息。当路由匹配到子应用后,请求获取到子应用的打包文件,由react-router进行加载和卸载的控制 子应用: 导出umd资源,上传到服务器,更新配置文件中对应的子应用信息
整个执行流程如图所示
在基座项目里创建子应用配置文件subApp.js
// container/scr/subApp.js const app = [ { name: 'subApp', // 子应用名称 host: process.env.REACT_APP_SUBAPP_HOST, // 子应用的静态资源地址 appName: 'App', // 导出的应用名 默认为'App' }, ... ]
引入subApp.js配置信息,通过AppManager进行子应用管理
// container/src/index.js function renderApp () { apps.forEach(AppManager.registerApp) ReactDOM.render(<App />,document.getElementById('root')); }
在AppManager中维护子应用信息,及加载子应用的loadSubApp函数,loadSubApp函数获取到子应用的资源文件地址后,通过添加script标签的方式加载资源,并将返回的资源组价挂载到window上
// container/src/AppManager.js class AppManager { subApps = {} subAppModules = {} registerApp = (subApp) => { this.subApps[subApp.name] = subApp } loadSubApp = (subAppInfo) => { const { name, host } = typeof subAppInfo === 'string'? this.subApps[subAppInfo] : subAppInfo if (!this.subAppModules[name]) { this.subAppModules[name] = new Promise((resolve, reject)=> { fetch(`${host}/${name}/asset-manifest.json`) .then(res => res.json()) .then(manifest => { const script = document.createElement('script'); script.src = `${host}${manifest.files['main.js']}`; const timeout = setTimeout(()=>{ console.error(`MicroApp ${name} timeout`); reject(new Error(`MicroApp ${name} timeout`)); }, 10000) script.onload = () => { clearTimeout(timeout) const app = window[name] console.log(`MicroApp ${name} loaded success`); resolve(app) } script.onerror = (e) => { clearTimeout(timeout); console.error(`MicroApp ${name} loaded error`, e); reject(e) } document.body.appendChild(script); // 加载css const link = document.createElement('link') link.rel = 'stylesheet' link.href = `${host}${manifest.files['main.css']}` document.head.appendChild(link) }) }) } return this.subAppModules[name] } }
什么是微前端
众所周知,微前端是一种架构方式,与后端的微服务可以说是一脉相承。其核心思想就是"分而治之"。简单来说就是讲某单体应用解耦拆分为若干个子应用,这些子应用可以独立运行、开发、部署、和维护、且互不影响。且各个子应用可以选择不同的技术栈开发等等。目前在项目中有所尝试,下面内容就以实际业务为基础的微前端实践
需求背景
说之前先来分析一下为什么需要微前端?
背景分析: 项目中的子应用越来越多,其中部分功能越来越复杂,加之系统开发中碰到的问题越来越多
开发过程中的痛点:
预期结果
技术方案
可以实现微前端的方案有很多,比较常见的一些微前端实现方案比如:iframe、Module Federation、动态路由等
iframe : 实现简单;可同时挂载多个应用;天然支持隔离,但是存在路由不同步,刷新页面状态丢失,通信困难只能通过postMessage等方式比较麻烦。
动态路由: 常见的实现方式比如single-spa 、qiankun等,通过统一维护所有应用的注册加载,挂载和卸载,针对不同技术栈的应用做聚合。但是通讯难,接入成本高。
Module Federation: 依赖webpack5,存在加载第三方依赖、版本控制等问题,会增加试错成本。
考虑到若选用上述方式,则会对项目本身带来较大影响,并且改造成本太高。 结合项目现状和改造成本,决定通过借鉴动态路由的思想实现通过以React为基座的特定动态路由的方案。对于动态路由的方式来说,关键部分就是注册、加载、挂载与卸载,由于我们的技术栈都是React,而且react-router自身就具备对组件挂载和卸载的能力。利用这一点优势,将所有子应用作为一个component,将挂载卸载的工作交由react-router来完成,也不用每次切换路由都重新初始化整个子应用,用户体验和单页应用保持一致。
架构设计
todo
实现
基座:从配置文件中读取配置信息,在路由注册层注册信息。当路由匹配到子应用后,请求获取到子应用的打包文件,由react-router进行加载和卸载的控制 子应用: 导出umd资源,上传到服务器,更新配置文件中对应的子应用信息
整个执行流程如图所示
todo
配置子应用信息
在基座项目里创建子应用配置文件subApp.js
管理子应用
引入subApp.js配置信息,通过AppManager进行子应用管理
在AppManager中维护子应用信息,及加载子应用的loadSubApp函数,loadSubApp函数获取到子应用的资源文件地址后,通过添加script标签的方式加载资源,并将返回的资源组价挂载到window上
总结
todo