- 一、引用
- 1.1、npm包
- 1.2、微前端
- 1.3、其它方案
- 二、模块联邦(module Federation)
- 三、实践
- 3.1、REMOTE1配置
- 3.2、HOST宿主配置
- 3.3、跨技术栈
- 3.3.1、REMOTE2配置
- 四、依赖共享
- 五、总结
在日常开发中,我们经常会有一种感觉,这个组件我在其它项目改了,又要copy一次?在协同开发的过程中磕磕碰碰,皆为了结果尽善尽美,手段无所不用其极,CV大法更是用的出神入化,后面发现副作用极其明显,项目杂乱、代码冗余等问题,尽管有相应的规范也难以约束人性,我们无时无刻不想把项目拆分、把模块抽离。
面对这个问题,我们通常会采用以下一些方案解决:
1.1、npm包
通常使用的将需要公共模块抽离通过npm发布,多系统引用实现模块共享,这也是常见的一种方案,但是都需要建立在模块比较稳定,不易经常改动,否则一旦模块改动需要去手动更新依赖,成本高还容易遗漏。
先看看网上十分火的一张图
微前端就是构建一个现代 Web 应用所需要的技术、策略和方法,并具备多个团队独立开发、部署的特性;微前端并不是一种新的技术,而是为了提高大型复杂应用的多团队协作,提升项目的可维护性、拓展性和灵活性,研究出的一种技术策略和方法,它是一种宏观上的架构模式;现在社区也有一些开源活跃方案如乾坤
、microapp
等,各有优缺点。
引用一位大佬说的:
@kuitos-乾坤
:
满足以下几点,你可能就不需要微前端
- 系统里的所有组件都是由一个小的团队开发,并有足够动力去治理、改造这个系统中的所有组件
- 系统及组织架构上,各部件之间本身就是强耦合、自洽、不可分离
- 极高的产品体验要求,对任何产品交互上的不一致零容忍
满足以下几点,你可能就不需要微前端
- 系统里的所有组件都是由一个小的团队开发,并有足够动力去治理、改造这个系统中的所有组件
- 系统及组织架构上,各部件之间本身就是强耦合、自洽、不可分离
- 极高的产品体验要求,对任何产品交互上的不一致零容忍
除此,还要考虑入侵程度、带来的性价比、共享公共依赖等因素。
1.3、其它方案- 打包UMD格式发布cdn
- 打包为ESmodule模块,采用systemjs加载模块
- …
本文要介绍的重点是webpack5出行的module Federation模块联邦。
二、模块联邦(module Federation)
@webpack官方
:
多个单独的构建应该形成一个应用程序。这些单独的构建之间不应该有依赖关系,因此它们可以单独开发和部署;这通常被称为微前端,但不限于此。
我们可以用module Federation实现去中心话的应用部署群,应用之前模块共享,如下:
上图所示,一个系统既可以作为模块的提供方也可以作为模块的消费者,公用模块我们不需要抽离,只需要通过配置将模块暴露给消费者即可,如此,就可以借助module Federation在一定程度上实现微前端。
三、实践新建两个vue项目
- HOST本地宿主系统,作为消费者
- REMOTE1远程项目,作为提供者
当前配置为提供方,暴露出的模块供消费者使用:
vue.config.js
const {ModuleFederationPlugin} = require('webpack').container;
const pkg = require('./package.json')
let publicPath = 'auto'
if (process.env.NODE_ENV === 'production') {
publicPath = 'http://localhost:10000'
}
const config = {
publicPath,
devServer: {
port: '10000',
headers: {}
},
configureWebpack: {
optimization: {
splitChunks: false,
},
plugins: [
new ModuleFederationPlugin({
name: 'vue2Project',
filename: 'remoteProvide.js',
exposes: {
'./HelloWorld': './src/components/HelloWorld',
'./ElementUI': './src/element.js'
},
shared: {
...pkg.dependencies,
vue: {
singleton: true,
requiredVersion: pkg.dependencies.vue,
eager: true
}
}
})
]
}
}
module.exports = config
说明:exposes、filename、name都供消费者使用
- name声明当前联邦模块的名称
vue2Project
- 以filename声明的文件名生成
remoteProvide.js
- exposes暴露了项目中
HelloWorld组件
和项目的ElementUI组件库
当前配置为消费者,获取REMOTE1暴露出的组件
vue.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
configureWebpack: {
optimization: {
splitChunks: false,
},
plugins: [
new ModuleFederationPlugin({
name: 'host',
filename: 'hello-world.js',
remotes: {
'vue2Project': 'vue2Project@http://localhost:10000/remoteProvide.js',
},
shared: {
vue: {
eager: true,
import: "vue",
shareKey: "vue",
shareScope: "default",
singleton: true
}
}
})
]
}
}
说明:
- 只需要关注remotes,形势如:
[hostName]:[remoteName]@[remoteAddr]
hostName:本地需要调用的别名,随意命名
remoteName:远程项目的name,如remote1的vue2Project
remoteAddr:远程项目的地址,如remote1的remoteProvide.js
使用组件:
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld />
<el-button type="primary">test</el-button>
</div>
</template>
<script>
import HelloWorld from 'vue2Project/HelloWorld'
import {ElementUI} from 'vue2Project/ElementUI'
export default {
name: 'App',
components: {
HelloWorld,
[ElementUI.Button.name]: ElementUI.Button
}
}
</script>
先看下暴露出来的是个啥?
说明:REMOTE1暴露的组件已经全部拿到了,标准的vue组件,我们正常引入即可
- 引入
HelloWorld
和ElementUI的Button
组件,格式按hostName/exposes.[key]
形势引入
呈现效果:
实时修改remote1的HelloWorld
组件代码之后,呈现效果:
已经可以实现了组件共享,由REMOTE1提供的组件在HOST宿主系统使用,并且修改了组件可以做到实时更新。
有时我们希望能够接入其它的技术栈的组件,以react
为例,create-react-app
脚手架新建项目
- REMOTE2远程项目,作为提供者
webpack.config.js增加配置提供消费者使用的组件
plugins: [
new webpack.container.ModuleFederationPlugin({
name: 'react2Project',
// library: { type: 'var', name: 'vue2Project' },
filename: 'remoteProvide.js',
exposes: {
'./App': './src/App.js',
}
}),
......其它配置,
]
说明:
- 暴露当前的App.js组件提供消费者使用
在HOST宿主系统中增加配置,vue.config.js增加remotes配置react2Project:
remotes: {
'vue2Project': 'vue2Project@http://localhost:10000/remoteProvide.js',
'react2Project': 'react2Project@http://localhost:3000/remoteProvide.js',
},
在组件中使用
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld />
<el-button type="primary">test</el-button>
<div id="reactApp"></div>
</div>
</template>
<script>
import HelloWorld from 'vue2Project/HelloWorld'
import {ElementUI} from 'vue2Project/ElementUI'
import ReactApp from 'react2Project/App'
// eslint-disable-next-line no-unused-vars
import React from 'react'
import { createRoot } from 'react-dom/client';
export default {
name: 'App',
mounted() {
const container = document.getElementById('reactApp');
const root = createRoot(container);
root.render(<ReactApp/>);
},
components: {
HelloWorld,
[ElementUI.Button.name]: ElementUI.Button
}
}
</script>
说明:
- vue中
mounted
钩子触发才确定能拿到reactApp的dom
,所以react
代码需要写在挂载完成钩子中。 - 依赖
react
和react-dom
,需要安装 - 依赖react的jsx语法,需要安装react的jsx插件
@babel/plugin-transform-react-jsx
,同时配置babel插件plugins: ["@babel/plugin-transform-react-jsx"]
呈现效果:
在使用跨技术栈接入需要做相应的兼容才能实现,现在也有一些三方库做了兼容方案,比如vuera…等
在如上的实践之后,有时候也希望一次只运行一个库实例,解决多版本导致的兼容问题等,module Federation提供了方案,如下在宿主系统和远程系统配置:
new ModuleFederationPlugin({
// 其它配置...,
shared: {
...pkg.dependencies,
vue: {
requiredVersion: pkg.dependencies.vue,
eager: true
}
}
})
以上我配置了生产环境所有的依赖,如果在宿主系统中有配置相关依赖,就可根据配置做到共享依赖,webpack还提供了其它共享依赖更详细的配置,详见shared配置
demo代码地址
五、总结- module Federation可以直接使用,为更大型的前端应用提供了开箱解决方案
- 需要升级为webpack5
- 跨技术栈需要做兼容技术方案
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)