模块联邦(Module Federation)
由Zack Jackson这位工程师提出的一种JavaScript架构,后来被他和其他几个工程师共同编写集成进了webpack5。 所以要使用 模块联邦 webpack 的版本至少5.
vue3 加载vue3的模块
这里我是在同一个项目内使用模块联邦 vue3设置模块
javascript
const { ModuleFederationPlugin } = require('webpack').container
new ModuleFederationPlugin({
name: 'workbench',
filename: 'remoteEntry.js',
exposes: {
'./button-group': './src/federations/button-group/index.tsx',
'./collapse': './src/federations/collapse/index.tsx',
'./collapse-plan': './src/federations/collapse-plan/index.tsx',
'./jz-tag': './src/federations/jz-tag/index.tsx',
'./plan': './src/federations/plan/index.tsx',
'./tab-bar': './src/federations/tab-bar/index.tsx'
}
})
vue3使用模块-webpack-plugin
javascript
new ModuleFederationPlugin({
name: 'workbench',
filename: 'remoteEntry.js',
remotes: {
workbench: ` workbench@${publicPath}/remoteEntry.js`
}
})
使用自己的模块时,不需要指定模块名称
javascript
const JzCollapse = defineAsyncComponent(
() => import('@/federations/collapse/index')
)
const JzCollapsePanel = defineAsyncComponent(
() => import('@/federations/collapse-plan/index')
)
vue3 加载vue2的模块
设置模块
css
new ModuleFederationPlugin({
name: 'recruit',
filename: 'remoteEntry.js',
exposes: {
'./vue2': './src/share/index.js',
"./purposeAdd": "./src/views/purpose/components/insert.vue",
"./clueAdd": "./src/components/addClub.vue",
},
shared: require('./package.json').dependencies,
})
注意 './vue2': './src/share/index.js',这个人模块是我用来共享项目环境的,共享的代码如下
scala
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import store from '@/store/index'
Vue.use(ElementUI)
class ShareVue extends Vue {
constructor(props) {
super({ ...props, store })
}
}
export default ShareVue
引入recruit模块
arduino
new ModuleFederationPlugin({
name: 'workbench',
filename: 'remoteEntry.js',
remotes: {
// 静态资源地址
"recruit": "recruit@https://jxxx.xxx.com/remoteEntry.js"}
})
vue2的模块并不能在vue3中直接使用(其实不同的项目,可能也会有问题),我们先写一个vue2的兼容代码。
php
// recruit是联邦模块的名称 这里
import Vue2 from 'recruit/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.
*/
export function vue2ToVue3 (WrapperComponent, wrapperId) {
if (WrapperComponent.options) {
WrapperComponent = WrapperComponent.options
}
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,
emit: this.$emit,
scopedSlots: this.$scopedSlots
},
slots
)
}
})
vm.$mount(`#${wrapperId}`)
},
props: WrapperComponent.props,
render () {
vm && vm.$forceUpdate()
},
destroyed () {
vm && vm.$destroy()
}
}
}
写了一个defineAsyncVue2Component的函数为了在使用的时候方便。
javascript
import {
defineAsyncComponent,
defineComponent,
onDeactivated,
onUnmounted
} from 'vue'
// 生成随机字母 其实没啥用
import { randomCoding } from '@/tools/tools'
import { vue2ToVue3 } from '@/tools/vue2ToVue3'
// 动态加载模块
export const defineAsyncVue2Component = (componentElm) => {
async function render () {
const component = await componentElm()
const wrapperId = `define_vue_${component.default.name}_${randomCoding(5)}`
const vue2Component = vue2ToVue3(component.default, wrapperId)
return defineComponent({
props: {
...vue2Component.props,
on: {
type: Object,
default: () => ({})
}
},
setup (props) {
onDeactivated(() => {
vue2Component.destroyed()
})
onUnmounted(() => {
vue2Component.destroyed()
})
return () => (
<>
<div id={wrapperId}></div>
<vue2Component {...props} {...props.on}></vue2Component>
</>
)
}
})
}
return defineAsyncComponent(render)
}
在主要代码中使用模块 recruit 是vue2的模块名称
javascript
// @ts-ignore
const clueAdd = defineAsyncVue2Component(() => import('recruit/clueAdd'))
// @ts-ignore
const purposeAdd = defineAsyncVue2Component(() => import('recruit/purposeAdd'))
加载组件时网络 的执行情况,其中前三位就是被共享的模块