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-...

相关推荐
托尼沙滩裤40 分钟前
【js面试题】js的数据结构
前端·javascript·数据结构
不熬夜的臭宝1 小时前
每天10个vue面试题(一)
前端·vue.js·面试
不如喫茶去1 小时前
VUE自定义新增、复制、删除dom元素
前端·javascript·vue.js
长而不宰1 小时前
vue3+electron项目搭建,遇到的坑
前端·vue.js·electron
阿垚啊2 小时前
vue事件参数
前端·javascript·vue.js
过去式的美好3 小时前
vue前端通过sessionStorage缓存字典
前端·vue.js·缓存
Simaoya3 小时前
vue判断元素滚动到底部后加载更多
前端·javascript·vue.js
头顶一只喵喵3 小时前
Vue基础知识:Vue3.3出现的defineOptions,如何使用,解决了什么问题?
前端·javascript·vue.js·vue3
掘金安东尼3 小时前
上周前端发生哪些新鲜事儿?#370
前端·javascript·面试
黑色的糖果4 小时前
echarts横向立体3D柱状图
前端·javascript·echarts