返回笔记首页

Wujie 无界微前端(2)- 性能优化与实战经验

主题配置

三、性能优化策略

3.1 预加载机制

Wujie 提供了强大的预加载能力,可以显著提升应用切换速度。

基础预加载

javascript
// 主应用 main.js
import { preloadApp } from 'wujie'

// 在应用启动时预加载高频子应用
preloadApp({
  name: 'app1',
  url: 'http://localhost:8081/',
  // 预加载时执行 JS
  exec: true,
  // 预取资源
  fetch: (url) => fetch(url),
  // 预加载的 props
  props: {
    preload: true
  }
})

// 批量预加载
const appsToPreload = [
  { name: 'app1', url: 'http://localhost:8081/' },
  { name: 'app2', url: 'http://localhost:8082/' }
]

Promise.all(
  appsToPreload.map(app => preloadApp(app))
).then(() => {
  console.log('所有应用预加载完成')
})

智能预加载策略

javascript
// 根据用户行为预加载
class SmartPreloader {
  constructor() {
    this.preloadQueue = []
    this.preloadHistory = new Map()
    this.userBehavior = {
      clicks: new Map(),
      visits: new Map()
    }
  }

  // 记录用户行为
  trackBehavior(appName, action = 'visit') {
    const map = action === 'visit' ? this.userBehavior.visits : this.userBehavior.clicks
    const count = map.get(appName) || 0
    map.set(appName, count + 1)

    // 分析并触发预加载
    this.analyze()
  }

  // 分析用户行为,决定预加载策略
  analyze() {
    const apps = Array.from(this.userBehavior.visits.entries())
      .sort((a, b) => b[1] - a[1]) // 按访问频率排序
      .slice(0, 3) // 取前3个高频应用
      .map(([name]) => name)

    apps.forEach(appName => {
      if (!this.preloadHistory.has(appName)) {
        this.preload(appName)
      }
    })
  }

  // 执行预加载
  async preload(appName) {
    const config = this.getAppConfig(appName)
    if (!config) return

    console.log(`预加载应用: ${appName}`)
    this.preloadHistory.set(appName, Date.now())

    try {
      await preloadApp(config)
      console.log(`${appName} 预加载成功`)
    } catch (error) {
      console.error(`${appName} 预加载失败`, error)
      this.preloadHistory.delete(appName)
    }
  }

  // 获取应用配置
  getAppConfig(appName) {
    const configs = {
      app1: { name: 'app1', url: 'http://localhost:8081/' },
      app2: { name: 'app2', url: 'http://localhost:8082/' },
      app3: { name: 'app3', url: 'http://localhost:8083/' }
    }
    return configs[appName]
  }

  // 空闲时预加载
  preloadOnIdle(appName) {
    if ('requestIdleCallback' in window) {
      requestIdleCallback(() => {
        this.preload(appName)
      }, { timeout: 2000 })
    } else {
      setTimeout(() => {
        this.preload(appName)
      }, 1000)
    }
  }
}

const preloader = new SmartPreloader()

// 使用示例
preloader.trackBehavior('app1', 'visit')
preloader.preloadOnIdle('app2')

3.2 保活机制深度优化

保活是 Wujie 的杀手锏功能,但需要合理使用。

保活配置

vue
<template>
  <div class="app-container">
    <!-- 开启保活 -->
    <WujieVue
      v-if="showApp1"
      name="app1"
      url="http://localhost:8081/"
      :alive="true"
      @activated="onAppActivated"
      @deactivated="onAppDeactivated"
    />

    <!-- 不保活,每次都重新加载 -->
    <WujieVue
      v-if="showApp2"
      name="app2"
      url="http://localhost:8082/"
      :alive="false"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const showApp1 = ref(true)
const showApp2 = ref(false)

const onAppActivated = () => {
  console.log('应用激活,状态保留')
  // 可以在这里做一些恢复操作
  // 比如刷新数据、恢复滚动位置等
}

const onAppDeactivated = () => {
  console.log('应用失活,状态保留')
  // 可以在这里做一些清理操作
  // 比如暂停视频、保存状态等
}
</script>

保活策略管理

javascript
class AliveStrategyManager {
  constructor() {
    this.aliveApps = new Set()
    this.maxAliveApps = 3 // 最多保活3个应用
    this.aliveQueue = [] // 保活队列(LRU)
  }

  // 判断是否应该保活
  shouldKeepAlive(appName) {
    // 策略1:高频应用总是保活
    const highFrequencyApps = ['app1', 'app3']
    if (highFrequencyApps.includes(appName)) {
      return true
    }

    // 策略2:根据访问频率
    const visitCount = this.getVisitCount(appName)
    if (visitCount > 5) {
      return true
    }

    // 策略3:最近使用的应用保活
    const recentApps = this.aliveQueue.slice(0, this.maxAliveApps)
    if (recentApps.includes(appName)) {
      return true
    }

    return false
  }

  // 激活应用
  activate(appName) {
    // 移到队列前面(LRU)
    const index = this.aliveQueue.indexOf(appName)
    if (index > -1) {
      this.aliveQueue.splice(index, 1)
    }
    this.aliveQueue.unshift(appName)

    // 如果超过限制,移除最久未使用的
    if (this.aliveQueue.length > this.maxAliveApps) {
      const removed = this.aliveQueue.pop()
      this.destroy(removed)
    }

    this.aliveApps.add(appName)
  }

  // 销毁应用
  destroy(appName) {
    import('wujie').then(({ destroyApp }) => {
      destroyApp(appName)
      this.aliveApps.delete(appName)
      console.log(`应用 ${appName} 已销毁`)
    })
  }

  // 获取访问次数
  getVisitCount(appName) {
    const count = localStorage.getItem(`app-visit-${appName}`)
    return parseInt(count) || 0
  }

  // 记录访问
  recordVisit(appName) {
    const count = this.getVisitCount(appName)
    localStorage.setItem(`app-visit-${appName}`, count + 1)
  }

  // 获取保活状态
  getAliveStatus() {
    return {
      alive: Array.from(this.aliveApps),
      queue: [...this.aliveQueue],
      count: this.aliveApps.size
    }
  }
}

const aliveManager = new AliveStrategyManager()

// 使用示例
const alive = computed(() => {
  return aliveManager.shouldKeepAlive(currentApp.value)
})

watch(currentApp, (newApp) => {
  aliveManager.recordVisit(newApp)
  aliveManager.activate(newApp)
})

3.3 资源加载优化

javascript
// 资源预取和并行加载
class ResourceOptimizer {
  constructor() {
    this.cache = new Map()
    this.loading = new Map()
  }

  // 预取资源
  async prefetch(url, type = 'script') {
    if (this.cache.has(url)) {
      return this.cache.get(url)
    }

    const link = document.createElement('link')
    link.rel = 'prefetch'
    link.as = type
    link.href = url

    return new Promise((resolve) => {
      link.onload = () => {
        this.cache.set(url, true)
        resolve()
      }
      link.onerror = () => resolve()
      document.head.appendChild(link)
    })
  }

  // 并行加载多个资源
  async loadParallel(resources) {
    const promises = resources.map(({ url, type }) =>
      this.prefetch(url, type)
    )

    return Promise.all(promises)
  }

  // 加载应用资源
  async loadAppResources(appUrl) {
    try {
      // 获取应用的 HTML
      const html = await fetch(appUrl).then(res => res.text())

      // 解析出所有资源
      const resources = this.parseResources(html)

      // 并行预取
      await this.loadParallel(resources)

      console.log(`应用资源预取完成: ${appUrl}`)
    } catch (error) {
      console.error(`资源预取失败: ${appUrl}`, error)
    }
  }

  // 解析 HTML 中的资源
  parseResources(html) {
    const resources = []

    // 解析 script
    const scriptRegex = /<script[^>]+src="([^"]+)"/g
    let match
    while ((match = scriptRegex.exec(html)) !== null) {
      resources.push({ url: match[1], type: 'script' })
    }

    // 解析 link
    const linkRegex = /<link[^>]+href="([^"]+)"[^>]*>/g
    while ((match = linkRegex.exec(html)) !== null) {
      resources.push({ url: match[1], type: 'style' })
    }

    return resources
  }
}

const optimizer = new ResourceOptimizer()

// 预取应用资源
optimizer.loadAppResources('http://localhost:8081/')

3.4 内存管理

javascript
// 内存监控和自动清理
class MemoryManager {
  constructor() {
    this.threshold = 100 * 1024 * 1024 // 100MB 阈值
    this.checkInterval = 30000 // 30秒检查一次
    this.apps = new Map()

    this.startMonitoring()
  }

  // 记录应用内存使用
  recordApp(appName, memoryUsage) {
    this.apps.set(appName, {
      memory: memoryUsage,
      timestamp: Date.now()
    })
  }

  // 开始监控
  startMonitoring() {
    setInterval(() => {
      this.checkMemory()
    }, this.checkInterval)
  }

  // 检查内存使用
  checkMemory() {
    if (!performance.memory) {
      console.warn('浏览器不支持内存监控')
      return
    }

    const { usedJSHeapSize, jsHeapSizeLimit } = performance.memory
    const usagePercent = (usedJSHeapSize / jsHeapSizeLimit) * 100

    console.log(`内存使用: ${(usedJSHeapSize / 1024 / 1024).toFixed(2)}MB (${usagePercent.toFixed(2)}%)`)

    // 如果超过阈值,触发清理
    if (usedJSHeapSize > this.threshold) {
      console.warn('内存使用过高,开始清理')
      this.cleanup()
    }
  }

  // 清理策略
  cleanup() {
    // 找到最久未使用的应用
    const sortedApps = Array.from(this.apps.entries())
      .sort((a, b) => a[1].timestamp - b[1].timestamp)

    // 销毁最久未使用的应用
    if (sortedApps.length > 0) {
      const [appName] = sortedApps[0]

      import('wujie').then(({ destroyApp }) => {
        destroyApp(appName)
        this.apps.delete(appName)
        console.log(`已清理应用: ${appName}`)
      })
    }

    // 触发垃圾回收(如果可用)
    if (window.gc) {
      window.gc()
    }
  }

  // 获取内存使用报告
  getReport() {
    if (!performance.memory) {
      return { error: '浏览器不支持内存监控' }
    }

    const { usedJSHeapSize, totalJSHeapSize, jsHeapSizeLimit } = performance.memory

    return {
      used: `${(usedJSHeapSize / 1024 / 1024).toFixed(2)}MB`,
      total: `${(totalJSHeapSize / 1024 / 1024).toFixed(2)}MB`,
      limit: `${(jsHeapSizeLimit / 1024 / 1024).toFixed(2)}MB`,
      usagePercent: `${((usedJSHeapSize / jsHeapSizeLimit) * 100).toFixed(2)}%`,
      apps: this.apps.size
    }
  }
}

const memoryManager = new MemoryManager()

// 应用加载时记录
const onAppMounted = (appName) => {
  if (performance.memory) {
    memoryManager.recordApp(appName, performance.memory.usedJSHeapSize)
  }
}

四、通信机制详解

4.1 Props 传递(单向数据流)

vue
<!-- 主应用 -->
<template>
  <WujieVue
    name="app1"
    url="http://localhost:8081/"
    :props="appProps"
  />
</template>

<script setup>
import { reactive, watch } from 'vue'

const userData = reactive({
  id: 1,
  name: 'Admin',
  role: 'admin',
  permissions: ['read', 'write']
})

const appProps = reactive({
  user: userData,
  theme: 'light',
  apiBaseUrl: '/api',
  // 传递方法
  logout: () => {
    console.log('用户退出')
    userData.name = ''
  },
  navigate: (path) => {
    console.log('导航到', path)
  }
})

// 监听用户数据变化,自动同步到子应用
watch(() => userData, (newVal) => {
  Object.assign(appProps.user, newVal)
}, { deep: true })
</script>

<!-- 子应用使用 -->
<script setup>
import { ref, onMounted, watch } from 'vue'

const mainAppData = ref(null)

onMounted(() => {
  // 获取主应用传递的 props
  if (window.$wujie) {
    mainAppData.value = window.$wujie.props

    console.log('用户信息:', mainAppData.value.user)
    console.log('主题:', mainAppData.value.theme)

    // 调用主应用方法
    // mainAppData.value.logout()
    // mainAppData.value.navigate('/home')
  }
})

// 监听 props 变化
watch(() => window.$wujie?.props, (newProps) => {
  if (newProps) {
    mainAppData.value = newProps
    console.log('Props 更新:', newProps)
  }
}, { deep: true })
</script>

4.2 EventBus 通信(双向)

javascript
// 主应用
import { bus } from 'wujie'

// 发送消息给子应用
bus.$emit('main-to-child', {
  type: 'notification',
  message: '主应用消息',
  data: { id: 123 }
})

// 监听子应用消息
bus.$on('child-to-main', (data) => {
  console.log('收到子应用消息', data)

  // 处理不同类型的消息
  switch (data.type) {
    case 'request-data':
      // 返回数据
      bus.$emit('main-response', { data: [] })
      break
    case 'user-action':
      // 处理用户操作
      handleUserAction(data.action)
      break
  }
})

// 子应用
// 监听主应用消息
window.$wujie?.bus.$on('main-to-child', (data) => {
  console.log('收到主应用消息', data)
})

// 发送消息给主应用
window.$wujie?.bus.$emit('child-to-main', {
  type: 'request-data',
  params: { page: 1 }
})

4.3 封装通信工具类

javascript
// communication.js
class WujieCommunication {
  constructor(appName) {
    this.appName = appName
    this.isMainApp = !window.$wujie
    this.handlers = new Map()

    this.init()
  }

  init() {
    if (this.isMainApp) {
      // 主应用
      import('wujie').then(({ bus }) => {
        this.bus = bus
        this.setupMainAppListeners()
      })
    } else {
      // 子应用
      this.bus = window.$wujie.bus
      this.props = window.$wujie.props
      this.setupChildAppListeners()
    }
  }

  setupMainAppListeners() {
    // 监听所有子应用消息
    this.bus.$on('*', (event, data) => {
      console.log(`[主应用] 收到事件: ${event}`, data)
      this.handleEvent(event, data)
    })
  }

  setupChildAppListeners() {
    // 监听主应用消息
    this.bus.$on(`to-${this.appName}`, (data) => {
      console.log(`[${this.appName}] 收到主应用消息`, data)
      this.handleEvent('main-message', data)
    })
  }

  // 发送消息
  send(event, data) {
    if (this.isMainApp) {
      // 主应用发送给指定子应用
      this.bus.$emit(`to-${data.target}`, {
        from: 'main',
        event,
        data: data.payload
      })
    } else {
      // 子应用发送给主应用
      this.bus.$emit('from-child', {
        from: this.appName,
        event,
        data
      })
    }
  }

  // 监听事件
  on(event, handler) {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, [])
    }
    this.handlers.get(event).push(handler)
  }

  // 移除监听
  off(event, handler) {
    if (!this.handlers.has(event)) return

    const handlers = this.handlers.get(event)
    const index = handlers.indexOf(handler)
    if (index > -1) {
      handlers.splice(index, 1)
    }
  }

  // 处理事件
  handleEvent(event, data) {
    const handlers = this.handlers.get(event)
    if (handlers) {
      handlers.forEach(handler => handler(data))
    }
  }

  // 请求-响应模式
  async request(event, data, timeout = 5000) {
    return new Promise((resolve, reject) => {
      const requestId = `${event}-${Date.now()}`

      // 设置超时
      const timer = setTimeout(() => {
        this.off(`response-${requestId}`)
        reject(new Error('Request timeout'))
      }, timeout)

      // 监听响应
      this.on(`response-${requestId}`, (response) => {
        clearTimeout(timer)
        this.off(`response-${requestId}`)
        resolve(response)
      })

      // 发送请求
      this.send(event, {
        ...data,
        requestId
      })
    })
  }

  // 响应请求
  response(requestId, data) {
    this.send(`response-${requestId}`, data)
  }
}

// 使用示例
// 主应用
const mainComm = new WujieCommunication('main')

mainComm.on('request-data', async (data) => {
  console.log('收到数据请求', data)

  const result = await fetchData(data.params)

  mainComm.response(data.requestId, result)
})

// 子应用
const childComm = new WujieCommunication('app1')

// 请求数据
const data = await childComm.request('request-data', {
  params: { page: 1 }
})

console.log('收到数据', data)

4.4 状态共享(进阶)

javascript
// 实现类似 Vuex 的全局状态管理
class WujieStore {
  constructor(initialState = {}) {
    this.state = reactive(initialState)
    this.mutations = {}
    this.actions = {}
    this.getters = {}

    this.setupCommunication()
  }

  setupCommunication() {
    if (window.$wujie) {
      // 子应用:监听状态更新
      window.$wujie.bus.$on('store-update', (newState) => {
        Object.assign(this.state, newState)
      })
    } else {
      // 主应用:广播状态更新
      watch(() => this.state, (newState) => {
        import('wujie').then(({ bus }) => {
          bus.$emit('store-update', newState)
        })
      }, { deep: true })
    }
  }

  // 注册 mutation
  registerMutation(name, handler) {
    this.mutations[name] = handler
  }

  // 提交 mutation
  commit(name, payload) {
    const mutation = this.mutations[name]
    if (!mutation) {
      console.error(`Mutation ${name} not found`)
      return
    }

    mutation(this.state, payload)
  }

  // 注册 action
  registerAction(name, handler) {
    this.actions[name] = handler
  }

  // 分发 action
  async dispatch(name, payload) {
    const action = this.actions[name]
    if (!action) {
      console.error(`Action ${name} not found`)
      return
    }

    return await action({
      state: this.state,
      commit: this.commit.bind(this),
      dispatch: this.dispatch.bind(this)
    }, payload)
  }

  // 注册 getter
  registerGetter(name, handler) {
    this.getters[name] = computed(() => handler(this.state))
  }

  // 获取 getter
  getGetter(name) {
    return this.getters[name]
  }
}

// 创建全局 store
const store = new WujieStore({
  user: null,
  token: null,
  theme: 'light'
})

// 注册 mutations
store.registerMutation('setUser', (state, user) => {
  state.user = user
})

store.registerMutation('setToken', (state, token) => {
  state.token = token
})

// 注册 actions
store.registerAction('login', async ({ commit }, { username, password }) => {
  const res = await fetch('/api/login', {
    method: 'POST',
    body: JSON.stringify({ username, password })
  })

  const data = await res.json()

  commit('setUser', data.user)
  commit('setToken', data.token)
})

// 注册 getters
store.registerGetter('isLoggedIn', (state) => {
  return !!state.token
})

// 使用
store.commit('setUser', { name: 'Admin' })
await store.dispatch('login', { username: 'admin', password: '123456' })
const isLoggedIn = store.getGetter('isLoggedIn')

// 导出供全局使用
window.__WUJIE_STORE__ = store

五、适用场景分析(续下文)