乐趣区

关于javascript:Module-Federation-模块联邦-在Vue3中使用Vue2搭建的微服务

前言:

备注:本文基于对 webpack Module Federation 有肯定理解的状况下

  1. 个别状况下应用模块联邦都是会应用雷同的版本,如 Vue2 的组件时在 Vue2 中应用,但我为什么会在 Vue3 我的项目中去应用 Vue2 的组件呢,其实是因为历史起因。好几个老的外围的我的项目都是应用 Vue2 来写的,在中期以及闲暇的时候团队是有机会应用 Vue3 去重构,然而并没有这样做,到了当初这个阶段曾经太晚了,我的项目变得宏大,人员也缩小了。
  2. 最近在保护一个我的项目,被折磨得不行,比方一个.vue 文件有 3 千行代码,框架设计不合理,不易于保护,更不易于多人保护。所以,我决定抽空去重构。
  3. Vue3 绝对于 Vue2 的益处显而易见,故索性应用 Vue3 + ts + pinia + element-plus + webpack,因为要应用 module federation,所以应用 webpack,因为 Vite 对 module federation 反对还是不太好

一、如何应用:

尽管 webpack 官网并没有介绍,然而在 GitHub 中找到了 Module Federation demo,传送门。

首先,咱们把 demo 跑起来。

1. 将代码拉下来:https://github.com/module-fed…

2. 在最外层目录装置依赖

yarn

3. 进入 vue2-in-vue3 这个目录,执行命令启动

yarn start

这时候会启动两个服务,其中 vue3 应用了 vue2 的 Button 组件:

  • 应用方 HOST (vue3): localhost:3002
  • 提供方 REMOTE (vue2): localhost:3001

webpack 相干配置:

vue2 webpack 配置

plugins: [
    ...
    new ModuleFederationPlugin({
      name: 'vue2App',
      filename: 'remoteEntry.js',
      library: {type: 'var', name: 'vue2App'},
      exposes: {
        './vue2': './node_modules/vue/dist/vue', // 留神点:这里须要把 vue 裸露进来,起因前面讲
        './Button': './src/components/Button',
      },
    }),
   ...
  ],

vue3 webpack 配置

plugins: [
    ...
    new ModuleFederationPlugin({
      name: 'vue3',
      filename: 'remoteEntry.js',
      remotes: {vue2App: '[email protected]://localhost:3001/remoteEntry.js',
      },
    }),
    ...
  ],

vue3 我的项目 App.vue 文件

<template>
  <div>
    <h3>Vue3 App</h3>
    <Content :count="count"/>
   
    <!-- 这里跟咱们应用一般组件有一点区别
     1、失常状况咱们只须要这样写:<Button @btnClick="inc"/>
     2、而这里则须要应用一个元素来将挂载组件
      -->
    <div id="vue2Button"></div>
    <vue2-button @btnClick="inc"/>
    
  </div>
</template>

<script>
import {ref} from "vue";
import Content from "./components/Content";
import {vue2ToVue3} from './utils';
import Button from 'vue2App/Button';

export default {
  components: {
    Content,
    // 通过一个办法,将 vue2 的组件转为 vue3 的组件,并挂载在 id 为 'vue2Button' 的元素上
    vue2Button: vue2ToVue3(Button, 'vue2Button'),
  },
  setup() {const count = ref(0);
    const inc = () => {count.value++;};

    return {
      count,
      inc,
    };
  }
};
</script>

utils.js,外围在于应用 vue2ToVue3 办法,应用 vue2 渲染函数将 vue2 组件挂载到指定元素

import Vue2 from 'vue2App/vue2';

function bindSlotContext(target = {}, context) {return Object.keys(target).map(key => {const vnode = target[key];
    vnode.context = context;
    return vnode;
  });
}

/* 外围
 * Transform vue2 components to DOM.(官网正文)* 了解:这里通过应用 vue2 的渲染函数,将导入的组件挂载在指定的节点上,奇妙地让 vue2 的组件能在 vue3 中应用
 * * 的确解决了问题,然而在应用的时候会有一些弊病:* * 1、存在弊病:每应用一个组件就须要写一个元素去承载,在应用比拟频繁的组件中,如:button、input 等罕用组件,*    一个页面可能应用十分多个,那么在书写的时候就须要创立很多元素去承载,所以在这种场景下不太适宜应用
 * * 2、应用场景:应用频率低,有交互的、略微简单一点的组件
 */
export function vue2ToVue3(WrapperComponent, wrapperId) {
  let vm;
  return {mounted() {const slots = bindSlotContext(this.$slots, this.__self);
      vm = new Vue2({
        render: createElement => {
          return createElement(
            WrapperComponent,
            {
              on: this.$attrs,
              attrs: this.$attrs,
              props: this.$props,
              scopedSlots: this.$scopedSlots,
            },
            slots,
          );
        },
      });
      vm.$mount(`#${wrapperId}`);
    },
    props: WrapperComponent.props,
    render() {vm && vm.$forceUpdate();
    },
  };
}

vue2 我的项目,Button.vue,留神点:

<template>
  <button @click="click">vue2 button click</button>
</template>

<script>

export default {
  methods: {click() {this.$emit("btnClick");
      // vue3 事件命名须要应用大驼峰,并且前以 on 结尾
      this.$emit("onBtnClick");
    }
  }
}
</script>

二、常见问题:

一、Uncaught Error: Shared module is not available for eager consumption

其实 webpack 官网是有给出解决办法的,详情见这里

官网给出的解决办法:新建一个 bootstrap.js,将 main.js 的内容放到 bootstrap.js 中,在主入口异步去加载 bootstrap.js

// bootstrap.js
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')

// 程序入口:main.js 有的是叫做 index.js
import ('./bootstrap.js')

依照故事情节倒退,这个问题就解决了,然而这时候我仍旧还是报这个错。在以前的其它我的项目中,我也是应用这种形式解决这个问题,为什么这一次不行了?

通过排查,我定位到了起因:

1、因为我是应用 vue-cli 创立的,vue-cli 中默认 2 个环境变量 NODE_ENV=’development’ 和 NODE_ENV=’production’,然而不满足我应用,我减少了一个 NODE_ENV=’test’,自定义了 –mode,如下:

package.json

serve:test":"vue-cli-service serve --mode test
    # 在根目录中应用了三个环境变量文件
    |-- .env.development
    |-- .env.production
    |-- .env.test

.env.development 代码:

NODE_ENV = 'development'
VUE_APP_API_ENV = 'dev'

.env.production:

NODE_ENV = 'production'
VUE_APP_API_ENV = 'prod'

.env.test:

NODE_ENV = 'test'
VUE_APP_API_ENV = 'test'

问题就呈现在.env.test 中,如果 NODE_ENV = ‘test’ 的话则会报错“Shared module is not available for eager consumption

我把.env.test 改为如下就好了:

NODE_ENV = 'development'  # 这里要改为 development
VUE_APP_API_ENV = 'test'

改过这样其实对于我的我的项目也没有影响,因为原本我的项目就只有正式和开发两个环境,我定义多了一个环境变量用来示意接口的环境,有时候能够给后端在本地调试应用,当然大家也能够应用 cross-env 的形式。

二、在 vue2 微服务中,应用了 vue3 不反对的第三方库,element-ui 在 vue3 中不能应用,须要应用 element-plus

在我的 vue2 组件库中,有一部分组件对 element-ui 进行了二次封装,在 vue3 应用模块联邦去应用的时候就报错了,如:无奈找到 el-table 等。
测试 element-plus 在我的项目中是失常应用的,那我就导入 element-ui 看看能不能用,当然是不可用的,如果反对 vue3 的话,就不必弄一个 element-plus 了。
当然,除了这种状况以外,微服务裸露的工具函数,或者本人手写的组件,都是能够应用的。

总结:

1、首先,自己不太举荐应用这种形式,万不得已不要用
2、Model Federation 在 vue3 中去应用 vue2 的组件,会有很多坑,以及不不便的中央,倡议有精力的话把组件库改为 vue3 的,或者 vue2、vue3 通用的

退出移动版