微前端[vue3 + vite + qiankun] 使用详解
本文用于记录qiankun结合vite搭建微前端项目完整过程
主要步骤如下:
1.主、子需要安装哪些依赖?
2.如何配置主、子应用?
3.如何应用间传参?
4.如何部署各个应用?
项目结构
└── micro-app
├── main-app # 主应用
├── app1 # 子应用1
├── app2 # 子应用2
1.主、子应用配置及需要的依赖
1.1 依赖
主应用安装 qiankun,子应用安装 vite-plugin-qiankun
1.2 配置
1.如何使用qiankun
2.如何配置vite
3.如何配置路由
1.2.1 主应用
-
新建 src/micro/index.ts 文件,注册子应用
// main > micro > index.ts import { registerMicroApps, start } from "qiankun"; import type { ObjectType, RegistrableApp } from "qiankun"; const appConfig: RegistrableApp<ObjectType>[] = [ { name: "app1", entry: "http://localhost:5555/", container: "#app", activeRule: "/app1", }, { name: "app2", entry: "http://localhost:5556/", container: "#app", activeRule: "/app2", }, ]; registerMicroApps(appConfig); start({ sandbox: { experimentalStyleIsolation: true, }, }); -
初始化
// main > src > main.ts // 在main.ts中导入 import "../micro"; -
路由配置
// main > router > index.ts import { createRouter, createWebHistory } from "vue-router"; const importViews = () => import(`../layout/index.vue`); // 使用动态路由方式会报错 import AppContainer from "../layout/index.vue"; const router = createRouter({ history: createWebHistory(), routes: [ { name: "home", path: "/", redirect: "/app1/home", }, { name: "app1Home", path: "/app1/home", component: AppContainer, }, { name: "app1about", path: "/app1/about", component: AppContainer, }, { name: "app2Home", path: "/app2/home", component: AppContainer, }, { name: "app2about", path: "/app2/about", component: AppContainer, }, ], }); export default router;
1.2.2 子应用
-
vite 配置
// app1 > vite.config.ts > index.ts import { fileURLToPath, URL } from "node:url"; import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import vueJsx from "@vitejs/plugin-vue-jsx"; import qiankun from "vite-plugin-qiankun"; // https://vite.dev/config/ export default defineConfig({ plugins: [ vue(), vueJsx(), qiankun("app1", { useDevMode: true, }), ], server: { port: 5555, cors: true, }, resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)), }, }, }); -
接入 qiankun 导出生命周期函数
// app1 > src > main.ts import { renderWithQiankun, qiankunWindow, type QiankunProps, } from "vite-plugin-qiankun/dist/helper"; function render(props: QiankunProps = {}) { const { container } = props; const app = createApp(App); app.use(createPinia()); app.use(router); container ? app.mount(container.querySelector("#app") as HTMLElement) : app.mount("#app"); } function initApp() { if (!qiankunWindow.__POWERED_BY_QIANKUN__) { console.log("%c 独立渲染", "color: red; font-size: 20px;"); render(); return; } renderWithQiankun({ mount(props) { console.log("%c qiankun 渲染", "color: red; font-size: 20px;"); console.log(props); render(props); }, bootstrap() { console.log("bootstrap"); }, unmount(props) { console.log("unmount", props); }, update(props) { console.log("update", props); }, }); } initApp(); -
路由配置
各页面路径需跟主应用路由路径保持一直
// app1 > src > router > index.ts import { createRouter, createWebHistory } from "vue-router"; import HomeView from "../views/HomeView.vue"; const router = createRouter({ history: createWebHistory(), routes: [ { name: "home", path: "/", redirect: "/app1/home", }, { path: "/app1/home", name: "app1Home", component: HomeView, }, { path: "/app1/about", name: "app1about", component: () => import("../views/AboutView.vue"), }, ], }); export default router;
2.应用传参
应用传参有一下几种情况
1.主 -> 子
2.子 -> 主
3.子 -> 子
2.1 主 -> 子
-
主应用定义全局共享数据
// main > src > stores > global.ts import { defineStore } from "pinia"; export const useGlobalState = defineStore("globalState", { state: () => { const userInfo = ref({ name: "张三", age: 18, }); return { userInfo, }; }, actions: { changeUserInfo(userInfo: any) { this.userInfo = userInfo; }, }, }); -
主应用通过 props 传参
// main > src > micro > store > index.ts import { useGlobalState } from "@/stores/globalState"; import { storeToRefs } from "pinia"; import { watch } from "vue"; export const initMicroAppStoreProps: Record<string, any> = { app1: { handleProps(app: any) { /** * 主 < -- > 子 应用通信 * * 实现: * 1. watch * 2. pinia */ const _globalStore = useGlobalState(); app["props"]["globalData"] = _globalStore; app["props"]["onChangeMainStore"] = ( callbackFn: (globalStore: any) => void ) => { watch( () => _globalStore.$state, () => { callbackFn(_globalStore); }, { deep: true, } ); }; }, }, }; -
子应用接受数据
// app1 > src > main.ts import "./assets/main.css"; import { createApp, ref } from "vue"; import { createPinia } from "pinia"; import App from "./App.vue"; import router from "./router"; import { renderWithQiankun, qiankunWindow, type QiankunProps, } from "vite-plugin-qiankun/dist/helper"; let app: null | any = null; const globalStore = ref(null); function setGlobalStore(store: any) { globalStore.value = { ...store }; } function render(props: QiankunProps = {}) { const { container } = props; app = createApp(App); app.use(createPinia()); app.use(router); container ? app.mount(container.querySelector("#app") as HTMLElement) : app.mount("#app"); } function initApp() { if (!qiankunWindow.__POWERED_BY_QIANKUN__) { console.log("%c app1 独立渲染", "color: red; font-size: 20px;"); render(); return; } renderWithQiankun({ bootstrap() {}, async mount(props) { await render(props); setGlobalStore(props.globalData); props.onChangeMainStore((val: any) => { setGlobalStore(val); }); app.provide("globalStore", globalStore); }, unmount(props) { app.unmount(); app._container = ""; app = null; globalStore.value = null; }, update(props) {}, }); } initApp(); -
子应用页面中使用数据
<!-- app1 > src > views > home.vue --> <template> <main> <h1>app1 home</h1> <p>name: {{ globalStore.userInfo.name }}</p> <p>age: {{ globalStore.userInfo.age }}</p> <button @click="changeUserInfo">changeUserInfo</button> </main> </template> <script setup lang="ts"> import { inject, watch } from "vue"; const globalStore = inject("globalStore") as any; const changeUserInfo = () => { globalStore.value.changeUserInfo({ name: "李四", age: globalStore.value.userInfo.age + 20, }); }; </script>
2.1 子 -> 主
也是一样的道理
在主应用先定义数据,然后子应用调用主应用的方法改变数据,实现传递到主应用
传参流程:
主应用 --> 子应用1 --> 主应用
2.1 子 -> 子
也是一样的道理
在主应用先定义数据,然后子应用调用主应用的方法改变数据,实现传递到主应用,
然后再传给其他子应用
传参流程:
主应用 --> 子应用1 --> 主应用 --> 子应用2
3.部署
使用所有应用部署在同一个 nginx 的方式部署,方便更新升级管理各个应用
-
nginx 配置文件
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 97; server_name localhost; location / { root html; index index.html index.htm; try_files $uri $uri/ /index.html; } # 子应用 app1 的静态资源路径 location ^~ /app1/ { alias html/app1/; try_files $uri $uri/ /app1/index.html; if (!-e $request_filename) { rewrite ^/app1/(.*)$ /index.html last; } } # 子应用 app2 的静态资源路径 location ^~ /app2/ { alias html/app2/; try_files $uri $uri/ /app2/index.html; if (!-e $request_filename) { rewrite ^/app2/(.*)$ /index.html last; } } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }