美文网首页
Module Federation 模块联邦 在Vue3中使用V

Module Federation 模块联邦 在Vue3中使用V

作者: 非洲难民 | 来源:发表于2022-09-24 16:24 被阅读0次

    前言:

    备注:本文基于对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-federation/module-federation-examples.git

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

    yarn
    

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

    目录结构
    yarn start
    

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

    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: 'vue2App@http://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通用的

    相关文章

      网友评论

          本文标题:Module Federation 模块联邦 在Vue3中使用V

          本文链接:https://www.haomeiwen.com/subject/ksmqortx.html