微前端Qiankun的不佳实践--从试试到逝世
微前端是什么
⼀种类似于微服务的架构,是⼀种由独⽴交付的多个前端应⽤组成整体的架构⻛格,将前端应⽤分解成⼀些更⼩、更简单的能够独⽴开发、测试、部署的应⽤,⽽在⽤户看来仍然是内聚的单个产品。
qiankun
是蚂蚁金服推出的微前端框架,基于 single-spa
进行二次开发。
特性:
- 📦 基**于 single-spa **封装,提供了更加开箱即用的 API。
- 📱 技**术栈无关,**任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
- 💪 H**TML Entry 接入方式,**让你接入微应用像使用 iframe 一样简单。
- 🛡 样**式隔离,**确保微应用之间样式互相不干扰。
- 🧳 J**S 沙箱,**确保微应用之间 全局变量/事件 不冲突。
- ⚡️ 资**源预加载,**在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
- 🔌 u**mi 插件,**提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
HTML Entry接入方式。
直接引用请求一个试试。
看看控制台输入的内容
- assetPublicPath 静态资源的公共路径
- execScripts 执行脚本的函数
- getExternalScripts 获取所有外部脚本的函数,返回脚本路径
- getExternalStyleSheets 获取所有外部样式的函数,返回样式文件的路径
- template 处理后的 HTML 模板
获取到 template
、execScripts
这些信息以后,基于 template
生成 render](https://github.com/umijs/qiankun/blob/master/src/loader.ts#L296)
[用于渲染子应用的页面。之后会根据需要生成沙盒,并将沙盒对象传给 execScripts
来获取子应用导出的声明周期函数。调用 single-spa 的 API 启动子应用。
这是大概的一个工作原理。
从0搓一个环境
简单概括主要的操作就两步:
- 配置主应用基座
- 配置子应用
这边分别搓三个不同的子应用。别问为什么不搓angular,本来想搓一个svelte的,因为懒放弃了。
基座应用
-
初始化基座应用
pnpm create vite ./
一路看着选择
-
安装tdesign,撸个框框,安装qiankun
pnpm add tdesign-vue-next
pnpm add qiankun
-
改造入口,如果应用暂时没有的时候,先注释掉。
import { registerMicroApps, start } from 'qiankun';
// 注册子应用
registerMicroApps([
{
name: 'app-vue', // app name registered
entry: '//localhost:5174',
container: '#main',
activeRule: '/sub-vue',
},
{
name: 'app-react', // app name registered
entry: '//localhost:5175',
container: '#main',
activeRule: '/sub-react',
},
{
name: 'app-html', // app name registered
entry: '//localhost:8080',
container: '#main',
activeRule: '/sub-html',
}
]);
// 启动
start();
-
增加子应用的挂载点
// main 是我的挂载节点
<t-layout>
<main id="main">
</main>
<RouterView />
</t-layout>
为了防止app ID与里面子应用的ID冲突,这里把挂点的节点修改一下
createApp(App).use(router).use(TDesign).mount('#base-app')
// 对应的html文件也要调整
<div id="base-app"></div>
主应用处理完成,测试主应用的两个路由是否正常。
VUE子应用
初始化项目略
安装qiankun插件
pnpm add vite-plugin-qiankun
修改vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx';
import qiankun from 'vite-plugin-qiankun'
// https://vitejs.dev/config/
export default ({mode}) =>{
const __DEV__ = mode === 'development';
return defineConfig({
base: __DEV__ ? '/' : '//localhost:5174',
plugins: [vue(),vueJsx({}),
qiankun('sub-vue',{
useDevMode:true,
})],
})
}
改造main.js(or ts)文件
import { createApp } from 'vue'
import { renderWithQiankun, qiankunWindow } from "vite-plugin-qiankun/es/helper.js";
import './style.css'
import App from './App.vue'
let app
// 判断是否在 qiankun 环境中运行
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
// 如果不是在 qiankun 环境中运行,直接创建 Vue 应用并挂载到 #app 元素上
createApp(App).mount('#app')
} else {
// 如果在 qiankun 环境中运行,使用 renderWithQiankun 函数注册微应用的生命周期
renderWithQiankun({
// 挂载函数,在微应用挂载时调用
mount(props) {
// 创建 Vue 应用
app = createApp(App);
// 将应用挂载到 qiankun 提供的容器中的 #app 元素上
app.mount(props.container.querySelector('#app'))
console.log('vue mount')
},
// 引导函数,在微应用启动时调用
bootstrap() {
console.log('vue bootstrap')
},
// 更新函数,在微应用更新时调用
update() {
console.log('vue update')
},
// 卸载函数,在微应用卸载时调用
unmount() {
console.log('vue unmount')
// 卸载 Vue 应用
app?.unmount()
}
})
}
测试一下。对应的路由是否成功。注意查看控制台的DOM结构
REACT子应用
初始化项目略
还是在vite.config.js里面配置
import { defineConfig } from 'vite';
import qiankun from 'vite-plugin-qiankun';
export default ({ mode }) => {
// 判断是否为开发模式
const __DEV__ = mode === 'development';
return defineConfig({
server: {
// 服务器端口号
port: 5175,
// 服务器源地址
origin: '//localhost:5175',
},
// 应用基础路径,开发模式下为根路径,生产模式下为服务器地址
base: __DEV__ ? '/' : '//localhost:5175',
plugins: [
// 使用 qiankun 插件,配置微应用
qiankun('sub-react', {
// 开发模式下使用 dev 模式
useDevMode: true,
}),
],
});
};
注意这里默认plugins里面会有react(),删除掉,如果控制台报错,改为在具体的页面上引用。
修改入口文件main.jsx
ReactDOM.createRoot是React 18的用法,如果是之前的版本,ReactDOM.render方法实现
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
let root = null;
// 渲染函数,用于渲染应用
function render(props) {
const { container } = props;
// 获取目标容器,优先使用传入的容器,否则使用默认的根元素
const targetContainer = container ? container.querySelector('#root') : document.getElementById('root');
// 创建 React 根元素
root = ReactDOM.createRoot(targetContainer);
// 渲染应用
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}
// 使用 qiankun 的渲染函数进行渲染
renderWithQiankun({
// 挂载函数,在微应用挂载时调用
mount(props) {
console.log('sub-vite2-react mount');
render(props);
},
// 引导函数,在微应用启动时调用
bootstrap() {
console.log('bootstrap');
},
// 卸载函数,在微应用卸载时调用
unmount(props) {
console.log('sub-vite2-react unmount');
root.unmount();
},
});
// 如果不是在 qiankun 环境下运行,直接渲染应用
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
render({});
}
运行,测试,成功!
引入jquery的HTML页面
忽然发现找不到一个jquery的页面了,网上下载个模板到处都是收费的。。。
刚好昨天想给blog换个程序,测试了hexo打包了一个默认页面。
编辑一个入口的js文件 entry.js
// 定义渲染函数,接收 jQuery 对象作为参数
const render = ($) => {
// 使用 jQuery 选择 #purehtml-container 元素,并设置其内容为 "Hello, render with jQuery"
$('#purehtml-container').html('Hello, render with jQuery');
// 返回一个已解决的 Promise,表示渲染完成
return Promise.resolve();
};
// 立即执行函数,接收全局对象 window 作为参数
((global) => {
// 在全局对象上定义 purehtml 对象,包含微应用的生命周期函数
global['purehtml'] = {
// 引导函数,在微应用启动时调用
bootstrap: () => {
console.log('purehtml bootstrap');
// 返回一个已解决的 Promise,表示引导完成
return Promise.resolve();
},
// 挂载函数,在微应用挂载时调用
mount: () => {
console.log('purehtml mount');
// 调用渲染函数,传入 jQuery 对象,并返回渲染结果
return render($);
},
// 卸载函数,在微应用卸载时调用
unmount: () => {
console.log('purehtml unmount');
// 返回一个已解决的 Promise,表示卸载完成
return Promise.resolve();
},
};
})(window);
在首页的html最下面引入该js
</body>
<script src="./entry.js" entry></script>
</html>
用http-server启了测试一下
提示cros的时候,加上启动命令 --cros
整体演示
待办事件:
不整了
-
样式隔离
-
子应用间通信
-
微应用之间的状态共享
预到的问题:
Q:打开基座应用的时候,没有问题,切换到其它子应用之后,基座的内容无法正常打开。
A:问题产生的原因是,当子应用被激活的时候会将子应用挂载到 ID 为 reactAppContainer
的元素上,那自然就替换了该元素的子元素,所以 <router-view />
就不存在了,再回到主应用的时候路由就会报错。
// 错误写法
<main id="main">
<RouterView />
</main>
// 正确写法
<main id="main">
</main>
<RouterView />
Q:如果加载react的报错。
[import-html-entry]: error occurs while executing normal script import { injectIntoGlobalHook } from "/@react-refresh";
修改vite.config.js 里面把plugins的react删除。在页面上引用
plugins: [
react(), // 删除
qiankun('sub-react', {
useDevMode: true,
}),
],
算彩蛋吗
看着正打开的qiankun的网站,心想,要不试了一下直接引用qiankun的网站。
除了样式有点乱之外,还都能使用。