背景
我们的很多web应用在持续迭代中功能越来越复杂,参与的人员、团队不断增多,导致项目出现难以维护的问题,这种情况PC端尤其常见,微前端为我们提供了一种高效管理复杂应用的方案。但是在使用微前端的过程中,通常会有一些公共方法或公共组件,本文将对如何实现父子应用以及兄弟应用之间进行方法及组件共享提出几种解决方案以及其各自优缺点及适用场景
模块联邦(Module Federation)
webpack5引入了模块联邦,可以让不同的应用共享模块。具体步骤如下:
Remote(提供者模块)
// webpack.config.js
module.exports = {
// 其他配置...
plugins: [
new ModuleFederationPlugin({
name: 'component_app',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button.jsx',
'./Dialog': './src/Dialog.jsx',
'./Logo': './src/Logo.jsx',
'./ToolTip': './src/ToolTip.jsx',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
}),
],
};
作为提供方,component 将自己的 Button、Dialog等组件暴露出去,同时将 react 和 react-dom 这两个依赖共享出去。
host(使用者模块)
// webpack.config.js
module.exports = {
entry: './index.js',
// ...
plugins: [
new ModuleFederationPlugin({
name: 'main_app',
//远程访问地址入口
remotes: {
'component-app': 'component_app@http://localhost:3001/remoteEntry.js',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
})
],
};
作为消费者的 main 应用需要定义需要消费的应用的名称以及地址,同时 main 应用也将自己的 react 和 react-dom 这两个依赖共享出去。
通过以上方式可以实现不同应用之间共享公共方法和组件,虽然vite本身不支持模块联邦,但是我们可以使用vite-plugin-federation这个插件。
优点
- 代码共享:不同的微应用可以共享相同的依赖库,减少重复下载和加载,提高性能。
- 独立部署:各个微应用可以独立开发、测试和部署,减少了团队之间的耦合,提高了开发效率。
- 动态加载:可以在运行时动态加载模块,按需加载,减少初始加载时间。
- 版本控制:支持不同版本的模块共存,解决了版本冲突的问题。
- 团队协作:各个团队可以独立开发自己的模块,减少了对中央仓库的依赖,提高了协作效率。
缺点
- 对于webpack5以下的应用不支持
- 复杂性增加:配置和管理模块联邦需要一定的学习成本和经验,增加了项目的复杂性。
- 调试困难:由于模块是动态加载的,调试和追踪问题可能会变得更加困难。
- 性能开销:虽然减少了初始加载时间,但动态加载模块可能会在运行时引入额外的性能开销。
- 依赖管理:需要仔细管理共享依赖的版本,避免潜在的兼容性问题。
- 安全性:动态加载外部模块可能会引入安全风险,需要额外的安全措施来防止恶意代码注入。
NPM包
将共享组件打包成一个 NPM 包,然后在父子应用中分别安装和使用。
创建共享组件/方法库
import React from 'react'; const SharedComponent = () => <div>Shared Component</div>; export default SharedComponent;
发布到NPM
npm publish
在父应用和子应用中安装
npm install shared-component-library
在应用中使用
import SharedComponent from 'shared-component-library'; function App() { return ; } export default App;
优点
- 模块化管理:通过npm包管理公共组件,可以实现模块化开发,便于维护和更新。
- 版本控制:npm包自带版本控制功能,可以方便地进行版本管理,确保不同微应用使用兼容的组件版本。
- 依赖管理:npm包可以自动管理依赖关系,减少手动处理依赖的复杂性。
- 复用性高:公共组件封装成npm包后,可以在多个微应用中复用,减少重复开发,提高开发效率。
- 社区支持:npm生态系统庞大,很多问题可以通过社区资源解决,减少开发难度。
缺点
- 发布和更新成本:每次更新公共组件都需要重新发布npm包,并在各个微应用中更新依赖,增加了一定的工作量。
- 版本兼容性问题:不同微应用可能对同一组件有不同的版本需求,处理版本兼容性问题可能会比较复杂。
- 性能开销:由于npm包需要通过网络下载和安装,可能会增加构建时间和初始加载时间。
- 调试复杂性:npm包作为独立模块,调试时可能需要额外的配置和步骤,增加了调试的复杂性。
- 安全性问题:使用第三方npm包时,需要注意其安全性,防止引入潜在的安全漏洞。
使用 CDN
将共享组件打包并上传到 CDN,然后在父子应用中通过 URL 引入。
打包共享组件:
npm run build
上传到 CDN。
在应用中引入:
<script src="https://cdn.example.com/shared-component.js"></script> // 假设共享组件暴露为全局变量 SharedComponent function App() { return <SharedComponent />; } export default App;
优点
性能提升:
- 快速加载:CDN 服务器分布在全球各地,用户可以从最近的服务器获取资源,从而减少加载时间。
- 缓存机制:CDN 提供了强大的缓存机制,可以减少服务器的负载,提高响应速度。
带宽优化:
- 减轻服务器压力:通过将静态资源托管在 CDN 上,可以减轻原始服务器的带宽压力。
- 分布式传输:CDN 可以将流量分散到多个节点,避免单点瓶颈。
高可用性:
- 冗余备份:CDN 通常有多个备份节点,即使某个节点出现故障,其他节点也能继续提供服务。
- 自动故障切换:CDN 能够自动检测并切换到可用的节点,确保服务的连续性。
版本管理:
- 统一管理:通过 CDN 可以统一管理公共组件的版本,确保所有微应用使用相同的组件版本,减少兼容性问题。
缺点
安全性问题:依赖第三方:使用第三方 CDN 服务可能会带来安全隐患,特别是如果 CDN 提供商遭到攻击或出现故障。
缓存一致性:
- 缓存更新延迟:CDN 的缓存机制可能导致更新的资源不能及时传播到所有节点,造成版本不一致的问题。
- 缓存失效:需要手动管理缓存失效策略,否则可能会导致旧版本资源被长期缓存。
依赖性:
- 网络依赖:如果用户的网络环境较差,可能会影响到通过 CDN 获取资源的速度和稳定性。
- 服务依赖:过度依赖 CDN 可能会导致在 CDN 服务出现问题时,整个应用的可用性受到影响。
使用 iframe
如果组件之间的交互较少,可以考虑使用 iframe 嵌入子应用。
在父应用中嵌入 iframe:
<iframe src="http://localhost:3001" width="100%" height="500px"></iframe>
优点
- 隔离性强:iframe 提供了一个独立的浏览上下文,能够有效隔离不同微应用之间的样式和脚本,避免相互干扰。
- 安全性高:由于 iframe 的隔离特性,能够防止跨站脚本攻击(XSS)和其他安全问题。
- 兼容性好:iframe 是浏览器原生支持的技术,兼容性较好,能够在大多数现代浏览器中正常工作。
- 独立部署:各个微应用可以独立部署和更新,不需要担心对其他应用的影响。
缺点
- 性能问题:iframe 会增加页面的加载时间和内存消耗,尤其是在嵌套多个 iframe 的情况下。
- 通信复杂:iframe 与父页面之间的通信需要通过 postMessage 等机制实现,增加了开发的复杂性。
- SEO 不友好:搜索引擎对 iframe 内容的抓取和索引支持较差,可能影响 SEO 效果。
- 样式和布局问题:iframe 的内容与父页面的样式和布局是独立的,可能需要额外的工作来确保一致性。
- 调试困难:iframe 内部的调试相对复杂,尤其是在跨域的情况下,调试工具的使用会受到限制。
总结
不同的方案有各自的特点,需要根据具体的应用场景和需求进行权衡。