Bootstrap

vite项目使用qiankun构建hash路由微前端


前言

本文主要介绍 hash路由 模式的微前端,项目都是基于vite构建,采用qiankun架构,实现方式主要分为两种类型:

  1. 主应用使用react@18 + react-router-dom@6,微应用分别使用react@18 + react-router-dom@6、vue3 + vue-router4
  2. 主应用使用vue3 + vue-router4,微应用分别使用react@18 + react-router-dom@6、vue3 + vue-router4

一、主应用使用react@18 + react-router-dom@6

1、项目安装

使用vite构建主应用项目,主应用需要安装qiankun,源码地址

代码如下(示例):

构建项目
pnpm create vite react-app --template react-ts
安装路由
pnpm add react-router-dom
安装qiankun
pnpm add qiankun

2、主应用中注册微应用

代码如下(示例):主应用main.tsx入口文件添加如下代码

registerMicroApps(
  [
    {
      name: "microReactApp",// 子应用名称
      entry: "http://localhost:8081",// 子应用地址
      container: "#subapp-viewport",// 子应用挂在主应用的某个id标签下
      activeRule: "#/microReactApp",// 子应用触发的路由匹配,hash模式需要(#)
    },
    {
      name: "microVueApp",
      entry: "http://localhost:8082",
      container: "#subapp-viewport",
      activeRule: "#/microVueApp",
    },
  ],
  {
    beforeLoad: [
      (app) => {
        console.log("[LifeCycle] before load %c%s", "color: green;", app.name);
        return Promise.resolve();
      },
    ],
    beforeMount: [
      (app) => {
        console.log("[LifeCycle] before mount %c%s", "color: green;", app.name);
        return Promise.resolve();
      },
    ],
    afterUnmount: [
      (app) => {
        console.log(
          "[LifeCycle] after unmount %c%s",
          "color: green;",
          app.name
        );
        return Promise.resolve();
      },
    ],
  }
);
start();

3、主应用中设置路由和挂载子应用的组件

代码如下(示例):主应用路由设置匹配微应用,MicroContain 组件是用来挂在子应用

const router = createHashRouter(
[
  {
     path: "microReactApp/*",
     element: <MicroContain />,
  },
  {
     path: "microVueApp/*",
     element: <MicroContain />,
  },
]
)
// MicroContain 组件
const MicroContain = () => {
  return (
    <>
      //subapp-viewport就是上面registerMicroApps的containerz字段用到的id
      <div id="subapp-viewport" /> 
    </>
  );
};
export default MicroContain;

二、创建react@18 + react-router-dom@6子应用

1、项目安装

使用vite构建主应用项目,子应用不需要安装qiankun,但是子应用需要安装vite-plugin-qiankun插件,用来解决引用module的错误,源码地址

代码如下(示例):

构建项目
pnpm create vite micro-react-app --template react-ts
安装路由
pnpm add react-router-dom
安装qiankun
pnpm add vite-plugin-qiankun -D

2、修改子应用 vite.config.ts

代码如下(示例):

import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import qiankun from "vite-plugin-qiankun";

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), "");
  const isDev = env.VITE_APP_ENV == "development";

  return {
    plugins: [
    // microReactApp 要和主应用注册子应用时的名称一样(registerMicroApps的name属性)
      qiankun("microReactApp", {
        useDevMode: isDev,
      }),
      !isDev && react(),
    ],
    server: {
      port: 8081,
    },
  };
});

3、修改子应用 main.tsx,区分qiankun环境和独立部署环境

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import {
  renderWithQiankun,
  qiankunWindow,
} from "vite-plugin-qiankun/dist/helper";

// ReactDOM.createRoot(document.getElementById("root")!).render(
//   <React.StrictMode>
//     <App />
//   </React.StrictMode>
// );
let instance: any;

const render = (props?: any) => {
  const { container } = props;
  const rootDom = container
    ? container.querySelector("#root")
    : document.querySelector("#root");

  instance = ReactDOM.createRoot(rootDom!);

  instance.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
};

// some code
renderWithQiankun({
  mount(props) {
    console.log("mount");
    render(props);
  },
  bootstrap() {
    console.log("bootstrap");
  },
  unmount(props: any) {
    console.log("unmount", props);
    instance.unmount();
  },
  update(props: any) {
    console.log("system app update", props);
    // console.log(props)
  },
});

if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
  render({});
}

4、子应用路由需要增加basename配置,用来匹配qiankun环境

代码如下(示例):

import { qiankunWindow } from "vite-plugin-qiankun/dist/helper";
const router = createHashRouter(
[
	{
	    index: true, // 默认加载的子路由 使用 index 代替 path
        element: <Home />,
	}
],
{
    basename: qiankunWindow.__POWERED_BY_QIANKUN__ ? "/microReactApp" : "/",
}
)

三、创建vue3 + vue-router4子应用

1、项目安装

使用vite构建主应用项目,子应用不需要安装qiankun,但是子应用需要安装vite-plugin-qiankun插件,用来解决引用module的错误,源码地址

代码如下(示例):

构建项目
pnpm create vite micro-vue-app --template vue-ts
安装路由
pnpm add vue-router
安装qiankun
pnpm add vite-plugin-qiankun -D

2、修改子应用 vite.config.ts

代码如下(示例):

import { defineConfig, loadEnv } from "vite";
import path from "path";
import vue from "@vitejs/plugin-vue";
import qiankun from "vite-plugin-qiankun";

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), "");
  const isDev = env.VITE_APP_ENV == "development";
  return {
    resolve: {
      alias: {
        "@": path.resolve(__dirname, "./src"),
      },
    },
    plugins: [
        // microVueApp要和主应用注册子应用时的名称一样(registerMicroApps的name属性)
      qiankun("microVueApp", {
        useDevMode: isDev,
      }),
      vue(),
    ],
    css: {
      preprocessorOptions: {
        less: {
          javascriptEnabled: true,
        },
      },
    },
    server: {
      port: 8082,
    },
  };
});

3、修改子应用 main.ts,区分qiankun环境和独立部署环境

代码如下(示例):路由列表

const routes = [
  {
    path: "/",
    name: "home",
    component: () => import("@/views/home/index.vue"),
  }, // 懒加载
  {
    path: "/about",
    name: "about",
    component: () =>
      import(/* webpackChunkName: "about-chunk" */ "@/views/about/index.vue"),
  }, // 懒加载 - 加分包about-chunk
  {
    path: "/route",
    name: "route",
    component: () => import("@/views/route/index.vue"),
  }, // 懒加载
];

代码如下(示例):

import { App, createApp } from "vue";
import "./style.css";
import AppComponent from "./App.vue";
import {
  renderWithQiankun,
  qiankunWindow,
} from "vite-plugin-qiankun/dist/helper";
import routes from "./router";
import { createWebHashHistory, createRouter } from "vue-router";

let app: App;
function render(props: any) {
  const { container } = props;

  let newRoutes = routes;
  if (qiankunWindow.__POWERED_BY_QIANKUN__) {
    /**
     * 这里的路由需要缓存到pinia或者localstorage,用于渲染菜单
     * routes多级菜单需要做递归处理,这个只展示一级
     */
    newRoutes = routes.map((item) => {
      return {
        ...item,
        path: `/microVueApp${item.path}`,
      };
    });
  }

  const router = createRouter({
    history: createWebHashHistory(),
    routes: newRoutes,
  });

  app = createApp(AppComponent);
  app.use(router).mount(container ? container.querySelector("#app") : "#app");
}

// some code
renderWithQiankun({
  mount(props) {
    console.log("mount");
    render(props);
  },
  bootstrap() {
    console.log("bootstrap");
  },
  unmount(props: any) {
    console.log("unmount", props, app);
    app.unmount();
  },
  update(props: any) {
    console.log("system app update", props);
    // console.log(props)
  },
});

if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
  render({});
}

四、主应用使用vue3 + vue-router4

1、项目安装

使用vite构建主应用项目,主应用需要安装qiankun,源码地址

代码如下(示例):

构建项目
pnpm create vite vue-app --template vue-ts
安装路由
pnpm add react-router-dom
安装qiankun
pnpm add qiankun

2、主应用中注册微应用

代码如下(示例):主应用main.ts入口文件添加如下代码,和react主应用相比,在这里没有执行start(),应为刷新时会报错挂载的节点没有加载,所以在挂载的组件中执行start(),

import { App, createApp } from "vue";
import "./style.css";
import AppComponent from "./App.vue";
import router from "./router";
import { registerMicroApps } from "qiankun";

createApp(AppComponent).use(router).mount("#app");

registerMicroApps(
  [
    {
      name: "microReactApp",
      entry: "http://localhost:8081",
      container: "#subapp-viewport",
      activeRule: "#/microReactApp",
    },
    {
      name: "microVueApp",
      entry: "http://localhost:8082",
      container: "#subapp-viewport",
      activeRule: "#/microVueApp",
    },
  ],
  {
    beforeLoad: [
      (app) => {
        console.log("[LifeCycle] before load %c%s", "color: green;", app.name);
        return Promise.resolve();
      },
    ],
    beforeMount: [
      (app) => {
        console.log("[LifeCycle] before mount %c%s", "color: green;", app.name);
        return Promise.resolve();
      },
    ],
    afterUnmount: [
      (app) => {
        console.log(
          "[LifeCycle] after unmount %c%s",
          "color: green;",
          app.name
        );
        return Promise.resolve();
      },
    ],
  }
);

3、主应用中设置路由和挂载子应用的组件

代码如下(示例):主应用路由设置匹配微应用,micro组件是用来挂在子应用

import { createWebHashHistory, createRouter } from "vue-router";

const routes = [
  {
    path: "/",
    name: "home",
    component: () => import("@/views/home/index.vue"),
  }, // 懒加载
  {
    path: "/about",
    name: "about",
    component: () =>
      import(/* webpackChunkName: "about-chunk" */ "@/views/about/index.vue"),
  }, // 懒加载 - 加分包
  {
    path: "/route",
    name: "route",
    component: () => import("@/views/route/index.vue"),
  }, // 懒加载
  {
    path: "/microVueApp/:pathMatch(.*)*",
    // path: "/microVueApp/:chapters*",
    name: "microVueApp",
    component: () => import("@/views/micro/index.vue"),
  }, // 懒加载
  {
    path: "/microReactApp/:pathMatch(.*)*",
    // path: "/microVueApp/:chapters*",
    name: "microReactApp",
    component: () => import("@/views/micro/index.vue"),
  }, // 懒加载
];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

export default router;

代码如下(示例):挂在组件

<template>
  <div id="subapp-viewport"></div>
</template>

<script setup lang="ts">
import { start } from "qiankun";
import { onMounted } from "vue";

onMounted(() => {
  if (!(window as any)?.qiankunStarted) {
    (window as any).qiankunStarted = true;
    start();
  }
});
</script>


总结

在使用两种不同的主应用去分别挂在两个子应用,发现两个大的区别

  1. 主应用qiankun的start()开启的地方
  • react项目可以在入口文件main.tsx中注册子应用后直接开启start()。
  • vue项目只能在组件挂在的时候去开启start(),否则会在页面刷新的时候,报错不存在挂在节点。
  1. 就是vue-router4和react-dom-router两个路由作为子应用的区别
  • 子应用中react-dom-router只需要在路由创建的时候添加basename,就可以很轻松配合主应用qiankun注册时的activeRule实现子应用的路由点击
  • 子应用vue-router4需要修改整个路由map,添加activeRule前缀,如果是已有的项目改造,就比较麻烦,需要递归路由表,还有路由组件的route-link的to地址,封装编程式导航的事件
;