上篇 nuxt3和qiankun上线落地:nuxt3主应用配置篇 已经配置好主应用,这篇讲解配置子应用过程。
vue3 子应用
npm create vue@latest
装包 qiankun
pnpm add vite-plugin-qiankun
增加 vite.config.ts 配置
主要是增加 vite-plugin-qiankun 插件
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import qiankun from "vite-plugin-qiankun";
import { configBundleSpiderPlugin } from "./build/bundleSpider";
const isBuild = process.env.NODE_ENV === "production";
// https://vitejs.dev/config/
export default defineConfig({
base: "/cy-jeecg-ui/",
plugins: [
vue(),
vueJsx(),
qiankun("cy-jeecg-ui", {
// 第一个参数 qiankunName 测试了可以任意,不用跟 package.json name 一致
useDevMode: true,
}),
configBundleSpiderPlugin(isBuild),
],
server: {
host: true,
port: 5173,
cors: true, // 主应用获取子应用时跨域响应头
},
preview: {
host: true,
port: 5173,
cors: true, // 主应用获取子应用时跨域响应头
},
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});
ts.config.node.json 增加配置
多增加了两点:一个是我们添加了vite插件目录 build,二是增加 noImplicitAny
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*",
"build/**/*" // 这句是增加的
],
"compilerOptions": {
"noImplicitAny": false, // 这句是增加的
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}
main.ts 配置qiankun渲染
特别注意是getNewRouter
这里获取路由对象,因为路由的前缀要通过主应用传递过来
import "./assets/main.css";
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
import { getNewRouter } from "./router";
import {
renderWithQiankun,
qiankunWindow,
type QiankunProps,
} from "vite-plugin-qiankun/dist/helper";
let app: any = null;
const initQianKun = () => {
renderWithQiankun({
// bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap
// 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等
bootstrap() {
console.log("bootstrap");
},
// 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法,也可以接受主应用传来的参数
mount(props: any) {
render(props.container, props);
},
// 应用每次 切出/卸载 会调用的unmount方法,通常在这里我们会卸载微应用的应用实例
unmount(props: any) {
console.log("unmount", props);
// 主应用registerMicroApps+start,这是挂自动档,.切到其他子应用后切回,会重新创建新的子应用实例并渲染。
// 是的,之前的子应用实例qiankun直接不要了,即使你没有手动销毁实例。
// 所以说,采用这种模式的话一定要在子应用暴露的unmount钩子里手动销毁实例,不然就内存泄漏了。
app.unmount();
},
update: function (props: QiankunProps): void | Promise<void> {
console.log("update");
},
});
};
const render = (container: any, props: any) => {
// 如果是在主应用的环境下就挂载主应用的节点,否则挂载到本地
app = createApp(App);
app.use(createPinia());
app.use(getNewRouter(props));
app.mount(container ? container.querySelector("#app") : "#app");
};
// 判断是否为乾坤环境,否则会报错iqiankun]: Target container with #subAppContainerVue3 not existed while subAppVue3 mounting!
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render(null, undefined);
子应用增加keep-alive
注意vue3的路由缓存和vue2不一样,不用通过include判定,在路由配置想里面写keepAlive:true就行,另外:key="$route.fullPath"
设置的目的是,嵌套子路由时通配符也可识别换路由页面
<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template>
<!-- vue3.0 keep-alive配置 -->
<router-view v-slot="{ Component }">
<keep-alive>
<component
:is="Component"
v-if="$route.meta.keepAlive"
:key="$route.fullPath"
/>
</keep-alive>
<component
:is="Component"
v-if="!$route.meta.keepAlive"
:key="$route.fullPath"
/>
</router-view>
</template>
路由配置
这里就契合了上面两点,一是通过主应用传递activeRule生成路由匹配前缀,而是在meta中添加keepAlive标记缓存
import { createRouter, createWebHistory } from "vue-router";
import { qiankunWindow } from "vite-plugin-qiankun/dist/helper";
import HomeView from "../views/HomeView.vue";
export const getNewRouter = (props: any) => {
const router = createRouter({
history: createWebHistory(
qiankunWindow.__POWERED_BY_QIANKUN__
? props.activeRule
: import.meta.env.BASE_URL
), // activeRule 是 /qkpage
routes: [
{
path: "/home",
name: "home",
component: HomeView,
meta: {
keepAlive: true,
},
},
{
path: "/about",
name: "about",
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("../views/AboutView.vue"),
},
],
});
return router;
};
增加优化主应用提前预先请求文件
这个插件的目的是在子应用打包后,生成bundle.json文件,里面有所有js和css文件地址,主应用可以提前请求这些文件,就可以达到尽快渲染页面的目的
import fs from "fs";
import path from "path";
export function configBundleSpiderPlugin(isBuild: boolean) {
return {
name: "build-js-spider",
writeBundle() {
if (!isBuild) return;
const srcPath = path.resolve(__dirname, "../", "dist");
const destPath = path.resolve(srcPath, "assets", "bundle.json");
const jsArr = getAllFile(srcPath, {
nodir: true,
})
.filter((it) => it.endsWith(".js") || it.endsWith(".css"))
.map((it) => it.replace(srcPath, "").replace(/\\/g, "/"));
fs.writeFileSync(
destPath,
JSON.stringify({
manifest: jsArr,
})
);
},
};
}
/**
* 遍历指定目录下的所有文件
* @param {*} dir
*/
const getAllFile = function (dir, option) {
const res: string[] = [];
function traverse(dir) {
fs.readdirSync(dir).forEach((file) => {
const pathname = path.join(dir, file);
if (fs.statSync(pathname).isDirectory()) {
traverse(pathname);
} else {
res.push(pathname);
}
});
}
traverse(dir);
return res;
};
源码
主应用源码
子应用源码
问题
开发模式 pnpm dev
子应用启动后,会报错,因为子应用是开发模式时,主应用entry要写完整url地址,如果是生产模式可以只写后面路径