微前端 无界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主应用

js 复制代码
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一变化,在非保活模式下,子应用就会重新加载

js 复制代码
<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>

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

js 复制代码
<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主应用,路由的配置

js 复制代码
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 配置

js 复制代码
// 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

js 复制代码
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 子应用,嵌套其他子应用

js 复制代码
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

相关推荐
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端
爱敲代码的小鱼10 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax