文章介绍
因为qiankun目前版本对于vite 和vue3支持并不是很友好,所需要配置的坑也很多,以下是我花了一个周找到的解决方法,目前暂时还没有解决的是base配置以后 在生产模式 微应用暂时不能独立运行,有大佬知道如何解决这个微应用在生产模式单独运行的配置如何改的话欢迎评论
微前端介绍
什么是微前端?
微前端是一种多个团队通过独立发布功能的方式,来共同构建现代化 web 应用的技术手段及方法策略。
微前端的特点
- 技术栈无关 主框架不限制接入应用的技术栈,子应用可自主选择技术栈
- 独立开发/部署 各个团队之间仓库独立,单独部署,互不依赖
- 增量升级 当一个应用庞大之后,技术升级或重构相当麻烦,而微应用具备渐进式升级的特性
- 独立运行时 微应用之间运行时互不依赖,有独立的状态管理
- 提升效率 应用越庞大,越难以维护,协作效率越低下。微应用可以很好拆分,提升效率
解决问题的理念有了,那要通过怎样的技术去实现呢?
- iframe 最早也是最熟悉的解决方案就是通过iframe,因为它可以独立运行另一个项目,这种方案的优势:
- 非常简单,无需任何改造
- 完美隔离,JS、CSS 都是独立的运行环境
- 不限制使用,页面上可以放多个
iframe
来组合业务
当然也是逃不过事务的两面性,有优点就有缺点:
- 无法保持路由状态,刷新后路由状态就丢失(这点也不是完全不能解决,可以讲路由作为参数拼接在链接后,刷新时去参数进行页面跳转)
- 完全的隔离导致与子应用的交互变得极其困难
iframe
中的弹窗无法突破其本身- 整个应用全量资源加载,加载太慢
既然有这么明显的问题,那就会有新的方案被创造出来
- 基于
single-spa
路由劫持方案
single-spa
通过劫持路由的方式来做子应用之间的切换,但接入方式需要融合自身的路由,有一定的局限性。
qiankun
孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台。它对 single-spa
做了一层封装。主要解决了 single-spa
的一些痛点和不足。通过 import-html-entry
包解析 HTML
获取资源路径,然后对资源进行解析、加载。
通过对执行环境的修改,它实现了 JS 沙箱
、样式隔离
等特性。
接下来我就好好的讲讲qiankun
是怎么落地到生产项目的,中间遇到过哪些坑。
qiankun
qiankun官网 qiankun.umijs.org/zh/guide
按照官方文档快速搞起来
主应用
- 安装
$ yarn add qiankun # 或者 npm i qiankun -S
- 在主应用注册微应用 main.js
放在main.js的尾部
qiankun + vite + Vue 3 配置文档
主应用 (React) - main.tsx
这一块只需要根据qiankun官网进行配置
// 🌟 主应用 (React) - main.tsx 🌟
// 引入必要的模块和样式
import React from 'react';
import ReactDOM from 'react-dom/client';
import { registerMicroApps, start, addGlobalUncaughtErrorHandler } from 'qiankun';
import { StyleProvider } from '@ant-design/cssinjs';
import './index.css';
import './style/lib/tailwind.css';
import { Provider } from 'react-redux';
import { store } from './store';
import Routes from './routes';
import 'https://gradio.s3-us-west-2.amazonaws.com/3.35.2/gradio.js';
import { ss } from './utils/storage';
// 渲染主应用
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<StyleProvider hashPriority="high">
<Provider store={store}>
<React.StrictMode>
<Routes />
</React.StrictMode>
</Provider>
</StyleProvider>
);
// 判断开发模式
const isDevelop = import.meta.env.DEV ? true : false;
// 注册微应用
registerMicroApps([
{
name: 'llmwbs',
entry: isDevelop ? '/chatbotinst/' : '/chatbotinst/',
container: '#chatBot',
activeRule: '/chatbot',
props: {
slogan: 'Hello Qiankun',
token: ss.get('token'),
},
},
],
{
beforeLoad: (app: any) => {
console.log("before load+++++++++++", app.name);
return Promise.resolve();
},
beforeMount: (app: any) => {
console.log("before mount----------", app.name);
return Promise.resolve();
},
afterMount: (app: any) => {
console.log("after mount============", app.name);
return Promise.resolve();
},
afterUnmount: (app: any) => {
console.log("after unmount===+++++----", app.name);
return Promise.resolve();
},
});
// 启动 qiankun 应用
start({ sandbox: { experimentalStyleIsolation: true } });
// 添加全局异常捕获
addGlobalUncaughtErrorHandler((handler) => {
console.log("异常捕获", handler);
});
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
如果微应用不是直接跟路由关联的时候,你也可以选择手动加载微应用的方式:
import { loadMicroApp } from 'qiankun';
loadMicroApp({
name: 'micro-clouds',
entry: '//localhost:7000',
activeRule: '/micro-clouds',
container: '#subapp2', // 子应用挂载的div
// 传递给子应用的参数
props: {
routerBase: '/micro-clouds',
}
});
微应用
main.js
import './public-path.js';
import { createApp } from 'vue'
import router from './router'
import './style.css'
import './style/lib/tailwind.css'
import debounceDirective from './utils/directive/throttle'
import { setupAssets, setupScrollbarStyle } from './plugins'
import { createPinia } from 'pinia'
import { loadMicroApp, start } from 'qiankun';
// 入口文件处 : main.ts
import { renderWithQiankun, QiankunProps ,qiankunWindow} from 'vite-plugin-qiankun/dist/helper'
import App from './App.vue'
const pinia = createPinia()
setupAssets()
const app = createApp(App);
app.directive('debounce', debounceDirective);
let instance: any = null;
function render(props: any = {}) {
// console.log(props);
const { container } = props;
// router = createRouter({
// history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/chatbot/' : '/'),
// routes,
// });
instance = app
.use(router).use(pinia)
.mount(container ? container.querySelector('#app') : '#app');
}
//这是在webpack vue2等中采用的配置
// export async function bootstrap() {
// console.log('[vue] vue app bootstraped');
// }
// export function mount(props) {
// console.log('props: ', props);
// console.log('微应用mount');
// render(props);
// }
// export async function unmount() {
// instance.unmount();
// instance._container.innerHTML = '';
// instance = null;
// // router = null;
// }
// vite 专属配置
// 判断是否为qiankun环境
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
app.use(router).use(pinia)
.mount('#app');
}
renderWithQiankun({
async mount(_props) {
console.log(_props, 'mount');
render(_props);
},
bootstrap() {
console.log('bootstrap');
},
unmount(_props: any) {
console.log(_props);
instance.unmount()
// ss.remove("chatStorage")
// ss.remove("appSetting")
},
update: function (props: QiankunProps): void | Promise<void> {
console.log(props);
}
});
public-path.js
(function () {
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
// __webpack_public_path__ = `${process.env.BASE_URL}/`
}
})()
//通常是下面这个
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
最重要的是vite.config.ts 配置
这里踩了很多坑 找了很多案列和网站才找到的解决方法
并且解决了生产模式样式
import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import path from 'path'; const packageName: any = require('./package.json').name; import qiankun from 'vite-plugin-qiankun'; export default defineConfig({ //base 代替的是qiankun文档中的publicpath 解决了文件资源等问题 base: '/chatbotinst/', plugins: [ vue(), //这里是vite必须要配置的 webpack是不需要配置这个的 qiankun(packageName, { useDevMode: true }) ], resolve: { alias: { '@': path.resolve(__dirname, 'src') } }, build: { //注释掉的这些都是webpack需要配置的 // outDir: 'dist', // target:'esnext', // assetsDir: './src/assets', // assetsInlineLimit: 4096, chunkSizeWarningLimit: 1500, // rollupOptions: { // input: { // main: './src/main.ts', // index: './index.html' // }, // output: { // // format: 'umd', // // manualChunks(id) { // // if (id.includes('node_modules')) { // // return id.toString().split('node_modules/')[1].split('/')[0].toString(); // // } // // }, // entryFileNames: `${packageName}-[name]`, // chunkFileNames: 'chunks/[name].js', // assetFileNames: '[name].[ext]', // inlineDynamicImports: false // } // }, //下面这个是vite需要配置的 这几个是重点!!! outDir: path.resolve(__dirname, "dist"), assetsDir: "static", emptyOutDir: false, target: "esnext", // default,最低为es2015 cssCodeSplit: true, // default sourcemap: false, // default minify: 'terser', terserOptions: { compress: { drop_console: true, drop_debugger: true } } }, server: { hmr: true, // proxy: { // '/api': { // target: 'http://10.142.38.239:19888', // changeOrigin: true, // rewrite: path => path.replace(/^\/api/, '') // } // }, proxy: { '/api': { target: 'http://10.211.19.13:8090', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } }, cors: true, headers: { 'Access-Control-Allow-Origin': '*' } }, }); // webpack解决方案 // output : { // library: `${packageName}-[name]`, // libraryTarget: 'umd', // jsonpFunction: `webpackJsonp_${packageName}`, // publicPath:'/chatbotinst/', // }
等问题