feng xiaohan

Wujie 使用

基本使用

  • 安装 wujie

    npm i wujie
    
  • 在主应用中引入(以 Vue 应用为例)

    index.js

    import { createApp } from "vue";
    import App from "./App.vue";
    
    import { startApp } from "wujie"; // 引入wujie,通过其中的startApp来设置子应用
    
    const app = createApp(App);
    setupApp({
      name: "唯一id",
      url: "子应用地址",
      exec: true,
      el: "容器",
      sync: true,
    });
    
    app.mount("#app");
    

Vue 组件封装

如果使用的是 vue2 或 vue3 的应用,可以直接使用封装好的组件。

  • 安装 wujie-vue2/ wujie-vue3

    # vue2 框架
    npm i wujie-vue2 -S
    # vue3 框架
    npm i wujie-vue3 -S
    
  • 在主应用中引入并挂载

    import { createApp } from "vue";
    import App from "./App.vue";
    import Wujie from "wujie-vue3"; // 引入wujie-vue3
    const app = createApp(App);
    
    app.use(Wujie); // 注册
    
    app.mount("#app");
    
  • 在主应用中使用,接入子应用

    在此之前要先启动子应用。

    <WujieVue url="http://127.0.0.1:5147" name="vue3"></WujieVue> <!--子应用vue3-->
    ...
    <WujieVue
      width="100%"
      height="100%"
      name="xxx"
      :url="xxx"
      :sync="true"
      :fetch="fetch"
      :props="props"
      :beforeLoad="beforeLoad"
      :beforeMount="beforeMount"
      :afterMount="afterMount"
      :beforeUnmount="beforeUnmount"
      :afterUnmount="afterUnmount"
    </WujieVue>
    ...
    

封装原理

wujie-vue3是按照一个组件的原理去实现的。

import {
  defineComponent,
  h,
  getCurrentInstance,
  onMounted,
  watch,
  onBeforeUnmount,
} from "vue";
import { startApp, bus } from "wujie";
import type { PropType } from "vue";
import { Props } from "./type";

const WujieVue = defineComponent({
  props: {
    width: { type: String, default: "" },
    height: { type: String, default: "" },
    name: { type: String, default: "", required: true },
    loading: { type: HTMLElement, default: undefined },
    url: { type: String, default: "", required: true },
    sync: { type: Boolean, default: undefined },
    prefix: { type: Object, default: undefined },
    alive: { type: Boolean, default: undefined },
    props: { type: Object, default: undefined },
    attrs: { type: Object, default: undefined },
    replace: {
      type: Function as PropType<Props["replace"]>,
      default: undefined,
    },
    fetch: { type: Function as PropType<Props["fetch"]>, default: undefined },
    fiber: { type: Boolean, default: undefined },
    degrade: { type: Boolean, default: undefined },
    plugins: { type: Array as PropType<Props["plugins"]>, default: null },
    beforeLoad: {
      type: Function as PropType<Props["beforeLoad"]>,
      default: null,
    },
    beforeMount: {
      type: Function as PropType<Props["beforeMount"]>,
      default: null,
    },
    afterMount: {
      type: Function as PropType<Props["afterMount"]>,
      default: null,
    },
    beforeUnmount: {
      type: Function as PropType<Props["beforeUnmount"]>,
      default: null,
    },
    afterUnmount: {
      type: Function as PropType<Props["afterUnmount"]>,
      default: null,
    },
    activated: {
      type: Function as PropType<Props["activated"]>,
      default: null,
    },
    deactivated: {
      type: Function as PropType<Props["deactivated"]>,
      default: null,
    },
  },
  setup(props, { emit }) {
    const init = () => {
      const instance = getCurrentInstance();
      startApp({
        // 微前端初始化所需方法
        name: props.name,
        url: props.url,
        el: instance?.refs.wujie as HTMLElement,
        loading: props.loading,
        alive: props.alive,
        fetch: props.fetch,
        props: props.props,
        attrs: props.attrs,
        replace: props.replace,
        sync: props.sync,
        prefix: props.prefix,
        fiber: props.fiber,
        degrade: props.degrade,
        plugins: props.plugins,
        beforeLoad: props.beforeLoad,
        beforeMount: props.beforeMount,
        afterMount: props.afterMount,
        beforeUnmount: props.beforeUnmount,
        afterUnmount: props.afterUnmount,
        activated: props.activated,
        deactivated: props.deactivated,
      });
    };
    // name和url可能是动态的,所以需要监听它们的变化,然后再初始化
    watch([props.name, props.url], () => {
      init();
    });

    // 传参函数
    const hanlderEmit = (event: string, ...args: any[]) => {
      emit(event, ...args); // 暴露给父组件(给bus做了一个中转)
    };

    onMounted(() => {
      bus.$onAll(hanlderEmit); // 监听所有事件
      init();
    });

    onBeforeUnmount(() => {
      bus.$offAll(hanlderEmit); // 销毁
    });

    return () =>
      h("div", {
        style: {
          width: props.width,
          height: props.height,
        },
        ref: "wujie", // 便于之后获取到这个dom
      });
  },
});
WujieVue.install = function (app) {
  app.component("WujieVue", WujieVue); // 组件注册
};

export default WujieVue;

注意:

  • PropType:有很多复杂的数据结构是办法没有标注类型的(在 ts 中类型与接口不对应会报错),所以 vue3 提供一个 PropType 来与定义的结构相对应。(此处的类型对应的就是导出的 Props 里的类型,见下文)
  • getCurrentInstance:在setup()中的this还是 undefined,所以没有办法通过this.$refs来读取。在 vue3 中可以使用getCurrentInstance()来获取组件的实例。
  • bus:wujie 封装的用于传参的对象。

定义所需参数(wujie 的参数)

import type { plugin } from "wujie";
type lifecycle = (appWindow: Window) => any;
interface Props {
  /** 唯一性用户必须保证 */
  name: string;
  /** 需要渲染的url */
  url: string;
  /** 需要渲染的html, 如果用户已有则无需从url请求 */
  html?: string;
  /** 渲染的容器 */
  loading?: HTMLElement;
  /** 路由同步开关, false刷新无效,但是前进后退依然有效 */
  sync?: boolean;
  /** 子应用短路径替换,路由同步时生效 */
  prefix?: { [key: string]: string };
  /** 子应用保活模式,state不会丢失 */
  alive?: boolean;
  /** 注入给子应用的数据 */
  props?: { [key: string]: any };
  /** js采用fiber模式执行 */
  fiber?: boolean;
  /** 子应用采用降级iframe方案 */
  degrade?: boolean;
  /** 自定义运行iframe的属性 */
  attrs?: { [key: string]: any };
  /** 自定义降级渲染iframe的属性 */
  degradeAttrs?: { [key: string]: any };
  /** 代码替换钩子 */
  replace?: (codeText: string) => string;
  /** 自定义fetch,资源和接口 */
  fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
  /** 子应插件 */
  plugins: Array<plugin>;
  /** 子应用生命周期 */
  beforeLoad?: lifecycle;
  /** 没有做生命周期改造的子应用不会调用 */
  beforeMount?: lifecycle;
  afterMount?: lifecycle;
  beforeUnmount?: lifecycle;
  afterUnmount?: lifecycle;
  /** 非保活应用不会调用 */
  activated?: lifecycle;
  deactivated?: lifecycle;
}

export { Props };

React 组件封装

wujie 基于react的组件封装,使用参数基本和 Vue 的封装类似,只是换了 react 的写法。

预加载

  • 解构出 preloadApp()
  • 使用 preloadApp(),将exec设置为 true 开启预加载
import { createApp } from "vue";
import App from "./App.vue";

import Wujie from "wujie-vue3";
const { preloadApp } = Wujie; // 解构出preloadApp()
const app = createApp(App);

app.use(Wujie);
app.mount("#app");

preloadApp({ name: "react", url: "http://127.0.0.1:5147", exec: true }); // 开启预加载(参数和setupApp相同,多了个exec)

应用通信

wujie 提供三种通信方式:

window 通信

由于子应用运行的iframesrc和主应用是同域的,所以相互可以直接通信。

  • 主应用调用子应用的全局数据

    window.document.querySelector("iframe[name=子应用id]").contentWindow.xxx;
    
  • 子应用调用主应用的全局数据

    window.parent.xxx;
    

例子:

主应用定义一个全局变量:

var a = "abc";

子应用通过window.parent.a访问:

const getParentA = () => {
  console.log(alter(window.parent.a));
};

注意:由于同源策略的限制,如果 iframe 的源与主页面的源不同,那么在控制 iframe 中的元素时可能会受到限制。(如果需要跨域控制 iframe 中的元素,可以考虑使用 postMessage API 来进行通信)

props 通信

  • 主应用可以通过props注入数据和方法

    <WujieVue
      name="xxx"
      url="xxx"
      :props="{ data: xxx, methods: xxx }"
    ></WujieVue>
    

    例子:

    <template>
      <WujieVue
        url="http://127.0.0.1:5147"
        name="vue3"
        :props="{age: 23, name: '张三'}"
      ></WujieVue>
    </template>
    
  • 子应用可以通过$wujie来获取:

    无界对子应用注入了$wujie对象,可以通过$wujie或者window.$wujie获取。

    const props = window.$wujie?.props; // {data: xxx, methods: xxx}
    

    注意:直接写window.$wujie.props可能能用,但是会报错,这是因为没有写$wujie的声明文件,在全局声明一下就好了:

    declare global {
      interface Window {
        $wujie: {
          props: Record<string, any>;
        };
      }
    }
    

eventBus 通信

无界提供一套去中心化的通信方案,主应用和子应用、子应用和子应用都可以通过这种方式进行通信。

通过$on监听,$emit触发双向传递消息。

  • 主应用使用

    // 如果使用wujie
    import { bus } from "wujie";
    
    // 如果使用wujie-vue
    import WujieVue from "wujie-vue";
    const { bus } = WujieVue;
    
    // 如果使用wujie-react
    import WujieReact from "wujie-react";
    const { bus } = WujieReact;
    
    // 主应用监听事件
    bus.$on("事件名字", function (arg1, arg2, ...) {});
    // 主应用发送事件
    bus.$emit("事件名字", arg1, arg2, ...);
    // 主应用取消事件监听
    bus.$off("事件名字", function (arg1, arg2, ...) {});
    

    例子:

    bus.$on("vue3", (data: any) => {
      console.log(date, "我是主应用");
    });
    
  • 子应用使用

    // 子应用监听事件
    window.$wujie?.bus.$on("事件名字", function (arg1, arg2, ...) {});
    // 子应用发送事件
    window.$wujie?.bus.$emit("事件名字", arg1, arg2, ...);
    // 子应用取消事件监听
    window.$wujie?.bus.$off("件名字", function (arg1, arg2, ...) {});
    

    例子:

    const send = () => {
      window.$wujie.bus.$emit("vue3", "我是子应用");
    };