Brick Next 架构解析与前端工程化实践心得

小艺嘉 框架 阅读 1,153
赞 105 收藏
二维码
手机扫码查看
反馈

又踩坑了,Brick Next 的微应用加载白屏

上周在搞一个用 Brick Next 搭的中后台系统,主应用加载子应用时,页面直接白屏,控制台报错:Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'mount')。我人傻了,明明本地 dev 跑得好好的,一上测试环境就挂。

Brick Next 架构解析与前端工程化实践心得

折腾了半天发现,问题出在子应用的入口文件没被正确识别。Brick Next 依赖 window 上挂载的生命周期函数(比如 mountbootstrap)来启动微应用。但我的子应用是用 Vite 打的包,而 Vite 默认不暴露全局变量,除非你手动配置。

一开始我以为是网络问题,反复刷新、清缓存、换浏览器,都没用。后来打开 Network 面板一看,JS 文件其实加载成功了,状态码 200,但执行完之后 window 上啥也没有。这时候我才意识到:打包配置有问题。

试了三种方案,最后选了最糙但最稳的

我先去翻了下 Brick Next 官方文档,里面提到子应用需要导出 mount 等方法,并挂在 window 上。示例代码是这么写的:

// 子应用入口
window.myApp = {
  bootstrap: () => { /* ... */ },
  mount: (props) => { /* ... */ },
  unmount: () => { /* ... */ }
}

但我的项目是用 Vue 3 + Vite 搭的,入口是 main.js,默认只调用 createApp().mount(),根本不会往 window 上挂东西。所以得改打包方式。

第一种方案:改 Vite 配置,用 build.lib 模式打包成 IIFE。理论上可行,但实测发现 Vite 的 lib 模式对 Vue 应用支持不太友好,尤其是带路由和状态管理的,容易出各种奇怪的依赖问题。我试了半小时,各种报错,放弃。

第二种方案:在 index.html 里手动加 script 标签,把入口 JS 改成自执行函数。这招太 hack 了,而且破坏了 Vite 的 HMR 开发体验,本地开发和线上构建逻辑不一致,后面维护肯定要出事,pass。

第三种方案:在 main.js 末尾显式挂载到 window。虽然有点“脏”,但简单直接,兼容性好,开发和生产环境行为一致。亲测有效。

核心代码就这几行

最后我是这么改的。在子应用的 src/main.js 最后加上:

// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

// 正常挂载到 DOM(用于独立运行)
const appInstance = app.use(router).mount('#app')

// 关键:暴露给 Brick Next 微应用容器
if (window.__POWERED_BY_BRICK_NEXT__) {
  // 微应用模式下,不自动挂载,由主应用控制
  let instance = null

  window.mySubApp = {
    bootstrap: async () => {
      console.log('sub app bootstrap')
    },
    mount: async (props) => {
      console.log('sub app mount', props)
      instance = createApp(App).use(router)
      instance.mount(props.container.querySelector('#app'))
    },
    unmount: async () => {
      if (instance) {
        instance.unmount()
        instance = null
      }
    }
  }
} else {
  // 独立运行模式
  appInstance
}

注意这里有个判断:window.__POWERED_BY_BRICK_NEXT__。这是 Brick Next 主应用在加载子应用时注入的全局标志,用来区分当前是独立运行还是作为微应用嵌入。这样一套代码既能独立开发调试,又能被主应用加载,不用维护两套入口。

然后在 vite.config.js 里,确保打包后的 JS 不被压缩掉全局变量(其实 Vite 默认不会动这个,但以防万一):

// vite.config.js
export default {
  build: {
    target: 'es2015',
    rollupOptions: {
      output: {
        // 不需要改 output,只要确保代码能执行就行
      }
    }
  }
}

另外,子应用的 index.html 要保留一个根节点,比如:

<!-- public/index.html -->
<div id="app"></div>

主应用那边配置子应用时,要指定 container 和 entry:

// 主应用的 brick-next.config.js
{
  apps: [
    {
      name: 'mySubApp',
      entry: 'https://jztheme.com/subapp/assets/index.js',
      container: '#subapp-container',
      activeRule: '/subapp'
    }
  ]
}

这里注意,entry 必须指向 JS 文件,不是 HTML。Brick Next 会动态创建 script 标签加载这个 JS,然后检查 window.mySubApp 是否存在。如果名字对不上(比如你挂的是 window.foo,但配置里写的是 name: 'mySubApp'),就会报找不到 mount 方法。

踩坑提醒:这三点一定注意

  • 名字必须严格匹配window[appName] 中的 appName 必须和配置里的 name 字段完全一致,大小写都不能错。我一开始写成 MySubApp,结果死活找不到,浪费了 20 分钟。
  • 别在 mount 里直接操作 DOMprops.container 是主应用传过来的 DOM 节点,子应用只能往里面 append,不能直接 document.getElementById。否则在微应用模式下会找不到元素。
  • 异步逻辑要处理好mountunmount 是 async 函数,Brick Next 会 await 它们。如果你里面有异步操作(比如请求权限、初始化 SDK),记得用 await,不然主应用可能以为加载完了,其实还没 ready。

改完之后,测试环境终于不白屏了。不过还有个小问题:首次加载时,子应用的 CSS 会闪一下(FOUC)。这是因为 Brick Next 只加载 JS,CSS 是通过 JS 动态插入的,有延迟。这个问题暂时不影响功能,我就没深究,后续可以考虑把关键样式内联,或者用主应用预加载 CSS。

另外,Vite 的 HMR 在微应用模式下是失效的,因为子应用是被当作外部脚本加载的。所以开发时我还是用独立模式跑,功能稳定后再切到主应用里联调。虽然麻烦点,但能接受。

总结一下

Brick Next 的微应用机制其实挺简单的,核心就是“挂全局变量 + 生命周期函数”。问题往往出在打包工具没按预期暴露这些变量。Vite 默认不干这事,所以得手动补上。Webpack 用户可能没这问题,因为 Webpack 打包时更容易控制全局输出。

以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。比如有没有办法让 Vite 自动注入这些生命周期,或者用插件自动化处理?我现在这套方案虽然 work,但每次新建子应用都要复制粘贴那段 boilerplate,有点烦。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论