基于qiankun+monorepo的微前端架构搭建流程(多技术栈适配)

2 阅读7分钟

结合 qiankun 微前端框架与 monorepo工程化方案,搭建支持 React+Umi4、Vue2、Vue3 多技术栈子应用的微前端体系。 源码仓库地址

一、环境准备与工程初始化

# 1. 检查环境版本 (推荐版本)
node -v  # >= 16.18.0
pnpm -v  # >= 7.0.0

# 2. 安装pnpm (已安装忽略)
npm install -g pnpm

二、目录架构

monorepo/
├── packages/          
│   ├── main/          # 主应用(React+TS+Vite+qiankun)- 端口7000
│   ├── react-app/     # 子应用1(React+Umi4+TS)- 端口7001
│   ├── vue2-app/      # 子应用2(Vue2+Webpack+JS)- 端口7002
│   └── vue3-app/      # 子应用3(Vue3+Vite+TS)- 端口7003
├── common/            # 公共工具库(全局复用)
├── pnpm-workspace.yaml# pnpm workspace配置       
└── package.json # 根项目配置

首先根目录初始化package.json,执行 pnpm init -y

在根目录创建 pnpm-workspace.yaml 文件,指定workspace范围,实现多包管理:

packages:
  - 'packages/*'  # 所有子应用与主应用存放目录
  - 'common'      # 公共工具库目录

三、主应用搭建(React+Vite+qiankun)

1.创建主应用项目

# 进入packages目录并创建main目录
cd packages && mkdir main && cd main

# 使用Vite创建React+TS项目
pnpm create vite@latest . -- --template react-ts

# 安装依赖
pnpm install

# 安装核心依赖:qiankun(微前端框架)、react-router-dom@6(路由)、axios(请求)
pnpm install qiankun axios react-router-dom@6

# 安装开发依赖:@types/node(Node类型定义)
pnpm install @types/node -D

2. 配置Vite(vite.config.ts)

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    // 配置别名,简化路径引入
    alias: { '@': path.resolve(__dirname, './src') }
  },
  server: {
    port: 7000, // 主应用固定端口,子应用基于此端口通信
    cors: true, // 允许跨域(子应用加载必须开启)
  },
  build: {
    outDir: 'dist', // 输出目录
    assetsDir: 'assets' // 静态资源目录
  }
})

3.实现主应用核心逻辑(src/App.tsx)

通过qiankun的 registerMicroApps 注册子应用,start 启动微前端框架,同时实现子应用切换导航:

//App.tsx
import {  useEffect } from 'react'
import { BrowserRouter } from 'react-router-dom'
import { registerMicroApps, start } from 'qiankun'
import './App.scss'

function AppContent() {
  
  const handleNavigate = (path: string) => {
    window.location.assign(path)
  }

  useEffect(() => {
    // 1. 注册所有子应用配置
    registerMicroApps(
      [
        {
          name: 'react-app', // 唯一标识,必须和子应用package.json的name一致
          entry: 'http://localhost:7001', // 子应用运行端口
          container: '#micro-app-container', // 渲染容器ID
          activeRule: '/sub1', // 路由匹配规则,主应用路由到/sub1时加载该子应用
         // props: { userInfo: { name: '主应用用户' } }, // 主应用向子应用 传参
        },
        {
          name: 'vue2-app',
          entry: 'http://localhost:7002',
          container: '#micro-app-container',
          activeRule: '/sub2',   
        },
        {
          name: 'vue3-app',
          entry: 'http://localhost:7003',
          container: '#micro-app-container',
          activeRule: '/sub3',
        },
      ],
    )

    //启动微前端框架
    start({
      sandbox: { experimentalStyleIsolation: true }, // 开启实验性样式隔离
      singular: true, // 单实例模式:同一时间只加载一个子应用
    })
  }, [])

  const navItems = [
    { path: '/sub1', label: '子应用1: React+Umi4' },
    { path: '/sub2', label: '子应用2: Vue2+Webpack' },
    { path: '/sub3', label: '子应用3: Vue3+Vite' },
  ]

  return (
    <div className="main-app">
      <h1>主应用</h1>
      { /* 主应用导航栏 - 切换子应用 */ }
      <nav className="main-nav">
        {navItems.map((item) => (
          <button
            key={item.path}
            onClick={() => handleNavigate(item.path)}
          
          >
            {item.label}
          </button>
        ))}
      </nav>
      <div id="micro-app-container" className="micro-container"></div>
    </div>
  )
}

function App() {
  return (
    <BrowserRouter 
      basename="/"
    >
      <AppContent />
    </BrowserRouter>
  )
}

export default App

四、子应用搭建

子应用需适配qiankun的加载规范,实现独立运行与嵌入主应用两种模式兼容。以下分别搭建React+Umi4、Vue2、Vue3三种技术栈的子应用。

子应用1:React+Umi4+TS

1. 创建项目并安装依赖

# 进入react-app
cd packages/react-app

# 创建Umi4项目(选择Simple App模板)
pnpm create umi@latest .

# 安装依赖
pnpm install

# 安装qiankun插件
pnpm add @umijs/plugins

2. 配置package.json

指定项目名称(需与主应用注册的name一致),配置开发端口:

{
  "name": "react-app",
  "scripts": {
    "dev": "set PORT=7001 && umi dev", // Windows环境指定端口
    // "dev": "PORT=7001 umi dev", // Mac/Linux环境指定端口
    "build": "umi build",
    "start": "umi start"
  }
}

3 配置Umi(.umirc.ts)

启用qiankun插件,配置基础路由:

import { defineConfig } from "umi";

export default defineConfig({
  base: '/sub1', // 基础路由(与主应用activeRule一致)
  npmClient: 'pnpm',
  plugins: ["@umijs/plugins/dist/qiankun"], // 启用qiankun插件
  qiankun: {
    slave: {} // 声明为子应用
  },
});

子应用2:Vue2+Webpack+JS

1.创建项目并安装依赖

# 进入vue2-app目录
cd packages/vue2-app

# 使用Vue CLI 4创建Vue2项目
npx @vue/cli@4 create .

# 安装vue-router@3.x(Vue2配套版本)
pnpm install vue-router@3.x

2.配置vue.config.js

const { name } = require("./package.json");

module.exports = {
  // 开发环境跨域配置
  devServer: {
    port: 7002,
    headers: {
      "Access-Control-Allow-Origin": "*", // 允许所有跨域请求
    },
  },
  // Webpack配置(适配qiankun)
  configureWebpack: {
    output: {
      library: `${name}-[name]`, // 暴露全局变量
      libraryTarget: "umd", // 打包为UMD格式(支持AMD/CMD/全局变量)
      jsonpFunction: `webpackJsonp_${name}`, // 避免与其他应用的jsonpFunction冲突
    },
    // 可选:外部依赖抽离(从主应用引入,减少子应用体积)
    // externals: {
    //   'vue': 'Vue',
    //   'vue-router': 'VueRouter'
    // }
  },
};

3.配置public-path.js(解决静态资源路径问题)

创建public-path.js文件

// 解决qiankun加载时静态资源路径错误问题
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

4. 配置路由(src/router/index.js)

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home
  }
];

const router = new VueRouter({
  mode: "history",
  // 动态设置base:qiankun环境下为/sub2,独立运行时为/
  base: window.__POWERED_BY_QIANKUN__ ? "/sub2" : "/",
  routes
});

export default router;

5. 改造入口文件(src/main.js)

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import "../public-path.js"; // 引入public-path配置

Vue.config.productionTip = false;

let instance = null; // 存储Vue实例

// 渲染函数(核心:适配qiankun容器挂载)
function render(props = {}) {
  const { container } = props;
  // 挂载到主应用容器或独立容器
  instance = new Vue({
    router,
    render: (h) => h(App)
  }).$mount(container ? container.querySelector("#app") : "#app");
}

// 独立运行时直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
  console.log("Vue2子应用独立运行");
  render();
}

// 1. bootstrap(可选):子应用启动前初始化(只执行一次)
export async function bootstrap(props) {
  console.log("Vue2子应用启动初始化", props);
}

// 2. mount(必须):子应用挂载(每次进入子应用时执行)
export async function mount(props) {
  console.log("Vue2子应用接收主应用参数", props);
  // 可选:监听主应用全局状态变化
  // if (props.setGlobalState) {
  //   props.onGlobalStateChange((state, prevState) => {
  //     console.log("主应用状态变化:", prevState, "->", state);
  //   }, true);
  // }
  render(props);
}

// 3. unmount(必须):子应用卸载(每次离开子应用时执行)
export async function unmount(props) {
  console.log("Vue2子应用卸载", props);
  // 销毁Vue实例,避免内存泄漏
  if (instance) {
    instance.$destroy();
    instance.$el.innerHTML = "";
    instance = null;
  }
}

子应用3:Vue3+Vite+TS

1.创建项目并安装依赖

cd packages/vue3-app

# 使用Vite创建Vue3+TS项目
pnpm create vite@latest . -- --template vue-ts

# 安装依赖
pnpm install

# 安装qiankun适配插件、vue-router@4、pinia
pnpm install vite-plugin-qiankun -D
pnpm install vue-router@4 pinia

2.配置Vite(vite.config.ts)

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

export default defineConfig({
  plugins: [
    vue(),
    // 配置qiankun插件:指定子应用名称,开启开发模式
    qiankun('vue3-app', { useDevMode: true })
  ],
  base: '/sub3', // 基础路由(与主应用activeRule一致)
  server: {
    port: 7003,
    cors: true, // 允许跨域
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
})

3.配置路由(src/router/index.ts)

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'

const router = createRouter({
  history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/sub3' : "/"),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    }
  ]
})

export default router

4.改造入口文件(src/main.ts)

// @ts-nocheck
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import { renderWithQiankun } from "vite-plugin-qiankun/dist/helper"

let app = null; // 存储Vue3应用实例

// 渲染函数
function render(props = {}) {
  const { container } = props;
  // 若应用已挂载,先卸载避免重复渲染
  if (app) {
    app.unmount();
    app = null;
  }
  app = createApp(App)
  app.use(router)
  // 挂载到指定容器
  app.mount(container ? container.querySelector('#app') : '#app')
}

// 独立运行时直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

// 适配qiankun生命周期
renderWithQiankun({
  // 启动初始化
  bootstrap() {
    console.log("Vue3子应用启动初始化")
  },
  // 挂载
  mount(props) {
    console.log("Vue3子应用接收主应用参数", props)
    render(props)
  },
  // 更新(可选)
  update(props) {
    console.log("Vue3子应用更新", props)
  },
  // 卸载
  unmount() {
    console.log("Vue3子应用卸载")
    app.unmount()
    app = null
  }
})

五、工程启动配置

vue2-app的package.json中scripts新增 "dev": "vue-cli-service serve",

在根目录package.json中配置脚本,实现一键启动所有应用:

{
  "name": "monorepo",
  "version": "1.0.0",
  "scripts": {
    "dev:main": "pnpm --filter main dev", // 启动主应用
    "dev:react": "pnpm --filter react-app dev", // 启动React子应用
    "dev:vue2": "pnpm --filter vue2-app serve", // 启动Vue2子应用
    "dev:vue3": "pnpm --filter vue3-app dev", // 启动Vue3子应用
    "dev": "pnpm -r --parallel dev" // 并行启动所有应用
  }
}

启动命令:

# 根目录执行
pnpm dev

启动成功后,访问 http://localhost:7000,点击导航即可切换不同技术栈的子应用。

六、公共工具库共享(monorepo核心优势)

通过monorepo的workspace机制,实现公共工具库在所有应用中共享,避免重复开发与维护。

1.新建common文件夹,在该目录下执行pnpm init -y

//示例common/package.json
{
  "name": "common", // 公共库名称(应用中通过此名称引入)
  "version": "1.0.0",
  "description": "微前端应用公共工具库",
  "main": "index.js", // 入口文件
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

2.编写公共工具函数

创建格式化日期工具(common/format.js):

/**
 * 格式化日期
 * @param {Date|string|number} date 日期
 * @param {string} format 格式,默认 'YYYY-MM-DD HH:mm'
 * @returns {string} 格式化后的日期字符串
 */
export function formatDate(date, format = "YYYY-MM-DD HH:mm") {
  if (!date) return "";

  const d = new Date(date);
  if (isNaN(d.getTime())) return "";

  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  const hours = String(d.getHours()).padStart(2, "0");
  const minutes = String(d.getMinutes()).padStart(2, "0");
  const seconds = String(d.getSeconds()).padStart(2, "0");

  return format
    .replace("YYYY", year)
    .replace("MM", month)
    .replace("DD", day)
    .replace("HH", hours)
    .replace("mm", minutes)
    .replace("ss", seconds);
}

3.配置公共库入口(common/index.js)

/**
 * 公共工具库入口文件
 */
// 导出格式化函数
export * from "./format.js";

4. 在应用main/packages.json等的"dependencies"手动添加 "common": "workspace:*",然后执行pnpm i

//在组件中引入使用:
import { formatDate } from 'common'

function AppContent() {
  // ... 其他逻辑
  return (
    <div className="main-app">
      <h1>qiankun微前端主应用</h1>
      <p>当前时间:{formatDate(new Date())}</p>
      {/* ... 其他内容 */}
    </div>
  )
}

image.png

七、总结

项目整体是用来练手去学习微前端项目,适合用来简单入门,上手,实际情况中还有很多未考虑的地方,为了实现多技术栈大乱炖也确实碰了很多壁,参考了很多才成功运行不报错。微前端子应用是同一个技术栈就更方便管理。

后续可扩展方向:全局状态管理(qiankun的initGlobalState)、权限统一控制、构建打包优化、CI/CD流程集成等。