likes
comments
collection
share

qiankun:vue3 + vite从开发到部署实现微前端

作者站长头像
站长
· 阅读数 21

背景

由于业务需要,本人需要在某应用中增加菜单,在页面上直接展示另外两个应用,并且要求登录信息共用,一番衡量过后决定使用qiankun实现。(注:本文主要记录一次从开发到部署相关接入qiankun的过程,并不探究相关原理)

文末有源码,记得先点赞再看噢!

开始

首先准备好三个应用:

主应用main:vue3+vite+history路由模式

子应用app-vue3:vue3+vite+history路由模式

子应用app-vue2:vue2+webpack+hash路由模式

qiankun:vue3 + vite从开发到部署实现微前端

主应用

安装qiankun

npm i qiankun -S

根目录下新建src/config.js用于存放子应用信息

export default {
  subApps: [
    {
      name: 'app-vue3', // 子应用名称,跟package.json一致
      entry: '//localhost:7001', // 子应用入口,本地环境下指定端口
      container: '#sub-container', // 挂载子应用的dom
      activeRule: '/app/app-vue3', // 路由匹配规则
      props: {} // 主应用与子应用通信传值
    },
    {
      name: 'app-vue2',
      entry: '//localhost:7002',
      container: '#sub-container',
      activeRule: '/app/app-vue2',
      props: {}
    }
  ]
}

根目录下新建src/utils/qiankun用于开启qiankun

import { registerMicroApps } from 'qiankun'
import config from '@/config'

const { subApps } = config

export function registerApps() {
  try {
    registerMicroApps(subApps, {
      beforeLoad: [
        app => {
          console.log('before load', app)
        }
      ],
      beforeMount: [
        app => {
          console.log('before mount', app)
        }
      ],
      afterUnmount: [
        app => {
          console.log('before unmount', app)
        }
      ]
    })
  } catch (err) {
    console.log(err)
  }
}

新建一个组件,用于加载子应用src/components/SubContainer.vue

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

<script>
import { start } from 'qiankun'
import { registerApps } from '@/utils/qiankun'
export default {
  mounted() {
    if (!window.qiankunStarted) {
      window.qiankunStarted = true
      registerApps()
      start({
        sandbox: {
          experimentalStyleIsolation: true // 样式隔离
        }
      })
    }
  }
}
</script>

配置一下路由src/router/routes

const routes = [
  {
    path: '',
    redirect: { name: 'home' },
    meta: { title: '首页' },
    children: [
      {
        path: '/home',
        name: 'home',
        component: () => import('../views/home/index.vue')
      },
      {
        // history模式需要通配所有路由,详见vue-router文档
        path: '/app/app-vue3/:pathMatch(.*)*',
        name: 'app-vue3',
        meta: {},
        component: () => import('@/components/SubContainer.vue')
      },
      {
        path: '/app/app-vue2/',
        name: 'app-vue2',
        meta: {},
        component: () => import('@/components/SubContainer.vue')
      }
    ]
  }
]

export default routes

最后在首页src/App.vue放置<router-view />

<template>
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <router-link to="/app/app-vue2/#/">vue-app2</router-link>
  &nbsp;
  <router-link to="/app/app-vue3">vue-app3</router-link>

  <router-view />
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

qiankun:vue3 + vite从开发到部署实现微前端

到此主应用配置修改完成,后续再补充router-link的路由跳转信息

子应用vue-app3

安装vite-plugin-qiankun

npm i vite-plugin-qiankun --save-dev

修改vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    qiankun('app-vue3', {
      useDevMode: true
    })
  ],
  server: {
    port: 7001,
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  }
})

由于路由模式为history,需要匹配子应用的入口规则,修改src/router/index

import {
  createRouter,
  createWebHistory
} from 'vue-router'
import routes from './routes'
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'

const router = createRouter({
  history: createWebHistory(
    qiankunWindow.__POWERED_BY_QIANKUN__
      ? '/app/app-vue3/'
      : '/'
  ),
  routes
})

export default router

最后在main.ts里添加生命周期

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

let app

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

const initQianKun = () => {
  renderWithQiankun({
    mount(props) {
      const { container } = props
      render(container)
    },
    bootstrap() {},
    unmount() {
      app.unmount()
    }
  })
}

qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()

单独运行,指定7001端口 qiankun:vue3 + vite从开发到部署实现微前端 微前端环境运行 qiankun:vue3 + vite从开发到部署实现微前端

子应用vue-app2

创建src/public-path

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

main.js修改

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

Vue.config.productionTip = false

let instance = null

function render(props = {}) {
  const { container } = props
  instance = new Vue({
    router,
    store,
    render: h => h(App)
  }).$mount(container ? container.querySelector('#app') : '#app')
}

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

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() {
  instance.$destroy()
  instance.$el.innerHTML = ''
  instance = null
}

vue.config.js修改

const { name } = require('./package')

module.exports = {
  devServer: {
    port: 7002,
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
}

单独运行,指定7002端口 qiankun:vue3 + vite从开发到部署实现微前端 微前端环境运行

qiankun:vue3 + vite从开发到部署实现微前端

qiankun:vue3 + vite从开发到部署实现微前端

至此,本地运行的所有配置全部修改完成

踩坑点:

子应用切换时出现以下警告:

qiankun:vue3 + vite从开发到部署实现微前端 解决方案:

主应用增加:

router.beforeEach((to, from, next) => {
  if (!window.history.state.current) window.history.state.current = to.fullPath
  if (!window.history.state.back) window.history.state.back = from.fullPath
  // 手动修改history的state
  return next()
})

部署相关

注意项:

1.修改子应用配置的entry

修改为部署到服务器上的index.html的入口

若把子应用部署到服务器根目录下的app-vue3文件夹,则修改为:

{
  name: 'app-vue3', // 子应用名称,跟package.json一致
  entry: process.env.NODE_ENV === 'development'
    ? '//localhost:7001'
    : '/app-vue3/index.html', // 子应用入口,本地环境下指定端口
  container: '#sub-container', // 挂载子应用的dom
  activeRule: '/app/app-vue3', // 路由匹配规则
  props: {} // 主应用与子应用通信传值
}

2.修改子应用配置的activeRule(取决于主应用在服务器上部署的路径)

由于主应用是history路由模式,activeRule需要完全匹配(hash模式只需匹配hash路由中#号开始部分)

如:

{
  name: 'app-vue3', // 子应用名称,跟package.json一致
  entry: process.env.NODE_ENV === 'development'
    ? '//localhost:7001'
    : '/app-vue3/index.html', // 子应用入口,本地环境下指定端口
  container: '#sub-container', // 挂载子应用的dom
  activeRule: process.env.NODE_ENV === 'development'
    ? '/app/app-vue3'
    : '/main/app/app-vue3', // 路由匹配规则
  props: {} // 主应用与子应用通信传值
}

3.修改子应用vite/webpack/vue-cli静态资源路径的配置(详细可在文末源码查看)

vite应用:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'

// https://vitejs.dev/config/
export default defineConfig({
  base: process.env.NODE_ENV === 'production' ? '/app-vue3/' : '/',
  plugins: [
    vue(),
    qiankun('app-vue3', {
      useDevMode: true
    })
  ],
  server: {
    port: 7001,
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  }
})

总结

至此,我们简单的完成了一个微前端的搭建,当然这只是一个简单的demo,要达到部署生产真正投入使用,还有许多工作要做(主、子应用通信,子应用静态资源代理,子应用共享主应用登录凭证等问题)

后续我们将继续探索以上问题...

附上源码:github.com/qaz61912862…

点个赞吧,求求了~~!