当前位置: 首页 > article >正文

微前端 qiankun vite vue3

文章目录

    • 简介
    • 主应用 qiankun-main vue3 vite
    • 子应用 qiankun-app-vue2 webpack5
    • 子应用 qiankun-react webpack5
    • 子应用 quankun-vue3 vite
    • 遇到的问题

在这里插入图片描述

简介

主要介绍以qiankun框架为基础,vite 搭建vue3 项目为主应用,wepack vue2 和 webpack react 搭建的子应用,形成的一个微前端框架。

主应用 qiankun-main vue3 vite

先用vite 创建一个vue3的标准框架 vue-router typescript 这些都可以选上

yarn create vue

主应用还要装上qiankun依赖

yarn add qiankun

创建一个qiankun-config.ts的文件

import { registerMicroApps, start } from 'qiankun'

const beforeLoad: (...args: any[]) => any = (app: any) => {
    console.log('before load', app)
}
const beforeUnmount: (...args: any[]) => any = (app: any) => {
    console.log('after unmount', app)
}
const beforeMount: (...args: any[]) => any = (app: any) => {
    console.log('before mount', app)
}
export function registerApps() {
    try {
        registerMicroApps(
            [
                {
                    name: 'app-vue3', // 子应用名称,跟package.json一致
                    entry: '//localhost:7001', // 子应用入口,本地环境下指定端口
                    container: '#sub-container', // 挂载子应用的dom
                    activeRule: '/app/app-vue3', // 路由匹配规则
                    props: {}, // 主应用与子应用通信传值
                },
                {
                    name: 'app-react',
                    entry: '//localhost:3000',
                    container: '#sub-container',
                    activeRule: '/app/app-react',
                    props: {},
                },
                {
                    name: 'app-vue2',
                    entry: '//localhost:7003',
                    container: '#sub-container',
                    activeRule: '/app/app-vue2',
                    props: {},
                }
            ],
            // 生命周期函数 
            {
                beforeLoad: [beforeLoad],
                beforeMount: [beforeMount],
                afterUnmount: [beforeUnmount],
            },
        )
        start({
            sandbox: {
                experimentalStyleIsolation: true, // 沙箱样式隔离
            },
        })
    } catch (err) {
        console.log(err)
    }
}

将上面的文件引入main.ts中

import './assets/main.css'
import { registerApps } from './qiankun-config'
import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(createPinia())
app.use(router)

app.mount('#qiankun') // 记得把html的挂载id dom改一改

registerApps()

创建一个专门用来存放子应用的组件 SubContainer.vue

<script setup lang="ts">
</script>

<template>
  <h1>Container</h1>
  <div id="sub-container"></div>
</template>

改造主应用的路由router/index.ts

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
// import Layout from '../layout/index.vue'
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView,
    },
    {
      // history模式需要通配所有路由,详见vue-router文档
      path: '/app/app-vue3/:pathMatch(.*)*',
      name: 'app-vue3',
      meta: {},
      component: () => import('@/views/SubContainer.vue'),
    },
    {
      path: '/app/app-react/:pathMatch(.*)*',
      name: 'app-react',
      meta: {},
      component: () => import('@/views/SubContainer.vue'),
    },
    {
      path: '/app/app-vue2/:pathMatch(.*)*',
      name: 'app-vue2',
      meta: {},
      component: () => import('@/views/SubContainer.vue'),
    },
  ],
})

export default router

主应用到这里开发环境基本就改造完成了 yarn dev启动开发环境服务器

子应用 qiankun-app-vue2 webpack5

安装vue2 webpack的项目
全局先安装vue-cli

npm install -g @vue/cli
# OR
yarn global add @vue/cli

配置电脑的环境变量 让vscode能够正确使用vue命令符
https://blog.csdn.net/yi_zongjishi/article/details/124831223
上述引用的文章里说以管理员权限启动vscode来处理vscode不能使用vue 的情况有点问题,实际上是以管理员权限启动powershell来执行那些命令后就可以了。

安装vue2框架

vue create qiankun-vue2

安装vue-router
vue2只能安装vue-router@3

yarn add vue-router@3

改webpack的output配置 和devServer配置
由于vue.confg.js存在,所以改造它就等于改造webpack

// const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package')
module.exports = {
  devServer: {
    port: 7003, // 与主应用配置子应用的端口号一致就行
    headers: {
      'Access-Control-Allow-Origin': '*', // 处理开发环境因为端口号不同而产生的跨域问题
    },
  },
  configureWebpack: {
  // output为quankun官方要求的必须的
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      chunkLoadingGlobal: `webpackJsonp_${name}`,
    },
  },
  transpileDependencies: true,
}

根目录下创建一个名为 public-path.js的文件

// 在window上为qiankun挂载关键参数
if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

在main.js第一行引入,并对main.js改造

import './public-path'
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// import router from './router'
// import store from './store'

Vue.config.productionTip = false // 关闭生产提示

let instance = null

function render(props = {}) {
  const { container } = props
  instance = new Vue({
    router,
    // store,
    render: h => h(App)
    // container 表示 是qiankun主应用访问,限制一下搜索子应用挂载dom的范围,最好能改一下这个id的名称
  }).$mount(container ? container.querySelector('#vue-app2') : '#vue-app2')
}

// 如果不是qiankun主应用下访问,而是子应用独立部署,正常执行render
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

// 根据qiankun官方要求,子应用需要导出三个生命周期钩子函数bootstrap mount unmount
export async function bootstrap() {
  console.log('[vue] vue app bootstraped')
}
export async function mount(props) {
  console.log('[vue] props from main framework', props);
  render(props)
}
export async function unmount() {
// unmount 的时候摧毁掉整个子应用
  instance.$destroy()
  instance.$el.innerHTML = ''
  instance = null
}

记得改掉html文件挂载dom的id为vue-app2

对router.js进行改造

import VueRouter from "vue-router";
import Vue from "vue";
Vue.use(VueRouter);

import Home from "./views/Home.vue";
import About from "./views/About.vue";
const routes = [
  {
    path: "/",
    redirect: "/home",
  },
  {
    path: "/home",
    component: Home,
  },
  {
    path: "/about",
    component: About,
  },
];

const router = new VueRouter({
  mode: "history",
  base: window.__POWERED_BY_QIANKUN__ ? 'app/app-vue2' : '', // 通过qiankun主应用访问子应用时,base需要设置成为主应用专门挂载子用用所在的路径
  routes,
});

export default router;

webpack 5 下的vue2框架项目在开发环境下的改造就差不多完成了。

子应用 qiankun-react webpack5

先从零安装一个react
https://blog.csdn.net/glorydx/article/details/115104561

安装 react-router-dom
改造App.js

import "./App.css";
import About from "./views/About.tsx";
import Home from "./views/Home.tsx";
import { NavLink, useRoutes,  Outlet} from "react-router-dom";
function App() {
  const base = window.__POWERED_BY_QIANKUN__ ? "/app/app-react/" : "/";
  const routes = useRoutes([
    { path: base, redirect: base + "home", element: <Home /> },
    { path: base + "home", element: <Home /> },
    { path: base + "about", element: <About /> },
  ]);
  return (
    <div className="app">
      <h1>React Router Dom</h1>
      <div className="main">
        <div className="leftToolBar">
          <NavLink to={base + "home"}>Home</NavLink>
          {/* 或者如下写法传入标签体 */}
          <NavLink to={base + "about"}>About</NavLink>
        </div>
        <div className="rightContent">{routes}</div>
      </div>
      <Outlet />
    </div>
  );
}

export default App;

根目录下的 public-path.js

if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

改造index.js 最好替换一下挂载的dom id ,同样的需要引入public-path.js

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import "./public-path.js";
import { BrowserRouter } from "react-router-dom";
function render(props) {
  const container = props?.container;
  const root = ReactDOM.createRoot(
    container
      ? container.querySelector("#app-react")
      : document.querySelector("#app-react")
  );
  root.render(
    <BrowserRouter>
      <React.StrictMode>
        <App />
      </React.StrictMode>
    </BrowserRouter>
  );
}

// 然后加入生命周期即可 同样的三个生命周期导出
export async function bootstrap() {
  console.log("ReactMicroApp bootstraped");
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  console.log("ReactMicroApp mount", props);
  render(props);
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  console.log("ReactMicroApp unmount");
  ReactDOM.unmountComponentAtNode(document.getElementById("#app-react"));
}

// 如果不是qiankun,正常挂载
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

改造webpack.config.js 前提是react不会自动暴露这东西,在这之前,你必须执行过 npm run eject 或者yarn eject 去暴露webpack的配置文件

主要是给 output加上qiankun需要的配置

	const packageName = require('../package.json').name;
    ...
    output: {
      ....
      library: `${packageName}-[name]`,
      libraryTarget: 'umd',
      chunkLoadingGlobal: `webpackJsonp_${packageName}`,
    },

记得把html挂载的id给换了

<div id="app-react"></div>

如果要引入图片或者其他的什么静态资源
比如react项目本身的logo.svg无法正常显示了
需要在react子项目先把打包方式的hash给干掉
在这里插入图片描述
然后把这个svg不改名的情况下,按照路径给copy到主应用中
在这里插入图片描述
这样qiankun主应用vue3的项目就能正常显示这张图片 如果是其它格式的静态资源也是差不多类似上面的操作

子应用 quankun-vue3 vite

还是同样的方式,先安装一个标准的vite vue3项目 需要有vue-router ts 就行

与webpack 5 的配置方式不同,需要安装依赖 vite-plugin-qiankun
yarn add vite-plugin-qiankun

还是先改造main.ts 记得引入public-path.js 文件内容跟之前一样

import './public-path'
import './assets/main.css'
// alert(1111)
import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'

let app: any
function render(props = {}) {
  // 每一次render都需要创建一个新的
  app = createApp(App)
  app.use(createPinia()).use(router)
  const { container } = props as any
  app.mount(container ? container.querySelector('#app-vue3') : '#app-vue3')
}

const bootstrap: (...args: any[]) => any = () => {
  console.log('vue app bootstraped')
}

const mount: (...args: any[]) => any = (props) => {
  const { container } = props
  render(container)
  console.log('vue3 app mount')
}

const unmount: (...args: any[]) => any = () => {
  app.unmount()
}

const initQianKun = () => {
  // @ts-ignore
  renderWithQiankun({
    mount,
    bootstrap,
    unmount,
  })
  // render()
}

qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()


改造一下router

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
const router = createRouter({
  history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? '/app/app-vue3/' : '/'),
  routes: [
    {
      path: '/',
      redirect: '/home',
    },
    {
      path: '/home',
      name: 'home',
      component: HomeView,
      children: [],
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/AboutView.vue'),
    },
  ],
})

export default router

配置一下vite.config.ts

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// import vueDevTools from 'vite-plugin-vue-devtools'
import qiankun from 'vite-plugin-qiankun'
// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    // vueDevTools(),
    qiankun('app-vue3', {
      useDevMode: true,
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
  server: {
    port: 7001,
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
})

基本上就改造完成了 然后启动就可以正常访问

遇到的问题

当子应用切换路由后,主应用再切换路由,会报错
当路由发生变化时,触发此pushState函数 ,解决qiankun的一个bug
详情查看 https://blog.csdn.net/chaoPerson/article/details/131398285

解决的办法,如链接所示,在主应用加一个router的hook

router.beforeEach((to, from, next) => {
  // 当路由发生变化时,触发此pushState函数 ,解决qiankun的一个bug
  // 详情查看 https://blog.csdn.net/chaoPerson/article/details/131398285
  if (window.history.state === null) {
    history.replaceState({
      back: from.path,
      current: to.path,
      forward: null,
      position: NaN,
      replaced: false,
      scroll: null
    }, 'localhost:5173' + to.path);
  }
  next();
});

在子应用当路由变化时 执行 window.history.pushState(null, "", "");
比如react

import { useEffect } from "react";
import "./App.css";
import About from "./views/About.tsx";
import Home from "./views/Home.tsx";
import { NavLink, useRoutes } from "react-router-dom";
function App() {
  const base = window.__POWERED_BY_QIANKUN__ ? "/app/app-react/" : "/";
  const routes = useRoutes([
    { path: base, redirect: base + "home", element: <Home /> },
    { path: base + "home", element: <Home /> },
    { path: base + "about", element: <About /> },
  ]);

  // 当路由发生变化时,触发此pushState函数 ,解决qiankun的一个bug
  // 详情查看 https://blog.csdn.net/chaoPerson/article/details/131398285
  useEffect(() => {
    if(window.__POWERED_BY_QIANKUN__) {
      window.history.pushState(null, "", "");
    }
  }, [routes]);

  return (
    <div className="app">
      <h1>React Router Dom</h1>
      <div className="main">
        <div className="leftToolBar">
          <NavLink to={base + "home"}>Home</NavLink>
          {/* 或者如下写法传入标签体 */}
          <NavLink to={base + "about"}>About</NavLink>
        </div>
        <div className="rightContent">{routes}</div>
      </div>
    </div>
  );
}

export default App;

比如vue2

<template>
  <div id="app">
    <h1>
      qiankun vue2
    </h1>
    <RouterLink to="/home">home</RouterLink>
    <RouterLink to="/about">about</RouterLink>
    <RouterView />
  </div>
</template>

<script>
import {RouterView, RouterLink} from 'vue-router'
export default {
  name: 'App',
  components: {
    RouterView,
    RouterLink
  },
  watch : {
    $route () {
      // 当路由发生变化时,触发此pushState函数 ,解决qiankun的一个bug
      // 详情查看 https://blog.csdn.net/chaoPerson/article/details/131398285
      if(window.__POWERED_BY_QIANKUN__) {
        window.history.pushState(null,'','')
      }
    }
  }
}
</script>

<style>
</style>

http://www.kler.cn/a/592437.html

相关文章:

  • 使用码云搭建CocoaPods远程私有库
  • 人事档案管理系统基于Spring BootSSM
  • LS-NET-006-思科MDS 9148S 查看内存
  • 【微服务】基于Lambda ESM的预留模式调整Kafka ESM吞吐量的实战
  • Spring Boot集成MyBatis与MySQL
  • Swagger-告别手写文档
  • 第十五届蓝桥杯C/C++组:宝石组合题目(从小学奥数到编程题详解)
  • 【嵌入式Linux】基于ArmLinux的智能垃圾分类系统项目
  • 构建高效复杂系统的关键:架构与模块详解
  • 【Java】Mybatis学习笔记
  • k8s常用知识点总结
  • Matlab 汽车振动多自由度非线性悬挂系统和参数研究
  • USB(Universal Serial Bus)详解
  • ETL中的实用功能以及数据集成方式
  • 基于Spring Boot的流浪动物救助平台的设计与实现(LW+源码+讲解)
  • Vmware中的centos7连接上网
  • ==和equals的区别?
  • VLLM专题(三十六)—自动前缀缓存
  • Java 中的引导类加载器(Bootstrap ClassLoader) 详解
  • 如何理解分布式光纤传感器?