qiankun微前端:qiankun+vite+vue3+ts(未完待续..)

news2024/12/24 21:10:48

目录

什么是微前端

目前现有的微前端

好处

使用


 子应用的页面在主应用里显示 

什么是微前端

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

 

我的理解就是将一个大型的前端应用拆分成多个模块,每个微前端模块可以由不同的团队进行管理,并可以自主选择框架,并且有自己的仓库,可以独立部署上线。

目前现有的微前端方案

iframe

通过iframe实现的话就是每个子应用通过iframe标签来嵌入到父应用中,iframe具有天然的隔离属性,各个子应用之间以及子应用和父应用之间都可以做到互不影响。

但是iframe也有很多缺点:

  1. url不同步,如果刷新页面,iframe中的页面的路由会丢失。
  2. 全局上下文完全隔离,内存变量不共享。
  3. UI不同步,比如iframe中的页面如果有带遮罩层的弹窗组件,则遮罩就不能覆盖整个浏览器,只能在iframe中生效。
  4. 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程
single-spa

single-spa是最早的微前端框架,可以兼容很多技术栈。

single-spa首先在基座中注册所有子应用的路由,当URL改变时就会去进行匹配,匹配到哪个子应用就会去加载对应的那个子应用。

相对于iframe的实现方案,single-spa中基座和各个子应用之间共享着一个全局上下文,并且不存在URL不同步和UI不同步的情况,但是single-spa也有以下的缺点:

  1. 没有实现js隔离和css隔离
  2. 需要修改大量的配置,包括基座和子应用的,不能开箱即用
qiankun

基于single-spa二次开发,封装了开箱即用的api

资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。

实现了样式隔离

基于qiankun的微前端实战

准备两个空项目

  • qiankun-base 主应用
  • qiankun-child vue 子应用
创建基座项目qiankun-base和qiankun-child-vue

创建一个vue3+vite+tsx项目详情见 创建一个vue3+vite+ts项目

vue子应用 qiankun-child-vue
修改.env
VITE_APP_NAME = qiankun-child-vue
修改根节点挂载id
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>qiankun-child-vue</title>
  </head>
  <body>
    <div id="qiankun-child-vue"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

main.ts

import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
import router from "./router";
import antv from "ant-design-vue";
const app = createApp(App);
app.use(router).use(antv).mount("#qiankun-child-vue");

配置子应用菜单 

/views/index.tsx

import { defineComponent, h, reactive, ref } from "vue";
import { Menu, SubMenu, MenuItem, ItemType } from "ant-design-vue";
import "./index.css";
import { RouterView, useRouter } from "vue-router";
// 展平数组
const flattenMenu = (list) => {
  const res: any = [];
  if (!list) return;
  list.forEach((item) => {
    res.push(item);
    if (item.children) res.push(...flattenMenu(item.children));
  });
  return res;
};
const getMenuKey = (menuList, key) => {
  const allList = flattenMenu(JSON.parse(JSON.stringify(menuList)));
  const cur = allList.find((item) => item.key == key);
  return cur ? cur : {};
};

export default defineComponent({
  setup() {
    const router = useRouter();

    const menuList = ref([
      {
        key: "1",
        label: "子应用菜单",
        url: "/qiankun-child-vue",
        children: [
          { label: "设置", key: "2", url: "/qiankun-child-vue/setting" },
        ],
      },
    ]);
    // 找到点击的菜单对象
    const handleMenuSelect = (params) => {
      const menu = getMenuKey(menuList.value, params.key);
      router.push(menu.url);
    };
    return () => (
      <a-layout class="layout">
        <a-layout-sider>
          <Menu
            onSelect={handleMenuSelect}
            style="height:100%"
            mode="inline"
            items={menuList.value}
          ></Menu>
        </a-layout-sider>
        <a-layout-content>
          <RouterView></RouterView>
        </a-layout-content>
      </a-layout>
    );
  },
});

新建/views/setting.tsx

import { defineComponent, ref } from "vue";

export default defineComponent({
  setup() {
    return () => <div>设置</div>;
  },
});

配置路由 /router/index.ts

import { createRouter, createWebHashHistory } from "vue-router";
import Index from "@/views/index";
const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: "/",
      component: Index,
      children: [
        {
          path: "/qiankun-child-vue/setting",
          name: "setting",
          component: () => import("@/views/setting"),
        },
      ],
    },
  ],
});

export default router;

基本的页面就搭建好了 

基座 qiankun-base

同样在/views/index.tsx 写好基本的菜单

配置路由 /router/index.ts
import { createRouter, createWebHashHistory } from "vue-router";
import Index from "@/views";

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: "/:afterUser(.*)", // 正则匹配url 跳转不会报错
      component: Index,
    },
  ],
});

export default router;
配置vite.config.js   根路径base
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import { resolve } from "path";
export const pathResolve = (dir: string) => resolve(process.cwd(), ".", dir);

export default defineConfig(({ mode }) => {
  return {
    base: "/qiankun-base/",
    plugins: [vue(), vueJsx()],
    server: {
      host: "0.0.0.0",
      port: 1000,
    },
    resolve: {
      alias: {
        "@": pathResolve("src"),
      },
    },
  };
});

修改dom根节点 和主应用一样改为子应用的项目名称 改这个是为了主应用和子应用的挂载在根节点的id不会重复,如果一样的话会导致主应用页面渲染不出来子应用(这里不在展示细节)

qiankun配置步骤(上面还没开始)
主应用qiankun-base中下载qiankun
yarn add qiankun

在main.ts中开启 

注意:在挂载之前开启

import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
import { start } from "qiankun";
import router from "./router";
import antv from "ant-design-vue";
start({
  sandbox: {
    // strictStyleIsolation: true, // 开启严格的样式隔离模式
    experimentalStyleIsolation: true, // 开启后所有样式都会加上一个类名 .app-main {} 
 ===>  div[data-qiankun-react16] .app-main {}
  },
  singular: false, // 单一时间只渲染一个微应用,默认为true
});
createApp(App).use(router).use(antv).mount("#qiankun-base");
vue子应用 qiankun-child-vue
下载vite-plugin-qiankun插件
yarn add vite-plugin-qiankun
配置vite.config.js 使用vite-plugin-qiankun
import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import qiankun from "vite-plugin-qiankun";
import { resolve } from "path";

export const pathResolve = (dir: string) => resolve(process.cwd(), ".", dir);

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd());
  return {
    base: mode == "production" ? `/${env.VITE_APP_NAME}/` : "",
    plugins: [
      vue(),
      vueJsx(),
      qiankun(env.VITE_APP_NAME, { useDevMode: true }),
    ],
    server: {
      host: "0.0.0.0",
      port: 2000,
    },
    resolve: {
      alias: {
        "@": pathResolve("src"),
      },
    },
  };
});
修改main.ts 判断是在主应用还是子应用中
import "./style.css";
import App from "./App.vue";
import router from "./router";
import antv from "ant-design-vue";
import { render } from "@/hooks/microApp";

render(App, "#qiankun-child-vue", (app, props) => {
  app.use(router).use(antv);
});

/hooks/microApp.ts

import renderWithQiankun, {
  QiankunProps,
  qiankunWindow,
} from "vite-plugin-qiankun/dist/helper";
import { App, Component, createApp } from "vue";

const isMicroApp = qiankunWindow.__POWERED_BY_QIANKUN__;
export const render = (
  AppRoot: Component,
  domId,
  configApp: (app: App, props?: QiankunProps) => any
) => {
  let app: App;
  const _render = (props: QiankunProps = {}) => {
    const { container } = props;
    const root: string | Element = container
      ? container.querySelector(domId)!
      : domId; // 避免 id 重复导致微应用挂载失败
    app = createApp(AppRoot);
    // 回调配置app的函数 让调用的地方 可以使用app
    configApp(app, props);
    app.mount(root);
  };
  const initQiankun = () => {
    renderWithQiankun({
      bootstrap() {
        // console.log("微应用:bootstrap");
      },
      mount(props) {
        // 获取主应用传入数据
        // console.log("微应用:mount", props);
        _render(props);
      },
      unmount(props) {
        // console.log("微应用:unmount", props);
        app.unmount();
      },
      update(props) {
        // console.log("微应用:update", props);
      },
    });
  };
  isMicroApp ? initQiankun() : _render();
};
在views/index.tsx 增加判断逻辑 是在主应用中还是在子应用中
import { qiankunWindow } from "vite-plugin-qiankun/dist/helper";
 
return () => {
// 判断如果在主应用中
      if (qiankunWindow.__POWERED_BY_QIANKUN__) {
        return <router-view></router-view>;
      }
// 在子应用中
      return (
        <a-layout class="layout">
          <a-layout-sider>
            <Menu
              onSelect={handleMenuSelect}
              style="height:100%"
              mode="inline"
              items={menuList.value}
            ></Menu>
          </a-layout-sider>
          <a-layout-content>
            <RouterView></RouterView>
          </a-layout-content>
        </a-layout>
      );
 };
易错点

1.主应用和子应用挂载在根节点的domid是同一个

2.主应用配置路径和子应用路径不一致

       

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1878732.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《编译原理》阅读笔记:p25-p32

《编译原理》学习第 5 天&#xff0c;p25-p32总结&#xff0c;总计 8 页。 一、技术总结 1.lexical lexical这个单词后续会经常用到&#xff0c;所以首先要搞懂它的英文意思&#xff0c;不然看到中文的“词法&#xff0c;语法&#xff0c;文法”这三个词的时候就会懵了——l…

【操作系统期末速成】EP05 | 学习笔记(基于五道口一只鸭)

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文&#xff1a;☀️☀️☀️2.1 考点十一&#xff1a;死锁的概念与预防2.2 考点十二&#xff1a;死锁的避免一银行间算法2.1 考点十三&#xff1a;死锁的检测与解除 一、前言&#x1f680;&#x1f680;&#x…

Oracle、MySQL、PostGreSQL、SQL Server-空值

Oracle、MySQL、PostGreSQL、SQL Server-null value 最近几年数据库市场百花齐放&#xff0c;在做跨数据库迁移的数据库选型时&#xff0c;除了性能、稳定、安全、运维、功能、可扩展外&#xff0c;像开发中对于值的处理往往容易被人忽视&#xff0c; 之前写过一篇关于PG区别O…

Linux下安装RocketMQ:从零开始的消息中间件之旅

感谢您阅读本文&#xff0c;欢迎“一键三连”。作者定会不负众望&#xff0c;按时按量创作出更优质的内容。 ❤️ 1. 毕业设计专栏&#xff0c;毕业季咱们不慌&#xff0c;上千款毕业设计等你来选。 RocketMQ是一款分布式消息中间件&#xff0c;具有高吞吐量、低延迟、高可用性…

【工具分享】SQLmap

文章目录 工具介绍安装方式环境准备安装 sqlmap 工具介绍 sqlmap 是一个非常强大的自动化 SQL 注入工具&#xff0c;主要用于渗透测试和安全审计。它能够检测和利用 SQL 注入漏洞&#xff0c;进而访问数据库服务器。 GitHub&#xff1a;https://github.com/sqlmapproject/sql…

【Vue】单向和双向数据绑定

在 Vue.js 中&#xff0c;数据绑定可以分为单向数据绑定和双向数据绑定两种类型。 单向数据绑定 单向数据绑定是指数据从模型流向视图&#xff0c;即数据的变化会自动反映到视图中&#xff0c;但视图中的变化不会自动反映回模型。Vue.js 中的单向数据绑定主要通过以下方式实现…

【数据可视化技术】1、如何使用Matplotlib和Seaborn库在Python中绘制热力图

热力图是一种数据可视化技术&#xff0c;可以显示变量之间的相关性。这个代码段是数据分析和可视化的常用方法&#xff0c;特别适合于展示变量之间的相关性&#xff0c;对于数据科学和机器学习项目非常有帮助。 1、 导入必要的库 首先&#xff0c;确保你已经安装了matplotlib…

机器学习引领教育革命:智能教育的新时代

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀目录 &#x1f4d2;1. 引言&#x1f4d9;2. 机器学习在教育中的应用&#x1f31e;个性化学习&#x1f319;评估与反馈的智能化⭐教学资源的优…

Ubuntu qemu虚拟机 NAT网络 第一次使用,VNC访问

比如Windows 7 虚拟机 要手工设置网络

什么是等级保护2.0?

等保的全称是信息安全等级保护&#xff0c;是《网络安全法》规定的必须强制执行的&#xff0c;保障公民、社会、国家利益的重要工作。 官方定义&#xff1a;等级保护是对信息和信息载体按照重要性等级分级别进行保护的一种工作&#xff0c;指对国家重要信息、法人和其他组织及公…

数据结构历年考研真题对应知识点(树的基本概念)

目录 5.1树的基本概念 5.1.2基本术语 【森林中树的数量、边数和结点数的关系&#xff08;2016&#xff09;】 5.1.3树的性质 【树中结点数和度数的关系的应用&#xff08;2010、2016&#xff09;】 【指定结点数的三叉树的最小高度分析&#xff08;2022&#xff09;】 5.1…

摸鱼大数据——Spark基础——Spark环境安装——Spark Local[*]搭建

一、虚拟机配置 查看每一台的虚拟机的IP地址和网关地址 查看路径: cat /etc/sysconfig/network-scripts/ifcfg-ens33 2.修改 VMware的网络地址: 使用VMnet8 3.修改windows的对应VMware的网卡地址 4.通过finalshell 或者其他的shell连接工具即可连接使用即可, 连接后, 测试一…

前端面试题(基础篇十三)

一、async 和 defer 的作用是什么&#xff1f;有什么区别&#xff1f; &#xff08;1&#xff09;脚本没有 defer 或 async&#xff0c;浏览器会立即加载并执行指定的脚本&#xff0c;也就是说不等待后续载入的文档元素&#xff0c;读到就加载并执行。 &#xff08;2&#xff0…

dledger原理源码分析系列(三)-选主

简介 dledger是openmessaging的一个组件&#xff0c; raft算法实现&#xff0c;用于分布式日志&#xff0c;本系列分析dledger如何实现raft概念&#xff0c;以及dledger在rocketmq的应用 本系列使用dledger v0.40 本文分析dledger的选主 关键词 Raft Openmessaging 心跳/选…

机电公司管理小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;管理员管理&#xff0c;客户管理&#xff0c;公告管理&#xff0c;考勤管理&#xff0c;请假管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;公告&#xff0c;机电零件&…

mmap()函数和munmap()函数的例子

代码&#xff1a; #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <string.h> #include <stdio.h> #include <unistd.h>#define FILELENGTH 80 int main(void) {int fd-1;char …

推荐4款好用到飞起的工具

爱发音 “爱发音”是一个专注于英语音标和字母发音学习的在线平台。该网站支持多平台访问&#xff0c;包括电脑、平板和手机&#xff0c;用户可以随时随地进行发音练习。爱发音提供美式音标、英式音标以及字母表的发音教学&#xff0c;用户可以通过点击音标来发音&#xff0c;长…

无水印视频素材库有哪些?高清无水印素材网站分享!

在这个数字化时代&#xff0c;短视频创作已成为流行趋势。为了让您的视频内容更具吸引力&#xff0c;选择合适的无水印高清视频素材至关重要。今天&#xff0c;我将向您推荐几个优秀的视频素材库&#xff0c;这些资源网站将大大提高您的创作效率和视频质感。 蛙学素材网&#…

热管的原理和棒芯的加工

当热管的一端受热时&#xff0c;毛细芯中的液体蒸发汽化&#xff0c;蒸汽在微小的压差下流向另一端&#xff0c;放出热量凝结成液体&#xff0c;液体再靠毛细力&#xff08;或重力&#xff09;的作用&#xff0c;沿多孔材料流回蒸发段。如此循环不已&#xff0c;热量便从一端传…

动态选线,动态的选择变量的位宽

一、原理 参考博客&#xff1a;&#xff1c;Verilog&#xff1e; 语法技巧&#xff1a;数据位操作_verilog移位操作-CSDN博客 下图是从作者的博客cv过来的一张图&#xff0c;讲的非常的清晰。实现了动态的选择选择数据的位宽&#xff0c;只需要动态的改变base_expr就可以。 …