手把手教你定制一套适合团队的微前端体系

编者按:大家在使用目前市面上的微前端解决方案时,可能会有些顾虑。比如遇到框架自身的问题和坑点,影响了业务进度怎么办?现在有这么一款框架 Satum,可以像 express/koa 框架一样提供中间件机制,其只负责核心的功能(规则计算和微应用加载/卸载)。可以基于该框架定制一套适合团队自身业务的微前端体系,另外该框架还有更多特性,如面向多实例集成、适配多终端等。

写在前面

随着前端工程复杂度逐渐增加,业务复杂的前端项目需要拆分成多个项目来维护。还有就是业务存在了 3-5 年,一些技术栈没及时升级,已经无法在其基础上做开发,需要搞个新项目一起集成。又或者是想通过新业务,对新技术/框架最新大版本试用。遇到上述场景(当然不限这些场景,还有更多场景待挖掘),大家也许会把目光转向微前端方案。

市面上也有一些微前端框架的实现,但黑盒化太严重。遇到问题/坑点,只能等框架的作者修复了才能正常工作。但业务开发时真正遇到了问题,如果寄希望于框架作者/社区,会不会感到凉凉?

本篇我们基于 Satum 定制一套微前端体系出来。 Satum 是一款面向多实例集成、功能可插拔的微前端框架,旨在解决多实例模式的微前端场景(当然单实例也包含其中)。即多个微应用被同时激活时,该如何协调加/卸载数据依赖组件共享渲染顺序的问题。且支持中间件&插件机制,可以很方便自定义沙箱路由协调缓存等。通过不同的中间件&插件组合,可以定制适合自己团队的微前端架构体系。

Satum 的优势(相较于社区常用的方案 qiankunmicro-app):

✅:完全支持, ❌:不支持, ⭕️:弱支持
Satum qiankun micro-app
使用简单,无需适配代码即可集成 无需适配 强依赖 需要
扩展性,控制流程中的输入或输出 ⭕️
定制化,支持定制适合业务的微前端体系
多应用同时激活,微应用调度和路由协调 支持 极弱 规则限制
挂载点基于路由规则,同个应用可渲染到页面不同处
多终端支持,相同 URL 在不同端激活不同的微应用
共享机制,支持共享三方包和组件
区块机制,可以加载任意 UI 组装页面
支持 Vite,沙箱环境中无缝支持

微前端体系

微前端体系包含微前端框架、配置中心、微应用开发配套工具及配套服务、物料市场、低代码平台等。
体系示例图如下:
手把手教你定制一套适合团队的微前端体系_第1张图片

带来的好处

微前端体系带来的好处不言而喻,简单说来,使用该体系可以将一个大型项目拆分成多个子项目,每个子项目各自独立,互不影响,又互相关联。
核心价值是什么呢?

  • 技术栈无关
    主框架不限制接入应用的技术栈,微应用具备完全自主权
  • 独立开发、独立部署
    微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  • 增量升级
    在面对各种复杂场景时,通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
  • 独立运行时
    每个微应用之间状态隔离,运行时状态不共享

    所需的工程板块

    从上述体系架构图可以很清楚地看到,我们需要以下工程板块:

    微前端框架

    建议利用 @satumjs/core 的定制化能力,打造适合团队的微前端框架。不太建议使用市面上黑盒化的微前端框架。这样做,可以利用其中间件/插件机制,定制个性化的微应用处理逻辑。

    配置中心

    所谓配置中心,其实是一个可视化的 json 编辑。如果有个线上的配置中心,可以灵活控制业务板块的热更新。有些业务需求,不需要修改和发布代码就能达到目的。

    微应用开发配套工具及配套服务

    配套工具和服务太重要了,因为我们开发的微应用不是完整的业务,有些功能模块甚至是从其他微应用共享而来。虽然微应用可以独立运行和部署,但开发过程中,还是需要及时看到集成后的效果。

    物料市场

    这块主要是基础组件和业务组件,可以是基于 ant-design 或其他 design 搞的组件家族。

    低代码平台

    微前端能够集成低代码平台生成的页面,我觉得也很必要。这样的好处是扬长避短,低代码平台一般不太建议搭建业务复杂的页面,所以单独存在的低代码平台意义有限;而纯代码打造的平台工期较长,需要的前端能力也较高;如果能够做到简单页面低代码实现,复杂逻辑纯代码实现,然后集成在一起,是最理想的方案。

    使用后达到的目标

    1、微应用合理分拆,独立运行和部署,降低整体复杂度;
    2、技术库无关,可以根据业务需要尝试各种组件库和技术栈;
    3、增量升级,新老系统可以集成在一起,新系统负责新业务功能;
    4、多终端适配,Satum 支持同一个 URL 不同终端唤起不同的微应用;
    5、单一职责,让诸如 React/Vue 等只做视图方面的事情,业务逻辑在其他微应用上承载;
    6、可自行定制沙箱、代码处理、路由协调等功能;

    如何定制

    初始化项目

    新建文件夹,添加必要的依赖

    新建文件夹 mf2e-test,通过命令 echo {} > package.jsonyarn init初始化 package.json。
    我们使用 rollup 做构建工具,需要先安装构建相关的依赖。

    yarn add rollup @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-dts rollup-plugin-terser @babel/cli @babel/core @babel/preset-env @babel/preset-typescript typescript rimraf --dev

    之后再创建构建相关的配置文件 .babelrcrollup.config.jstsconfig.json

    {
    "presets": [
      "@babel/typescript",
      "@babel/preset-env"
    ]
    }
    import commonjs from '@rollup/plugin-commonjs';
    import { nodeResolve } from '@rollup/plugin-node-resolve';
    import { babel } from '@rollup/plugin-babel';
    import { terser } from 'rollup-plugin-terser';
    import dts from "rollup-plugin-dts";
    import * as pkg from './package.json';
    
    const extensions = ['.js', '.ts'];
    const plugins = [
    commonjs(),
    nodeResolve({ extensions }),
    babel({ extensions, include: ['./src/**/*'] }),
    terser(),
    ];
    
    const umdConfig = {
    input: 'src/index.ts',
    // 此处 name 是会在 window 上附加,需注意请自定义
    output: { dir: 'lib', name: 'mf2eTest', format: 'umd' },
    plugins,
    };
    
    const esConfig = {
    input: 'src/index.ts',
    external: Object.keys(pkg.dependencies),
    output: { file: 'lib/index.es.js', format: 'es' },
    plugins,
    };
    
    const dtsConfig = {
    input: './src/index.ts',
    output: { file: './lib/index.d.ts', format: 'es' },
    plugins: [dts()]
    }
    
    export default [umdConfig, esConfig, dtsConfig];
    {
    "compilerOptions": {
      "module": "es2015",
      "target": "es5",
      "lib": ["esnext", "dom"],
      "baseUrl": "./src",
      "esModuleInterop": true,
      "noImplicitAny": true,
      "strictNullChecks": true,
      "noImplicitReturns": true,
      "noFallthroughCasesInSwitch": true,
      "noUnusedLocals": false,
      "noUnusedParameters": true,
      "suppressImplicitAnyIndexErrors": true,
      "allowSyntheticDefaultImports": true,
      "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

    这时候我们可以安装框架逻辑需要的依赖了,执行下面命令:

    yarn add @satumjs/core @satumjs/simple-midwares @satumjs/midware-single-spa

    我们可以测试下 es6 的源码是否能够正常构建出 es5 的代码,当然在此之前 package.json里要添加一些 scripts

    "scripts": {
    "build": "rimraf lib && rollup -c",
    "build:w": "npm run build -- -w",
    "pub": "npm run build && npm publish --access public"
    },

    之后请在 index.ts里添加如下代码,然后执行命令 yarn build可以看到能正常生成代码。

    import { register, start } from '@satumjs/core';
    export { register, start };

    那么到此,恭喜你,初始化工作告一段落,我们提交一下代码。

    添加必要的中间件

    @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';
    import singleSpaMidware from '@satumjs/midware-single-spa';
    
    use(simpleSandboxMidware);
    use(mountNodeMidware);
    use(singleSpaMidware);
    
    export { register, start };

    做完这一步,其实我们可以执行一下 yarn link,搞个本地示例工程出来直接看该框架的实际运行情况了。本地示例工程请参考 https://stackblitz.com/edit/github-gacap7,换一下依赖 @satumjs/coremf2e-test即可。

    添加微应用本地调试工具

    所谓调试工具,其本质是获取到微应用的配置后,如果调试 whiteList 里包含该微应用,则使用 whiteList 里的 entry 地址,而这个地址可以指向本地启动的微应用。所以我们继续在 index.ts里添加逻辑,我以 whiteList 存储在 localStorage 里为例。

    use((system, microApps, next) => {
    system.set(MidwareName.proxyEntry, (entry: string | string[], appName: string) => {
      const proxyMap = {};
      const proxySetting = localStorage.getItem('proxyEntries') || '';
      const proxyData = proxySetting.replace(/\s/g, '').split(',');
      
      proxyData.forEach((item) => {
        const [itemAppName, proxyUrl] = item.split('|');
        if (itemAppName && proxyUrl) proxyMap[itemAppName] = proxyUrl;
      });
    
      const currentApp = microApps.find((item) => item.name === appName);
      return currentApp && proxyMap[appName] ? proxyMap[appName] : entry;
    });
    next();
    });

    这样你往浏览器的 localStorage 里添加形如 micro-name|micro-entry-url规则,当框架加载微应用时就会使用本地启动的。多个可以以 ,隔开。

    添加跨域服务,以方便远程非跨域微应用集成

    好多时候,我们把微应用部署到开发、测试、预发等环境,可能是直接部署到了 cdn 上。这些环境并不支持跨域,进而影响集成。我们可以自己搞个跨域服务出来,做一下中转,然后方便微应用集成。框架层面上也要做下支持,其实本质是遇到微应用的 entry,为其加一层跨域中转请求。

    use((system, _, next) => {
    system.set(MidwareName.urlOption, {
      corsRule: `https://cors-server-test.com/?target=${corsRuleLabel}`,
    });
    next();
    });

    代码编写到这个地方,恭喜你,微前端框架部分就完成了。不过作为框架,需要让用户可以传一些参数进来,控制一些中间件的行为。整体代码如下:

    import {
    register,
    start as coreStart,
    use,
    MidwareName,
    corsRuleLabel,
    KeyObject,
    } from '@satumjs/core';
    import {
    simpleSandboxMidware,
    mountNodeMidware,
    simpleCacheMidware,
    } from '@satumjs/simple-midwares';
    import singleSpaMidware from '@satumjs/midware-single-spa';
    
    use((system, microApps, next) => {
    system.set(
      MidwareName.proxyEntry,
      (entry: string | string[], appName: string) => {
        const proxyMap = {};
        const proxySetting = localStorage.getItem('proxyEntries') || '';
        const proxyData = proxySetting.replace(/\s/g, '').split(',');
    
        proxyData.forEach((item) => {
          const [itemAppName, proxyUrl] = item.split('|');
          if (itemAppName && proxyUrl) proxyMap[itemAppName] = proxyUrl;
        });
    
        const currentApp = microApps.find((item) => item.name === appName);
        return currentApp && proxyMap[appName] ? proxyMap[appName] : entry;
      }
    );
    next();
    });
    
    function start(options: KeyObject) {
    const { enableCache, corsServerUrl, ...opts } = options || {};
    
    if (enableCache) use(simpleCacheMidware, opts);
    
    use((system, _, next) => {
      system.set(MidwareName.urlOption, {
        corsRule: `${corsServerUrl}?target=${corsRuleLabel}`,
      });
      next();
    });
    
    use(simpleSandboxMidware, opts);
    use(mountNodeMidware, opts);
    use(singleSpaMidware, opts);
    
    coreStart(opts);
    }
    
    export { register, start };

微前端框架示例仓库请访问 https://github.com/satumjs/mf2e-test

创建配置中心

最简单的是,发一个 json 文件到 cdn。当然可以使用 nodejs 搭建一个可视化编排 json 的配置中心出来,这样可以做到不用修改代码,即可上下线微应用。

物料市场

可以使用业界开源的 x-design 来做基础组件库,根据业务定制一些业务组件出来。这些组件可以在微应用里通过共享,分发到其他子应用。

低代码平台

需要自行实现该平台,然后基于 satumjs 强悍的集成能力,把分散的页面集成到一个网站下。

后续

本文通过代码的方式一步步定制出一套微前端框架(示例仓库 https://github.com/satumjs/mf2e-test),然后又把该框架周边最常用的工具的实现展现了出来。还有一些工程板块,可以自行搭建,也可以使用公司或开源的。
如果大家对 Satum 感兴趣,欢迎访问其官网 https://satumjs.github.io/website 了解更多。如果对中间件和插件感兴趣,官网上也有一些资料。建议大家加微信群或钉钉群,方便后续交流。感谢大家耐心阅读这篇文章~

你可能感兴趣的:(前端微前端前端架构)