项目选型
微前端有qiankun,micro-app等,为什么选中了micro-app?
两个框架都使用了微前端概念,但是micro-app对原有的项目入侵最少,极大减少修改原项目;qiankun是需要修改原项目的,毕竟作为子应用的项目都运行很久了;为了减少开发成本,减少代码选择京东开源的micro-app
micro-app介绍
官网的介绍可以自己去看,这里不就不做重复了 直接上教程
项目中使用
你可以使用任何一项构建工具,当做基座使用
目录如下
基座版本如下:
"vue":"^3.2.37"
"@micro-zoe/micro-app": "^0.8.10"
"vite": "^3.0.7"
"vue-router": "^4.1.5"
基座配置
1 第一步在main.js中引入micro-app,并且执行micro.start函数;开启微前端模式
import microApp from '@micro-zoe/micro-app'
microApp.start()
2 第二步 在相应的文件中使用micro-app的标签 (这里以vue3的子应用举例)
<micro-app baseroute='/vue3' name='vue3' url='http://localhost:3003/'></micro-app>
micro-app标签中
name(必传且唯一):应用名称,类似key的作用
url(必传且唯一):应用地址,会被自动补全为http://localhost:3000/index.html ,子应用的地址
baseroute(可选):基座应用分配给子应用的基础路由,就是上面的 /vu3,用于区分子应用的path,也可以不选
如果关闭了沙箱机制 baseroute失效,且样式隔离失效
子应用配置
3 第三步在vue3子应用中设置
vue3子应用为vue-cli 创建的项目,如是vite或者react创建的项目需要单独配置
-
3.1 在vue.config.js中添加云讯跨域
devServer: { headers: { 'Access-Control-Allow-Origin': '*', } } -
3.2 如果基座是histroy路由.子应用是hash路由此步骤跳过
const index = createRouter({ // 👇 __MICRO_APP_BASE_ROUTE__ 为micro-app传入的基础路由 history: createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL), routes }) -
3.3 设置 publicPath 如果子应用不是webpack构建的,这一步可以省略。
步骤1: 在子应用src目录下创建名称为public-path.js的文件,并添加如下内
// __MICRO_APP_ENVIRONMENT__和__MICRO_APP_PUBLIC_PATH__是由micro-app注入的全局变量
if (window.__MICRO_APP_ENVIRONMENT__) { // eslint-disable-next-line
__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__ }
步骤2: 在子应用入口文件的最顶部引入public-path.js
import './public-path'
-
3.4 在main.js中监听卸载
//1 监听卸载操作 window.addEventListener('unmount', function () { createApp(App).unmount() }) // 2 如果子应用频繁卸载可以使用umdm模式 两者取其一 // main.js import { createApp } from 'vue' import * as VueRouter from 'vue-router' import routes from './router' import App from './App.vue' let app = null let router = null let history = null // 👇 将渲染操作放入 mount 函数 -- 必填 function mount () { history = VueRouter.createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || '/') router = VueRouter.createRouter({ history, routes, }) app = createApp(App) app.use(router) app.mount('#app') } // 👇 将卸载操作放入 unmount 函数 -- 必填 function unmount () { app.unmount() history.destroy() app = null router = null history = null } // 微前端环境下,注册mount和unmount方法 if (window.__MICRO_APP_ENVIRONMENT__) { window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } } else { // 非微前端环境直接渲染 mount() }
以上就是vue3作为子应用的配置
分别启动基座项目与子应用的项目后点击vue3应用
vite子应用使用需要单独处理 官网地址
由于vite作为子应用的构建方式与webpack不同需要单独去适配,下面是使用方法
基座处理
<micro-app name='child-name'
url='http://localhost:3001/basename/'
inline // 使用内联script模式
disableSandbox // 关闭沙箱
/>
这里关闭沙箱机制,所以baseUrl失效,样式隔离也失效了,基座的数据传输需要单独去处理;如果项目中接入了vite子应用需要考虑怎么处理样式隔离;考虑没有了沙箱机制出现的问题该如何去解决(我也不知道会出现什么问题,官网的建议是不接入等,1.0版本发布;) 我做的项目是vite基座+vite子应用,下面我会详细的介绍我是都碰到了什么问题,以及怎么解决的;ps:我做的项目以上线.
基座是vite应用的话需要再处理main.js 如果不是 忽略此步骤;
此处是处理vite引用开发环境中
import microApp from '@micro-zoe/micro-app'
microApp.start({
plugins: {
modules: {
// appName即应用的name值
appName: [{
loader(code) {
if (process.env.NODE_ENV === 'development') {
// 这里 basename 需要和子应用vite.config.js中base的配置保持一致
// 这里处理的是开发环境下匹配到from 或者import 引入的文件以子应用域名/basename去替换
code = code.replace(/(from|import)(\s*['"])(/basename/)/g, all => {
return all.replace('/basename/', '子应用域名/basename/')
})
}
return code
}
}]
}
}
})
vite子应用处理 官网地址
按照官网的配置处理,下面是我的配置
- 修改容器id
2. 修改route为hash路由
- 图片处理
4. vite.config.js处理(这里后面会讲到处理了什么,具体在部署那块)
到此都已经处理完成. 因为项目中没用到Angular,next.js 这里就不讲了,具体可以看官网;
路由处理 官网地址
注意以下几点:
路由配置:
基座是hash路由,子应用也必须是hash路由
基座是history路由,子应用可以是hash或history路由
baserouter 是基座分发给子应用的路径,子应用可以从window.__MICRO_APP_BASE_ROUTE__上获取baseroute的值,用于设置基础路由(此处是用于标识子应用的路由)
注意此处如果基座是history路由,且子应用也是history路由,并且设置了baseroute, 那么当前子应用页面的路由path需要与baseroute 一致; 因为子应用是根据游览器上的地址渲染对应的页面
子应用不会根据micro-app的url属性渲染对应的页面,而是根据浏览器地址渲染对应的页面
此句话可以理解为 子应用只会加载当前index.html的地址
例: 子应用地址为:http://localhost:3003
如果我们想渲染的地址为http://localhost:3003/page
那么 我们正确的写法应该是 而不是url='http://localhost:3003/page'
<micro-app name='vue3' url='http://localhost:3003/' baseroute='/vue3'></micro-app>
那么 我们如何跳转到这个页面呢? 基座中跳转的参数为:
router.push('/page')
跳转处理
场景一:基座 → 子应用
基座直接使用 router.push 即可,路径会自动匹配到对应子应用的路由:
// 基座中跳转到子应用的 /page 路由
// 前提:子应用的 baseroute 为 '/vue3',且子应用内部定义了 /page 路由
router.push('/vue3/page')
场景二:子应用内部跳转
子应用内部直接用自身的 router 跳转即可,和在独立应用中一样:
// 子应用内部
this.$router.push('/page')
// 或
router.push('/page')
场景三:子应用 → 基座(跨应用跳转)
子应用需要跳转到另一个子应用或基座页面时,有两种方式:
// 方式1:通过 window 直接操作浏览器地址
window.location.href = '/other-app/page'
// 方式2:通过 microApp.router(推荐,支持 history/hash 模式统一处理)
// 需要在子应用中通过 window.microApp 访问
// 注意:关闭沙箱时 window.microApp 不可用,见下文"关闭沙箱的通信"
场景四:基座通过 microApp.router 控制子应用跳转
micro-app 提供了虚拟路由 API,基座可以编程式控制子应用的跳转:
import microApp from '@micro-zoe/micro-app'
// 基座中控制指定子应用跳转
microApp.router.push({
name: 'vue3', // 子应用 name
path: '/page', // 目标路径
})
// 也支持 replace
microApp.router.replace({
name: 'vue3',
path: '/detail',
})
场景五:keep-alive 模式下的路由缓存
如果子应用开启了 keep-alive,需要注意页面切换时缓存的清理:
<micro-app name='vue3' url='http://localhost:3003/' keep-alive></micro-app>
启用 keep-alive 后,子应用卸载时不会销毁,再次切换回来时会恢复之前的状态。适合列表→详情→返回列表保留滚动位置的场景。
1.0版本修改了什么
micro-app 1.0 是一次大版本升级,主要变化:
1. 虚拟路由系统重构
1.0 版本重新设计了虚拟路由,解决了以下历史问题:
- 子应用 history 模式路由的兼容性大幅提升
- 不再强制要求基座 history 对应子应用 hash 的限制
- 路由跳转的匹配逻辑更加准确
2. Vite 子应用沙箱支持(重大更新)
这是 1.0 最受期待的功能 — Vite 子应用不再需要关闭沙箱:
- 支持 Vite 子应用在沙箱内运行
- 样式隔离(scoped css)对 Vite 子应用生效
baseroute在 Vite 子应用中恢复正常功能
如果你是 0.x 版本且 Vite 子应用关了沙箱,升级 1.0 后可以重新开启:
<!-- 升级前(0.x):被迫关闭沙箱 -->
<micro-app name='child' url='http://localhost:3001/' inline disableSandbox />
<!-- 升级后(1.0+):可以开启沙箱,样式隔离与 baseroute 均正常 -->
<micro-app name='child' url='http://localhost:3001/' baseroute='/child' />
3. iframe 沙箱模式
新增 iframe 属性,可以用 iframe 作为沙箱载体,替代默认的 with 沙箱,隔离性更强:
<micro-app name='child' url='http://localhost:3001/' iframe />
4. fiber 模式(实验性)
引入了 fiber 渲染模式,用于优化大型子应用的首次加载体验,按需注入资源。
5. 数据通信 API 增强
forceSetData/forceDispatch支持强制推送相同数据clearData/clearDataListener更细粒度的清理控制
升级建议: 如果你的项目是 0.8.x 且已经上线稳定运行,不需要立刻升级。新项目可以直接上 1.0。老项目如果要升,重点关注 Vite 子应用沙箱这块的回归测试。
数据传输
子应用获取基座数据
基座通过 microApp.setData 向子应用推送数据:
// 基座
import microApp from '@micro-zoe/micro-app'
// 向名为 'vue3' 的子应用发送数据
microApp.setData('vue3', {
token: 'xxx',
userInfo: { name: '张三', role: 'admin' },
theme: 'dark'
})
子应用监听并接收:
// 子应用
if (window.__MICRO_APP_ENVIRONMENT__) {
// autoTrigger: true — 监听时立即用基座上次发送的数据触发一次回调
window.microApp.addDataListener((data) => {
console.log('收到基座数据:', data)
// data => { token: 'xxx', userInfo: {...}, theme: 'dark' }
// 在这里把数据存储到 store 或做后续处理
}, true)
}
注意: 子应用挂载后才监听,所以要设置 autoTrigger: true 确保拿到基座提前发送的数据。
子应用向基座发送数据通信
// 子应用
window.microApp.dispatch({
type: 'NAV_CHANGE',
path: '/vue3/detail',
params: { id: 123 }
})
// 如果数据对象引用没变但需要强制推送
window.microApp.forceDispatch({
timestamp: Date.now()
})
基座接收:
// 基座
microApp.addDataListener('vue3', (data) => {
console.log('收到子应用数据:', data)
// data => { type: 'NAV_CHANGE', path: '/vue3/detail', params: { id: 123 } }
}, true)
基座获取子应用数据
有两种方式:
// 方式1:被动监听(推荐)
microApp.addDataListener('vue3', (data) => {
// 子应用每次 dispatch 都会触发
console.log('实时数据:', data)
}, true)
// 方式2:主动获取缓存数据
const cachedData = microApp.getData('vue3')
console.log('缓存数据:', cachedData)
getData 拿的是最后一次子应用 dispatch 上来或基座 setData 下去的数据,不是实时拉取,所以推荐用监听方式。
基座向子应用发送数据
同理,基座也可以用 setData + addDataListener 的组合方式:
// 基座发送
microApp.setData('vue3', { locale: 'zh-CN' })
// 基座也能主动读取自己发过的数据(第二个参数 true 表示读取基座→子应用的数据)
const sent = microApp.getData('vue3', true)
全局通信
全局通信用于基座和所有子应用之间广播消息,适合主题切换、语言切换、登出通知等场景:
// 基座 — 全局广播
microApp.setGlobalData({ theme: 'dark', lang: 'en' })
// 基座 — 监听全局数据
microApp.addGlobalDataListener((data) => {
console.log('全局数据变更:', data)
})
// 子应用 — 监听全局数据
window.microApp.addGlobalDataListener((data) => {
if (data.theme === 'dark') {
document.body.classList.add('dark')
}
}, true)
// 子应用 — 也可以发送全局数据
window.microApp.setGlobalData({ userLogout: true })
// 获取当前全局数据快照
microApp.getGlobalData()
// 清理
microApp.clearGlobalData()
microApp.clearGlobalDataListener()
关闭沙箱的通信
关闭沙箱时 window.microApp 不可用,因为 window.microApp 是注入在沙箱中的对象。此时需要换一种方式通信:
方案一:用 CustomEvent(推荐)
// 子应用 — 发送
window.dispatchEvent(new CustomEvent('child-to-base', {
detail: { type: 'ready', data: '子应用已挂载' }
}))
// 基座 — 接收
window.addEventListener('child-to-base', (e) => {
console.log('收到子应用数据:', e.detail)
})
// 基座 — 发送给子应用
window.dispatchEvent(new CustomEvent('base-to-child', {
detail: { token: 'xxx' }
}))
// 子应用 — 接收
window.addEventListener('base-to-child', (e) => {
console.log('收到基座数据:', e.detail)
})
方案二:用基座全局变量
// 基座 main.js
window.__BASE_STORE__ = reactive({
token: '',
userInfo: null,
theme: 'light'
})
// 子应用中直接读写
const store = window.__BASE_STORE__
console.log(store.token)
store.theme = 'dark'
方案三:micro-app 1.0 开启沙箱后直接用标准 API
升级 1.0 后 Vite 子应用也能用沙箱了,所以 window.microApp 恢复正常,直接用标准通信 API 即可,不需要上面两种 workaround。
打包处理
基座打包
基座和普通 Vite 项目一样,执行 npm run build 就行。注意几点:
- 路由模式:如果用 history 模式,Nginx 要配
try_files - 子应用地址:打包后
<micro-app>的 url 要指向子应用线上地址,开发环境用环境变量区分:
<micro-app
name='vue3'
:url='subAppUrl'
baseroute='/vue3'
/>
// 基座中动态设置子应用地址
const isDev = import.meta.env.DEV
const subAppUrl = isDev
? 'http://localhost:3003/'
: 'https://子应用线上域名/'
子应用打包
webpack 子应用打包无特殊处理。
Vite 子应用打包重点:
vite.config.js的base配置需要支持微前端环境:
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
base: '/',
// 如果在微前端环境中,需要动态 base,这里生产环境写死线上路径
// 或者用环境变量控制
build: {
// 确保资源路径正确
assetsDir: 'assets',
},
server: {
port: 3003,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
})
- 图片等静态资源路径问题
Vite 子应用中,new URL('./img.png', import.meta.url) 这种写法在微前端环境中可能 404。建议:
// 用绝对路径引用 public 下的资源
'./img.png' → '/img.png'
// 或者动态拼接
const imgUrl = window.__MICRO_APP_ENVIRONMENT__
? window.__MICRO_APP_PUBLIC_PATH__ + 'img.png'
: '/img.png'
关闭沙箱机制遇到的问题
由于项目是 Vite 基座 + Vite 子应用 的架构,micro-app 0.8.x 必须关沙箱(disableSandbox + inline)。关沙箱后踩了以下几个坑:
1. 全局变量污染
没沙箱隔离,子应用的 window.xxx 会和基座以及其他子应用共享。比如:
// 子应用 A 中定义
window.version = 'app-a-v1'
// 子应用 B 中覆盖
window.version = 'app-b-v2'
// 基座读到的是后加载的那个值
解决: 给全局变量加命名空间前缀:
// 子应用 A
window.__APP_A_VERSION__ = 'v1'
// 子应用 B
window.__APP_B_VERSION__ = 'v2'
2. CSS 样式冲突
关闭沙箱后 disable-scopecss 自动生效,子应用的 CSS 会直接注入基座 DOM,产生样式覆盖。
解决:
子应用各自使用 CSS 命名空间或 CSS Modules:
// vite.config.js 中配置 CSS Modules 前缀
export default defineConfig({
css: {
modules: {
// 给每个子应用不同的前缀
generateScopedName: '[name]__[local]___[hash:base64:5]',
},
},
})
或者统一用 Tailwind CSS prefix 选项:
// tailwind.config.js
module.exports = {
prefix: 'app-b-', // 子应用 B 的所有类名前加 app-b-
}
3. 事件监听未清理
关沙箱后 unmont 时 removeEventListener 不会自动执行,导致内存泄漏。
解决: 子应用中所有 window.addEventListener 必须在 unmont 时手动清理:
function mount() {
window.addEventListener('resize', handleResize)
window.addEventListener('custom-event', handleCustom)
}
function unmount() {
window.removeEventListener('resize', handleResize)
window.removeEventListener('custom-event', handleCustom)
app.unmount()
app = null
}
if (window.__MICRO_APP_ENVIRONMENT__) {
window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
} else {
mount()
}
4. baseroute 失效
关沙箱后 baseroute 不生效,子应用路由需要自己处理基准路径:
// 子应用 router
const router = createRouter({
// 关沙箱时 __MICRO_APP_BASE_ROUTE__ 为 undefined
// 手动指定 '/vue3' 或用 hash 路由规避
history: createWebHashHistory(),
routes,
})
建议:关闭沙箱时用 hash 路由,这样基座 history + 子应用 hash 不会冲突。
部署上线问题
1. Nginx 配置
基座和所有子应用都需要配置跨域:
# 基座
server {
listen 80;
server_name base.your-domain.com;
location / {
root /app/base/dist;
# history 模式需要 try_files
try_files $uri $uri/ /index.html;
# 允许子应用请求
add_header Access-Control-Allow-Origin *;
}
}
# 子应用
server {
listen 80;
server_name child.your-domain.com;
location / {
root /app/child/dist;
try_files $uri $uri/ /index.html;
# 跨域 — 必须配置
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
}
# 处理 OPTIONS 预检请求
if ($request_method = 'OPTIONS') {
return 204;
}
}
2. 资源 404 问题
部署后 Vite 子应用的 chunk 文件读取失败常见原因:
- base 路径不对:子应用 vite.config.js 的
base如果写死了/,但实际部署在子路径下,资源会 404。
// vite.config.js 修正
export default defineConfig(({ command }) => ({
// 线上路径根据实际部署位置填写
base: command === 'build' ? 'https://child.your-domain.com/' : '/',
}))
- publicPath 注入:micro-app 会自动注入
__MICRO_APP_PUBLIC_PATH__,但 Vite 开发模式下不走这个变量,所以要区分环境。
3. 子应用独立运行 + 嵌入运行双模式
上线后子应用可能需要独立访问(调试、灰度),也要被基座嵌入。需求——同一份构建产物支持两种模式:
// 子应用 main.js
let app = null
function mount() {
app = createApp(App)
app.mount('#app')
// ...初始化
}
function unmount() {
app?.unmount()
app = null
}
if (window.__MICRO_APP_ENVIRONMENT__) {
// 微前端模式:挂载/卸载由基座控制
window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
} else {
// 独立运行模式:直接挂载
mount()
}
4. 环境变量区分
部署时子应用的 API 地址需要正确指向:
// 子应用中判断环境
const getBaseURL = () => {
// 微前端环境 — 和基座共享域名,用相对路径
if (window.__MICRO_APP_ENVIRONMENT__) {
return '/api'
}
// 独立部署 — 用子应用自己的 API 域名
return 'https://api.child-domain.com'
}
以上就是 Vite 基座 + Vite 子应用使用 micro-app 从配置到部署的完整实践。核心痛点就两个:0.x 版本关沙箱后的样式隔离和通信,1.0 版本升级后这两块基本解决。如果项目还没启动,直接上 1.0;如果已经在跑且稳定,保持现有版本即可。