Wujie微前端

9 阅读4分钟

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)
  • 优点:
    • 状态全局共享(适合需要单点登录、共享用户数据等)
    • 内存占用更少(只有一个实例)
  • 缺点:
    • 需要改造子应用代码
    • 如果不结合保活,切换可能有轻微重建
  • 适合场景: 子应用需要在基座多个位置出现,但状态一致(比如全局共享的侧边栏,头部组件)
  • 最强组合:单例模式+保活模式一起用!

第四步:常见问题注意事项(小白必看)

  1. 跨域问题:子应用的地址必须和基座同域,或者子应用服务器开 CORS(允许跨域)。本地开发时,两个项目用不同端口,就要开代理或用 nginx。
  2. 子应用地址:本地开发时,子应用先单独跑起来(npm run dev),拿到 http://localhost:xxxx,然后填到基座的 url。
  3. 样式隔离:Wujie 天生隔离好,子应用样式不会污染基座。
  4. 通信:基座传数据用 props;子应用发事件用 bus.emit(事件,数据);基座监听bus.emit('事件名', 数据);基座监听 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 emit的事件,子应用B可以直接emit 的事件,子应用B 可以直接 on 收到。
  • 事件名建议加前缀避免冲突,比如 'sub-app1-click'。
  • 支持一次性监听:bus.$once('event', handler)
  • 销毁监听:$off

优点:双向、实时、子应用间直接通信、非常灵活 缺点:需要手动管理事件名和销毁监听

方案3:基座 → 所有子应用广播(结合 props + bus)

如果你想基座主动推送数据给所有子应用:

  • 用 bus.$emit 发事件,所有子应用监听同一个事件。
  • 或者用插件(见下面)共享状态。
方案4:共享状态(插件方式,适合复杂状态管理)

如果你的项目状态很多,推荐用 Wujie 的插件系统共享 Pinia/Vuex/Redux 等。

官方推荐一个插件:wujie-polyfill,可以共享状态管理库。

示例:共享 Pinia(Vue)

  1. 基座和所有子应用都安装同一个 Pinia store。
  2. 用插件让它们共享实例(具体代码看官方插件文档)。

或者更简单:用 window 共享一个全局对象(不推荐,容易污染)。

总结对比(小白一看就懂)
场景推荐方案方向是否实时代码复杂度备注
基座 → 子应用(传数据/函数)props单向最常用
子应用 → 基座bus.emit+emit + on双向
子应用 → 子应用bus.emit+emit + 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一样隔离强,性能好,无白屏 整体流程:

  1. 加载子应用HTML:用importHTML函数fetch子应用的index.html,解析它,提取所有的

  2. 解析HTML:用preocessTp把HTML拆成template,scripts,styles

  3. 创建沙箱:new 一个Wujie的类实例。它会:

    • 创建一个隐藏的iframe(JS执行环境)
    • 用Shadow DOM(影子根)隔离CSS/DOM
    • 用Proxy代理window/document/location(JS隔离,最牛的地方)
    • 如果浏览器太老,降级用纯iframe
  4. 执行资源:加载CSS(内联或替换路径),执行JS(内联eval或动态script),支持插件修改代码。

  5. 挂载:把解析后的template塞到Shadow DOM或iframe,执行mount生命周期。

  6. 通信: 左右子应用和基座共享一个bus(EventBus),emit/emit/on发事件。

  7. 保活/销毁:支持保活模式(不销毁DOM),销毁时恢复变量,清除事件。

  8. 插件系统:所有步骤可以插插件(比如改CSS路径,排除JS)

为什么强

* JS隔离:Proxy代理window,子应用改变量只改变自己的“假window”
* CSS隔离:Shdow DOM
* 样式/JS路径自动修正
* 通信简单:全局Bus
* 性能好: 预加载,保活,无白屏

wujie-core 中所有重要函数/类罗列(按重要度排序)

从源码文件看,核心是这些(没有一个“startApp”函数,可能是框架包里调的,core里是底层):

  1. bus(全局 EventBus 实例,index.ts)—— 通信核心
  2. EventBus 类(event.ts)—— on/on/emit/$off 等通信方法
  3. importHTML(entry.ts)—— 加载和解析子应用HTML,最重要加载函数
  4. processTpl(template.ts)—— 解析HTML提取script/style
  5. class Wujie(sandbox.ts)—— 沙箱主类,constructor/mount/destroy 等
  6. proxyGenerator / localGenerator(proxy.ts)—— 创建Proxy代理 window/document/location
  7. getPlugins / defaultPlugin(plugin.ts)—— 插件系统
  8. 其他辅助:processCssLoader、getEmbedHTML 等(加载CSS)