背景
我们项目有时会有网页中嵌入其他网页的需求,或者拆分巨石应用的需求,以前都是使用iframe,现在更高级的是使用微前端框架。
iframe的好处是天然的沙箱隔离、元素隔离、样式隔离,坏处是加载慢、性能差、无法全局弹窗、通信难等。
微前端的好处是解决拆分巨石应用、缩小代码主项目代码提高首页加载、可路由匹配页面、可预请求文件、允许不同语言前端项目等,坏处是主项目和子项目接入麻烦,部署麻烦,打包配置要求更高,微前端框架可能造成性能加载瓶颈等
虽然iframe有好处也有坏处,同理微前端框架也同样有好处也有坏处,建议根据团队前端实力,前端运维能力合理选择,并不是说iframe和微前端一定谁最优,脱离场景都是耍流氓。
推荐众所周知:
-
nuxt3 服务端渲染 nuxtjs.org.cn/
-
阿里
qiankun
qiankun.umijs.org/zh -
和京东
micro-app
micro-zoe.github.io/micro-app/
我们项目是后台管理系统,是常规vue3单页应用,改造成nuxt3服务端渲染以及qiankun微前端,对于首屏首页第一次渲染来说,基本秒级出来。
对比
qiankun目前版本 v2.10.16,周下载量17921,micro-app目前版本 v1.0.0-rc.5,周下载量1644
使用量都不多,相比下qiankun版本号更稳定,所以选择qiankun。
附录micro-app原理
想了解原理的,可以看github上官方给出的原理解释
-
micro-app介绍 github.com/micro-zoe/m...
-
从零开始写一个微前端框架-沙箱篇 github.com/micro-zoe/m...
-
从零开始写一个微前端框架-渲染篇 github.com/micro-zoe/m...
-
从零开始写一个微前端框架-样式隔离篇 github.com/micro-zoe/m...
-
从零开始写一个微前端框架-数据通信篇 github.com/micro-zoe/m...
Nuxt3 主应用
中文官网 nuxtjs.org.cn/
做服务端渲染有两种方式:
- 第一种是根据vue官网介绍 cn.vuejs.org/guide/scali... 和 vite官网介绍 cn.vitejs.dev/guide/ssr.h...
手动增加 server.js
、entry-client.js
、entry-server.js
,把原来单一模式的Vue实例,store实例,router实例改成每个请求独立这些实例等,参考模版项目 github.com/bluwy/creat...
- 第二种
推荐
是使用成熟服务端框架 Nuxt、Quasar 等,根据框架规范写组件、服务接口请求、中间件、模板页、插件等
初始化 nuxt3 项目
swift
npx nuxi@latest init <project-name>
安装上 pinia,pwa,antdv 包
sql
pnpm add pinia @pinia/nuxt @pinia-plugin-persistedstate/nuxt @nuxt/image ant-design-vue qiankun
pnpm add -D @ant-design-vue/nuxt @vite-pwa/nuxt less
nuxt.config.ts引入模块
javascript
import { fileURLToPath, URL } from "node:url";
import { resolve } from "path";
const { VITE_PROXY } = process.env; // 环境变量在package.json的scripts命令里面nuxt dev --dotenv .env.development增加的开发服务器地址
export default defineNuxtConfig({
devtools: { enabled: false }, // 关闭页面的开发工具
ssr: true, // 默认启动服务端渲染
routeRules: {
"/qkpage/**": { ssr: false }, // qiankun子应用页面不用服务端渲染
},
modules: [
"@pinia/nuxt", // nuxt的pinia模块
"@pinia-plugin-persistedstate/nuxt", // pinia的持久化
"@nuxt/image", // NuxtImg图片组件
"@ant-design-vue/nuxt", // nuxt的antdv组件库自动按需加载
"@vite-pwa/nuxt", // pwa模块
],
piniaPersistedstate: {
storage: "localStorage", // pinia持久化到localStorage
},
imports: { // 根据实际需要做文件自动import导入
dirs: [
"store/**/*.{ts,js,mjs,mts}",
"composables/base/**/*.{ts,js,mjs,mts}",
],
},
nitro: { // 开发模式接口代理转发
compressPublicAssets: true,
devProxy:
VITE_PROXY &&
JSON.parse(VITE_PROXY!).reduce((p, c) => {
p[c[0]] = {
target: c[1],
changeOrigin: true,
prependPath: true,
};
return p;
}, {}),
},
vite: { // 增加 /@/开头的前缀可识别,默认已经有@/前缀可识别了
resolve: {
alias: [
// [/]@/xxxx => xxxx
{
find: /[\/]@\//,
replacement: fileURLToPath(new URL("./", import.meta.url)),
},
],
},
},
hooks: {
"pages:extend": (pages) => {
// qiankun微前端需要的追加自定义的路由
pages.push({
path: "/qkpage",
file: resolve(__dirname, "components/QianKunContent.vue"),
children: [
{
path: "/:slug(.*)*", // 一定要加上这段兜底,不然qiankun匹配不到子应用的路由
file: resolve(__dirname, "components/QianKunContent.vue"),
},
],
});
},
},
});
pinia 配置和持久化
根目录新建store目录,新建user.ts文件,做持久化
typescript
export const useUserStore = defineStore({
id: "app-user",
state: () => ({
userInfo: null,
}),
actions: {
async login(params) {
try {
const data: any = await fetch("/sys/login", {
method: "POST",
body: params,
});
const { userInfo } = data;
this.userInfo = userInfo;
} catch (error) {
return Promise.reject(error);
}
},
},
persist: true, // 持久化
});
qiankun默认空组件
根目录新建 components 目录,新建 QianKunContent.vue
xml
<template>
<div id="qiankun-content"></div>
</template>
<script setup lang="ts">
import { start } from "qiankun";
onMounted(() => {
start({
prefetch: "all", // 预加载
sandbox: {
experimentalStyleIsolation: true, // 添加顶部样式选择器 css scoped 样式隔离
// strictStyleIsolation: true, // 开启 shadowDOM css scoped 样式隔离
},
});
});
</script>
<style>
#qiankun-content,
#qiankun-content > div {
height: 100%;
}
</style>
layouts默认布局
根目录新增layouts目录,新建default.vue,在slot位置会填充微前端页面,一般管理系统其他部分比如顶部和左边都是固定的功能菜单等
xml
<template>
<a-layout class="layouts-default">
<a-layout-header class="header"> 顶部菜单区域 </a-layout-header>
<a-layout>
<a-layout-sider class="left-slider"> 左侧菜单区域 </a-layout-sider>
<a-layout>
<!-- 中间区域默认插槽 -->
<slot />
</a-layout>
</a-layout>
</a-layout>
</template>
注册qiankun主应用配置
根目录新增plugins目录,新建 qiankun.client.ts,其中 xxxx.client.ts 或者 xxxx.server.ts 分别表示只在客户端或者服务端使用
javascript
import { prefetchApps, registerMicroApps, start } from "qiankun";
const registerApps = () => {
const createMicroApp = ({ name, entry, container, activeRule }) => {
return {
name,
entry, // 入口地址
container,
activeRule,
props: {
activeRule, // 传递给子应用的识别路由前缀
defHttp, // 传递给子应用接口请求方式
},
};
};
const isDevMode = import.meta.env.MODE == "development";
const microApps = [
createMicroApp({
name: "cy-jeecg-ui", // 很多文章说name是子应用的package.json中的name,其实试了这里没有任何关系,任意都行
entry: isDevMode ? "http://localhost:5173/cy-jeecg-ui/" : "/cy-jeecg-ui/", // 开发模式要写完整url地址,生产上可以用nginx转发
container: "#qiankun-content",
activeRule: "/qkpage",
}),
];
registerMicroApps(microApps, {
beforeLoad: (a, b, c) => {
console.log(a, b, c);
console.log("加载前");
},
beforeMount: (a, b, c) => {
console.log(a, b, c);
console.log("挂载前");
},
afterMount: (a, b, c) => {
console.log(a, b, c);
console.log("挂载后");
},
beforeUnmount: (a, b, c) => {
console.log(a, b, c);
console.log("销毁前");
},
afterUnmount: (a, b, c) => {
console.log(a, b, c);
console.log("销毁后");
},
});
/**
* 方式一:采用qiankun自带的预请求,但是效果不好,因为子应用的很多js和css文件不会预先请求到
*/
// start({
// prefetch: "all", // 预加载
// sandbox: {
// // experimentalStyleIsolation: true, // 添加顶部样式选择器 css scoped 样式隔离
// strictStyleIsolation: true, // 开启 shadowDOM css scoped 样式隔离
// },
// });
/**
* 方式二:手动预请求子应用js和css文件,子应用生成一个manifest.json文件,里面包含子应用所有js和css,自己做文件预请求缓存
*/
microApps.forEach((app) => {
setPrefetchBundleJson(app);
});
};
export default defineNuxtPlugin((nuxtApp) => {
registerApps();
});
async function setPrefetchBundleJson(app) {
const t = new Date().getTime();
fetch(app.entry + "?v=" + t);
const res = await fetch(app.entry + "assets/bundle.json?v=" + t).then((r) => {
try {
return r.json();
} catch {
return { manifest: [] };
}
});
res.manifest.forEach((url) => {
prefetchScript([app.entry, url]);
});
}
function prefetchScript(urls) {
let cbUrl = urls
.map((u) => {
if (u.startsWith("/")) {
u = u.substring(1);
}
if (u.endsWith("/")) {
u = u.substring(0, u.length - 1);
}
return u;
})
.join("/");
if (!/^(http[s]|\/\/)/.test(cbUrl)) {
cbUrl = "/" + cbUrl;
}
const dom = document.createElement("link");
dom.setAttribute("href", cbUrl);
dom.setAttribute("rel", "prefetch");
document.body.append(dom);
dom.onload = () => {
dom.parentElement?.removeChild(dom);
};
}
注册antdv组件库按需加载
在 nuxt.config.ts 中已经使用 @ant-design-vue/nuxt
,会自动按需加载
如果不用按需加载就去掉@ant-design-vue/nuxt
,则要在plugins中新增antd.client.ts,推荐就用按需加载
源码
首页是主应用页面
路由qkpage开头的是加载的子应用
主应用源码,源码中服务器地址改为了 xxxxx