前言:为什么需要微前端?
场景痛点 :
当企业级应用逐渐演变为“巨石应用”时,你可能面临:
- 多个团队维护同一仓库,协作困难,有可能改到同一文件,造成冲突,解决冲突增加心智负担。
- 技术栈升级成本高(如Vue 2迁移到Vue 3)
- 独立部署困难,一个小改动需要全量发布
微前端核心思想 :
- 将单体应用拆分为多个独立开发、独立部署 的子应用
- 主应用作为容器,动态加载子应用
- 技术栈无关性(Vue、React、Angular共存)
一、qiankun框架核心原理
1. 关键技术实现
-
JS沙箱:通过 Proxy 代理 window对象,隔离子应用全局变量
-
样式隔离:动态加载/卸载子应用样式表(Shadow DOM可选)
-
资源加载:劫持 fetch/XMLHttpRequest,匹配子应用资源路径
(这些会在另外的文章详细解释,这里先不多赘述)
2. 生命周期管理
// 子应用必须导出以下生命周期钩子
export async function bootstrap() { /* 初始化 */ }
export async function mount(props) { /* 渲染 */ }
export async function unmount() { /* 卸载 */ }
二、创建主应用
-
创建主应用项目
在终端中运行以下命令:vue create main-app选择
Vue 2模板,其他配置按默认即可。 -
安装 qiankun 和 router
npm install qiankun -S npm install vue-router@3 -
修改主应用入口文件
src/main.js
替换为以下代码:import Vue from 'vue' import App from './App.vue' import router from './router' import { registerMicroApps, start } from 'qiankun' Vue.config.productionTip = false // 注册微应用 registerMicroApps([ { name: 'vue-sub-app', // 子应用名称 entry: '//localhost:8081', // 子应用入口(开发环境) container: '#subapp-container', // 挂载容器 activeRule: '/sub-app' // 激活路径 } ]) // 启动 qiankun start() new Vue({ router, render: h => h(App) }).$mount('#app') -
修改主应用布局
src/App.vue
替换为以下代码:<template> <div id="app"> <nav> <router-link to="/">主应用首页</router-link> <router-link to="/sub-app">子应用</router-link> </nav> <router-view></router-view> <div id="subapp-container"></div> </div> </template> <script> export default { name: 'App' } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; text-align: center; color: #2c3e50; margin-top: 60px; } nav { margin-bottom: 20px; } nav a { margin: 0 10px; } </style> -
修改主应用路由
src/router/index.js
替换为以下代码:import Vue from "vue"; import VueRouter from "vue-router"; import MainHome from "../views/MainHome.vue"; Vue.use(VueRouter); const routes = [ { path: "/", name: "MainHome", component: MainHome, }, ]; const router = new VueRouter({ mode: "history", routes, }); export default router; -
主应用 MainHome 文件
<template> <div>MainHome</div> </template> <script> export default { name: "MainHome", }; </script> -
启动主应用
运行以下命令启动主应用:npm run serve主应用将运行在
http://localhost:8080。
三、创建子应用
-
创建子应用项目
在终端中运行以下命令:vue create sub-app同样选择
Vue 2模板。 -
修改子应用入口文件
src/main.js
替换为以下代码:import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false 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() } // qiankun 生命周期钩子 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 } -
修改子应用路由
src/router/index.js
替换为以下代码:import Vue from "vue"; import VueRouter from "vue-router"; import SubHome from "../views/SubHome.vue"; Vue.use(VueRouter); const routes = [ { path: "/", name: "SubHome", component: SubHome, }, ]; const router = new VueRouter({ mode: "history", base: window.__POWERED_BY_QIANKUN__ ? "/sub-app/" : "/", // 主应用设置了激活路径 routes, }); export default router; -
修改子应用配置
vue.config.js
在子应用根目录下创建或修改vue.config.js:const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, devServer: { port: 8081, // 确保端口唯一 headers: { 'Access-Control-Allow-Origin': '*' // 允许跨域 } }, configureWebpack: { output: { library: `vue-sub-app`, // 定义子应用的全局变量名 qiankun会通过该变量名访问钩子函数 libraryTarget: 'umd', // 指定打包输出的模块格式为 UMD chunkLoadingGlobal: `webpackJsonp_vue-sub-app` //Webpack 会使用 window.webpackJsonp_vueSubApp 来管理子应用的异步 chunk 加载。 } } })UMD 是一种通用的模块定义格式,兼容多种环境(如 CommonJS、AMD 和浏览器全局变量)。
-
子应用App文件
<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png" /> <router-view></router-view> </div> </template> <script> export default { name: "App", }; </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> -
子应用SubHome文件
运行以下命令启动子应用:<template> <div id="app">SubHome</div> </template> <script> export default { name: "SubHome", }; </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> -
启动子应用
运行以下命令启动子应用:npm run serve子应用将运行在
http://localhost:8081。
四、运行微前端项目1. 启动主应用和子应用
- 主应用:
http://localhost:8080 - 子应用:
http://localhost:8081
- 访问主应用
打开浏览器,访问http://localhost:8080,点击导航栏中的“子应用”链接,子应用的内容将加载到主应用的#subapp-container容器中。
五、项目结构
main-app/ # 主应用
│ ├── src/
│ │ ├── main.js # 主应用入口
│ │ ├── App.vue # 主应用布局
│ │ └── router/ # 主应用路由
│ └── vue.config.js
sub-app/ # 子应用
│ ├── src/
│ │ ├── main.js # 子应用入口
│ │ ├── App.vue # 子应用布局
│ │ └── router/ # 子应用路由
│ └── vue.config.js
这样就成功搭建了一个基于 Vue 2 和 qiankun 的微前端项目。主应用和子应用可以独立开发和运行,同时通过 qiankun 实现微前端集成。
接下来说一些优化。
六、沙箱配置优化
// 启动qiankun,配置沙箱
start({
sandbox: {
strictStyleIsolation: true, // // 子应用将被包裹在Shadow DOM中
experimentalStyleIsolation: false, // 样式前缀隔离
},
});
// 需要严格隔离样式 开启 strictStyleIsolation
// 需要兼容旧浏览器且允许样式继承 开启 experimentalStyleIsolation
| 特性 | strictStyleIsolation: true (Shadow DOM) | experimentalStyleIsolation: true (样式前缀) |
|---|---|---|
| 样式隔离强度 | 完全隔离 | 部分隔离 |
| 全局样式继承 | ❌ 无法继承 | ✅ 可以继承 |
| 浏览器兼容性 | ❌ 不支持IE | ✅ 支持所有浏览器 |
| 第三方库样式处理 | ✅ 自动隔离 | ❌ 需手动处理 |
| DOM操作限制 | ❌ 子应用无法直接访问主应用DOM | ✅ 无限制 |
七、子应用间通信
1.主应用初始化全局状态
// main-app/src/main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import { registerMicroApps, start, initGlobalState } from "qiankun";
Vue.config.productionTip = false;
// 初始化全局状态
const actions = initGlobalState({
user: null,
token: "",
});
// 监听全局状态变化
actions.onGlobalStateChange((state, prevState) => {
console.log("主应用监听到状态变化:", prevState, "→", state);
});
// 注册微应用
registerMicroApps([
{
name: "vue-sub-app", // 子应用名称
entry: "//localhost:8081", // 子应用入口(开发环境)
container: "#subapp-container", // 挂载容器
activeRule: "/sub-app", // 激活路径,
props: {
// 传递通信方法给子应用
onGlobalStateChange: actions.onGlobalStateChange,
setGlobalState: actions.setGlobalState,
},
},
]);
// 启动 qiankun
start();
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
2.子应用 main.js 钩子函数修改
// sub-app/src/App.vue
export async function mount(props) {
console.log("[vue] props from main framework", props);
window.mainAppProps = props;
render(props);
}
3.子应用使用全局状态
// sub-app/src/App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<router-view></router-view>
</div>
</template>
<script>
export default {
mounted() {
const { onGlobalStateChange, setGlobalState } = window.mainAppProps;
// 监听全局状态变化
onGlobalStateChange((state) => {
console.log("子应用监听到状态变化:", state);
});
// 更新全局状态
setGlobalState({ user: { name: "张三" } });
},
};
</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>
还有许多的优化方案和实用场景,后续持续更新。。