官方网站

介绍

qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

微前端架构具备以下几个核心价值:

  • 技术栈无关
    主框架不限制接入应用的技术栈,微应用具备完全自主权
  • 独立开发、独立部署
    微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  • 增量升级
    在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
  • 独立运行时
    每个微应用之间状态隔离,运行时状态不共享

微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。

实现

这里创建 vue2 项目作为主应用:

vue create main

安装 qiankun

yarn add qiankun
npm i qiankun -S

这里展现一个菜单项渲染一个微应用

/router/index.js :

import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import Layout from '../components/Layout'

Vue.use(VueRouter)
const routes = [
  {
    path: '/',
    component: Layout,
    redirect: '/index',
    children: [
      {
        path: 'index',
        component: HomeView
      }
    ],
  },
  // 微应用的路由访问的时候 主路由需要有对应的配置渲染layout
  {
    path: '/mic-*',
    component: Layout
  }
]
const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

在主应用中注册微应用

/components/Layout.vue :

<template>
    <div class="main">
        <div class="topnav"></div>
        <div class="mainbt">
            <div class="menu">
                <div>
                    <router-link to="/mic-vue2">vue2 应用</router-link>
                </div>
                <div>
                    <router-link to="/mic-vue3">vue3 应用</router-link>
                </div>
            </div>
            <div class="content">
                <router-view />
                <div id="mcapp"></div>
            </div>
        </div>
    </div>
</template>
<script>
import { registerMicroApps, start } from 'qiankun';

export default {
    mounted() {
        // 在vue挂载完成之后再去注册乾坤的微应用
        const apps = [
            {
                name: 'vue2', // nane确定唯一
                entry: '//localhost:5001', // 应用的入口链接
                container: '#mcapp', // 微应用要挂载的节点位置
                activeRule: '/mic-vue2', // 微应用的路由激活路径  主应用当中访问该路由会 挂载对应的微应用
            },
            {
                name: 'vue3',
                entry: '//localhost:5002',
                container: '#mcapp',
                activeRule: '/mic-vue3',
            }
        ];
        registerMicroApps(apps); // 注册微应用
        start(); // 开始启用乾坤
    }
}
</script>
<style>
body {
    margin: 0;
}

.main {
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
}

.topnav {
    height: 80px;
    border: 1px solid #000;
}

.mainbt {
    height: 0;
    flex-grow: 1;
    display: flex;
}

.menu {
    width: 220px;
    border: 1px solid red;
}

.content {
    width: 0;
    flex-grow: 1;
    border: 2px solid blue;
}</style>

当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。

创建一个 vue2 项目作为微应用 :

vue create vueproject

微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。

在配置的 webpack 的 入口 js 文件导出相应的生命周期钩子

/src/main.js :

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

Vue.config.productionTip = false
// 作为乾坤的子应用不能自己 立即实例化  要使用乾坤子应用对应的生命周期中去实例化
function render() {
  new Vue({
    router,
    store,
    render: h => h(App)
  }).$mount('#app')
}
/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap() {
}
/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  render();
}
/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount(props) {
}
/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {
  console.log('update props', props);
}

配置微应用的打包工具

除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置

webpack v5 :

vue.config.js :

const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package')
module.exports = defineConfig({
  transpileDependencies: true,
  publicPath: './',
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 乾坤的子应用要打包成umd的模式
      chunkLoadingGlobal: `webpackJsonp_${name}`
    }
  },
  devServer: {
    port: 5001, // 指定子应用的端口
    headers: { // 支持静态资源引入的时候的跨域配置
      'Access-Control-Allow-Origin': '*'
    }
  }
})

在 src 目录新增 public-path.js :

// 收到的去修改 webpack打包文件的基础依赖路径
if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

创建一个 vue3 项目作为微应用 :

vue create vueapp

vue.config.js :

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

module.exports = defineConfig({
  transpileDependencies: true,
  publicPath: './',
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 乾坤的子应用要打包成umd的模式
      chunkLoadingGlobal: `webpackJson_${name}`
    }
  },
  devServer: {
    port: 5002,
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  }
})

/src/public-path.js :

if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path_ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

/src/main.js :

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

function render() {
    createApp(App).use(store).use(router).mount('#app')
}
export async function bootstrap() {
}
export async function mount() {
    render();
}
export async function unmount() {
}
export async function update(props) {
    console.log('update props', props);
}