🧑‍💻 关于我

三年全栈实践 · Vue + Java
真实项目沉淀,可复用代码与踩坑解法

👋 关注我,收获实战经验,少走弯路

📬 绿泡泡:it全栈小屋

🔗 代码获取:https://gitee.com/xiaohelikesleep

🔗 CSDN:https://blog.csdn.net/interest_ing_

🔗 掘金:https://juejin.cn/user/3556246962047559

🔗 博客:https://blog.hfh.asia

把 Vue2 项目“黑盒”嵌进 Vue3:qiankun 微前端实战笔记

30分钟无痛迁移,不改一行老代码

引子:一个让人头大的需求

最近接到一个看似简单实则棘手的需求:把已经上线的 Vue2 老项目,整合到 Vue3 新项目中

老项目运行稳定,但源码已经“尘封”,没人敢动;新项目要用 Vue3 技术栈,不能为了迁就老项目降低版本。

更麻烦的是:

  • 老项目有独立的路由、状态管理、UI 组件库
  • 两个项目技术栈不同,构建工具也不同(Webpack vs Vite)
  • 要求双向通信:主应用能传参给子应用,子应用也能触发主应用的事件

一句话总结:把 Vue2 项目当一个“黑盒”,塞进 Vue3 项目里。

选型:为什么是 qiankun?

先看看市面上的方案:

方案 是否改源码 样式隔离 重复打包 体验
iframe ❌ 不改 ✅ 天然隔离 ❌ 不重复 宽高难调、刷新丢失状态、通信麻烦
npm 包 ✅ 必须改 ❌ 需额外处理 ✅ 重复打包 改到怀疑人生
qiankun ❌ 不改 ✅ 自动隔离 ❌ 独立加载 真香

qiankun 的核心优势:

  • 零改造:子应用只需导出生命周期函数,业务代码一行不动
  • 样式隔离:内置沙箱,不用担心 Vue2 和 Vue3 样式互相污染
  • 按需加载:访问对应路由时才加载子应用资源
  • 通信机制:官方提供 props 传参和全局状态管理

整体架构

┌─────────────────────────────────────────────────────┐
│                 Vue3 主应用 (qiankun)               │
│  ┌─────────────────────────────────────────────┐    │
│  │  路由: /vue2  →  加载子应用                  │    │
│  │  props: { userName, userId, onLogin }       │    │
│  │  container: #subapp-view                    │    │
│  └─────────────────────────────────────────────┘    │
│                         ↓                           │
│              qiankun 加载 & 通信                     │
│                         ↓                           │
│  ┌─────────────────────────────────────────────┐    │
│  │      Vue2 子应用 (独立部署)                  │    │
│  │  - 接收 props 数据                          │    │
│  │  - 回调主应用方法                           │    │
│  │  - 业务代码一行不改                         │    │
│  └─────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────┘

第一步:改造子应用(Vue2)

只需添加 3 个文件改动,业务代码完全不碰。

1. 新增 public-path.js

解决子应用资源加载路径问题:

// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

2. 改造 main.js

导出 qiankun 需要的生命周期函数:

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

let instance = null

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__) {
  render()
}

// 微前端生命周期(必须导出)
export async function bootstrap() {
  console.log('Vue2 子应用启动')
}

export async function mount(props) {
  // 把 props 挂到全局,方便业务组件使用
  Vue.prototype.$mainProps = props
  render(props)
}

export async function unmount() {
  instance.$destroy()
  instance = null
}

3. 配置 vue.config.js

关键配置:UMD 打包格式 + 跨域头

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

module.exports = defineConfig({
  publicPath: '/vue2-child/',    // 子应用部署路径
  outputDir: 'dist',
  
  configureWebpack: {
    output: {
      library: 'vue2App',         // 必须与入口 entry 匹配
      libraryTarget: 'umd',       // 必须是 umd
    }
  },
  
  devServer: {
    port: 7100,
    headers: {
      'Access-Control-Allow-Origin': '*'   // 必须允许跨域
    }
  }
})

⚠️ 注意:library 名称要与主应用注册时 name 一致,否则加载会失败。

打完包,扔到 nginx 即可独立部署。

第二步:主应用(Vue3)接入

1. 安装依赖

npm i qiankun

2. 注册子应用

创建 src/qiankun/index.js

import { registerMicroApps, start } from 'qiankun'

registerMicroApps([
  {
    name: 'vue2App',                    // 与子应用 library 名称一致
    entry: '//localhost:7100',          // 子应用地址(开发环境)
    container: '#subapp-view',          // 挂载 DOM 节点
    activeRule: '/vue2',                // 路由匹配规则
    props: {                            // 向下传递的数据和方法
      userName: 'Alice',
      userId: 123,
      onLogin: (data) => {
        console.log('子应用登录了', data)
        // 可以在这里触发主应用的状态更新
      }
    }
  }
])

// 启动 qiankun,开启样式隔离
start({ 
  sandbox: { 
    strictStyleIsolation: true 
  } 
})

3. 添加挂载点

App.vue 或对应布局中添加:

<template>
  <div id="app">
    <nav>
      <router-link to="/">首页</router-link>
      <router-link to="/vue2">进入老系统</router-link>
    </nav>
    
    <!-- 子应用挂载点 -->
    <div id="subapp-view" />
  </div>
</template>

4. 配置路由

在路由配置中添加对应路径(或者使用通配符):

const routes = [
  // ... 其他路由
  {
    path: '/vue2',
    name: 'Vue2App',
    component: { render: () => null },  // 占位组件
    meta: { title: '老系统' }
  },
  // 子应用内部路由也需要主应用识别
  {
    path: '/vue2/:pathMatch(.*)*',
    component: { render: () => null }
  }
]

访问 /vue2 时,qiankun 会自动拉取子应用资源并渲染到 #subapp-view 中。

第三步:双向通信

主应用 → 子应用(数据下发)

注册时通过 props 下发的数据,子应用在 mount 阶段接收:

// 子应用 main.js
export async function mount(props) {
  // 挂载到 Vue 原型,全局可用
  Vue.prototype.$mainProps = props
  render(props)
}

// 子应用任意组件中使用
export default {
  computed: {
    userName() {
      return this.$mainProps?.userName || '未登录'
    }
  }
}

子应用 → 主应用(事件回调)

主应用通过 props 传递回调函数,子应用按需调用:

// 子应用登录组件
export default {
  methods: {
    handleLogin() {
      // 调用主应用传入的回调
      this.$mainProps?.onLogin?.({
        token: 'abc123',
        userId: this.$mainProps?.userId
      })
    }
  }
}

全局状态通信(跨层级)

qiankun 提供了 initGlobalState 实现跨应用全局状态:

// 主应用
import { initGlobalState } from 'qiankun'

const actions = initGlobalState({ 
  count: 0,
  theme: 'light' 
})

// 监听变化
actions.onGlobalStateChange((newState, oldState) => {
  console.log('主应用收到状态变化', newState)
})

// 子应用
export async function mount(props) {
  // 监听全局状态
  props.onGlobalStateChange((newState, oldState) => {
    console.log('子应用收到', newState)
  })
  
  // 更新全局状态
  props.setGlobalState({ count: 1 })
}

第四步:打包部署

子应用部署

子应用 dist 目录部署到 nginx 静态目录:

# nginx 配置
server {
    listen 80;
    server_name static.example.com;
    
    location /vue2-child/ {
        alias /path/to/vue2-app/dist/;
        try_files $uri $uri/ /vue2-child/index.html;
        
        # 必须配置跨域
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
    }
}

主应用部署

主应用 nginx 添加反向代理(解决跨域):

server {
    listen 80;
    server_name main.example.com;
    
    location / {
        root /path/to/vue3-app/dist/;
        try_files $uri $uri/ /index.html;
    }
    
    # 代理子应用静态资源
    location /vue2-child/ {
        proxy_pass http://static.example.com/vue2-child/;
        proxy_set_header Host $host;
        add_header Access-Control-Allow-Origin *;
    }
}

生产环境配置

修改主应用注册时的 entry

registerMicroApps([
  {
    // ...
    entry: '//your-domain.com/vue2-child/',  // 生产地址
    // ...
  }
])

常见问题 & 解决方案

1. 子应用资源 404

原因:publicPath 未正确配置

解决:确保 public-path.jsmain.js 最顶部引入,且 webpack 配置了正确的 publicPath

2. 样式互相污染

原因:没有开启沙箱隔离

解决:启动时配置 strictStyleIsolation: true

start({ 
  sandbox: { strictStyleIsolation: true } 
})

3. 子应用路由跳转失败

原因:history 模式与主应用 basename 不匹配

解决:子应用路由配置 baseactiveRule 保持一致

// 子应用 router/index.js
const router = new VueRouter({
  mode: 'history',
  base: '/vue2',    // 与主应用 activeRule 一致
  routes
})

4. 开发环境热更新失效

原因:webpack5 默认限制了允许访问的主机

解决:配置 devServer.allowedHosts

devServer: {
  allowedHosts: 'all',   // 或指定域名
  // ...
}

5. 子应用重复渲染

原因unmount 生命周期未正确销毁实例

解决:确保 unmount 中调用 instance.$destroy() 并置 null

总结

通过 qiankun,我们成功实现了:

零业务代码改动:Vue2 老项目原封不动
技术栈无关:Vue3 主应用 + Vue2 子应用和谐共存
双向通信:父子应用数据互通、事件联动
样式隔离:不用担心样式冲突
按需加载:访问时才加载,不影响首屏性能

整个过程 30 分钟完成,老项目和新项目各自独立迭代,互不干扰。

微前端不是银弹,但对于“遗留系统收编”这种场景,qiankun 确实是最优解。


如果你也在做微前端迁移,或者遇到其他技术难题,欢迎在评论区交流讨论!

📬 更多实战干货

三年全栈经验持续输出
可复用代码 · 踩坑记录 · 成长心得

👋 全平台关注,一起成长

📬 绿泡泡:it全栈小屋

🔗 代码获取:https://gitee.com/xiaohelikesleep

🔗 CSDN:https://blog.csdn.net/interest_ing_

🔗 掘金:https://juejin.cn/user/3556246962047559

🔗 博客:https://blog.hfh.asia

用代码记录成长,用分享对抗遗忘

感谢阅读,我们下篇见 👋

Logo

一站式 AI 云服务平台

更多推荐