qiankun微前端实战:从接入到优化的完整踩坑指南
优化前:卡得不行
上个月接手一个老项目,主应用用 qiankun 搞了 5 个子应用,开发环境跑起来还行,一到测试环境就卡成 PPT。用户点一下菜单,等 3 秒才加载出子应用,有时候直接白屏。我本地测还好,但测试环境网络慢一点,首屏加载时间飙到 5 秒以上,运维同事都来问是不是前端又搞什么大动作了。
其实不是代码逻辑问题,就是子应用加载太慢。每个子应用都是独立打包的,主应用一启动就一股脑去加载所有子应用的 entry HTML,哪怕用户根本没点进去。更离谱的是,有些子应用还带了十几兆的图表库,光 JS 就 4MB,浏览器直接卡住。
找到瓶颈了!
我先用 Chrome DevTools 的 Performance 面板录了一次加载过程,发现主线程被大量解析 JS 和 CSS 占满,而且 Network 面板里一堆子应用的 HTML、JS、CSS 并行请求,互相抢带宽。特别是那个带 ECharts 的子应用,一个 vendor.js 就 3.8MB,加载花了 2.1 秒。
再看 qiankun 的源码,发现默认的 loadApp 是在注册子应用时就触发的,不管用户是否访问。这明显是“预加载过度”——我们不需要一上来就把所有子应用都塞进内存。
另外,子应用之间还有重复依赖,比如主应用和两个子应用都用了 lodash,结果 bundle 里各打一份,白白增加体积。
核心优化:懒加载 + 公共依赖提取
折腾了半天,最后靠两招搞定:
- 子应用按需加载:用户点菜单时才加载对应子应用
- 提取公共依赖:主应用统一提供 React、lodash 等,子应用 externals 掉
先说懒加载。qiankun 官方其实支持动态注册,但文档写得比较隐晦。我改了主应用的注册逻辑,把子应用的注册延迟到路由切换时:
// 主应用 - 原来的写法(一上来全注册)
const apps = [
{ name: 'app1', entry: '//localhost:8081', container: '#subapp-viewport', activeRule: '/app1' },
{ name: 'app2', entry: '//localhost:8082', container: '#subapp-viewport', activeRule: '/app2' },
];
apps.forEach(registerMicroApps);
start();
// 优化后:只在需要时注册
let registeredApps = new Set();
function loadAppWhenNeeded(appName) {
if (registeredApps.has(appName)) return;
const appConfig = {
app1: { name: 'app1', entry: '//localhost:8081', container: '#subapp-viewport', activeRule: '/app1' },
app2: { name: 'app2', entry: '//localhost:8082', container: '#subapp-viewport', activeRule: '/app2' },
}[appName];
if (appConfig) {
registerMicroApps([appConfig]);
registeredApps.add(appName);
}
}
// 在路由守卫或菜单点击处调用
window.addEventListener('hashchange', () => {
const path = window.location.hash.slice(1);
if (path.startsWith('/app1')) loadAppWhenNeeded('app1');
if (path.startsWith('/app2')) loadAppWhenNeeded('app2');
});
这里注意我踩过好几次坑:不要重复注册同一个子应用,否则 qiankun 会报错。所以加了个 registeredApps Set 来记录已注册的。
然后是公共依赖。我在主应用的 webpack 配置里把 React、ReactDOM、lodash 打包成全局变量:
// 主应用 webpack.config.js
module.exports = {
// ...
output: {
library: 'MainApp',
libraryTarget: 'umd',
},
externals: {
react: 'React',
'react-dom': 'ReactDOM',
lodash: '_',
},
};
子应用也做对应配置,不打包这些依赖:
// 子应用 webpack.config.js
module.exports = {
// ...
externals: {
react: 'React',
'react-dom': 'ReactDOM',
lodash: '_',
},
};
同时在主应用的 HTML 里提前引入这些库:
<!-- 主应用 index.html -->
<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
这样每个子应用的 bundle 少了 1~2MB,效果立竿见影。
其他小优化(顺手做了)
除了上面两个大头,我还顺手干了几件事:
- 给子应用 entry 加了缓存头(Cache-Control: max-age=31536000),避免重复下载
- 主应用用
<link rel="prefetch">提前拉取可能用到的子应用 HTML(但不执行 JS) - 关掉子应用的 sourcemap(生产环境没必要)
prefetch 的代码长这样:
// 在主应用首页,预加载高频子应用
function prefetchSubApp(entryUrl) {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = entryUrl;
document.head.appendChild(link);
}
// 比如用户常去 app1,就提前拉
prefetchSubApp('//localhost:8081');
不过这个要谨慎用,别把冷门子应用也 prefetch 了,反而浪费带宽。
性能数据对比
优化前后测了 10 次取平均值(测试环境,4G 网络模拟):
- 首屏加载时间(主应用 + 默认子应用):从 5.2s 降到 800ms
- 切换到新子应用的耗时:从 3.1s 降到 400ms(首次)/ 150ms(后续)
- 总 JS 下载量:从 12.4MB 降到 4.7MB
最明显的是,以前点菜单要盯着 loading 转半天,现在几乎秒开。虽然第一次进某个子应用还是会稍慢(毕竟要下载),但比之前好太多。
当然,也不是完美。比如用户如果快速切换多个子应用,prefetch 可能还没完成,还是会卡一下。但这种情况不多,暂时没动它。
结尾唠叨
以上是我对 qiankun 性能优化的实战总结。核心就两点:别一上来就加载所有子应用,别让每个子应用都打包公共库。这两招下去,性能提升肉眼可见。
这个方案不是最优解(比如可以进一步做子应用分块加载),但胜在简单、改动小、见效快。如果你也在用 qiankun,不妨试试。
以上是我踩坑后的总结,希望对你有帮助。有更优的实现方式欢迎评论区交流!

暂无评论