# vue3 + vite + qiankun **Repository Path**: ABCSDSD_123456/vue3-vite-qiankun ## Basic Information - **Project Name**: vue3 + vite + qiankun - **Description**: vue3 + vite + qiankun 实现主应用,接入 react18 和 vue3 微应用 - **Primary Language**: TypeScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 1 - **Created**: 2024-07-09 - **Last Updated**: 2025-02-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # vue3 + vite + qiankun #### 介绍 vue3 + vite + qiankun 或 react18 + vite + qiankun 或 react18 + vite + qiankun 实现主应用,接入 react18 和 vue3 微应用 # 具体实现 # 以 vue3+vite+qiankun 为例实现微前端架构 ## 项目源码地址: > 项目源码地址:https://gitee.com/ABCSDSD_123456/vue3-vite-qiankun ## 项目初始化 ### 第一步 新建项目文件夹 /qiankun-micro-frontEnd ![项目结构](./img/微前端-qiankun实现-1.png) ### 第二步 初始化项目生成 package.json 文件 ```cmd # 生成 package.json 文件 npm init ``` ```json // package.json 文件配置如下 { "name": "package.json", "version": "1.0.0", "description": "使用 vue3 为主应用,react18 和 vue3 为微应用的 qiankun 微前端实现", "scripts": { "dev": "cd main-app && vite", "dev:sub-app-vue3": "cd sub-app-vue3 && vite", "dev:sub-app-react18": "cd sub-app-react18 && vite", "serve": "cd main-app && vite", "serve:sub-app-vue3": "cd sub-app-vue3 && vite", "serve:sub-app-react18": "cd sub-app-react18 && vite", "build": "cd main-app && vite build", "build:sub-app-vue3": "cd sub-app-vue3 && vite build", "build:sub-app-react18": "cd sub-app-react18 && vite build", "preview": "cd main-app && preview", "preview:sub-app-vue3": "cd sub-app-vue3 && preview", "preview:sub-app-react18": "cd sub-app-react18 && preview" }, "author": "jianhaijiyusheng", "license": "MIT" } ``` ## 主应用配置 ### 第一步 初始化一个 vue3 + vite 项目并在项目中安装 qiankun > vue3 + vite 项目框架代码示例:[vue-project-framework(gitee.com)](https://gitee.com/ABCSDSD_123456/vue-project-framework) ```cmd npm i qiankun # 当前示例所使用的 qiankun 版本为 2.10.16 ``` ### 第二步 在 src/utils 下新建一个 micro-app.ts 文件用于存放微前端配置 ```ts // micro-app.ts 文件内容如下: const microApps = [{ name: 'subVueAPP', // 子应用的唯一 id entry: '//localhost:3001', // 子应用的访问链接 activeRule: '/vue-app', // 当访问的 url 中匹配到 activeRule 中的参数,就会到跳转到对应的子应用 }, { name: 'subReactAPP', entry: '//localhost:3002', activeRule: '/react-app' }]; const apps = microApps.map(item => { return { ...item, container: "#sub-app", // 用于声明 子应用 应该挂载到 主应用 中的哪个 DOM 下 }; }); export default apps; ``` ### 第三步 配置主应用中的 main.ts ```ts import { createApp } from 'vue' import './style.scss' import App from './App.vue' import router from './router/index' import { createPinia } from 'pinia' import { Http } from '@/utils/http' import components from '@/components/index' import i18n from '@/i18n/index' import 'element-plus/dist/index.css' import 'element-plus/theme-chalk/display.css' import ElementPlus from 'element-plus' import directive from '@/directive' import '@/plugin/last-commit-msg/toggle.js' import { mockRuqest } from './mock' import { registerMicroApps, start } from 'qiankun' import microApp from './utils/micro-app' import '@/utils/qiankun-action' Http.init() if (import.meta.env.MODE === 'mock') { mockRuqest() } const store = createPinia() const app = createApp(App) // ---- qiankun 主要配置 ------- // 配置微应用所需参数 registerMicroApps(microApp) // 使用 qiankun 启动项目 start() app.use(ElementPlus) app.use(store) app.use(router) app.use(components) app.use(i18n) app.use(directive) app.mount('#app') ``` ### 第四步 增加子应用启动页面 micro-app.vue ```vue // src/micro-app/micro-app.vue 文件内容如下 ``` ### 第四步 配置主应用路由 ```ts import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' import Layout from '@/page/layout/layout.vue' const routes: Array = [{ path: '/', redirect: '/index', }, { path: '/', name: 'layout', component: Layout, children: [{ path: 'index', name: 'index', component: () => import('@/page/main-app/index/index.vue'), }, { path: 'about', name: 'about', component: () => import('@/page/main-app/about/about.vue'), }, { path: 'setting', name: 'setting', component: () => import('@/page/main-app/setting/setting.vue'), }, { path: 'user-info', name: 'userInfo', component: () => import('@/page/main-app/user-info/user-info.vue'), }, { // vue 子应用路由 path: 'vue-app/index', name: 'vueApp', component: () => import('@/page/micro-app/micro-app.vue'), // 子应用中转配置页面,用于启动子应用 }, { // react 子应用路由 path: 'react-app/index', name: 'reactApp', component: () => import('@/page/micro-app/micro-app.vue'), // 子应用中转配置页面,用于启动子应用 }] }] const router = createRouter({ history: createWebHistory(), routes, }) router.beforeEach((to, from, next) => { next() }) export default router ``` ### 第五步 主应用路由跳转逻辑 > 跳转子应用可以使用路由方式跳转,也可以使用 window.history.pushState 跳转 > > 1、state:一个与添加的记录相关联的状态对象,主要用于popstate事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填null。 > > 2、title:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。 > > 3、url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。 > > window.history.pushState(null, '', '/vue-app/index') ```vue ``` ## 子应用配置(vue3 + vite) ### 第一步 安装插件 ```cmd npm i vite-plugin-qiankun ``` ### 第二步 配置 vite.config.ts ```ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' import VueSetupExtend from 'vite-plugin-vue-setup-extend' import qiankun from 'vite-plugin-qiankun' export default defineConfig({ base: '/vue-app/', plugins: [ // ...此处省略其他插件 // qiankun(name, options) name 的取值需要和 主应用 @/utils/micro-app.ts 配置的 name 保持一致 qiankun('subVueAPP', { useDevMode: true }) ], build: { outDir: 'dist/sub-app-vue3' } }) ``` ### 第三步 配置 main.ts ```ts import { createApp } from 'vue' import './style.scss' import App from './App.vue' import router from './router/index' import { createPinia } from 'pinia' import { Http } from '@/utils/http' import components from '@/components/index' import i18n from '@/i18n/index' import 'element-plus/dist/index.css' import 'element-plus/theme-chalk/display.css' import ElementPlus from 'element-plus' import directive from '@/directive' import { renderWithQiankun, qiankunWindow } from "vite-plugin-qiankun/dist/helper" Http.init() let app: any = null const render = (props?: any) => { const { container } = props ?? {} const store = createPinia() app = createApp(App) app.use(ElementPlus) app.use(store) app.use(router) app.use(components) app.use(i18n) app.use(directive) app?.mount(container ? container.querySelector('#container') : '#container'); } renderWithQiankun({ async mount(props: any): Promise { // await props.onGlobalStateChange((state: any) => { // console.log("子应用接收的参数", state); // state.publicPath && window.localStorage.setItem("mainJumpPublicPath", state.publicPath); // }, true); return new Promise((resolve, reject) => { resolve('mount'); }) }, bootstrap(): Promise { console.log("%c", "color:green;", " ChildOne bootstrap"); return new Promise((resolve, reject) => { resolve('bootstrap') }) }, update() { console.log("%c", "color:green;", " ChildOne update"); }, unmount(props: any) { console.log("sub app vue3 unmount", props); app.unmount(); app._container.innerHTML = ""; app = null; // 如果 子应用 是直接挂载到 主应用的 #app DOM 下, // 那从 子应用 通过浏览器回退按钮回退到 主应用 时会出现白屏,刷新后又正常显示 // 这是因为通过浏览器回退按钮回退到 主应用 时主应用的 main.ts 文件不会重新执行,所以 DOM 被重新加载 // 可以在子应用卸载后刷新页面 // window.location.reload() } }); // 判断是否是 qiankun 渲染 if (!qiankunWindow.__POWERED_BY_QIANKUN__) { render() } ``` ## 子应用配置(react18) ### 第一步 配置基本 > 使用的插件和vite.config.ts 文件中的配置同上述 vue3 > > 但是如果在 vite.config.ts 中使用 @vitejs/plugin-react 会如下错误,在 qiankun 中运行时需要删除 ``` [import-html-entry]: error occurs while executing normal script ``` ### 第二步 配置main.tsx 文件 ```tsx import './public-path.js' import ReactDOM from 'react-dom/client'; import App from './App.tsx'; import './style.less'; import { BrowserRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { ConfigProvider } from 'antd'; import store from '@/store'; import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'; const render = (props: any = {}) => { const { container } = props; ReactDOM.createRoot(container ? container.querySelector('#root')! : document.getElementById('root')!).render( ); }; if (!qiankunWindow.__POWERED_BY_QIANKUN__) { render({}); } renderWithQiankun({ async mount(props: any): Promise { render(props); // await props.onGlobalStateChange((state: any) => { // // console.log('子应用接收的参数', state); // }); return new Promise((resolve, reject) => { resolve('mount') }) }, bootstrap(): Promise { console.log('%c', 'color:green;', ' ChildOne bootstrap'); return new Promise((resolve, reject) => { resolve('bootstrap'); }); }, update() { console.log('%c', 'color:green;', ' ChildOne update'); }, unmount(props: any) { console.log('sub app vue3 unmount', props); // const { container } = props; // ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); // window.location.reload() }, }); ```