关于微前端框架wujie的一次企业级应用实践demo?

前言

本文将介绍我一种wujie的一次具体的应用,包括使用的场景、方式等等,完成一个具体的demo;

为什么要用微前端

事情是这样的,我们之前的业务有一个vue3+ts+vite的后台项目,后来公司决定新开发一个新的业务线,但是由于人力有限,如果重新搭建一个新的后台时间和人力成本较大都,尤其是其中的权限登录功能的设计都比较复杂,所以我们综合考虑,有没有一种可以直接用旧后台的权限和登录功能,然后其它功能完全隔离的,且旧后台和新后台可用两个部门的人来开发,可以独立开发、测试、部署,甚至技术栈也可以不受影响呢?这里我们想到了微前端方案;

微前端方案选择

我们经过调研,目光逐步瞄向了两种微前端的方案:无界乾坤

对比我们的业务,经过调研发现无界相比于乾坤更有优势:

  • 1、对旧后台项目影响较小,侵入程度低:只需要在旧有后台的项目上新起page页,以及新增一个路由即可;
  • 2、可单独开发、部署:子应用可以单独开发、部署,也可以使用一个全新的技术栈,即使生产环境无界挂了,出现问题了,也可以直接访问子应用;

综上两种原因,我们决定使用无界的方案;

怎么用无界(demo演示)

我们的主应用是vue3,这里将子应用通过菜单栏的形式嵌入到父应用中间,点击菜单即可进入到子应用

登录场景 ,在子应用请求时,若发现登录失效,通过子组件通信window.$wujie.bus.$emit('notLogin')向父应用传递未登录消息,父应用执行后续逻辑

权限逻辑 ,天然就互通,当子应用的菜单权限在某些角色下不可见时,在父应用下直接隐藏掉菜单就行;如果是子应用下按钮权限等功能权限时,可在子应用单独再次调用权限接口,或通过父子应用通信方式获取权限信息

具体步骤

父应用改造

  • 下载新依赖
  • wujie相关文件
  • 路由

下载相关依赖

js 复制代码
pnpm install wujie-vue3

创建wujie文件

用于补充wujie的相关逻辑:

  • wujietemplate相关属性
    • name: 子应用唯一标识
    • url: 子应用运行地址
    • props:向子应用传递的参数
  • 父子应用通信
    • 通知子应用路由发生改变
    • 通知子应用其他数据
    • 子应用告知父应用未登录
    • 子应用告知父应用其他信息
js 复制代码
<template>
  <div class="main-app">
    <h1>Vue3 主应用</h1>
    <!-- 嵌入 React 子应用 -->
    <WujieVue width="100%" height="600px" :url="subAppUrl" :name="subAppName" :props="subAppProps" />
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useRouter } from "vue-router";
import { watch } from "vue";
import WujieVue from "wujie-vue3";

const { bus } = WujieVue;

// 子应用配置(React 子应用的运行地址,后续启动子应用后会用到)
const subAppName = ref("react-sub-app"); // 子应用唯一标识(必须唯一)
const subAppUrl = ref("http://localhost:1074/#/wujieDemo1"); // 子应用端口(后续配置 React 子应用为 3001)

// 主应用向子应用传递的 props(可选)
const subAppProps = ref({
  mainAppName: "Vue3 主应用",
  token: "main-app-token-123",
});

const router = useRouter();
/** 监听子应用的数据 */
bus.$on("subAppData", (data: { type: string, payload?: any }) => {
  const { type } = data;
  if (type == "noLogin") {
    alert("未登录")
  }
});

/** 监听子应用的数据 */


watch(
  () => router.currentRoute.value.meta.subAppPath,
  (newVal) => {
    if (newVal === undefined) return;
    bus.$emit("routeChange", newVal);
  },
  {
    immediate: true,
  }
);
</script>

创建wujie路由

这里新建了一个路由的文件wujieRouter.ts

通过监听subAppPath去判断跳转到子应用对应路由,且这里的subAppPath其实对应的是子应用的路由path

js 复制代码
const routerName = "wujiePage";

const wujieRouters = [
  {
    path: `/${routerName}`,
    name: `${routerName}`,
    component: () => import("@/pages/wujie/index.vue"),
    meta: {
      title: '新项目-react', // 菜单显示文本
      icon: 'CreditCard', // 菜单图标
      hidden: false,
      level: 0,
    },
    children: [
      {
        path: "wujieDemo1", // 子路由直接使用相对路径,不要包含父路由名称
        name: `${routerName}wujieDemo1`, // 名称保持唯一,不要使用斜杠
        component: () => import("@/pages/wujie/wujie.vue"),
        meta: {
          title: 'wujieDemo1', // 菜单显示文本
          icon: 'Present', // 子菜单图标
          hidden: false,
          level: 1,
          subAppPath: "/wujieDemo1",
        },
      },
      {
        path: "wujieDemo2", // 子路由直接使用相对路径,不要包含父路由名称
        name: `${routerName}wujieDemo2`, // 名称保持唯一,不要使用斜杠
        component: () => import("@/pages/wujie/wujie.vue"),
        meta: {
          title: 'wujieDemo2', // 菜单显示文本
          icon: 'Present', // 子菜单图标
          hidden: false,
          level: 1,
          subAppPath: "/wujieDemo2",
        },
      },
    ]
  },

]

export default wujieRouters;

子应用改造

  • 运行环境判断
  • 路由通信
  • 嵌入子页面
  • 路由
  • 接口响应拦截器

运行环境判断

这里我们在main.tsx文件通过判断window.$wujie属性是否存在,来判断当前的运行环境是独立运行还是微前端环境

原理wujie会自动给子应用的window上挂载一个$wujie对象

js 复制代码
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { HashRouter } from "react-router-dom";
import "./style/index.css";
import App from "./App";

import { Provider } from "react-redux";
import { store } from "./model/store";

// Wujie 子应用生命周期:挂载(主应用嵌入时调用)
const mount = (container: HTMLElement | ShadowRoot, props: any) => {
  // 将主应用 props 存入 React 上下文(方便子应用内部使用)
  createRoot(container).render(
    <StrictMode>
      <Provider store={store}>
        <HashRouter>
          <App {...props} />
        </HashRouter>
      </Provider>
    </StrictMode>
  );
};

// 判断是否在 Wujie 微前端环境中
if (window.$wujie) {
  mount(document.getElementById("root")!, window.$wujie.props);
} else {
  // 独立运行环境(正常启动)
  mount(document.getElementById("root")!, {
    mainAppName: "独立运行",
    token: "local-token",
  });
}

路由通信

app.tsx文件中修改

子应用监听到父应用的路由发生了改变,立即进行路由跳转

js 复制代码
import { router } from "./router/createRouteConfig";
import { useNavigate, useRoutes } from "react-router-dom";
import useLocationChange from "./router/useLocationChange";
import routerListener from "./router/routerListener";
import "./style/index.css";
import { useEffect } from "react";

const App = function (props: any) {
  const elements = useRoutes(router);
  const navigate = useNavigate();

  useEffect(() => {
    const wujieBus = window.$wujie?.bus;
    const routeChangeHandler = (path: string) => {
      navigate(path);
    };
    wujieBus?.$on("routeChange", routeChangeHandler);
    return () => {
      wujieBus?.$off("routeChange", routeChangeHandler);
    };                                                                                                                               
  }, [navigate]);

  useLocationChange((to, from) => {
    routerListener(navigate, to, from);
  });
  return elements;
};

export default App;

嵌入的子页面

新建立一个文件用于放嵌入的子页面,且在该子页面中还可以向父应用通信

js 复制代码
const wujieDemo1 = () => {

  return (
    <div>
      <h1>我是子应用(react)的wujieDemo1</h1>
      <button onClick={() =>  window.$wujie?.bus.$emit("subAppData", "我是子应用数据")}>向主应用提交数据</button>
    </div>
  );
};

export default wujieDemo1;

路由

新建路由用于对应上面的子页面

其中需要注意的是,路由的path需要对应父应用路由上的subAppPath

js 复制代码
......
  {
      name: "wujieDemo1",
      path: "/wujieDemo1",
      component: lazy(() => import("../page/wujiePage/wujieDemo1/index")),
      isMenu: false,
    },
......

接口响应拦截器

在响应拦截器中,主要是针对未登录的场景,在未登录时,告知父应用

这里也做了运行环境的判断,用于判断是进入子应用的登录页面还是父应用的登录页面

js 复制代码
// 将方法封装成一个函数
const http = async (config: IAxiosParam): Promise<any> => {
  return request(config)
    .then((res: IResponse) => {
      switch (res.code) {
        case ResCode.notLogin:
          // 未登录
          if (window.$wujie) {
            window.$wujie?.bus.$emit("subAppData", {
              type: "noLogin"
            })
          } else {
            window.location.href = "/login";
          }
          break;
      }

      if (res.code !== 0 && !config.noAlert) {
        // 异常提示
        alert(res.msg || "出现问题啦~");
        return;
      }
      return config.needRes ? res : res.data;
    })
    .catch((res) => {
      return Promise.reject(res);
    });
};

总结

这里我完成了一个基础的demo,在时间的应用还有一些需要注意或优化的点:

  • 子应用的运行地址可配置化
  • 子应用的预加载与保活
  • 多个子应用的配置

后续可根据自己的实际场景来配置

相关推荐
崔庆才丨静觅10 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606111 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了11 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅11 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅11 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅12 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊12 小时前
jwt介绍
前端
爱敲代码的小鱼12 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax