Brick Next 架构解析与前端工程化实践心得
又踩坑了,Brick Next 的微应用加载白屏
上周在搞一个用 Brick Next 搭的中后台系统,主应用加载子应用时,页面直接白屏,控制台报错:Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'mount')。我人傻了,明明本地 dev 跑得好好的,一上测试环境就挂。
折腾了半天发现,问题出在子应用的入口文件没被正确识别。Brick Next 依赖 window 上挂载的生命周期函数(比如 mount、bootstrap)来启动微应用。但我的子应用是用 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 里直接操作 DOM:
props.container是主应用传过来的 DOM 节点,子应用只能往里面 append,不能直接document.getElementById。否则在微应用模式下会找不到元素。 - 异步逻辑要处理好:
mount和unmount是 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,有点烦。

暂无评论