一、简介
在现代前端开发中,随着项目规模的不断扩大以及团队协作的日益复杂,传统的单体应用架构逐渐暴露出诸多问题,如代码维护困难、技术栈升级艰难、团队协作效率低下等。为了解决这些问题,微前端架构应运而生,而 qiankun 作为基于 single-spa 的微前端实现库,以其简单易用、高效灵活的特点,成为了众多开发者构建微前端架构系统的首选工具。它旨在帮助开发者更轻松、无痛地构建出生产可用的微前端架构系统,无论是从零开始搭建新项目,还是对现有项目进行微前端改造,都能提供强大的支持。
微前端架构的核心价值
微前端架构作为一种先进的前端开发理念,具备以下核心价值:
技术栈无关
主框架不对接入应用的技术栈进行限制,微应用拥有完全的自主权,可以自由选择适合自身业务的技术栈进行开发。这意味着不同的微应用可以使用不同的框架(如 React、Vue、Angular 等)或库来构建,从而充分发挥各自技术栈的优势,满足多样化的业务需求,同时也为团队提供了更大的灵活性,能够根据项目实际情况选择最合适的技术方案。
独立开发、独立部署
微应用的仓库是独立的,前后端可以独立进行开发,互不干扰。开发完成后,微应用可以独立进行部署,而主框架能够自动完成同步更新,无需手动干预。这种独立性极大地提高了开发效率,减少了团队之间的协作成本,使得各个微应用团队可以专注于自身的业务逻辑开发,而无需担心与其他应用之间的冲突和依赖问题。同时,独立部署的特性也使得微应用的更新和维护更加灵活,能够快速响应业务变化,及时发布新功能或修复问题。
增量升级
在实际开发中,我们常常会面临各种复杂场景,对于一个已经存在的系统,很难进行全量的技术栈升级或重构。而微前端架构提供了一种非常有效的渐进式重构手段和策略,允许开发者逐步对系统进行升级和优化,从而降低风险,提高系统的可维护性和可扩展性。通过将系统拆分为多个微应用,可以针对每个微应用独立进行技术升级或重构,而不会对整个系统造成过大影响,从而实现系统的平滑演进。
独立运行时
每个微应用之间实现了状态隔离,运行时状态不共享。这有效避免了不同应用之间的状态冲突和干扰,确保了微应用之间的独立性和稳定性。每个微应用都可以独立运行,互不依赖,从而提高了系统的整体可靠性和性能。
微前端架构主要针对单体应用在长时间发展过程中,由于参与人员和团队的增多、变迁,逐渐演变成巨石应用(Frontend Monolith)后所带来的不可维护性问题。这类问题在企业级 Web 应用中尤为常见,严重影响了项目的持续发展和团队的工作效率。而微前端架构通过将单体应用拆分为多个独立的微应用,解决了这些问题,使得项目能够更加健康、可持续地发展。
二、qiankun 接入核心要点
要成功接入 qiankun 并实现微前端架构,需要遵循以下核心要点:
1. 主子应用安装依赖
无论是基座应用还是子应用,都需要安装 qiankun 依赖。这是接入 qiankun 的基础,确保双方能够通过 qiankun 提供的接口和机制进行交互和协作。
2. 基座应用注册子应用
基座应用需要在项目入口文件中通过 registerMicroApps 方法注册子应用,并为其指定入口地址、挂载容器、匹配路由以及初始化通信数据等。这一步骤是让基座应用知晓子应用的存在,并为其后续的加载和渲染做好准备。
3. 子应用修改打包配置
子应用需要修改打包输出格式为 UMD(Universal Module Definition),以便能够作为库被基座应用加载和使用。同时,还需要解决开发环境中的跨域问题(CORS),可以通过配置代理或设置响应头等方式来实现。具体的配置方式可以参考 qiankun 官方文档,以确保子应用能够正常运行和与基座应用进行通信。
const { name } = require('./package');
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`, // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
},
},
};
4. 子应用暴露生命周期钩子
子应用需要在其入口文件中暴露 qiankun 提供的生命周期钩子函数,如 bootstrap、mount 和 unmount 等。这些生命周期钩子函数分别在应用加载之前、挂载之前和卸载之后触发,用于执行一些初始化操作、渲染应用以及清理资源等任务。通过这些生命周期钩子,子应用可以更好地控制自身的加载、渲染和卸载过程,确保与其他应用的协作更加顺畅。
三、接入场景与接入流程
接入场景主要分为umi项目和非umi项目,原因是因为umi提供了@umijs/plugin-qiankun插件一键开启微前端。
场景一
- 基座应用与子应用都非
umi项目
接入流程
- 主子应用安装
qiankun依赖 - 主应用在项目入口文件中注入子应用
import {registerMicroApps, start} from 'qiankun';
registerMicroApps([{
name: 'child', // app name registered
entry: '//localhost:8080', //子应用项目入口地址
container: '#container', //微前端挂在节点
activeRule: '/', //匹配路由
props: { //初始化通信数据
eventName: "TransferValue",
}
}]);
start(); //开启qiankun
- 子应用入口文件暴露
qiankun生命周期钩子函数
export async function bootstrap() {
console.log(' react app bootstraped');
}
export async function mount(props) {
}
export async function unmount(props) {
}
- 子应用需要在
mount和unmount去找到应用的挂在点和应用的卸载点
function render(props) {
const {container} = props;
ReactDOM.render(<App/>, container ? container.querySelector('#root') : document.querySelector('#root'));
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
} //判断应用是否为qiankun挂在
export async function mount(props) {
console.log(props)
render(props);
}
export async function unmount(props) {
const {container} = props;
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
- 修改
webpack(vite同理)打包配置,以下以create-react-app脚手架项目为例;安装react-app-rewired依赖,然后在项目目录下新建config-overrides.js文件,配置打包,修改启动脚本。
6. 完成接入
场景二
- 基座应用非umi项目
- 子应用umi4
接入流程
- 基座应用接入流程不变
- 子应用
umi4分为umi-max和umi-simple-app以umi-simple-app子应用接入为例 umi-simple-app安装umi插件集@umijs/plugins- 在umi配置文件中开启
qiankkun插件并且开启子应用qiankun配置
export default defineConfig({
plugins: ["@umijs/plugins/dist/qiankun"],
qiankun:{
slave:{}
},
});
- 在
app.js文件中开启qiankun生命周期
export const qiankun = {
// 应用加载之前
async bootstrap(props: any) {
console.log('app2 bootstrap', props);
},
// 应用 render 之前触发
async mount(props: any) {
console.log('app2 mount', props);
},
// 应用卸载之后触发
async unmount(props: any) {
console.log('app2 unmount', props);
},
};
- 接入完成(
umi-max内部集成qiankun插件、直接启动qiankun配置、暴漏出qiankun生命周期钩子即可)
场景三
- 基座应用与子应用都是umi4
接入流程
- 基座应用和子应用同时安装
@umijs/plugins,在umi配置文件中开启qiankun插件配置
plugins: ["@umijs/plugins/dist/qiankun"]
umi-max跳过此步骤、umi-max集成了此插件
- 基座应用开启
qiankun配置
qiankun: {
master: {
apps: [
{
name: "child1",
entry: "//localhost:3010"
},
]
}
}
- 基座应用注入路由使用
microApp匹配子应用
routes: [{path: "/child1", microApp: "child1"}]
基座应用配置完毕
- 子应用开启
qiankun配置、并注入路由
export default defineConfig({
plugins: ["@umijs/plugins/dist/qiankun"],
qiankun:{
slave:{}
},
base:"/child2",
routes: [
{ path: "/", component: "index" },
],
npmClient: 'pnpm',
});
- 在app.js入口文件中暴漏
qiankun生命周期
export const qiankun = {
// 应用加载之前
async bootstrap(props: any) {
console.log('app2 bootstrap', props);
},
// 应用 render 之前触发
async mount(props: any) {
console.log('app2 mount', props);
},
// 应用卸载之后触发
async unmount(props: any) {
console.log('app2 unmount', props);
},
};
- 接入流程完毕
小结
qiankun的使用场景主要分为umi项目和非umi项目。无论是哪种类型的项目,都可以根据上述场景灵活搭配接入流程,实现微前端架构的构建。- 对于
umi项目,无论是基座应用还是子应用,都可以利用umi提供的插件快速开启微前端配置,大大简化了接入流程,提高了开发效率。 - 对于非
umi项目,需要根据qiankun官方文档进行详细的配置和接入,虽然相对复杂一些,但也能实现微前端架构的构建,满足项目需求。
四、通信机制
在微前端架构中,通信是各个微应用之间协作的关键。对于 umi 项目,无论是基座应用还是子应用,核心都是通过 useModel 进行通信。参考:umijs.org/docs/max/da…
基座应用
在基座应用的 app.js 中,可以通过 useQiankunStateForSlave 函数暴露全局状态。例如:
export function useQiankunStateForSlave() {
const [globalState, setGlobalState] = useState<any>({
slogan: 'Hello MicroFrontend',
});
return {
globalState,
setGlobalState,
};
}
子应用
子应用在需要使用全局状态的地方,可以通过 useModel 钩子直接获取基座应用传递的全局状态。例如:
const masterProps = useModel('@@qiankunStateFromMaster');
五、子应用打包成静态资源引入场景场景
在qiankun的文档中entry支持配置成对象,html一定是html字符串,script可以配置为需要注入的脚本地址
如以下所示:
fetch('http://localhost:7001/dist/index.html') // 配置静态资源地址
.then(response => response.text())
.then(htmlString => {
console.log(htmlString)
registerMicroApps([{
name: 'qiankun', // app name registered
entry: {
html: htmlString,
scripts: ["http://localhost:7001/dist/umi.js", "http://localhost:7001/dist/p__index.async.js"] //需要注入的脚本地址
},
container: '#root-subapp-container', activeRule: '/test',
}]);
start();
})
.catch(error => {
console.error('Error fetching the HTML file:', error);
});
注意
- 基座应用与子应用路由的模式需要同一,比如基座应用是hash路由,子应用也需要配置hash路由
- 路由路径要同一(因为子应用需要基座应用注入的路由去进行匹配)