Module Federation在vue3中使用vue2的组件

news2025/1/27 12:08:18

前言:

备注:本文基于对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组件:

  • 使用方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: '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通用的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/407731.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

点击《el-table》让选中的行变色,亲测实用

前期回顾 Vue项目实战 —— 哔哩哔哩移动端开发_0.活在风浪里的博客-CSDN博客撑着下班前半小时我用vue写《哔哩哔哩 项目》移动端、新手还在哭、老鸟一直在笑。。。技术选型Vue2&#xff0c;技术栈有axios、Vh等&#xff0c;下班过来敲哈哈https://blog.csdn.net/m0_5790…

vite+vue3+ts 手把手教你创建一个vue3项目

使用vite 创建一个vue3项目&#xff0c;基本全程cv。 安装router&#xff0c;less / scss&#xff0c;pinia&#xff0c;axios&#xff0c; element / Ant Design Vue&#xff0c;Echarts 以及如何配置别名&#xff0c;自定义组件&#xff0c;vue-devtools插件。 目录 1、创建…

vue设置cookie和路由守卫

vue项目中登录页面用户登录成功后&#xff0c;会把用户信息存储到cookie中&#xff0c;然后跳转进入首页&#xff0c;当用户没有登录时&#xff0c;直接输入页面地址会经过路由守卫检测cookie中是否存在用户信息&#xff0c;如果不存在&#xff0c;重定向到登录页让用户进行登录…

使用idea运行VUE项目

1.电脑先安装好node.js并配置好环境变量&#xff0c; 安装方法参考windows mac 2.使用管理员身份运行idea&#xff0c;把项目拉取下来&#xff0c; 并给idea安装vue的插件 安装方法参考链接 3.删除“node_modules”文件夹和“package-lock.json” 将项目里的“node_modules”…

简单的Java web项目代码(8个)

引言&#xff1a;Java web项目主要采用mvc的的设计思想&#xff0c;系统主要采用javajspservletmysqleclipse实现&#xff0c;具有登陆、分页、导出excel&#xff0c;增删改查等功能,适合初学者&#xff0c;满足基本的实训需求&#xff0c;以下是推荐的几款&#xff0c;总有适合…

uniapp及uniCloud开发中经常会出现的问题汇总

一、manifest.json内未配置Appld,请重新获取后再 uniCloud本地调试服务启动失败&#xff0c;错误信息为:manifest.json内未配置Appld,请重新获取后再试 新建uniapp项目会出现以下报错&#xff0c;说明还没有创建APPID。 解决办法&#xff1a; 打开DCloud开发者中心&#xff…

Vite基础配置之 - 分包

Vite基础配置之 - 分包 什么是分包呢&#xff1f;它有什么好处呢&#xff1f; 还是举个例子&#xff0c;来回忆一下吧&#xff0c;什么呢&#xff1f; 比如说&#xff0c;我使用了 loadsh.js 的东西&#xff0c;那么我们在打包的时候&#xff0c;就会把 loadsh 相关资源也会打…

AJAX跨域问题及解决方案

文章目录跨域哪些方式可以进行跨域部署服务器部署模块ajax1ajax2测试跨域解决方案方案1&#xff1a;设置响应头方案2&#xff1a;jsonp深入一下jsonp方案3&#xff1a;代理机制&#xff08;httpclient&#xff09;第一种方案&#xff1a;使用JDK内置的API第二种方案&#xff1a…

Vite 打包性能优化

Vite 打包性能优化开始一个 Vite ts 项目分包策略gzip 压缩cdn 加速开始一个 Vite ts 项目 这里我们开始了一个 Vite ts 的项目&#xff0c;其中关于 ts 的配置直接看内容注释即可 npm init -y npm i vite -D npm vite-plugin-checker -D #用来强制提示ts报错<!-- inde…

反射时竟然NoSuchMethodException了!看这篇超详细的解决方案吧

前几天九哥在讲Servlet时&#xff0c;为了灵活地使用同一个Servlet来处理对同一张表的业务操作请求&#xff0c;我给学生讲解了BaseServlet工具类的封装&#xff0c;基本实现思路有如下几个步骤。 一. 反射封装BaseServlet工具类 使用反射封装BaseServlet工具类&#xff0c;无…

uview常用组件案例操作及详解(一) 选择器 picker

uview常用组件案例操作及详解&#xff08;一&#xff09; 选择器 picker 1.图片示例 2.使用方法 *为简便代码不提供样式 <view><view><text>行业性质</text><text>*</text></view><view><text v-if"!text.industry…

echarts中的legend属性

legend: {orient: "vertical",right: "0%",top: "15%",icon: "circle", //小圆点itemWidth: 8,itemHeight: 8,itemGap: 15, //间隔formatter: function (params) {let tip1 "";let tip "";let le params.leng…

【uni-app】swiper的使用

最近在学习小程序的开发&#xff0c;其中有用到swiper&#xff0c;在这里记录一下我的学习历程 有一些人&#xff0c;他刚开始并不会开发小程序&#xff0c;但是在任务面前&#xff0c;没有什么是不可以学的… 刚开始接触到swiper的时候&#xff0c;是在uni-app的官方文档里&am…

Vue中的Pinia状态管理工具 | 一篇文章教会你全部使用细节

文章目录Pinia状态管理Pinia和Vuex的对比Pinia基本使用&#x1f364;创建Pinia&#x1f364;创建StorePinia核心概念State&#x1f35f;state基本使用&#x1f35f;state其他操作Pinia核心Getters&#x1f355;getters基本使用&#x1f355;getters其他操作Pinia核心Actions&am…

React -- useState 的使用及注意事项

一、基本使用 useState是 react 提供的一个定义响应式变量的 hook 函数&#xff0c;基本语法如下&#xff1a; const [count, setCount] useState(initialCount)它返回一个状态和一个修改状态的方法&#xff0c;状态需要通过这个方法来进行修改&#xff1b;initialCount 是我…

Vue3+Vite实现动态路由

项目基本目录 1.首先定义初始默认的路由routes(router.js文件),vue文件使用import引入可以按需加载 import {createRouter,createWebHashHistory } from "vue-router";import store from ../store/index.jsconst routes [{path: "/login",component: () …

jsjiami.com V6版本,js解密的方法。

我们在爬内容&#xff0c;抓取页面的时候&#xff0c;总会遇到sojson v5&#xff0c;jsjiami.com的v6加密。 jsjiami v6 &#xff1a; JS加密,JS不可逆加密,JS混淆,JS混淆加密,JS压缩加密 - [JavaScript加密] 我看了下这个js完全有效。废话不多说。直接上代码。 (function (…

vue中深度选择器

scoped的作用 scoped 可以使当前的样式只在自己当前的组件内起作用。为了防止在一个组件内引入了子组件&#xff0c;而子组件没有加scoped。这个时候如果父子组件有相同的类名&#xff0c;就会产生样式的影响。 原理: 加了scoped就相当于给当前组件所有的标签添加一个【data-v-…

微信小程序实现轮播图

实现轮播图之前必须知道以下三点&#xff1a; 一、轮播图外层容器swiper 二、每一个轮播项swiper-item 三、swiper标签存在默认样式 1. width 100% 2. height 默认为 150px 3 .swiper高度无法实现由内容撑开 默认的150px高度的轮播图如下图: 原图是长这个样子的&#xf…

bootstrap-fileinput(二:编辑(修改)界面文件的上传,回显,删除(数据库同时删除)的操作 )

文章目录bootstrap-fileinput(二&#xff1a;编辑(修改)界面文件的上传&#xff0c;回显&#xff0c;删除(数据库同时删除)的操作 )一、编辑界面文件的上传二、编辑界面文件的回显1.文件的实体类&#xff1a;2.想要回显文件&#xff0c;首先要在工程类(你的编辑界面的主类)里面…