wujie使用
基座应用是“容器”,负责加载和展示子应用。
假设你用 Vue3 作为基座(最常见),配置超级简单。
1. 创建一个普通的 Vue3 项目(如果你没有)
用 Vue CLI 或 Vite 创建:
text
npm create vue@latest
# 或者 vite:npm create vite@latest
选 Vue + TypeScript 或 JS 都行,随便。
2. 安装 Wujie 的 Vue3 包
在你的基座项目里运行:
text
npm i wujie-vue3 -S
3. 在 main.js(或 main.ts)里引入并注册
打开 src/main.js,加上这些代码:
JavaScript
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 如果你有路由
// 引入 WujieVue
import WujieVue from 'wujie-vue3'
const app = createApp(App)
// 注册 WujieVue 插件
app.use(WujieVue)
// 如果有路由
app.use(router)
app.mount('#app')
4. 在页面里使用wujie-vue组件加载子应用
打开一个页面组件,比如 src/views/Home.vue 或 App.vue,写一个容器:
vue
<template>
<div>
<h1>这是基座应用</h1>
<!-- 这里放子应用,width 和 height 自己调 -->
<wujie-vue
width="100%"
height="800px"
name="唯一名字" <!-- 随便取,但要唯一,比如 "vue3-app" -->
url="子应用的地址" <!-- 子应用的线上地址或本地地址,比如 http://localhost:3000 -->
:sync="true" <!-- 可选:路由同步,推荐开 -->
:fetch="自定义fetch" <!-- 可选:如果子应用有跨域问题,可以自定义fetch -->
:props="{ token: '123' }" <!-- 可选:给子应用传数据 -->
:beforeLoad="beforeLoad" <!-- 可选:生命周期钩子 -->
:beforeMount="beforeMount"
:afterMount="afterMount"
></wujie-vue>
</div>
</template>
<script setup>
// 可选:生命周期钩子函数
const beforeLoad = () => {
console.log('子应用开始加载')
}
const beforeMount = () => {
console.log('子应用即将挂载')
}
const afterMount = () => {
console.log('子应用挂载完成')
}
</script>
关键参数解释(小白必看):
- name:子应用的唯一名字,不能重复。
- url:子应用的入口地址(必须是完整的 http:// 或 https://)。
- sync:true 表示路由同步(子应用路由变了,浏览器地址栏也会变)。
- props:可以给子应用传数据,子应用里用 window.$wujie.props 拿。
5. 预加载(推荐,加速切换)
在基座的入口文件(比如 App.vue 的 mounted)里预加载:
JavaScript
import { preloadApp } from 'wujie-vue3'
preloadApp({
name: '唯一名字',
url: '子应用地址'
})
6. 启动基座
text
npm run dev
打开页面,你就会看到子应用嵌进来了!
第三步:子应用怎么配置(最友好的是零改造!)
子应用就是你原来的普通项目(Vue、React、Vite 等)。
超级好消息:用“保活模式”或“重建模式”,子应用完全不用改代码!
推荐方式:保活模式(keep-alive,最丝滑,无白屏)
在基座的 组件上加一个属性:
vue
<wujie-vue
...
alive="true" <!-- 就是这一行!开启保活 -->
></wujie-vue>
完了!子应用零改造,切换回来不会重新加载,超级快。
如果你想用单例模式(多个地方共用同一个实例,需要改造一点)
只在子应用的入口文件(main.js 或 index.js)里包一层判断:
Vue3 示例(vite 项目) : 在 main.js 最外面包起来:
JavaScript
let instance
if (window.__POWERED_BY_WUJIE__) {
// 被 Wujie 加载时的生命周期
window.__WUJIE_MOUNT = () => {
instance = createApp(App).use(router).mount('#app')
}
window.__WUJIE_UNMOUNT = () => {
instance.unmount()
}
// 如果是 Vite 构建的,需要主动触发
window.__WUJIE.mount()
} else {
// 独立运行时
createApp(App).use(router).mount('#app')
}
其他框架(React、Vue2)文档里都有示例,基本就是把 mount 和 unmount 挂到 window 上。
强烈建议小白先用保活模式,完全不用改子应用。
单例模式VS保活模式
保活模式
什么是保活模式?想象子应用是一个“玩具房子”;
- 正常情况,你离开房间(切换页面),房子就被拆掉(卸载),回来需要重新搭(重新加载,有白屏)
- 保活模式,房子一直留在原地不拆(保持活着),你回来直接玩(零白屏,状态完全保留,比如输入框的内容还在)
- 优点:
- 配置最简单
- 零改造子应用
- 切换最快、无白屏
- 缺点: 如果基座多个地方加载同一个子应用,状态不共享(每个是独立的“房子”)
- 适合场景:导航菜单切换子应用,Tab页切换,大多数项目都先用这个
单例模式
什么是单例模式? 全局只有一个房子!不管你在几个房间放几个门(多个容器加载同一个子应用),开门进门的都是同一个房子(共享同一个实例和状态)。 怎么配置: * 基座不用特殊配置(可以结合alive=true一起使用) * 关键: 子应用需要改造代码(手动控制挂载和卸载) 子应用改造示例(Vue3+Vite项目为例,在main.js或main.ts最外面包一层):
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
let instance = null // 全局只有一个 instance
function mount() {
if (!instance) {
instance = createApp(App)
instance.use(router)
instance.mount('#app') // 挂载到容器
}
}
function unmount() {
if (instance) {
instance.unmount()
instance = null // 可选:销毁实例
}
}
// 被 Wujie 加载时(微前端模式)
if (window.__POWERED_BY_WUJIE__) {
// 挂到 window 上,Wujie 会自动调用
window.__WUJIE_MOUNT = () => {
mount()
}
window.__WUJIE_UNMOUNT = () => {
unmount()
}
// Vite 项目需要主动触发一次(有些构建工具需要)
window.__WUJIE.mount?.()
} else {
// 独立运行时(直接 npm run dev)
createApp(App).use(router).mount('#app')
}
- 单例模式特点:
- 全局只有一个子应用实例
- 多个地方加载同一个url的子应用,共享状态(比如登录用户信息全局共享)
- 切换时可以不重建(如果结合保活)
- 需要改在子应用(__WUJIE_MOUNT和__WUJIE_UNMOUNT)
- 优点:
- 状态全局共享(适合需要单点登录、共享用户数据等)
- 内存占用更少(只有一个实例)
- 缺点:
- 需要改造子应用代码
- 如果不结合保活,切换可能有轻微重建
- 适合场景: 子应用需要在基座多个位置出现,但状态一致(比如全局共享的侧边栏,头部组件)
- 最强组合:单例模式+保活模式一起用!
第四步:常见问题注意事项(小白必看)
- 跨域问题:子应用的地址必须和基座同域,或者子应用服务器开 CORS(允许跨域)。本地开发时,两个项目用不同端口,就要开代理或用 nginx。
- 子应用地址:本地开发时,子应用先单独跑起来(npm run dev),拿到 http://localhost:xxxx,然后填到基座的 url。
- 样式隔离:Wujie 天生隔离好,子应用样式不会污染基座。
- 通信:基座传数据用 props;子应用发事件用 bus.on('事件名', handler)。
Wujie微前端的通信方案(基座 ↔ 子应用、子应用 ↔ 子应用)
Wujie 的通信设计非常简单和强大,它内置了一个全局事件总线(EventBus) ,所有子应用和基座都共享同一个 bus 对象。这意味着:
- 基座 → 子应用:单向传值最常用的是 props
- 子应用 → 基座:子应用通过 bus.$emit 发事件,基座监听
- 子应用 ↔ 子应用:直接通过同一个 bus 互相发事件和监听(不需要经过基座中转!)
- 另外还有一些辅助方式(如插件共享状态)
下面我一步一步慢慢讲,每种方案都配代码示例(基于 Vue3 基座 + Vue3/React 子应用通用)。
方案1:基座 → 子应用(推荐:props 单向传值)
这是最简单、最常用的方式。基座把数据作为 props 传给子应用,子应用实时接收(数据变化会自动同步)。
基座配置( 组件上加 props) :
vue
<template>
<wujie-vue
name="sub-app1"
url="http://localhost:3001"
:props="subAppProps"
></wujie-vue>
</template>
<script setup>
import { ref } from 'vue'
const subAppProps = ref({
token: 'abc123',
userName: '小明',
count: 0,
// 甚至可以传函数!
jumpTo: (path) => {
console.log('子应用调用了基座的函数,跳转到', path)
// 这里可以控制基座路由
}
})
// 基座修改数据,子应用会自动收到更新
const updateCount = () => {
subAppProps.value.count += 1
}
</script>
子应用接收(在任何地方都可以拿到) :
JavaScript
// 子应用里(Vue/React 都一样)
if (window.__POWERED_BY_WUJIE__) {
// 被 Wujie 加载时
const props = window.$wujie?.props || {}
console.log('收到基座传来的数据:', props)
// props.token, props.userName, props.count, props.jumpTo()
// 如果想响应式(Vue3 示例)
import { watch } from 'vue'
watch(() => window.$wujie?.props, (newProps) => {
console.log('props 更新了:', newProps)
}, { deep: true })
}
优点:实时同步、类型安全、支持传函数(子应用可以调用基座方法) 缺点:单向(子应用不能直接改基座的数据)
方案2:子应用 → 基座、子应用 → 子应用(全局 EventBus)
Wujie 内置了一个全局 bus,所有子应用和基座共享同一个实例。子应用之间可以直接通信,不需要基座中转。
基座监听事件(在任意组件里) :
vue
<script setup>
import { onMounted, onUnmounted } from 'vue'
import { bus } from 'wujie-vue3' // 基座直接导入
onMounted(() => {
// 监听所有子应用发的事件
bus.$on('click-btn', (data) => {
console.log('基座收到事件,数据是:', data)
// 这里可以更新基座状态、跳转路由等
})
// 监听特定子应用(可选)
bus.$on('sub-app1-event', (data) => { ... })
})
onUnmounted(() => {
bus.$off('click-btn') // 记得销毁,防止内存泄漏
})
</script>
子应用发事件(在子应用任意位置) :
JavaScript
// 子应用里(不需要额外安装,直接用 window.$wujie.bus)
const sendEvent = () => {
window.$wujie?.bus.$emit('click-btn', {
msg: '来自子应用1的数据',
value: 999
})
}
// 子应用2 也可以直接监听子应用1的事件!
window.$wujie?.bus.$on('click-btn', (data) => {
console.log('子应用2 收到子应用1的事件:', data)
})
关键点:
- 所有子应用共享同一个 bus,所以子应用A on 收到。
- 事件名建议加前缀避免冲突,比如 'sub-app1-click'。
- 支持一次性监听:bus.$once('event', handler)
- 销毁监听:$off
优点:双向、实时、子应用间直接通信、非常灵活 缺点:需要手动管理事件名和销毁监听
方案3:基座 → 所有子应用广播(结合 props + bus)
如果你想基座主动推送数据给所有子应用:
- 用 bus.$emit 发事件,所有子应用监听同一个事件。
- 或者用插件(见下面)共享状态。
方案4:共享状态(插件方式,适合复杂状态管理)
如果你的项目状态很多,推荐用 Wujie 的插件系统共享 Pinia/Vuex/Redux 等。
官方推荐一个插件:wujie-polyfill,可以共享状态管理库。
示例:共享 Pinia(Vue) :
- 基座和所有子应用都安装同一个 Pinia store。
- 用插件让它们共享实例(具体代码看官方插件文档)。
或者更简单:用 window 共享一个全局对象(不推荐,容易污染)。
总结对比(小白一看就懂)
| 场景 | 推荐方案 | 方向 | 是否实时 | 代码复杂度 | 备注 |
|---|---|---|---|---|---|
| 基座 → 子应用(传数据/函数) | props | 单向 | 是 | 低 | 最常用 |
| 子应用 → 基座 | bus.on | 双向 | 是 | 中 | |
| 子应用 → 子应用 | bus.on | 双向 | 是 | 中 | 直接通信,超方便 |
| 全局共享复杂状态 | 插件(Pinia等) | 双向 | 是 | 高 | 项目大时用 |
wujie源码
wujie源码主要包含以下几部分:
- wujie-core
- wujie-vue2/wujie-vue3
- wujie-react
- wujie-polyfill
wujie-core
- 入口文件:index.ts
// 导出核心 API
export { startApp, preloadApp, destroyApp } from './start' // 启动、预加载、销毁
export { bus } from './bus' // 通信总线
export { Wujie } from './wujie' // 主类
// 全局配置
export function setupApp(options) { ... }
// 预加载
export async function preloadApp(options: PreloadAppOptions) {
// 提前下载资源,缓存 HTML/JS/CSS
}
// 启动应用
export async function startApp(options: StartAppOptions) {
// 创建沙箱、加载资源、执行代码
}
* startApp主要是创建沙箱-》fetch资源=〉执行JS=》挂载DOM
* Wujie有两种沙箱模式
* 默认:Proxy沙箱(快,推荐):wujie用Proxy代理window对象,让子应用以为自己在操作全局window,但其实操作的是“假window”
* iframe沙箱(最强隔离,但慢)
class WujieSandbox {
active = true; // 沙箱是否激活
proxyWindow: WindowProxy; // 代理的 window
constructor(name) {
// 创建假 window(其实是空对象)
const fakeWindow = Object.create(null);
// 记录修改的变量(为了销毁时恢复)
const modifiedMap = new Map();
const reversedMap = new Map(); // 恢复用
this.proxyWindow = new Proxy(fakeWindow, {
get(target, key) {
// 1. 先从子应用自己的变量拿
if (key in target) return target[key];
// 2. 否则从真实 window 拿(基座共享)
return window[key];
},
set(target, key, value) {
if (this.active) {
// 激活时:子应用修改变量,只改假 window
modifiedMap.set(key, value);
target[key] = value;
} else {
// 不激活时:直接改真实 window(共享)
window[key] = value;
}
return true;
},
// delete、has 等也类似处理
});
// 避免 with 语句(历史问题,已优化)
// 用 with 包裹代码时特殊处理
}
// 销毁沙箱:恢复基座 window
destroy() {
this.active = false;
// 把子应用改的变量恢复原样
modifiedMap.forEach((value, key) => {
window[key] = reversedMap.get(key) || originalValue;
});
}
}
- 怎么解决全局变量冲突?Proxy拦截get/set,只让子应用改自己的;这个比single-spa的snapshot快,比乾坤的Proxy更完善
框架适配包
- wujie-vue3(vue组件怎么实现的?)其实就是一个wrapper,核心还是core的startApp
总结
Wujie的目标:让子应用(Vue/React等)像嵌入iframe一样隔离强,性能好,无白屏 整体流程:
-
加载子应用HTML:用importHTML函数fetch子应用的index.html,解析它,提取所有的
-
解析HTML:用preocessTp把HTML拆成template,scripts,styles
-
创建沙箱:new 一个Wujie的类实例。它会:
- 创建一个隐藏的iframe(JS执行环境)
- 用Shadow DOM(影子根)隔离CSS/DOM
- 用Proxy代理window/document/location(JS隔离,最牛的地方)
- 如果浏览器太老,降级用纯iframe
-
执行资源:加载CSS(内联或替换路径),执行JS(内联eval或动态script),支持插件修改代码。
-
挂载:把解析后的template塞到Shadow DOM或iframe,执行mount生命周期。
-
通信: 左右子应用和基座共享一个bus(EventBus),on发事件。
-
保活/销毁:支持保活模式(不销毁DOM),销毁时恢复变量,清除事件。
-
插件系统:所有步骤可以插插件(比如改CSS路径,排除JS)
为什么强
* JS隔离:Proxy代理window,子应用改变量只改变自己的“假window”
* CSS隔离:Shdow DOM
* 样式/JS路径自动修正
* 通信简单:全局Bus
* 性能好: 预加载,保活,无白屏
wujie-core 中所有重要函数/类罗列(按重要度排序)
从源码文件看,核心是这些(没有一个“startApp”函数,可能是框架包里调的,core里是底层):
- bus(全局 EventBus 实例,index.ts)—— 通信核心
- EventBus 类(event.ts)—— emit/$off 等通信方法
- importHTML(entry.ts)—— 加载和解析子应用HTML,最重要加载函数
- processTpl(template.ts)—— 解析HTML提取script/style
- class Wujie(sandbox.ts)—— 沙箱主类,constructor/mount/destroy 等
- proxyGenerator / localGenerator(proxy.ts)—— 创建Proxy代理 window/document/location
- getPlugins / defaultPlugin(plugin.ts)—— 插件系统
- 其他辅助:processCssLoader、getEmbedHTML 等(加载CSS)