nuxt3和qiankun上线落地:nuxt3主应用配置篇

背景

我们项目有时会有网页中嵌入其他网页的需求,或者拆分巨石应用的需求,以前都是使用iframe,现在更高级的是使用微前端框架。

iframe的好处是天然的沙箱隔离、元素隔离、样式隔离,坏处是加载慢、性能差、无法全局弹窗、通信难等。

微前端的好处是解决拆分巨石应用、缩小代码主项目代码提高首页加载、可路由匹配页面、可预请求文件、允许不同语言前端项目等,坏处是主项目和子项目接入麻烦,部署麻烦,打包配置要求更高,微前端框架可能造成性能加载瓶颈等

虽然iframe有好处也有坏处,同理微前端框架也同样有好处也有坏处,建议根据团队前端实力,前端运维能力合理选择,并不是说iframe和微前端一定谁最优,脱离场景都是耍流氓。

推荐众所周知:

我们项目是后台管理系统,是常规vue3单页应用,改造成nuxt3服务端渲染以及qiankun微前端,对于首屏首页第一次渲染来说,基本秒级出来。

对比

qiankun目前版本 v2.10.16,周下载量17921,micro-app目前版本 v1.0.0-rc.5,周下载量1644

使用量都不多,相比下qiankun版本号更稳定,所以选择qiankun。

附录micro-app原理

想了解原理的,可以看github上官方给出的原理解释

Nuxt3 主应用

中文官网 nuxtjs.org.cn/

做服务端渲染有两种方式:

手动增加 server.jsentry-client.jsentry-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

gitee.com/rootegg/my-...

相关推荐
恋猫de小郭9 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅16 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606116 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了16 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅17 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅17 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅17 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment17 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅18 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊18 小时前
jwt介绍
前端