微前端架构落地实战:用qiankun轻松拆分巨石应用
引言
随着前端项目规模不断膨胀,代码库动辄数十万行,团队协作变得困难,构建部署耗时也越来越长。传统的单体应用(Monolith)已经难以应对多团队并行开发、技术栈交叉并存的复杂场景。微前端(Micro Frontends)借鉴了后端微服务的思想,将前端拆分为更小、更自治的“微应用”,由不同团队独立开发、测试和部署,最后在浏览器端通过一个容器应用将它们无缝集成。
本文将先梳理微前端的核心概念,随后以一个基于qiankun的完整示例,展示如何从零搭建可运行的微前端架构,并分享生产环境中必须解决的问题。所有代码均可直接复制运行,帮助你快速上手。
一、核心概念
1. 什么是微前端
微前端是一种架构风格,将前端应用分解为多个可独立交付的垂直切片。每个微应用可以使用不同的技术栈(React、Vue、Angular 甚至原生 JS),拥有独立的仓库、构建流程和部署周期。主应用(基座)负责生命周期调度、路由分发和全局状态管理。
2. 实现方式对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| iframe | 完美隔离,简单易用 | 通信繁琐,性能差,URL 不同步,样式难以统一 |
| Web Components | 标准原生,跨技术栈 | 浏览器兼容性、状态管理困难,生态不完善 |
| Module Federation(Webpack 5) | 运行时动态加载,去中心化 | 配置复杂,对非 Webpack 技术栈不友好 |
| single-spa / qiankun | 成熟稳定,开箱即用,社区活跃 | 微应用需暴露生命周期钩子,改造量适中 |
qiankun基于 single-spa,封装了更为友好的 API,内置样式隔离(Shadow DOM / Scoped CSS)和 JS 沙箱(快照 / Proxy),是目前业界采用最广的微前端框架,下文将围绕它展开实战。
二、实战:用 qiankun 搭建 React + React 微前端
我们实现一个主应用(基座)和一个子应用(用户 management 模块)。两者均使用 Create React App 创建,以便演示原生环境下的集成。
完整项目结构:
micro-frontend-demo/ ├── main-app/ # 主应用(基座) └── sub-app/ # 子应用
2.1 创建主应用 main-app
npx create-react-app main-app cd main-app npm install qiankun修改src/index.js,注册微应用并启动 qiankun:
// main-app/src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import { registerMicroApps, start } from 'qiankun'; // 主应用自身渲染 ReactDOM.render(<App />, document.getElementById('root')); // 1. 注册子应用列表 registerMicroApps([ { name: 'sub-app', // 子应用唯一名称 entry: '//localhost:3001', // 子应用运行地址(开发环境) container: '#sub-app-container', // 子应用挂载的 DOM 节点 activeRule: '/sub', // 路由匹配规则,激活子应用 }, ]); // 2. 启动 qiankun start({ sandbox: { experimentalStyleIsolation: true, // 启用 Scoped CSS 样式隔离 }, });接着在src/App.js中放置子应用的挂载容器和导航:
// main-app/src/App.js import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <h1>微前端基座</h1> <nav> <a href="/">Home</a> | <a href="/sub">用户管理模块</a> </nav> </header> <main> {/* 子应用挂载点:id 必须与 registerMicroApps 中的 container 一致 */} <div id="sub-app-container"></div> </main> </div> ); } export default App;为支持 qiankun 的路由匹配,需要将 CRA 默认的页面刷新行为改为 history 模式,但为了避免端口冲突,我们仅通过activeRule做前缀匹配,无需额外配置路由库。这里我们使用的是传统<a>标签切换路径,qiankun 会监听 URL 变化并自动挂载/卸载子应用。
2.2 创建子应用 sub-app
同样用 CRA 创建,但需注意端口设为 3001(与 entry 一致):
npx create-react-app sub-app cd sub-app首先安装 react-router-dom(用于子应用内部路由):
npm install react-router-dom修改src/public-path.js,让子应用资源加载路径正确(用于生产环境):
// sub-app/src/public-path.js if (window.__POWERED_BY_QIANKUN__) { // 动态设置 webpack publicPath,防止资源加载 404 // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }在src/index.js中暴露生命周期钩子,并配置路由:
// sub-app/src/index.js import './public-path'; // 必须放在最顶部 import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import App from './App'; let root = null; // 渲染函数,打包为微应用和独立运行时复用 function render(props = {}) { const { container } = props; const dom = container ? container.querySelector('#root') // 挂载到 qiankun 容器中 : document.getElementById('root'); // 独立运行时挂载到 body root = ReactDOM.createRoot(dom); root.render( <BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/sub' : '/'}> <App /> </BrowserRouter> ); } // 如果是独立运行(非 qiankun 环境),直接渲染 if (!window.__POWERED_BY_QIANKUN__) { render(); } /** * qiankun 要求暴露三个生命周期: * bootstrap:初始化(仅一次) * mount:挂载时调用 * unmount:卸载时调用 */ export async function bootstrap() { console.log('sub-app bootstraped'); } export async function mount(props) { console.log('sub-app mounted', props); render(props); } export async function unmount(props) { console.log('sub-app unmounted'); if (root) { root.unmount(); // React 18 卸载方式 root = null; } }为了适应子应用端口 3001,在package.json中添加 dev 脚本的端口指定,并允许跨域:
// sub-app/package.json 的 scripts 部分 "scripts": { "start": "PORT=3001 WDS_SOCKET_PORT=3001 react-scripts start", ... }同时,子应用的 webpack 需要配置Access-Control-Allow-Origin,以便主应用加载资源时不受跨域限制。由于 CRA 默认不暴露 webpack 配置,我们借助craco或直接在src/setupProxy.js中设置(开发环境使用 http-proxy-middleware 是代理请求,但这里是资源加载,需要响应头)。简单方案:在子应用的src/setupProxy.js中无法直接修改 webpack-dev-server 的 headers。更直接的:在package.json中加入 CORS 头:
npm install -D @craco/craco创建craco.config.js:
// sub-app/craco.config.js module.exports = { devServer: (devServerConfig) => { devServerConfig.headers = { 'Access-Control-Allow-Origin': '*', }; return devServerConfig; }, };然后将package.json的 scripts 改为使用 craco:
"scripts": { "start": "PORT=3001 craco start", "build": "craco build", "test": "craco test" }最后,修改子应用的App.js,加入几个简单页面和路由:
// sub-app/src/App.js import React from 'react'; import { Routes, Route, Link } from 'react-router-dom'; function Home() { return <h2>用户管理首页</h2>; } function List() { return <h2>用户列表</h2>; } function Detail() { return <h2>用户详情</h2>; } function App() { return ( <div style={{ padding: 20, background: '#f0f2f5' }}> <h3>【子应用】用户管理</h3> <nav> <Link to="/">首页</Link> | <Link to="/list">列表</Link> | <Link to="/detail">详情</Link> </nav> <Routes> <Route path="/" element={<Home />} /> <Route path="/list" element={<List />} /> <Route path="/detail" element={<Detail />} /> </Routes> </div> ); } export default App;2.3 运行验证
- 启动子应用:
cd sub-app && npm start访问http://localhost:3001,确认子应用可独立运行。
- 启动主应用:
cd main-app && npm start默认在http://localhost:3000打开。
- 点击导航栏的“用户管理模块”,主应用路由变为
/sub,随即加载并渲染子应用。子应用的样式被隔离(因为开启了experimentalStyleIsolation),且能够在/sub/list等子路由下正常切换。
至此,一个生产可用的微前端最小原型便完成了。
三、常见问题与注意事项
3.1 样式隔离
qiankun 提供两种样式隔离:
- Shadow DOM 隔离:
strictStyleIsolation: true(实验性),样式完全封闭,但会有细小的 UI 差异(如弹窗挂载问题)。 - Scoped CSS:
experimentalStyleIsolation: true,自动为所有样式规则添加一个特殊属性选择器,避免污染。
建议优先使用 Scoped CSS,若仍存在冲突,可在子应用中使用 CSS Modules 或 BEM 命名规范。
3.2 JS 沙箱与全局变量污染
qiankun 默认使用ProxySandbox,每个微应用运行在独立的 window 代理对象中,避免全局变量冲突。但若子应用使用某些浏览器原生 API(如postMessage、localStorage)需注意:它们可能在多个子应用间共享,需要设计命名空间或用通信方案隔离。
3.3 微应用间通信
qiankun 通过initGlobalState提供简易的全局状态管理:
// 主应用 import { initGlobalState } from 'qiankun'; const actions = initGlobalState({ user: 'admin' }); actions.onGlobalStateChange((state, prev) => { console.log('状态变化', state, prev); }); // 子应用可通过 props.onGlobalStateChange 和 props.setGlobalState 收发对于复杂通信,建议使用自定义事件(CustomEvent)或公共依赖(如 Redux Store 注入)。
3.4 公共依赖抽取
多个子应用共享 React、ReactDOM 等大体积库时,可在主应用通过externals排除,并通过 CDN 或全局变量方式引入,减少重复加载。qiankun 也支持通过getTemplate钩子自动提取公共资源。
3.5 部署与 Nginx 配置
生产环境中,主应用和子应用需部署在同一个域名下(防止跨域),并配置 Nginx 的 try_files 使其正确回退到 index.html:
location / { try_files $uri $uri/ /index.html; } location /sub { try_files $uri $uri/ /sub/index.html; }子应用的publicPath需要设置为对应的路径前缀。
3.6 技术栈混合
若接入 Vue 子应用,需在mount钩子中实例化 Vue,在unmount中销毁,并注意路由的 basename。qiankun 官方文档有详细的 Vue 适配指南。
四、总结
微前端不是银弹,它引入了一定的复杂度:基座与微应用的协调、样式隔离的副作用、跨应用调试困难等。但对于大型组织、多团队协作、需要逐步迁移旧系统的场景,它带来的价值远远超过成本。qiankun 通过简洁的生命周期模型、健壮的沙箱机制和丰富的插件能力,极大降低了微前端的落地门槛。
本文从概念到完整代码,展示了 React 主应用接入 React 微应用的全过程。你可以以此为起点,尝试接入不同技术栈的子应用,并进一步探索自动化部署、自定义沙箱、性能监控等高级话题。
希望这篇文章能帮你理清微前端的思路,并让你有信心在实际项目中迈出第一步。有任何疑问,欢迎在评论区交流。
