Vue项目使用 qiankun 微应用,只说重点

1,847 阅读3分钟

Vue微前端,使用的一些重点,打个总结。

首先,看一下 qiankun 官网的介绍。

qiankun官网传送门

核心价值和设计理念,在此不做赘述。有兴趣自己慢慢看。我这篇文章,以项目实践为例,只说重点。

代码已经放到 Gitee上面。

版本信息

我使用的 node 版本是 v20.13.1

vue脚手架版本是 @vue/cli 5.0.8

vue-router 版本 3.4.9

Vue Router官网传送门

创建主应用

vue create qiankun-main

在主应用中,新建组件

src\components\Main.vue

主应用的Main组件:

<template>
  <div class="wrapper">我是主应用的页面</div>
</template>

<script>
export default {
  name: "Main",
};
</script>

src\router\index.js 配置路由:

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

const routes = [
  {
    path: '/',
    redirect: '/main'
  },
  {
    path: '/main',
    component: () => import('@/components/Main'),
  }

]

// 防止连续点击多次路由报错
let routerPush = Router.prototype.push;
let routerReplace = Router.prototype.replace;
// push
Router.prototype.push = function push(location) {
  return routerPush.call(this, location).catch(err => err)
}
// replace
Router.prototype.replace = function push(location) {
  return routerReplace.call(this, location).catch(err => err)
}

export default new Router({
  mode: 'history',
  scrollBehavior: () => ({ y: 0 }),
  routes
})

main.js中引入路由:

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

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

修改 vue.config.js 允许跨域访问子应用页面

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 8086,
    headers: {			
      // 允许跨域访问子应用页面
      'Access-Control-Allow-Origin': '*',
    }
  }
})

修改 App.vue 组件

/qiankun-child 是微应用的名称,后面要用到。

id="child" 是子项目容器id 后面要用到。

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <router-link to="/">跳转主应用的页面</router-link>
    <!-- /qiankun-child 是微应用的名称 后面要用到 -->
    <span style="margin: 12px;"></span>
    <router-link to="/qiankun-child">跳转子应用的页面</router-link>
    <router-view />
    <!-- id="child" 是子项目容器id 后面要用到 -->
    <div id="child"></div>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

主应用安装 qiankun 注册微应用

详细介绍,参考官网,快速上手

qiankun官网快速上手

安装 qiankun

npm i qiankun --save

修改 main.js 在主应用中,注册微应用

重点:

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

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

import { registerMicroApps, start } from "qiankun"

// 在主应用中注册子应用
registerMicroApps([
  {
      name: "vue app",
      entry: "//localhost:8087",	
      // 子项目容器id
      container: '#child',			
      // 子项目的名称
      activeRule: '/qiankun-child'		
  }]
);
// 启动
start();

创建子应用

vue create qiankun-child

新建 src\components\Child.vue 组件:

<template>
  <div class="wrapper">我是子应用的页面</div>
</template>

<script>
export default {
  name:"Child",
}
</script>

src\router\index.js 添加路由信息

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)

const routes = [
  {
    path: '/',
    redirect: '/child'
  },
  {
    path: '/child',
    component: () => import('@/components/Child'),
  }

]
export default routes

新建 src\public-path.js 文件:

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

然后,在main.js中引入 public-path.js 并且,进行微应用的相关配置:

重点: base: window.__POWERED_BY_QIANKUN__ ? '/qiankun-child/' : '/',

import "./public-path.js"

import Vue from 'vue'
import App from './App.vue'
import routes from './router'
import VueRouter from 'vue-router'


Vue.config.productionTip = false

let router = null;
let instance = null;
function render(props = {}) {
  const { container } = props;
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/qiankun-child/' : '/',
    mode: 'history',
    routes,
  });

  instance = new Vue({
    router,
    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;
  router = null;
}

修改 vue.config.js

const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package.json')
module.exports = defineConfig({
  devServer: {
    port: 8087,
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  // wepack配置
  configureWebpack: {
    // 子应用打包成 umd 库格式
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd',
      // webpack v4:   
      // jsonpFunction: `webpackJsonp_${name}`,
      // webpack v5:   chunkLoadingGlobal
      chunkLoadingGlobal: `webpackJsonp_${name}`,
    },
  },
})

子应用的 App.vue 组件

<template>
  <div id="app">
    子应用 qiankun-child
    <router-view />
  </div>
</template>

<script>

export default {
  name: 'App',
}
</script>

效果展示:

通过 loadMicroApp 加载微应用

主应用中,新增 src\components\TestLoadMicroApp.vue 组件:

<template>
  <div>
    <button @click="loadApp">挂载微应用</button>
    <button @click="unloadApp">卸载微应用</button>
    <!-- 提供挂载容器 -->
    <div id="sub-app-container"></div>
  </div>
</template>

<script>
import { loadMicroApp } from "qiankun";
export default {
  data() {
    return {
      microApp: null, // 微应用实例
    };
  },
  methods: {
    loadApp() {
      if (this.microApp) return;
      this.microApp = loadMicroApp({
        // 微应用注册名字相同
        name: "qiankun-child",
        // 微应用的入库路径
        entry: "http://localhost:8087",
        // 微应用挂载的容器
        container: "#sub-app-container",
        props: {
          // 主应用向微应用传递参数
          userInfo:{
            name: "张三",
            age: 18,
          }
        },
      });
      this.microApp.mountPromise.then(() => {
        console.log("微应用加载完成");
      });
    },
    unloadApp() {
      if (!this.microApp) return;
      this.microApp.unmount(); // 卸载微应用
      this.microApp = null;
    },
  },
};
</script>

修改主应用中的 App.vue

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <router-link to="/">跳转主应用的页面</router-link>
    <!-- /qiankun-child 是微应用的名称 后面要用到 -->
    <span style="margin: 12px;"></span>
    <router-link to="/qiankun-child">跳转子应用的页面</router-link>
    <span style="margin: 12px;"></span>
    <router-link to="/test">手动加载微应用</router-link>
    <router-view />
    <!-- id="child" 是子项目容器id 后面要用到 -->
    <div id="child"></div>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

修改主应用中 src\router\index.js 的 routes

const routes = [
  {
    path: '/',
    redirect: '/main'
  },
  {
    path: '/main',
    component: () => import('@/components/Main'),
  },
  {
    path: '/test',
    component: () => import('@/components/TestLoadMicroApp'),
  }
]

修改子应用中的 src\router\index.js 的 routes

const routes = [
  {
    path: '/',
    redirect: '/child'
  },
  {
    path: '/child',
    component: () => import('@/components/Child'),
  },
  {
    path: '/test',
    component: () => import('@/components/Child'),
  }

]

注意,保持主应用和子应用中的 /test 路由名称一样。

修改子应用中的 main.js 里的 render 方法。在微应用入口文件接收到主应用传递的参数,挂载到Vue原型上面,方便子应用中的组件进行访问。

function render(props = {}) {
  const { container } = props;
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/qiankun-child/' : '/',
    mode: 'history',
    routes,
  });

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


    // 接收主应用传递的props
    const userInfo = props.userInfo;
    console.log('在微应用入口文件接收到主应用传递的userInfo:', userInfo);
     // 将userInfo挂载到Vue.prototype上,方便组件访问
    Vue.prototype.$userInfo = userInfo;
}

效果展示:

以上,如果对你有用的话,不妨点赞收藏关注一下,谢谢 🙏

😊 微信公众号: OrzR3

💖 不定期更新一些技术类,生活类,读书类的文章。