前言:
备注:本文基于对webpack Module Federation有肯定理解的状况下
- 个别状况下应用模块联邦都是会应用雷同的版本,如Vue2的组件时在Vue2中应用,但我为什么会在Vue3我的项目中去应用Vue2的组件呢,其实是因为历史起因。好几个老的外围的我的项目都是应用Vue2来写的,在中期以及闲暇的时候团队是有机会应用Vue3去重构,然而并没有这样做,到了当初这个阶段曾经太晚了,我的项目变得宏大,人员也缩小了。
- 最近在保护一个我的项目,被折磨得不行,比方一个.vue文件有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.jsimport { createApp } from 'vue'import App from './App.vue'import router from './router'createApp(App).use(router).mount('#app')// 程序入口:main.js 有的是叫做 index.jsimport ('./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' # 这里要改为developmentVUE_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通用的