《qiankun 微前端架构:umi 项目与非 umi 项目的接入与优化》

2,353 阅读9分钟

一、简介

在现代前端开发中,随着项目规模的不断扩大以及团队协作的日益复杂,传统的单体应用架构逐渐暴露出诸多问题,如代码维护困难、技术栈升级艰难、团队协作效率低下等。为了解决这些问题,微前端架构应运而生,而 qiankun 作为基于 single-spa 的微前端实现库,以其简单易用、高效灵活的特点,成为了众多开发者构建微前端架构系统的首选工具。它旨在帮助开发者更轻松、无痛地构建出生产可用的微前端架构系统,无论是从零开始搭建新项目,还是对现有项目进行微前端改造,都能提供强大的支持。

微前端架构的核心价值

微前端架构作为一种先进的前端开发理念,具备以下核心价值:

技术栈无关

主框架不对接入应用的技术栈进行限制,微应用拥有完全的自主权,可以自由选择适合自身业务的技术栈进行开发。这意味着不同的微应用可以使用不同的框架(如 React、Vue、Angular 等)或库来构建,从而充分发挥各自技术栈的优势,满足多样化的业务需求,同时也为团队提供了更大的灵活性,能够根据项目实际情况选择最合适的技术方案。

独立开发、独立部署

微应用的仓库是独立的,前后端可以独立进行开发,互不干扰。开发完成后,微应用可以独立进行部署,而主框架能够自动完成同步更新,无需手动干预。这种独立性极大地提高了开发效率,减少了团队之间的协作成本,使得各个微应用团队可以专注于自身的业务逻辑开发,而无需担心与其他应用之间的冲突和依赖问题。同时,独立部署的特性也使得微应用的更新和维护更加灵活,能够快速响应业务变化,及时发布新功能或修复问题。

增量升级

在实际开发中,我们常常会面临各种复杂场景,对于一个已经存在的系统,很难进行全量的技术栈升级或重构。而微前端架构提供了一种非常有效的渐进式重构手段和策略,允许开发者逐步对系统进行升级和优化,从而降低风险,提高系统的可维护性和可扩展性。通过将系统拆分为多个微应用,可以针对每个微应用独立进行技术升级或重构,而不会对整个系统造成过大影响,从而实现系统的平滑演进。

独立运行时

每个微应用之间实现了状态隔离,运行时状态不共享。这有效避免了不同应用之间的状态冲突和干扰,确保了微应用之间的独立性和稳定性。每个微应用都可以独立运行,互不依赖,从而提高了系统的整体可靠性和性能。

微前端架构主要针对单体应用在长时间发展过程中,由于参与人员和团队的增多、变迁,逐渐演变成巨石应用(Frontend Monolith)后所带来的不可维护性问题。这类问题在企业级 Web 应用中尤为常见,严重影响了项目的持续发展和团队的工作效率。而微前端架构通过将单体应用拆分为多个独立的微应用,解决了这些问题,使得项目能够更加健康、可持续地发展。

二、qiankun 接入核心要点

要成功接入 qiankun 并实现微前端架构,需要遵循以下核心要点:

1. 主子应用安装依赖

无论是基座应用还是子应用,都需要安装 qiankun 依赖。这是接入 qiankun 的基础,确保双方能够通过 qiankun 提供的接口和机制进行交互和协作。

2. 基座应用注册子应用

基座应用需要在项目入口文件中通过 registerMicroApps 方法注册子应用,并为其指定入口地址、挂载容器、匹配路由以及初始化通信数据等。这一步骤是让基座应用知晓子应用的存在,并为其后续的加载和渲染做好准备。

3. 子应用修改打包配置

子应用需要修改打包输出格式为 UMD(Universal Module Definition),以便能够作为库被基座应用加载和使用。同时,还需要解决开发环境中的跨域问题(CORS),可以通过配置代理或设置响应头等方式来实现。具体的配置方式可以参考 qiankun 官方文档,以确保子应用能够正常运行和与基座应用进行通信。

const { name } = require('./package');
module.exports = {
devServer: {
  headers: {
    'Access-Control-Allow-Origin': '*',
  },
},
configureWebpack: {
  output: {
    library: `${name}-[name]`,
    libraryTarget: 'umd', // 把微应用打包成 umd 库格式
    jsonpFunction: `webpackJsonp_${name}`, // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
  },
},
};

4. 子应用暴露生命周期钩子

子应用需要在其入口文件中暴露 qiankun 提供的生命周期钩子函数,如 bootstrapmountunmount 等。这些生命周期钩子函数分别在应用加载之前、挂载之前和卸载之后触发,用于执行一些初始化操作、渲染应用以及清理资源等任务。通过这些生命周期钩子,子应用可以更好地控制自身的加载、渲染和卸载过程,确保与其他应用的协作更加顺畅。

三、接入场景与接入流程

接入场景主要分为umi项目和非umi项目,原因是因为umi提供了@umijs/plugin-qiankun插件一键开启微前端。

场景一

  • 基座应用与子应用都非umi项目

接入流程

  1. 主子应用安装qiankun依赖
  2. 主应用在项目入口文件中注入子应用
import {registerMicroApps, start} from 'qiankun';

registerMicroApps([{
  name: 'child', // app name registered
  entry: '//localhost:8080', //子应用项目入口地址
  container: '#container',    //微前端挂在节点
  activeRule: '/',            //匹配路由
  props: {                    //初始化通信数据
    eventName: "TransferValue",
  }
}]);
start();   //开启qiankun
  1. 子应用入口文件暴露qiankun生命周期钩子函数
export async function bootstrap() {
  console.log(' react app bootstraped');
}

export async function mount(props) {
}

export async function unmount(props) {
}
  1. 子应用需要在mountunmount去找到应用的挂在点和应用的卸载点
function render(props) {
  const {container} = props;
  ReactDOM.render(<App/>, container ? container.querySelector('#root') : document.querySelector('#root'));
}

if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}               //判断应用是否为qiankun挂在

export async function mount(props) {
  console.log(props)
  render(props);
}

export async function unmount(props) {
  const {container} = props;
  ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
  1. 修改webpack(vite同理)打包配置,以下以create-react-app脚手架项目为例;安装react-app-rewired 依赖,然后在项目目录下新建config-overrides.js文件,配置打包,修改启动脚本。

1708508014131_D17B5784-D837-4851-A983-EDE1F3F073AC.png

1708508072380_D7C5FDFA-F902-4965-AEAB-6A066D7556E3.png

1708508097819_AAE65BC1-071B-454b-8CC3-B174B662C55B.png 6. 完成接入

场景二

  • 基座应用非umi项目
  • 子应用umi4

接入流程

  1. 基座应用接入流程不变
  2. 子应用umi4分为umi-maxumi-simple-appumi-simple-app子应用接入为例
  3. umi-simple-app安装umi插件集@umijs/plugins
  4. 在umi配置文件中开启qiankkun插件并且开启子应用qiankun配置
export default defineConfig({
  plugins: ["@umijs/plugins/dist/qiankun"],
  qiankun:{
    slave:{}
  },
});
  1. app.js文件中开启qiankun生命周期
export const qiankun = {
    // 应用加载之前
    async bootstrap(props: any) {
        console.log('app2 bootstrap', props);
    },
    // 应用 render 之前触发
    async mount(props: any) {
        console.log('app2 mount', props);
    },
    // 应用卸载之后触发
    async unmount(props: any) {
        console.log('app2 unmount', props);
    },
};
  1. 接入完成(umi-max内部集成qiankun插件、直接启动qiankun配置、暴漏出qiankun生命周期钩子即可)

场景三

  • 基座应用与子应用都是umi4

接入流程

  1. 基座应用和子应用同时安装@umijs/plugins,在umi配置文件中开启qiankun插件配置
 plugins: ["@umijs/plugins/dist/qiankun"]

umi-max跳过此步骤、umi-max集成了此插件

  1. 基座应用开启qiankun配置
   qiankun: {
        master: {
            apps: [
                {
                    name: "child1",
                    entry: "//localhost:3010"
                },
            ]
        }
    }
  1. 基座应用注入路由使用microApp匹配子应用
 routes: [{path: "/child1", microApp: "child1"}]

基座应用配置完毕

  1. 子应用开启qiankun配置、并注入路由
export default defineConfig({
  plugins: ["@umijs/plugins/dist/qiankun"],
  qiankun:{
    slave:{}
  },
  base:"/child2",
  routes: [
    { path: "/", component: "index" },
  ],
  npmClient: 'pnpm',
});

  1. 在app.js入口文件中暴漏qiankun生命周期
export const qiankun = {
    // 应用加载之前
    async bootstrap(props: any) {
        console.log('app2 bootstrap', props);
    },
    // 应用 render 之前触发
    async mount(props: any) {
        console.log('app2 mount', props);
    },
    // 应用卸载之后触发
    async unmount(props: any) {
        console.log('app2 unmount', props);
    },
};
  1. 接入流程完毕

小结

  • qiankun 的使用场景主要分为 umi 项目和非 umi 项目。无论是哪种类型的项目,都可以根据上述场景灵活搭配接入流程,实现微前端架构的构建。
  • 对于 umi 项目,无论是基座应用还是子应用,都可以利用 umi 提供的插件快速开启微前端配置,大大简化了接入流程,提高了开发效率。
  • 对于非 umi 项目,需要根据 qiankun 官方文档进行详细的配置和接入,虽然相对复杂一些,但也能实现微前端架构的构建,满足项目需求。

四、通信机制

在微前端架构中,通信是各个微应用之间协作的关键。对于 umi 项目,无论是基座应用还是子应用,核心都是通过 useModel 进行通信。参考:umijs.org/docs/max/da…

基座应用

在基座应用的 app.js 中,可以通过 useQiankunStateForSlave 函数暴露全局状态。例如:

export function useQiankunStateForSlave() {
  const [globalState, setGlobalState] = useState<any>({
    slogan: 'Hello MicroFrontend',
  });

  return {
    globalState,
    setGlobalState,
  };
}

子应用

子应用在需要使用全局状态的地方,可以通过 useModel 钩子直接获取基座应用传递的全局状态。例如:

const masterProps = useModel('@@qiankunStateFromMaster');

五、子应用打包成静态资源引入场景场景

qiankun的文档中entry支持配置成对象,html一定是html字符串,script可以配置为需要注入的脚本地址 如以下所示:

fetch('http://localhost:7001/dist/index.html') // 配置静态资源地址
  .then(response => response.text())
  .then(htmlString => {
    console.log(htmlString)
    registerMicroApps([{
      name: 'qiankun', // app name registered
      entry: {
        html: htmlString,
        scripts: ["http://localhost:7001/dist/umi.js", "http://localhost:7001/dist/p__index.async.js"] //需要注入的脚本地址
      },
      container: '#root-subapp-container', activeRule: '/test',
    }]);
    start();
  })
  .catch(error => {
    console.error('Error fetching the HTML file:', error);
  });

注意

  • 基座应用与子应用路由的模式需要同一,比如基座应用是hash路由,子应用也需要配置hash路由
  • 路由路径要同一(因为子应用需要基座应用注入的路由去进行匹配)