为什么要使用微前端

微前端架构具备以下几个核心价值:

  • 技术栈无关

    主框架不限制接入应用的技术栈,微应用具备完全自主权
  • 独立开发、独立部署

    微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  • 增量升级在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
  • 独立运行时

    每个微应用之间状态隔离,运行时状态不共享

iframe存在问题

iframe最大的特性就是提供了测觉游原生的使用离方案,不论是样式隔离,is隔离这英问题统规定美解决。但他的最大问题也在于他的隔离性无法破突破,导致应用间上下文无法被共享,随之带来的开发体验,产品体验的问题

  • 浏览器的前进后退问题

iframe 页面没有自己的历史记录,共用父页面的浏览历史,当iframe 页在内部进行跳转时,浏览器地址栏无变化,当浏览器刷新时,无法停留在iframe内部跳转后的页面上,需要用户重新走一遍操作

  • 组件复用问题,应用之间的组件无法复用--需要单独提出来放到一个单独的项目里
  • iframe的弹窗及遮罩层问题

iframe 页产生的弹窗,一般只能遮罩 iframe 区域。

  • 项目之间的通讯--只能使用postMessage
  • iframe里的全屏问题--原生方法使用的是Element.requestFullscreen活使用vue-fullscreen插件
  • 加载速度慢

每次子应用进入都是一次浏览器上下文重建,资源重新加载的过程。

qiankun介绍

官方文档 https://qiankun.umijs.org/zh/guide

qiankun 是一个基于 single-spa微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

qiankun 的核心设计理念

  • 简单,由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。
  • 解耦,技术栈无关微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。

特性

  • **基于 **single-spa 封装,提供了更加开箱即用的 API。
  • 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
  • HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
  • 样式隔离,确保微应用之间样式互相不干扰。
  • JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
  • 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
  • umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。

项目中接入qiankun

创建一个主应用和两个子应用,这里我使用的react18.2.0版本

主应用

  1. 创建主应用

npx create-react-app micro-main

  1. 运行并访问 http://localhost:3000/

yarn start

  1. 安装qiankun

yarn add qiankun

  1. 注册子应用

在项目入口处页面注册子应用并启动

import {
registerMicroApps,
start,
addGlobalUncaughtErrorHandler,
addErrorHandler,
} from "qiankun"; registerMicroApps([
{
name: "childOne",
//默认会加载这个html,解析里面的js,动态执行(子应用必须支持跨域)里面,是用fetch去请求的数据
entry: "//localhost:3001",
//挂载到主应用的哪个元素下
container: "#container",
//当我劫持到路由地址为//child-one时,我就把http://localhost:3001这个应用挂载到#container的元素下
activeRule: "/child-one",
},
{
name: "childTwo",
entry: "//localhost:3002",
container: "#container",
activeRule: "/child-two",
},
]);
// 启动 qiankun
start();
  1. 修改App.js组件添加导航链接,并添加#container元素,用于挂载子应用
import "./App.css";
import Home from "./pages/Home"; import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
Link,
} from "react-router-dom"; const Header = () => {
return (
<header>
<div>微前端实践-qiankun</div>
<div>主应用</div>
<nav>
<Link to={"/"}>主应用首页</Link>
<Link to={"/child-one"}>访问子应用1</Link>
<Link to={"/child-two"}>访问子应用2</Link>
</nav>
</header>
);
}; function App() {
return (
<div className="App">
<Router>
<Header />
<Switch>
<Redirect from="/" to="/home" exact />
<Route key="home" path="/home" exact={true} component={Home} />
</Switch>
</Router>
{/* 加载子应用时,将其挂载到该#container元素下 */}
<div id="container"></div>
</div>
);
} export default App;

子应用

同样,我们使用react创建两个子应用

npx create-react-app pro-1

npx create-react-app pro-2

  1. 创建public-path.js文件

src目录下创建public-path.js文件并在入口文件引入,用于修改运行时的 publicPath

注意:运行时的 publicPath 和构建时的 publicPath 是不同的,两者不能等价替代。

if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
  1. 设置histroy模式路由base

建议使用 history 模式的路由,设置histroy模式路由base,值和它的 activeRule 是一样的

<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/child-one' : '/'}>
  1. 修改入口文件,并导出几个钩子函数

分别对应bootstrap、mount、unmount、update几个生命周期函数

为了避免根 id #root 与其他的 DOM 冲突,需要限制查找范围。

let root = null;

function render(props) {
const { container } = props;
// ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root')); root = ReactDOM.createRoot(
container
? container.querySelector('#root')
: document.getElementById("root")
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
} if (!window.__POWERED_BY_QIANKUN__) {
render({});
} /**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log("[react18] react app bootstraped");
} /**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
console.log("[react18] props from main framework", props);
render(props);
} /**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount() {
if (root) {
root.unmount(); // 卸载组件
root = null;
}
} /**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
  1. 修改 webpack 打包,允许开发环境跨域和 umd 打包。

这里我们使用react-app-rewired来修改webpack配置,作用是可以在不eject的情况下自定义配置CRA脚手架创建的app

yarn add react-app-rewired --dev

在根目录下创建config-overrides.js文件

const { name } = require("./package");

module.exports = {
webpack: function (config, env) {
config.output.library = `${name}-[name]`;
config.output.libraryTarget = "umd";
// webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
// config.output.jsonpFunction = `webpackJsonp_${name}`;
config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
config.output.globalObject = "window";
return config;
}, devServer: (_) => {
const config = _;
config.headers = {
"Access-Control-Allow-Origin": "*",
};
config.historyApiFallback = true;
config.hot = false;
config.watchContentBase = false;
config.liveReload = false; return config;
},
};

然后修改 package.json,scripts中将react-scripts替换成react-app-rewired即可

注意:eject不需要

"scripts": {
"start": "PORT=3001 react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},

运行效果

问题总结

主应用访问子应用,qiankun报错情况

报错1:Uncaught Error: application 'childOne' died in status LOADING_SOURCE_CODE: [qiankun]: Target container with #container not existed while childOne loading!

主应用container容器不存在导致无法挂载子应用

解决:不要将<div id="container"></div>放在Router标签内,跟Router同级即可

报错2:Uncaught TypeError: application 'childOne' died in status NOT_MOUNTED: container.getElementById is not a function

container.getElementById不是一个方法

解决:container情况下使用container.querySelector('#root')方式

function render(props) {
const { container } = props;
// ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root')); root = ReactDOM.createRoot(
container
? container.querySelector('#root')
: document.getElementById("root")
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}

报错3:Uncaught (in promise) TypeError: Failed to fetch

子应用没有运行的情况

当子应用服务关闭,主应用请求失败时,如何配置跳转404错误页面?

qiankun的错误处理问题,当主应用请求不到子应用页面时,如何设置跳转到自定义的404页面,而不是显示错误

解决:

在app.ts中配置qiankun运行时配置,通过qiankun.addGlobalUncaughtErrorHandler()和qiankun.addErrorHandler()添加错误处理

import {
addGlobalUncaughtErrorHandler,
addErrorHandler,
} from "qiankun"; // 异常捕获
addGlobalUncaughtErrorHandler((event) =>
console.log("addGlobalUncaughtErrorHandler err", event)
);
addErrorHandler((err) => {
console.log("addErrorHandler err", err);
});

查看github代码

代码已经上传到github,感兴趣的欢迎clone & star

https://github.com/fozero/hello-qiankun

参考阅读

qiankun微前端实践的更多相关文章

  1. 「微前端实践」使用Vue+qiankun微前端方案重构老项目的本地验证

    10月份换了新的工作,参与完一个月的需求迭代后,接到了项目重构的任务.简单来说,需要在短时间内提出方案设想,同时进行本地验证,最终需要拿出一套技术替换方案来.于是,埋头苦干了一个月,总算干了点成绩出来 ...

  2. 微前端 & 微前端实践 & 微前端教程

    微前端 & 微前端实践 & 微前端教程 微前端 micro frontends https://micro-frontends.org/ https://github.com/neul ...

  3. vivo 商品中台的可视化微前端实践

    一.背景 在电商领域内,商品是一个重要组成部分,与其对应的商品管理系统,则负责商品的新建.编辑.复制等功能.随着商品管理系统的成熟稳定和业务上的扩展需求,催化出了商品中台的诞生.它可以将现有商品功能最 ...

  4. Node.js躬行记(9)——微前端实践

    后台管理系统使用的是umi框架,随着公司业务的发展,目前已经变成了一个巨石应用,越来越难维护,有必要对其进行拆分了. 计划是从市面上挑选一个成熟的微前端框架,首先选择的是 icestark,虽然文档中 ...

  5. 基于微前端qiankun的多页签缓存方案实践

    作者:vivo 互联网前端团队- Tang Xiao 本文梳理了基于阿里开源微前端框架qiankun,实现多页签及子应用缓存的方案,同时还类比了多个不同方案之间的区别及优劣势,为使用微前端进行多页签开 ...

  6. vue-qiankun公司微前端项稳定目落地后的总结(附github仓库demo,将会持续更新)

    ️本文为博客园社区首发文章,未获授权禁止转载 大家好,我是aehyok,一个住在深圳城市的佛系码农‍♀️,如果你喜欢我的文章,可以通过点赞帮我聚集灵力️. 个人github仓库地址: https:gi ...

  7. 微前端大赏二-singlespa实践

    微前端大赏二-singlespa实践 微前端大赏二-singlespa实践 序 介绍singleSpa singleSpa核心逻辑 搭建环境 vue main react child 生命周期 结论 ...

  8. 【微前端】微前端最终章-qiankun指南以及微前端整体探索

    序 这才2月中旬,广州就已经渐渐地进入了夏季,--夏天总是让人焦虑的.过年闲暇时间写下了微前端这系列的终章,欢迎拍砖.如果你习惯直接上手代码,不妨跳到实践一节,直接上代码教程玩一玩. qiankun原 ...

  9. 微前端框架 qiankun 技术分析

    我们在single-spa 技术分析 基本实现了一个微前端框架需要具备的各种功能,但是又实现的不够彻底,遗留了很多问题需要解决.虽然官方提供了很多样例和最佳实践,但是总显得过于单薄,总给人一种&quo ...

  10. 微前端框架 之 qiankun 从入门到源码分析

    封面 简介 从 single-spa 的缺陷讲起 -> qiankun 是如何从框架层面解决 single-spa 存在的问题 -> qiankun 源码解读,带你全方位刨析 qianku ...

随机推荐

  1. Python Excel 操作 | xlrd+xlwt 模块笔记

    Python 的pandas模块使用xlrd作为读取 excel 文件的默认引擎.但是,xlrd在其最新版本(从 2.0.1 版本开始)中删除了对 xls 文件以外的任何文件的支持. xlsx fil ...

  2. Bio+IT 爱好者社区,欢迎你!

    生物信息学是一个跨学科的大领域,一直以来,做生物信息分析的工程师都需要跟 IT 打交道. 现实问题是,有很多生物学.医学.遗传学背景的 Bio 人对生物领域拥有非常专业的知识经验,而对 IT 领域知之 ...

  3. ChatGPT玩法(三):AI玩转PPT

    前言 在线免费体验ChatGpt:https://www.topgpt.one 作为许多职场人士的必备工具,PPT制作一直是一个琐碎而费时的任务.但最近我发现了一个非常有用的工具网站,它可以通过人工智 ...

  4. C++面试八股文:在C++中,你知道哪些运算符?

    某日二师兄参加XXX科技公司的C++工程师开发岗位第11面: 面试官:在C++中,你都知道都哪些运算符? 二师兄:啥?运算符?+-*/=这些算吗? 面试官:嗯,还有其他的吗? 二师兄:当然还有,+=, ...

  5. 现代C++学习指南-类型系统

    在前一篇,我们提供了一个方向性的指南,但是学什么,怎么学却没有详细展开.本篇将在前文的基础上,着重介绍下怎样学习C++的类型系统. 写在前面 在进入类型系统之前,我们应该先达成一项共识--尽可能使用C ...

  6. CF1817E Half-sum 另解与 Trygub Number

    一题水两篇怎么说. 上一篇中我们采用智慧方法减少了比较次数,避免了使用复杂的高精度数.现在我们有高论!可以做到 \(\mathrm O(\log_B V\log_2 n)\) 在某一位加或者减一个大小 ...

  7. Hugging News #0626: 音频课程更新、在线体验 baichuan-7B 模型、ChatGLM2-6B 重磅发

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  8. JVM中的-Xms 、-Xmx 参数该如何设置

    在 Java 虚拟机(JVM)中,-Xms 和 -Xmx 都是用来设置 JVM 堆内存大小的参数.其中,-Xms 用于设置 JVM 启动时分配的初始堆内存大小,而 -Xmx 用于设置 JVM 堆内存的 ...

  9. windows内核情景分析-毛德操(第一章)

    微内核操作系统的特点内核尽量缩小 windows内核包括了两大部分 操作系统内核(ntoskrnl.exe),另一部分则是迁移到了内核中即系统空间中的视窗服务(win32k.sys) 用户空间和系统空 ...

  10. 你的Spring应用启动很慢?不妨试试这个工具!

    睡不着闲逛,在GitHub上看到一个挺实用的开源项目:Spring Startup Analyzer. 从项目名称中就大概能猜到,这是一个分析Spring应用启动过程的工具.Spring Startu ...