编者按:当你在使用目前市面上的微前端方案时,可能会有一些顾虑。比如遇到框架本身的问题和陷阱,影响业务进度怎么办?现在有这样一个框架Satum,可以像express/koa框架一样提供中间件机制,只负责核心功能(规则计算和微应用加载/卸载)。基于这个框架,可以定制一套适合团队自身业务的微前端系统。此外,该框架还具有更多特性,例如多实例集成和适配多终端。写在前端随着前端工程的复杂度逐渐增加,业务复杂的前端项目需要拆分成多个项目进行维护。另外,业务已经存在了3-5年,有些技术栈没有及时升级,无法再在其基础上发展,需要一个新的项目一起整合。或者你想通过新业务尝试最新版本的新技术/框架。当遇到以上场景时(当然不限于这些场景,还有更多的场景需要去探索),你可能会把目光转向微前端的解决方案。市面上也有一些微前端框架的实现,但是黑盒太严重了。如果遇到问题/坑点,需要框架作者修复后才能正常工作。但是当业务开发真的遇到问题的时候,如果把希望寄托在框架作者/社区上,你会觉得爽吗?在本文中,我们基于Satum定制了一个微前端系统。Satum是一个可插拔的多实例集成微前端框架,旨在解决多实例模式(当然也包括单实例)的微前端场景。即多个微应用同时启动时,如何协调加载/卸载、数据依赖、组件共享、渲染顺序。并且支持中间件&插件机制,可以轻松自定义沙箱、路由协调、缓存等,通过不同的中间件&插件组合,定制适合自己团队的微前端架构体系。Satum的优点(相对于社区常用的qiankun和微app):?:完全支持,?:不支持,??:弱支持需要Scalability来控制流程中的输入或输出????自定义,支持定制适合业务的微前端系统???可以同时启动多个应用,微应用调度和路由协同支持极弱规则根据路由规则限制挂载点,可以渲染同一个应用到页面的不同部分???多端支持,同一个URL在不同端激活不同的微应用???分享机制,支持分享三方包和组件强弱块机制,可以加载任何UI组装页面???支持Vite,沙箱环境下无缝支持???微前端系统微前端系统包括微前端框架、配置中心、微应用开发支持ols及配套服务、素材市场、低代码平台等。系统示例图如下:好处微前端系统带来的好处是不言而喻的。简单的说,使用这个系统可以将一个大型项目拆分成多个子项目,每个子项目都是独立的,互不影响。并且相互关联。核心价值观是什么?技术栈独立主框架不限制接入应用的技术栈。微应用具有完全的自主性和独立开发、独立部署的能力。微应用仓库独立,前后端可独立开发。对于各种复杂的场景,通常很难升级或重构现有系统的整个技术栈,而微前端是实现渐进重构的一个很好的手段和策略。应用之间的状态隔离,运行时状态不共享需要的工程版块从上面的架构图可以清楚的看出我们需要以下工程版块:微前端框架推荐使用@的定制能力satumjs/core构建适合团队的微前端框架。不推荐使用市面上的黑盒微前端框架。这样做,您可以使用它的中间件/插件机制来定制个性化的微应用处理逻辑。配置中心所谓配置中心,其实就是一个可视化的json编辑器。如果有在线配置中心,可以灵活控制业务部门的热更新。有些业务需求不需要修改和发布代码就可以实现。微应用开发的配套工具和配套服务配套工具和服务太重要了,因为我们开发的微应用并不是完整的业务,有些功能模块甚至是其他微应用共享的。虽然微应用可以独立运行部署,但在开发过程中还是需要及时看到集成效果。素材市场主要由基础组件和业务组件组成,可以是基于ant-design或其他设计的组件族。我觉得低代码平台的微前端也有必要能够集成低代码平台生成的页面。这样做的好处是扬长避短。一般不推荐低代码平台构建复杂的业务页面,所以单独存在的低代码平台意义有限;纯代码构建的平台,构建周期较长,对前端能力要求较高;如果你能做到,用低代码实现简单的页面,用纯代码实现复杂的逻辑,然后将它们集成在一起,是最理想的方案。使用后实现的目标1.微应用合理拆分,独立运行部署,降低整体复杂度;2、技术库无关,可以根据业务需要尝试各种组件库和技术栈;3、增量升级,新旧系统可以集成在一起,新系统负责新的业务功能;4、多终端适配,Satum支持同一个URL不同终端唤起不同的小程序;5.单一职责,让React/Vue等只做视图的事情,业务逻辑在其他微应用上承载;6、可以自定义沙箱、代码处理、路由协调等功能;如何自定义初始化工程新建文件夹,添加必要依赖新建文件夹mf2e-test,使用命令echo{}>package.json或yarninit初始化package.json。我们使用rollup作为构建工具,需要先安装构建相关的依赖。纱线添加汇总@rollup/plugin-babel@rollup/plugin-commonjs@rollup/plugin-node-resolverollup-plugin-dtsrollup-plugin-terser@babel/cli@babel/core@babel/preset-env@babel/preset-typescripttypescriptrimraf--dev然后创建构建相关的配置文件。babelrc,rollup.config.js,tsconfig.json{"presets":["@babel/typescript","@babel/preset-env"]}从'@rollup/plugin-commonjs'导入commonjs;import{nodeResolve}从'@rollup/plugin-node-resolve';从'@rollup/plugin-babel'导入{babel};从'rollup-plugin-terser'导入{terser};从“rollup-plugin-dts”导入dts;import*aspkgfrom'./package.json';constextensions=['.js','.ts'];constplugins=[commonjs(),nodeResolve({extensions}),babel({extensions,include:['./src/**/*']}),terser(),];constumdConfig={input:'src/index.ts',//这里的名字会附在window上,注意请自定义output:{dir:'lib',name:'mf2eTest',format:'嗯'},插件,};constesConfig={输入:'src/index.ts',external:Object.keys(pkg.dependencies),output:{file:'lib/index.es.js',format:'es'},plugins,};constdtsConfig={input:'./src/index.ts',output:{file:'./lib/index.d.ts',format:'es'},plugins:[dts()]}exportdefault[umdConfig,esConfig,dtsConfig];{"compilerOptions":{"module":"es2015","target":"es5","lib":["esnext","dom"],"baseUrl":"./src","esModuleInterop":真,“noImplicitAny”:真,“strictNullChecks”:真,“noImplicitReturns”:真,“noFallthroughCasesInSwitch”:真,“noUnusedLocals”:假,“noUnusedParameters”:真,“suppressImplicitAnyIndexErrors”:真,“allowSyntheticDefaultImports”:真,“emitDecoratorMetadata”:true,“experimentalDecorators”:true,“removeComments”:true,"sourceMap":true,"declaration":true,"outDir":"./lib","allowJs":true,"moduleResolution":"Node"}}创建源代码文件夹src,添加入口文件index.ts创建文件夹src作为源代码文件夹,并在其中添加入口文件index.ts。完成这些事情后,我们的文件夹应该如下所示:mf2e-test|-src|-index.ts|-.babelrc|-package.json|-rollup.config.js|-tsconfig.json现在我们可以安装框架逻辑需要的依赖,执行如下命令:yarnadd@satumjs/core@satumjs/simple-midwares@satumjs/midware-single-spa我们可以测试es6的源码能否正常构建es5的代码,当然在此之前我们需要在package.json中添加一些脚本"scripts":{"build":"rimraflib&&rollup-c","build:w":"npmrunbuild---w","pub":"npmrunbuild&&npmpublish--accesspublic"},然后在index.ts中添加如下代码,并然后执行命令yarnbuild可以看到正常生成代码了。从'@satumjs/core'导入{注册,开始};导出{注册,开始};那么到这里,恭喜你,初始化工作结束了,我们来提交代码吧。添加必要的中间件@satumjs/simple-midwares是中间件的集合,包括缓存、沙箱、css隔离、获取和挂载dom、路由协调等常用的中间件,我们可以先使用沙箱,获取和挂载两者dom的中间件。然后我们还使用@satumjs/midware-single-spa中间件来帮助管理微应用的加载和卸载(这个中间件可以不用,内置了一个简单的single-spa功能,但是强烈推荐).Index.ts修改代码如下:import{register,start,use}from'@satumjs/core';import{simpleSandboxMidware,mountNodeMidware,}from'@satumjs/simple-midwares';importsingleSpaMidwarefrom'@satumjs/midware-single-spa';使用(simpleSandboxMidware);使用(mountNodeMidware);使用(singleSpaMidware);导出{注册,开始};这一步之后,我们就可以实际执行yarnlink,创建一个本地示例工程,直接看框架实际运行情况。本地示例项目请参考https://stackblitz.com/edit/github-gacap7,只需将依赖@satumjs/core更改为mf2e-test即可。添加小程序本地调试工具所谓调试工具的本质就是获取小程序的配置信息。如果微应用包含在调试白名单中,则使用白名单中的入口地址,该地址可以指向本地启动的微应用。所以我们继续在index.ts中添加逻辑,我以存储在localStorage中的whiteList为例。use((system,microApps,next)=>{system.set(MidwareName.proxyEntry,(entry:string|string[],appName:string)=>{constproxyMap={};constproxySetting=localStorage.getItem('proxyEntries')||'';constproxyData=proxySetting.replace(/\s/g,'').split(',');proxyData.forEach((item)=>{const[itemAppName,proxyUrl]=item.split('|');if(itemAppName&&proxyUrl)proxyMap[itemAppName]=proxyUrl;});constcurrentApp=microApps.find((item)=>item.name===appName);returncurrentApp&&proxyMap[appName]?proxyMap[appName]:entry;});next();});这样,你在浏览器的localStorage中添加一个类似micro-name|micro-entry-url这样的规则,当框架加载微应用时,它就会在本地启动。多个可以用,隔开。添加跨域服务,方便集成远程非跨域微应用很多时候,我们将微应用部署到开发、测试、预发布等环境中,可能直接部署在CDN上。这些环境不支持跨域,影响集成。我们可以自己创建一个跨域服务,做一些中转,然后方便微应用集成。还需要框架级别的支持。其实本质就是在微应用的入口加上一层跨域传输请求。use((system,_,next)=>{system.set(MidwareName.urlOption,{corsRule:`https://cors-server-test.com/?target=${corsRuleLabel}`,});next();});代码写到这里,恭喜,微前端框架部分完成。但是作为一个框架,用户需要传入一些参数来控制一些中间件的行为。整体代码如下:import{register,startascoreStart,use,MidwareName,corsRuleLabel,KeyObject,}from'@satumjs/core';import{simpleSandboxMidware,mountNodeMidware,simpleCacheMidware,}from'@satumjs/simple-midwares';importsingleSpaMidware来自'@satumjs/midware-single-spa';use((system,microApps,next)=>{system.set(MidwareName.proxyEntry,(entry:string|string[],appName:string)=>{constproxyMap={};constproxySetting=localStorage.getItem('proxyEntries')||'';constproxyData=proxySetting.replace(/\s/g,'').split(',');proxyData.forEach((item)=>{const[itemAppName,proxyUrl]=item.split('|');if(itemAppName&&proxyUrl)proxyMap[itemAppName]=proxyUrl;});constcurrentApp=microApps.find((item)=>item.name===appName);returncurrentApp&&proxyMap[appName]?proxyMap[appName]:entry;});next();});functionstart(options:KeyObject
