vue3 webpack qiankun微前端

发布时间 2023-09-06 14:22:16作者: 梦羽微澜

qiankun: https://qiankun.umijs.org/zh/guide
demo源码gitee:https://gitee.com/philippines-kisses-snow/qiankun-demo

官方对微应用的说明:通常这种场景下微应用是一个不带路由的可独立运行的业务组件。 微应用不宜拆分过细,建议按照业务域来做拆分。业务关联紧密的功能单元应该做成一个微应用,反之关联不紧密的可以考虑拆分成多个微应用。 一个判断业务关联是否紧密的标准:看这个微应用与其他微应用是否有频繁的通信需求。如果有可能说明这两个微应用本身就是服务于同一个业务场景,合并成一个微应用可能会更合适。

准备:
主应用(qiankun-main)
子应用(qiankun_child1)

vue create qiankun-main
vue create qiankun_child1

一、主应用(qiankun-main)

安装包

npm i vue-router
npm i qiankun -S

添加路由

//router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      redirect: '/home'
    },
    {
      path: '/home',
      name: 'Home',
      component: () => import('../views/Home.vue'),
    },
    {
      path: '/:catchAll(.*)*',
      name: 'error',
      meta: {
        name: '404',
      },
      component: () => import('../views/404.vue'),
    },
  ]
})

export default router
//views/Home.vue
<template>
  <div>HOME</div>
  <div>
    <button @click="toChild1">应用1</button>
    <button @click="toChild2">应用2</button>
  </div>
</template>

<script setup lang="ts">
import { useRouter } from 'vue-router'

const router = useRouter()

function toChild1() {
  // 切换到子应用的/demo路由页面
  router.push('/child1/demo')
}

function toChild2() {
  // 切换到子应用的/page1路由页面
  router.push('/child2/page1')
}
</script>
// 404.vue
<template>
  <div>404</div>
</template>
//App.vue
<template>
  <div>
    <router-view></router-view>
  </div>
</template>

配置main.ts

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { addGlobalUncaughtErrorHandler, registerMicroApps, start } from 'qiankun';
import router from './router'

createApp(App).use(router).mount('#app')

registerMicroApps([]);

// 全局异常捕获(可省去)
addGlobalUncaughtErrorHandler((event: any) => {
  if(typeof event !== 'string') {
    const mircoAppName = event?.error?.appOrParcelName
    if(mircoAppName && event.type === 'error') {
      console.error(`${mircoAppName}应用加载失败`)
    }
  }
})

start();

二、子应用(qiakun_child1)

webpack配置

// vue.config.js
const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package.json');

module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 8085,
    headers: {
      'Access-Control-Allow-Origin': '*',	// 设置允许跨域请求,否则会因为在其他端口号获取资源报错
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd',
      // webpack 4 将chunkLoadingGlobal改为jsonpFunction
      chunkLoadingGlobal: `webpackJsonp_` + name,
    },
  },
})

main.js配置

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { createRouter, createWebHistory } from 'vue-router'
import { routes } from './router'

if ((window as any).__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

let router: any
let app: any

function render({ container } = {} as any) {
  router = createRouter({
    history: createWebHistory((window as any).__POWERED_BY_QIANKUN__ ? '/child2' : '/'),
    routes,
  })

  app = createApp(App)
  app.use(router).mount(container ? container.querySelector('#app') : '#app')
}


if(!(window as any).__POWERED_BY_QIANKUN__) {
  render()
}

// 导出第一次进入当前子应用的钩子函数
export async function bootstrap() {
}

// 导出每次创建挂载时的钩子函数
export async function mount(props: any) {
  render(props)
}

// 导出每次销毁时的钩子函数
export async function unmount(props: any) {
  app.unmount()
  app._container.innerHTML = ''
  app = null
  router = null
}

路由配置

// router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

import Demo from '../components/Demo.vue'
import HelloWorld from '../components/HelloWorld.vue'

export const routes: RouteRecordRaw[] = [
  {
    path: '/demo',
    component: Demo
  },
  {
    path: '/hello',
    component: HelloWorld
  }
]

三、将子应用添加到主应用

// 在主应用的 main.ts 的 registerMicroApps 当中加入子应用路由地址
registerMicroApps([
  {
    name: 'qiankun_child1',
    entry: 'http://xxx.xxx.xxx.xxx:xxx', // 子应用的访问地址
    container: '#child1',
    activeRule: 'child1',
  },
]);
// 在主应用中添加子应用路由页面,主应用router/index.ts的router中加入:
{
  path: '/child1/:pathMatch(.*)',
  name: 'child1',
  component: () => import('../views/app1.vue'),
}
// app1.vue
<template>
  <div id="child1"></div>
</template>

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

onMounted(() => {
  start({
    prefetch: false,
    sandbox: {
      experimentalStyleIsolation: true,
    }
  });
})
</script>