当前位置: 首页 > article >正文

微前端 无界wujie

开发环境配置:

Node.js 版本 < 18.0.0
pnpm 脚手架示例模版基于 pnpm + turborepo 管理项目
如果您的当前环境中需要切换 node.js 版本, 可以使用 nvm or fnm 进行安装.

以下是通过 nvm 或者nvs 安装 Node.js 16 LTS 版本
nvs安装教程 https://blog.csdn.net/glorydx/article/details/134056903

C:\>node -v
v20.18.1

C:\>nvs use 16
Specified version not found.
To add this version now: nvs add node/16

C:\>nvs add node/16
Extracting  [###########################################################################################] 100%
Added at: %LOCALAPPDATA%\nvs\node\16.20.2\x64\node.exe
To use this version now: nvs use node/16.20.2/x64

C:\>npx create-wujie@latest
Need to install the following packages:
create-wujie@0.4.0
Ok to proceed? (y) y

📦 Welcome To Create Template for WuJie! V0.3.2
√ Project name: ... wujie-main
√ What framework do you choose as your main application ? » Webpack + Vue2
√ Select the main application route pattern » history
√ What framework do you choose as your sub application ? » Vite, Vue2, Vue3, React16, React17
√ Select the sub application route pattern » history

安装完成以后,分别单独启动wujie的主应用,和子应用,记得将node的版本都统一设置为 16 这样就可以正常体验wujie官方提供的demo。

wujie代码分析 vue2主应用

import "whatwg-fetch"; // fetch polyfill
import "custom-event-polyfill"; // CustomEvent polyfill

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import WujieVue from "wujie-vue2";
import hostMap from "../wujie-config/hostMap";
import credentialsFetch from "../wujie-config/fetch";
import Switch from "ant-design-vue/es/switch";
import Tooltip from "ant-design-vue/es/tooltip";
import button from "ant-design-vue/es/button/index";
import Icon from "ant-design-vue/es/icon/index";
import "ant-design-vue/es/button/style/index.css";
import "ant-design-vue/es/style/index.css";
import "ant-design-vue/es/switch/style/index.css";
import "ant-design-vue/es/tooltip/style/index.css";
import "ant-design-vue/es/icon/style/index.css";
import lifecycles from "../wujie-config/lifecycle";
import plugins from "../wujie-config/plugin";

const isProduction = process.env.NODE_ENV === "production";
const { setupApp, preloadApp, bus } = WujieVue;
Vue.use(WujieVue).use(Switch).use(Tooltip).use(button).use(Icon);

Vue.config.productionTip = false; // 关闭生产提示

bus.$on("click", (msg) => window.alert(msg));

// 在 xxx-sub 路由下子应用将激活路由同步给主应用,主应用跳转对应路由高亮菜单栏
bus.$on("sub-route-change", (name, path) => {
  const mainName = `${name}-sub`;
  const mainPath = `/${name}-sub${path}`;
  const currentName = router.currentRoute.name;
  const currentPath = router.currentRoute.path;
  if (mainName === currentName && mainPath !== currentPath) {
    router.push({ path: mainPath });
  }
});

// 根据浏览器的版本,如果不支持 Proxy,则降级 Object.defineProperty 如果不支持webcomponent 则降级iframe 理论上可以兼容到 IE 9
const degrade =
  window.localStorage.getItem("degrade") === "true" ||
  !window.Proxy ||
  !window.CustomElementRegistry;

const props = {
  // 将主应用的router.push方法传递给子应用,这样子应用就能通过获取jump方法,来控制主应用的跳转
  jump: (name) => {
    router.push({ name });
  },
};
/**
 * 大部分业务无需设置 attrs
 * 此处修正 iframe 的 src,是防止github pages csp报错
 * 因为默认是只有 host+port,没有携带路径
 */
const attrs = isProduction ? { src: hostMap("//localhost:8000/") } : {};
/**
 * 配置应用,主要是设置默认配置
 * preloadApp、startApp的配置会基于这个配置做覆盖
 */

setupApp({
  name: "react16",
  url: hostMap("//localhost:7600/"),
  attrs,
  exec: true,
  props, // 给子应用传递的参数
  fetch: credentialsFetch,
  plugins,
  // prefix 用于改变子路径过长,在主应用中进行替换
  prefix: { "prefix-dialog": "/dialog", "prefix-location": "/location" },
  degrade,
  ...lifecycles,
});

setupApp({
  name: "react17",
  url: hostMap("//localhost:7100/"),
  attrs,
  exec: true, //是否预先执行子应用
  alive: true, // 是否保存子应用的状态
  props,
  fetch: credentialsFetch,
  degrade,
  ...lifecycles,
});
setupApp({
  name: "vue2",
  url: hostMap("//localhost:6100/"),
  attrs,
  exec: true,
  props,
  fetch: credentialsFetch,
  degrade,
  ...lifecycles,
});

setupApp({
  name: "vue3",
  url: hostMap("//localhost:8082/"),
  attrs,
  exec: true,
  alive: true,
  plugins: [
    {
      cssExcludes: [
        "https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css",
      ],
    },
  ],
  props,
  // 引入了的第三方样式不需要添加credentials
  fetch: (url, options) =>
    url.includes(hostMap("//localhost:8082/"))
      ? credentialsFetch(url, options)
      : window.fetch(url, options),
  degrade,
  ...lifecycles,
});
setupApp({
  name: "vite",
  url: hostMap("//localhost:8083/"),
  attrs,
  exec: true,
  props,
  fetch: credentialsFetch,
  degrade,
  ...lifecycles,
});

// 因为已经setupApp注册了,这里可以简写,预加载只写name就可以了
if (window.localStorage.getItem("preload") !== "false") {
  preloadApp({
    name: "react16",
  });

  preloadApp({
    name: "react17",
  });

  preloadApp({
    name: "vue2",
  });

  if (window.Proxy) {
    preloadApp({
      name: "vue3",
    });
    preloadApp({
      name: "vite",
    });
  }
}

new Vue({
  router,
  render: (h) => h(App),
}).$mount("#app");

主应用中,用来显示子应用的配置
wujieVue这个组件只要url一变化,在非保活模式下,子应用就会重新加载

<template>
  <!--保活模式,name相同则复用一个子应用实例,改变url无效,必须采用通信的方式告知路由变化 -->
  <WujieVue width="100%" height="100%" name="react17" :url="react17Url"></WujieVue>
</template>

<script>
import hostMap from "../../wujie-config/hostMap";
import wujieVue from "wujie-vue2"; // 引入wujie-vue2,如果是vue3请引入wujie-vue3 用来显示子应用
export default {
  data() {
    return {
      react17Url: hostMap("//localhost:7100/") + this.$route.params.path, //hostMap区分开发环境和生产环境
    };
  },
  watch: {
    // 保活模式,name相同则复用一个子应用实例,改变url无效,必须采用通信的方式告知路由变化
    "$route.params.path": {
      handler: function () {
        wujieVue.bus.$emit("react17-router-change", `/${this.$route.params.path}`);
      },
      immediate: true,
    },
  },
};
</script>

<style lang="scss" scoped></style>

非保活模式的子应用在主应用中的配置

<template>
  <!--单例模式,name相同则复用一个无界实例,改变url则子应用重新渲染实例到对应路由 -->
  <WujieVue width="100%" height="100%" name="vite" :url="viteUrl"></WujieVue>
</template>

<script>
import hostMap from "../../wujie-config/hostMap";

export default {
  // 如果是非保活模式,不需要watch这个$route.params.path变化,并去调用wujieVue.bus.$emit
  computed: {
    viteUrl() {
      return hostMap("//localhost:8083/") + this.$route.params.path;
    },
  },
};
</script>

<style lang="scss" scoped></style>

vue2主应用,路由的配置

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import Multiple from "../views/Multiple.vue";
import Vue2 from "../views/Vue2.vue";
import Vue2Sub from "../views/Vue2-sub.vue";
import Vue3 from "../views/Vue3.vue";
import Vue3Sub from "../views/Vue3-sub.vue";
import Vite from "../views/Vite.vue";
import ViteSub from "../views/Vite-sub.vue";
import React16 from "../views/React16.vue";
import React16Sub from "../views/React16-sub.vue";
import React17 from "../views/React17.vue";
import React17Sub from "../views/React17-sub.vue";

const basename = process.env.NODE_ENV === "production" ? "/demo-main-vue/" : ""; // 区分不同环境下,资源所在的不同文件夹
Vue.use(VueRouter);

const routes = [
  {
    path: "/all",
    name: "all",
    component: Multiple,
  },
  {
    path: "/",
    redirect: "/home",
  },
  {
    path: "/home",
    name: "home",
    component: Home,
  },
  {
    path: "/vue2",
    name: "vue2",
    component: Vue2,
  },
  {
    path: "/vue2-sub/:path",
    name: "vue2-sub",
    component: Vue2Sub,
  },
  {
    path: "/vue3",
    name: "vue3",
    component: Vue3,
  },
  {
    path: "/vue3-sub/:path",
    name: "vue3-sub",
    component: Vue3Sub,
  },
  {
    path: "/vite",
    name: "vite",
    component: Vite,
  },
  {
    path: "/vite-sub/:path",
    name: "vite-sub",
    component: ViteSub,
  },
  {
    path: "/react16",
    name: "react16",
    component: React16,
  },
  {
    path: "/react16-sub/:path",
    name: "react16-sub",
    component: React16Sub,
  },
  {
    path: "/react17",
    name: "react17",
    component: React17,
  },
  {
    path: "/react17-sub/:path",
    name: "react17-sub",
    component: React17Sub,
  },
];

// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置

const router = new VueRouter({
  mode: "history",
  base: basename,
  routes,
});

export default router;

vue2主应用vue.config 配置

// vue.config.js

/**
 * @type {import('@vue/cli-service').ProjectOptions}
 */
module.exports = {
  publicPath: process.env.NODE_ENV === "production" ? "/demo-main-vue/" : "/", // 区分开发和生产服务器的路径
  devServer: {
    headers: {
      "Access-Control-Allow-Origin": "*", // 如果需要跨域,请打开此配置
    },
    open: process.env.NODE_ENV === "development", // 只有开发环境需要使用devServer
    port: "8000",
  },
  lintOnSave: false // 是否关闭eslint检查,只在保存时才检查
};

react子应用代码分析

子应用可以用到的wujie的数据 https://wujie-micro.github.io/doc/api/wujie.html#VPSidebarNav

import "react-app-polyfill/stable";
import "react-app-polyfill/ie11";

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter as Router } from "react-router-dom";
import "./styles.css";

const basename = process.env.NODE_ENV === "production" ? "/demo-react16/" : "";

// 如果作为无界的子应用打开,就需要使用 window.__POWERED_BY_WUJIE__ 判断,然后挂载函数__WUJIE_MOUNT和卸载函数__WUJIE_UNMOUNT
if (window.__POWERED_BY_WUJIE__) {
  // eslint-disable-next-line no-undef
  window.__WUJIE_MOUNT = () => {
    ReactDOM.render(
      <Router basename={basename}>
        <App />
      </Router>,
      document.getElementById("root")
    );
  };
  window.__WUJIE_UNMOUNT = () => {
    ReactDOM.unmountComponentAtNode(document.getElementById("root"));
  };
} else {
  ReactDOM.render(
    <Router basename={basename}>
      <App />
    </Router>,
    document.getElementById("root")
  );
}

react 子应用,嵌套其他子应用

import React from "react";
import WujieReact from "wujie-react"; // 需要引入的wujie react 组件
import lifecycles from "./lifecycle"; // 对应的生命周期
import hostMap from "./hostMap"; // 对应的一些开发环境和生产环境的host映射

function selfFetch(url, options) {
  const includeFlag = process.env.NODE_ENV === "production";
  return window.fetch(url, { ...options, credentials: includeFlag ? "include" : "omit" });
}

export default function React17() {
  const react17Url = hostMap("//localhost:7100/");
  const degrade = window.localStorage.getItem("degrade") === "true";
  const props = {
    jump: (name) => {
      window?.$wujie.props.jump(name); // 从主应用vue2中得到的改变主应用router的函数jump,再传递给嵌套的子应用
    },
  };
  return (
    <div>
      <h2>子应用嵌套</h2>
      <div className="content" style={{ border: "1px dashed #ccc", overflow: "auto" }}>
        <WujieReact
          width="100%"
          height="500px"
          name="react17"
          url={react17Url}
          alive={true}
          sync={true}
          fetch={selfFetch}
          props={props}
          degrade={degrade}
          beforeLoad={lifecycles.beforeLoad}
          beforeMount={lifecycles.beforeMount}
          afterMount={lifecycles.afterMount}
          beforeUnmount={lifecycles.beforeUnmount}
          afterUnmount={lifecycles.afterUnmount}
        ></WujieReact>
      </div>
    </div>
  );
}

子应用可能还会遇到的问题
无界快速上手 https://wujie-micro.github.io/doc/guide/start.html


http://www.kler.cn/a/591636.html

相关文章:

  • IT工具 | node.js 进程管理工具 PM2 大升级!支持 Bun.js
  • git bundle创建和复制分支的方法
  • go语言中数组、map和切片的异同
  • Java---JavaSpringMVC解析(1)
  • 【ES6】04-对象 + 类 + 模板字符串 + 解构 + 字符串
  • 贝叶斯分层回归(Bayesian Hierarchical Regression)是一种基于贝叶斯统计理论的数据分析方法
  • 【css酷炫效果】纯CSS实现虫洞穿越效果
  • 力扣128. 最长连续序列 || 452. 用最少数量的箭引爆气球
  • 如何使用 DeepEval 优化 Elasticsearch 中的 RAG 检索
  • 搭建主从服务器
  • pandas中excel自定义单元格颜色
  • 利用余弦相似度在大量文章中找出抄袭的文章
  • 深度学习框架PyTorch——从入门到精通(3)数据集和数据加载器
  • 3.5 Spring Boot邮件服务:从基础发送到模板邮件进阶
  • 国内首台太空采矿机器人亮相,宇宙资源开发迈入新阶段
  • 【Java/数据结构】ArrayList的实现及使用
  • 【linux】用SSH连接服务器进行通信
  • 北斗+多技术融合地面沉降监测:精准守护城市安全
  • 计算机网络的框架结构
  • Python+Django网页前后端rsp云端摄像头人数监控系统