返回笔记首页

微前端关键问题解决

主题配置

一、应用隔离

1.1 JS 沙箱实现原理

问题描述

多个子应用运行时,JavaScript 全局变量可能冲突,如何实现完全隔离?

三种沙箱方案对比

方案1:Proxy 沙箱(最优)
javascript
class ProxySandbox {
  constructor(name) {
    this.name = name
    this.running = false

    // 子应用的沙箱环境
    const fakeWindow = Object.create(null)

    // 记录变更
    this.addedPropsMap = new Map()
    this.modifiedPropsMap = new Map()

    this.proxyWindow = new Proxy(fakeWindow, {
      get: (target, prop) => {
        // 优先从沙箱取值
        if (prop in target) {
          return target[prop]
        }

        // 某些属性需要从真实 window 取
        const rawValue = window[prop]

        // 如果是函数,绑定 this 为真实 window
        if (typeof rawValue === 'function') {
          const boundFunc = rawValue.bind(window)
          // 对于构造函数,保留原型链
          if (rawValue.prototype) {
            Object.setPrototypeOf(boundFunc, rawValue)
          }
          return boundFunc
        }

        return rawValue
      },

      set: (target, prop, value) => {
        if (this.running) {
          // 记录新增的属性
          if (!window.hasOwnProperty(prop)) {
            this.addedPropsMap.set(prop, value)
          }
          // 记录修改的属性
          else if (!this.modifiedPropsMap.has(prop)) {
            this.modifiedPropsMap.set(prop, window[prop])
          }

          target[prop] = value
          return true
        }
        return true
      },

      has: (target, prop) => {
        return prop in target || prop in window
      },

      deleteProperty: (target, prop) => {
        if (target.hasOwnProperty(prop)) {
          delete target[prop]
          return true
        }
        return true
      }
    })
  }

  active() {
    if (!this.running) {
      this.running = true
    }
  }

  inactive() {
    this.running = false
    // 清理沙箱环境
    this.addedPropsMap.clear()
    this.modifiedPropsMap.clear()
  }
}

// 使用示例
const sandbox1 = new ProxySandbox('app1')
const sandbox2 = new ProxySandbox('app2')

sandbox1.active()
// 在 sandbox1 中执行代码
with(sandbox1.proxyWindow) {
  var count = 1
  function test() {
    console.log('app1 test')
  }
}

sandbox2.active()
// 在 sandbox2 中执行代码
with(sandbox2.proxyWindow) {
  var count = 2
  function test() {
    console.log('app2 test')
  }
}

// 两个沙箱互不影响
console.log(window.count) // undefined
方案2:快照沙箱
javascript
class SnapshotSandbox {
  constructor() {
    this.windowSnapshot = {}
    this.modifyPropsMap = {}
  }

  active() {
    // 保存当前 window 快照
    for (const prop in window) {
      if (window.hasOwnProperty(prop)) {
        this.windowSnapshot[prop] = window[prop]
      }
    }

    // 恢复上次的修改
    Object.keys(this.modifyPropsMap).forEach(prop => {
      window[prop] = this.modifyPropsMap[prop]
    })
  }

  inactive() {
    // 记录变更
    for (const prop in window) {
      if (window.hasOwnProperty(prop)) {
        if (window[prop] !== this.windowSnapshot[prop]) {
          this.modifyPropsMap[prop] = window[prop]
          // 还原
          window[prop] = this.windowSnapshot[prop]
        }
      }
    }
  }
}

// 使用示例(只支持单实例)
const sandbox = new SnapshotSandbox()

sandbox.active()
window.count = 1
sandbox.inactive()

console.log(window.count) // undefined

sandbox.active()
console.log(window.count) // 1
方案3:iframe 沙箱
javascript
class IframeSandbox {
  constructor() {
    this.iframe = document.createElement('iframe')
    this.iframe.style.display = 'none'
    document.body.appendChild(this.iframe)

    this.sandboxWindow = this.iframe.contentWindow
  }

  execScript(code) {
    this.sandboxWindow.eval(code)
  }

  destroy() {
    document.body.removeChild(this.iframe)
  }
}

// 使用示例
const sandbox = new IframeSandbox()
sandbox.execScript('var count = 1')
console.log(window.count) // undefined
console.log(sandbox.sandboxWindow.count) // 1

1.2 CSS 样式隔离方案

方案1:Shadow DOM(严格隔离)

javascript
// 创建 Shadow DOM
class ShadowCSSIsolation {
  constructor(container) {
    this.shadowRoot = container.attachShadow({ mode: 'open' })
  }

  mount(html, css) {
    this.shadowRoot.innerHTML = `
      <style>${css}</style>
      ${html}
    `
  }

  unmount() {
    this.shadowRoot.innerHTML = ''
  }
}

// 使用示例
const container = document.getElementById('app')
const isolation = new ShadowCSSIsolation(container)

isolation.mount(
  '<div class="title">标题</div>',
  '.title { color: red; }'
)

// 样式完全隔离,不会影响外部
解决 Modal 等弹窗问题
javascript
// 劫持 appendChild,将弹窗挂载到 Shadow DOM 内
const originalAppendChild = Node.prototype.appendChild

Node.prototype.appendChild = function(node) {
  // 如果是 Modal 等弹窗
  if (node.classList?.contains('ant-modal-wrap')) {
    // 挂载到 Shadow DOM 内
    return shadowRoot.appendChild(node)
  }
  return originalAppendChild.call(this, node)
}

方案2:Scoped CSS

javascript
class ScopedCSS {
  constructor(appName) {
    this.appName = appName
    this.styleNodes = []
  }

  process(cssText) {
    // 给所有选择器加前缀
    const prefix = `[data-app="${this.appName}"]`

    return cssText.replace(/([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)/g, (match, selector, connector) => {
      // 过滤掉特殊选择器
      if (selector.match(/^\s*(@|keyframes|font-face|import|charset)/)) {
        return match
      }

      // 添加前缀
      const trimmedSelector = selector.trim()
      return `${prefix} ${trimmedSelector}${connector}`
    })
  }

  mount(cssText) {
    const processedCSS = this.process(cssText)
    const style = document.createElement('style')
    style.textContent = processedCSS
    document.head.appendChild(style)
    this.styleNodes.push(style)
  }

  unmount() {
    this.styleNodes.forEach(node => {
      document.head.removeChild(node)
    })
    this.styleNodes = []
  }
}

// 使用示例
const scopedCSS = new ScopedCSS('app1')

scopedCSS.mount(`
  .title { color: red; }
  .content { font-size: 14px; }
`)

// 处理后的 CSS:
// [data-app="app1"] .title { color: red; }
// [data-app="app1"] .content { font-size: 14px; }

方案3:动态加载/卸载

javascript
class DynamicStylesheet {
  constructor() {
    this.styleNodes = []
  }

  loadCSS(href) {
    return new Promise((resolve, reject) => {
      const link = document.createElement('link')
      link.rel = 'stylesheet'
      link.href = href
      link.onload = () => resolve()
      link.onerror = () => reject()

      document.head.appendChild(link)
      this.styleNodes.push(link)
    })
  }

  loadInlineCSS(css) {
    const style = document.createElement('style')
    style.textContent = css
    document.head.appendChild(style)
    this.styleNodes.push(style)
  }

  unloadAll() {
    this.styleNodes.forEach(node => {
      if (node.parentNode) {
        node.parentNode.removeChild(node)
      }
    })
    this.styleNodes = []
  }
}

// 使用示例
const stylesheet = new DynamicStylesheet()

// 子应用挂载时加载样式
await stylesheet.loadCSS('app1.css')
stylesheet.loadInlineCSS('.title { color: red; }')

// 子应用卸载时移除样式
stylesheet.unloadAll()

1.3 全局变量污染处理

javascript
// 全局变量白名单
const GLOBAL_VAR_WHITELIST = [
  'Vue',
  'VueRouter',
  'Vuex',
  'axios',
  'moment',
  'lodash',
  '_',
  '$'
]

class GlobalVarProtector {
  constructor(appName) {
    this.appName = appName
    this.snapshot = {}
  }

  protect() {
    // 保存白名单之外的全局变量
    for (const key in window) {
      if (!GLOBAL_VAR_WHITELIST.includes(key)) {
        this.snapshot[key] = window[key]
      }
    }
  }

  restore() {
    // 检测新增的全局变量
    for (const key in window) {
      if (!GLOBAL_VAR_WHITELIST.includes(key) && !(key in this.snapshot)) {
        console.warn(`[${this.appName}] 检测到新增全局变量: ${key}`)
        delete window[key]
      }
    }

    // 恢复修改的全局变量
    for (const key in this.snapshot) {
      if (window[key] !== this.snapshot[key]) {
        console.warn(`[${this.appName}] 检测到全局变量被修改: ${key}`)
        window[key] = this.snapshot[key]
      }
    }
  }
}

// 使用示例
const protector = new GlobalVarProtector('app1')

protector.protect()
// 子应用运行
window.myGlobalVar = 123 // 会被检测到
protector.restore()

1.4 事件监听隔离

javascript
class EventListenerManager {
  constructor(appName) {
    this.appName = appName
    this.listeners = []

    // 劫持 addEventListener
    this.originalAddEventListener = window.addEventListener
    this.originalRemoveEventListener = window.removeEventListener

    this.hijack()
  }

  hijack() {
    const self = this

    window.addEventListener = function(type, listener, options) {
      // 记录监听器
      self.listeners.push({ type, listener, options })

      // 调用原始方法
      return self.originalAddEventListener.call(window, type, listener, options)
    }
  }

  restore() {
    // 恢复原始方法
    window.addEventListener = this.originalAddEventListener
    window.removeEventListener = this.originalRemoveEventListener
  }

  removeAll() {
    // 移除所有监听器
    this.listeners.forEach(({ type, listener, options }) => {
      this.originalRemoveEventListener.call(window, type, listener, options)
    })

    this.listeners = []
    console.log(`[${this.appName}] 已清理 ${this.listeners.length} 个事件监听器`)
  }
}

// 使用示例
const eventManager = new EventListenerManager('app1')

// 子应用添加监听
window.addEventListener('resize', handleResize)
window.addEventListener('scroll', handleScroll)

// 子应用卸载时清理
eventManager.removeAll()
eventManager.restore()

二、应用通信

2.1 Props 传递

javascript
// 主应用
registerMicroApps([
  {
    name: 'app1',
    entry: '//localhost:8081',
    container: '#container',
    activeRule: '/app1',
    props: {
      // 传递数据
      user: { name: 'Admin', role: 'admin' },
      token: 'xxxx',
      // 传递方法
      logout: () => { /* 退出登录 */ },
      navigate: (path) => { /* 路由跳转 */ },
      // 传递工具
      api: axios.create({ baseURL: '/api' })
    }
  }
])

// 子应用接收
export async function mount(props) {
  const { user, token, logout, navigate, api } = props

  // 使用传递的数据和方法
  console.log('用户信息', user)

  // 调用主应用方法
  logout()
  navigate('/home')

  // 使用主应用的 API 实例
  const res = await api.get('/users')
}

2.2 全局状态管理

javascript
// 方案1:qiankun 的 initGlobalState
import { initGlobalState } from 'qiankun'

const actions = initGlobalState({
  user: null,
  token: null,
  theme: 'light'
})

// 主应用监听
actions.onGlobalStateChange((state, prev) => {
  console.log('状态变化', state, prev)
})

// 主应用修改
actions.setGlobalState({ user: { name: 'Admin' } })

// 子应用监听
export async function mount(props) {
  props.onGlobalStateChange((state, prev) => {
    console.log('子应用收到状态变化', state)
  })
}

// 子应用修改
props.setGlobalState({ theme: 'dark' })
javascript
// 方案2:自定义全局状态
class GlobalStateManager {
  constructor() {
    this.state = {}
    this.listeners = []
  }

  getState() {
    return this.state
  }

  setState(newState) {
    const prevState = { ...this.state }
    this.state = { ...this.state, ...newState }

    // 通知所有监听者
    this.listeners.forEach(listener => {
      listener(this.state, prevState)
    })
  }

  onStateChange(listener) {
    this.listeners.push(listener)

    // 返回取消监听函数
    return () => {
      const index = this.listeners.indexOf(listener)
      if (index > -1) {
        this.listeners.splice(index, 1)
      }
    }
  }
}

// 创建全局实例
window.__GLOBAL_STATE__ = new GlobalStateManager()

// 使用
window.__GLOBAL_STATE__.setState({ user: { name: 'Admin' } })
window.__GLOBAL_STATE__.onStateChange((state, prev) => {
  console.log('状态变化', state)
})

2.3 EventBus 事件总线

javascript
class EventBus {
  constructor() {
    this.events = {}
  }

  on(event, handler) {
    if (!this.events[event]) {
      this.events[event] = []
    }
    this.events[event].push(handler)
  }

  once(event, handler) {
    const onceHandler = (...args) => {
      handler(...args)
      this.off(event, onceHandler)
    }
    this.on(event, onceHandler)
  }

  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach(handler => {
        handler(...args)
      })
    }
  }

  off(event, handler) {
    if (!this.events[event]) return

    if (!handler) {
      // 移除所有监听
      delete this.events[event]
    } else {
      // 移除指定监听
      const index = this.events[event].indexOf(handler)
      if (index > -1) {
        this.events[event].splice(index, 1)
      }
    }
  }
}

// 创建全局 EventBus
window.__EVENT_BUS__ = new EventBus()

// 主应用
window.__EVENT_BUS__.on('user-login', (user) => {
  console.log('用户登录', user)
})

// 子应用
window.__EVENT_BUS__.emit('user-login', { name: 'Admin' })

2.4 LocalStorage/SessionStorage

javascript
// 方案1:直接使用(简单但不推荐)
// 主应用
localStorage.setItem('user', JSON.stringify({ name: 'Admin' }))

// 子应用
const user = JSON.parse(localStorage.getItem('user'))

// 方案2:封装统一的 Storage(推荐)
class MicroAppStorage {
  constructor(appName) {
    this.appName = appName
    this.prefix = `micro-app-${appName}-`
  }

  set(key, value) {
    const prefixedKey = this.prefix + key
    const data = {
      value,
      timestamp: Date.now(),
      appName: this.appName
    }
    localStorage.setItem(prefixedKey, JSON.stringify(data))
  }

  get(key) {
    const prefixedKey = this.prefix + key
    const data = localStorage.getItem(prefixedKey)

    if (!data) return null

    try {
      const parsed = JSON.parse(data)
      return parsed.value
    } catch (e) {
      return null
    }
  }

  remove(key) {
    const prefixedKey = this.prefix + key
    localStorage.removeItem(prefixedKey)
  }

  clear() {
    // 清除本应用的所有数据
    Object.keys(localStorage).forEach(key => {
      if (key.startsWith(this.prefix)) {
        localStorage.removeItem(key)
      }
    })
  }
}

// 使用
const storage = new MicroAppStorage('app1')
storage.set('user', { name: 'Admin' })
const user = storage.get('user')

2.5 CustomEvent 自定义事件

javascript
// 主应用派发事件
const event = new CustomEvent('micro-app-message', {
  detail: {
    from: 'main',
    type: 'user-update',
    data: { name: 'Admin' }
  }
})
window.dispatchEvent(event)

// 子应用监听事件
window.addEventListener('micro-app-message', (e) => {
  const { from, type, data } = e.detail
  console.log('收到消息', from, type, data)

  if (type === 'user-update') {
    // 处理用户更新
  }
})

// 子应用派发事件给主应用
const replyEvent = new CustomEvent('micro-app-message', {
  detail: {
    from: 'app1',
    type: 'notification',
    data: { message: '操作成功' }
  }
})
window.dispatchEvent(replyEvent)

三、公共依赖处理

3.1 externals 外部化

javascript
// 主应用 index.html
<!DOCTYPE html>
<html>
<head>
  <title>微前端主应用</title>
  <!-- 公共依赖通过 CDN 引入 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue-router@4.2.4/dist/vue-router.global.prod.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios@1.5.0/dist/axios.min.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

// 子应用 webpack 配置
module.exports = {
  configureWebpack: {
    externals: {
      'vue': 'Vue',
      'vue-router': 'VueRouter',
      'axios': 'axios'
    }
  }
}

// 子应用正常使用
import { createApp } from 'vue' // 实际从 window.Vue 获取
import axios from 'axios' // 实际从 window.axios 获取

3.2 shared 共享依赖(qiankun)

javascript
// 主应用
import { loadMicroApp } from 'qiankun'

loadMicroApp({
  name: 'app1',
  entry: '//localhost:8081',
  container: '#container',
  props: {
    // 共享依赖
    shared: {
      vue: window.Vue,
      'vue-router': window.VueRouter,
      axios: window.axios
    }
  }
})

// 子应用
export async function mount(props) {
  const { shared } = props

  // 使用共享依赖
  const app = shared.vue.createApp(App)
  const router = shared['vue-router'].createRouter({...})

  app.use(router)
  app.mount('#app')
}

3.3 版本管理策略

javascript
// 依赖版本配置文件
// shared-deps.config.js
module.exports = {
  dependencies: {
    vue: {
      version: '3.3.4',
      cdn: 'https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js',
      global: 'Vue'
    },
    'vue-router': {
      version: '4.2.4',
      cdn: 'https://cdn.jsdelivr.net/npm/vue-router@4.2.4/dist/vue-router.global.prod.js',
      global: 'VueRouter'
    },
    axios: {
      version: '1.5.0',
      cdn: 'https://cdn.jsdelivr.net/npm/axios@1.5.0/dist/axios.min.js',
      global: 'axios'
    }
  }
}

// 主应用动态加载
class SharedDepsLoader {
  constructor(config) {
    this.config = config
    this.loadedDeps = new Set()
  }

  async load(deps) {
    const promises = deps.map(dep => this.loadDep(dep))
    await Promise.all(promises)
  }

  async loadDep(depName) {
    if (this.loadedDeps.has(depName)) {
      return Promise.resolve()
    }

    const dep = this.config.dependencies[depName]
    if (!dep) {
      throw new Error(`未找到依赖配置: ${depName}`)
    }

    return new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = dep.cdn
      script.onload = () => {
        this.loadedDeps.add(depName)
        console.log(`${depName}@${dep.version} 加载完成`)
        resolve()
      }
      script.onerror = () => {
        reject(new Error(`${depName} 加载失败`))
      }
      document.head.appendChild(script)
    })
  }
}

// 使用
const sharedConfig = require('./shared-deps.config.js')
const loader = new SharedDepsLoader(sharedConfig)

await loader.load(['vue', 'vue-router', 'axios'])

四、路由管理

4.1 主应用路由劫持

javascript
// 劫持 history API
const originalPushState = window.history.pushState
const originalReplaceState = window.history.replaceState

window.history.pushState = function(...args) {
  const result = originalPushState.apply(this, args)

  // 触发自定义事件
  window.dispatchEvent(new CustomEvent('micro-app-route-change', {
    detail: { type: 'pushState', args }
  }))

  return result
}

window.history.replaceState = function(...args) {
  const result = originalReplaceState.apply(this, args)

  window.dispatchEvent(new CustomEvent('micro-app-route-change', {
    detail: { type: 'replaceState', args }
  }))

  return result
}

// 监听 popstate
window.addEventListener('popstate', (e) => {
  window.dispatchEvent(new CustomEvent('micro-app-route-change', {
    detail: { type: 'popstate', state: e.state }
  }))
})

4.2 子应用路由同步

javascript
// 子应用路由配置
const router = createRouter({
  history: createWebHistory('/app1'), // 使用子路径
  routes: [...]
})

// 监听路由变化,通知主应用
router.afterEach((to, from) => {
  if (window.__POWERED_BY_QIANKUN__) {
    // 通知主应用路由变化
    window.dispatchEvent(new CustomEvent('子应用路由变化', {
      detail: {
        app: 'app1',
        path: to.path,
        query: to.query
      }
    }))
  }
})

4.3 浏览器前进后退

javascript
// 主应用处理前进后退
class RouteManager {
  constructor() {
    this.history = []
    this.currentIndex = -1

    this.listen()
  }

  listen() {
    window.addEventListener('popstate', (e) => {
      const direction = this.getDirection()
      console.log('浏览器', direction === 1 ? '前进' : '后退')

      // 可以在这里做一些处理
      // 比如记录日志、埋点等
    })
  }

  push(path) {
    this.currentIndex++
    this.history[this.currentIndex] = path
    this.history.length = this.currentIndex + 1
  }

  getDirection() {
    // 判断是前进还是后退
    // 实际实现会更复杂
    return 1 // 1: 前进, -1: 后退
  }
}

const routeManager = new RouteManager()

五、性能优化

5.1 预加载子应用

javascript
// qiankun 预加载
import { prefetchApps } from 'qiankun'

// 在空闲时间预加载
prefetchApps([
  { name: 'app1', entry: '//localhost:8081' },
  { name: 'app2', entry: '//localhost:8082' }
])

// 自定义预加载策略
class PreloadStrategy {
  constructor() {
    this.queue = []
    this.loading = false
  }

  add(app) {
    this.queue.push(app)
    this.process()
  }

  async process() {
    if (this.loading || this.queue.length === 0) {
      return
    }

    this.loading = true
    const app = this.queue.shift()

    try {
      await this.preloadApp(app)
      console.log(`${app.name} 预加载完成`)
    } catch (error) {
      console.error(`${app.name} 预加载失败`, error)
    } finally {
      this.loading = false
      this.process()
    }
  }

  async preloadApp(app) {
    // 获取应用资源
    const html = await fetch(app.entry).then(res => res.text())

    // 解析并预加载 JS/CSS
    const scripts = this.extractScripts(html)
    const styles = this.extractStyles(html)

    await Promise.all([
      ...scripts.map(src => this.preloadScript(src)),
      ...styles.map(href => this.preloadStyle(href))
    ])
  }

  preloadScript(src) {
    return new Promise((resolve) => {
      const link = document.createElement('link')
      link.rel = 'prefetch'
      link.as = 'script'
      link.href = src
      link.onload = resolve
      link.onerror = resolve
      document.head.appendChild(link)
    })
  }

  preloadStyle(href) {
    return new Promise((resolve) => {
      const link = document.createElement('link')
      link.rel = 'prefetch'
      link.as = 'style'
      link.href = href
      link.onload = resolve
      link.onerror = resolve
      document.head.appendChild(link)
    })
  }

  extractScripts(html) {
    const scriptRegex = /<script[^>]+src="([^"]+)"/g
    const scripts = []
    let match

    while ((match = scriptRegex.exec(html)) !== null) {
      scripts.push(match[1])
    }

    return scripts
  }

  extractStyles(html) {
    const styleRegex = /<link[^>]+href="([^"]+)"[^>]*rel="stylesheet"/g
    const styles = []
    let match

    while ((match = styleRegex.exec(html)) !== null) {
      styles.push(match[1])
    }

    return styles
  }
}

// 使用
const strategy = new PreloadStrategy()
strategy.add({ name: 'app1', entry: '//localhost:8081' })

5.2 子应用缓存

javascript
class AppCache {
  constructor() {
    this.cache = new Map()
  }

  set(name, resources) {
    this.cache.set(name, {
      resources,
      timestamp: Date.now()
    })
  }

  get(name) {
    const item = this.cache.get(name)

    if (!item) return null

    // 检查是否过期(1小时)
    const isExpired = Date.now() - item.timestamp > 3600000

    if (isExpired) {
      this.cache.delete(name)
      return null
    }

    return item.resources
  }

  clear(name) {
    if (name) {
      this.cache.delete(name)
    } else {
      this.cache.clear()
    }
  }
}

const appCache = new AppCache()

// 加载应用时先检查缓存
async function loadApp(name, entry) {
  let resources = appCache.get(name)

  if (!resources) {
    // 没有缓存,重新加载
    resources = await fetchAppResources(entry)
    appCache.set(name, resources)
  }

  return resources
}

5.3 按需加载

javascript
// 路由懒加载
const routes = [
  {
    path: '/app1',
    component: () => import('./views/App1.vue')
  },
  {
    path: '/app2',
    component: () => import('./views/App2.vue')
  }
]

// 组件懒加载
<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <Loading />
    </template>
  </Suspense>
</template>

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

const AsyncComponent = defineAsyncComponent(() =>
  import('./components/Heavy.vue')
)
</script>

5.4 资源预取

javascript
// 使用 <link rel="prefetch">
function prefetchResource(url, type = 'script') {
  const link = document.createElement('link')
  link.rel = 'prefetch'
  link.as = type
  link.href = url
  document.head.appendChild(link)
}

// 预取子应用资源
prefetchResource('//localhost:8081/app.js', 'script')
prefetchResource('//localhost:8081/app.css', 'style')

本文档提供了微前端开发中最关键的技术问题解决方案,每个方案都包含详细的原理说明和可运行的代码示例。这些技术点是微前端项目中必须掌握的核心知识。