Webpack Module Federation是Webpack 5引入的一个特性,它支持微前端架构,允许不同的Web应用之间共享模块,而不需要运行时的容器或服务器端的构建步骤。
项目结构
假设有两个独立的React应用:app1和app2,其中app2将通过Module Federation作为远程模块被app1消费。
- app1:主应用
- app2:作为远程微应用
app2配置
首先,在app2中配置Webpack以使其成为可被其他应用消费的远程微应用。
webpack.config.js (app2)
javascript
复制代码
const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); module.exports = { output: { publicPath: 'http://localhost:3002/', // 公共路径 uniqueName: 'app2', // 应用唯一标识 }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', }), new ModuleFederationPlugin({ name: 'app2', // 微应用名称 library: { type: 'var', name: 'app2' }, // 导出方式 filename: 'remoteEntry.js', // 输出文件名 exposes: { './Button': './src/Button', // 暴露的模块 }, }), ], };
app1配置
接下来,在app1中配置Webpack以消费来自app2的模块。
webpack.config.js (app1)
javascript
复制代码
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); module.exports = { output: { publicPath: 'http://localhost:3001/', }, plugins: [ new ModuleFederationPlugin({ name: 'app1', remotes: { app2: 'app2@http://localhost:3002/remoteEntry.js', // 引入app2的远程模块 }, }), ], };
app1中的消费代码
在app1中,你可以像导入本地模块一样直接导入来自app2的模块。
App.js (app1/src)
jsx
复制代码
import React from 'react'; import { Button } from 'app2/Button'; function App() { return ( <div> <h1>Welcome to App1</h1> <Button text="Click me!" /> </div> ); } export default App;
启动应用
分别启动app1和app2,app1会成功地从app2加载并显示Button组件。
ModuleFederationPlugin
:是Webpack的核心插件,用于配置模块联邦。它允许应用声明自己暴露哪些模块给其他应用,以及从哪些远程应用消费模块。exposes
:在app2的配置中,通过exposes字段指定了要暴露给其他应用的模块路径,这里暴露了./src/Button作为一个名为Button的模块。remotes
:在app1的配置中,通过remotes字段定义了远程应用的名称(app2)及其远程Entry文件的URL。这使得app1能够找到并加载app2的模块。- 动态导入:在实际应用中,通常使用动态导入(import()表达式)来异步加载远程模块,以避免阻塞主应用的加载。
在实际的微前端项目中,除了基本的配置外,还需要考虑以下高级特性:
共享库:
- 可以通过Module Federation共享公共库,避免每个应用都包含相同的库副本,从而减小包体积和加载时间。
- 在app1和app2的Webpack配置中,添加共享库配置,如下所示:
javascript
复制代码
shared: { react: { singleton: true, version: '17.0.2' }, 'react-dom': { singleton: true, version: '17.0.2' }, },
懒加载:
通过动态导入远程组件,可以实现按需加载,只在用户需要时才加载微应用,提高页面加载速度。 示例代码:
jsx
复制代码
import React, { lazy, Suspense } from 'react'; const Button = lazy(() => import('app2/Button')); function App() { return ( <div> <h1>Welcome to App1</h1> <Suspense fallback={<div>Loading...</div>}> <Button text="Click me!" /> </Suspense> </div> ); } export default App;
路由集成:
为了实现微应用之间的路由切换,可以使用single-spa
或react-router-dom
的Route组件,配合Microfrontends库来处理路由导航。 示例代码(使用react-router-dom
):
jsx
复制代码
import React from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import App1 from './App1'; import App2 from './App2'; function MainRouter() { return ( <Router> <Switch> <Route path="/app1" component={App1} /> <Route path="/app2" component={App2} /> </Switch> </Router> ); } export default MainRouter;
全局状态管理:
- 要在微应用之间共享状态,可以使用Redux、MobX或Context API。需要确保在每个微应用中使用相同的状态管理解决方案,并通过Module Federation共享状态管理库。
样式隔离:
- 为了避免样式冲突,可以使用CSS Modules或Scope Hoisting,确保每个微应用的样式仅影响其自身。
错误处理:
- 添加全局错误处理,捕捉并处理微应用中可能抛出的错误。
性能优化:
- 使用Webpack的Tree Shaking和Code Splitting进一步减小包大小。
- 使用HTTP/2和Service Worker提高加载速度和离线可用性。
通信和协调:
微应用之间可能需要进行通信,例如传递数据或触发事件。可以使用事件总线、MessageChannel API或自定义钩子函数实现跨应用通信。 示例(使用事件总线):
jsx
复制代码
// 在app1中发布事件 import { eventBus } from './eventBus'; eventBus.emit('customEvent', data); // 在app2中订阅事件 import { eventBus } from './eventBus'; eventBus.on('customEvent', (data) => { console.log('Received data:', data); });
加载和渲染顺序:
确保微应用的加载和渲染顺序,避免依赖于未加载的微应用。可以使用single-spa的bootstrap和mount生命周期钩子进行控制。
服务端渲染(SSR):
如果需要服务端渲染,可以使用single-spa-react-server-rendering或next.js等库,确保微应用在服务器端也能正确渲染。
兼容性和更新策略:
考虑到微应用可能由不同团队和时间点开发,需要制定兼容性和更新策略。例如,使用特定的Webpack版本,或者确保微应用的API兼容性。
测试和部署:
单独测试每个微应用,同时确保整体应用的集成测试。 使用CD/CD(持续部署/持续交付)策略,自动化部署过程,确保微应用的快速迭代和更新。
监控和日志:
实施统一的日志和监控系统,以便跟踪微应用的性能、错误和用户体验。
用户体验:
优化微应用之间的切换体验,如使用骨架屏或过渡动画,减少用户感知的延迟。
安全和权限:
考虑微应用的安全性和权限控制,确保每个微应用只能访问必要的数据和资源。
原文链接:https://juejin.cn/post/7379149008357900297