Webpack 5模块联邦微前端架构与Module Federation跨应用共享实战

模块联邦微前端架构设计原理

Webpack 5引入的Module Federation(模块联邦)允许多个独立构建的应用在运行时共享模块,实现了真正的微前端运行时集成。与NPM包共享不同,模块联邦不需要在构建时打包共享代码,而是在浏览器运行时动态加载远程模块,各子应用独立开发、独立部署、独立运行,通过HTTP协议暴露和消费模块。

模块联邦有三个核心概念:Host(宿主应用)是消费远程模块的应用;Remote(远程应用)是暴露模块给其他应用消费的应用;Shared(共享依赖)是多个应用之间共享的第三方库(如React、Vue),避免重复加载。一个应用可以同时扮演Host和Remote角色,形成去中心化的微前端网络。

与qiankun等基于iframe或沙箱隔离的微前端方案相比,Module Federation在同一个JavaScript运行时中加载模块,应用间通信通过原生import/export完成,没有跨域和沙箱开销。但这也意味着子应用之间需要保持依赖版本兼容,共享依赖的版本冲突需要通过shared配置解决。

Host宿主应用配置

Host应用通过ModuleFederationPlugin配置远程应用的入口和共享依赖:

// webpack.config.js (Host应用)
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;

module.exports = {
  entry: './src/index.js',
  mode: 'production',
  output: {
    publicPath: 'https://host.yunthe.com/',
  },
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'host_app',
      remotes: {
        // 远程应用配置,名称对应Remote的name字段
        user_center: 'user_center@https://user.yunthe.com/remoteEntry.js',
        order_system: 'order_system@https://order.yunthe.com/remoteEntry.js',
      },
      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        'react-dom': { singleton: true, requiredVersion: deps['react-dom'] },
        'react-router-dom': { singleton: true, requiredVersion: deps['react-router-dom'] },
      },
    }),
  ],
};

remotes字段声明远程应用的入口URL,格式为”应用名@远程入口URL”。remoteEntry.js是Remote应用构建后生成的入口文件,Host通过异步加载该文件获取远程模块的导出内容。shared字段配置共享依赖,singleton: true确保全局只加载一个React实例,避免Hooks失效等问题。requiredVersion指定共享依赖的版本要求,当Host和Remote的版本不兼容时,Webpack会加载各自版本的依赖。

Remote远程应用配置与模块暴露

Remote应用配置暴露的组件和模块:

// webpack.config.js (Remote应用 - 用户中心)
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;

module.exports = {
  entry: './src/index.js',
  mode: 'production',
  output: {
    publicPath: 'https://user.yunthe.com/',
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'user_center',
      filename: 'remoteEntry.js',
      exposes: {
        // 暴露的模块路径 -> 模块实际位置
        './UserProfile': './src/components/UserProfile',
        './UserList': './src/components/UserList',
        './useUserService': './src/hooks/useUserService',
      },
      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        'react-dom': { singleton: true, requiredVersion: deps['react-dom'] },
      },
    }),
  ],
};

exposes字段定义远程应用对外暴露的模块。key是消费者引用的路径(如user_center/UserProfile),value是模块在Remote应用中的实际路径。filename指定生成的远程入口文件名,Host应用通过加载该文件获取exposes配置的映射关系。

Remote应用需要保留一个异步入口边界,避免在入口文件中直接引入远程模块:

// src/index.js (Remote应用入口)
import('./bootstrap').then(({ default: bootstrap }) => bootstrap());

// src/bootstrap.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const root = createRoot(document.getElementById('root'));
root.render();

index.js中使用动态import延迟加载bootstrap.js,确保共享依赖在远程模块加载前完成初始化。如果直接在index.js中引入React,Webpack会在共享依赖解析完成前执行模块代码,导致”Shared module is not available for eager consumption”错误。

Host应用动态加载远程组件

Host应用中使用React.lazy和Suspense动态加载Remote应用暴露的组件:

// src/App.js (Host应用)
import React, { Suspense, lazy } from 'react';

// 动态加载远程组件
const UserProfile = lazy(() => import('user_center/UserProfile'));
const UserList = lazy(() => import('user_center/UserList'));

// 加载远程Hook
const useUserService = lazy(() => import('user_center/useUserService'));

function App() {
  return (
    
加载中...
}> 加载中...
}>
); } export default App;

import(‘user_center/UserProfile’)在运行时向https://user.yunthe.com/remoteEntry.js发起请求,加载并执行远程入口脚本后获取UserProfile组件。Suspense组件在远程模块加载期间显示fallback内容,加载完成后渲染实际组件。首次加载会有网络延迟,后续导航到同一组件时Webpack已缓存模块不再重复请求。

共享依赖版本协商与运行时加载

多个应用共享React等核心库时,Webpack在运行时进行版本协商。当Host应用声明shared: { react: { singleton: true, requiredVersion: “^18.0.0” } },Remote应用也声明相同的共享配置时,Webpack会选择版本最高的满足requiredVersion要求的React实例加载。singleton: true强制全局只加载一个实例,即使版本不完全匹配也只加载一个,避免多个React实例导致的Context失效问题。

// 共享依赖版本协商逻辑
// Host: react ^18.2.0, singleton: true
// Remote A: react ^18.0.0, singleton: true
// Remote B: react ^18.3.0, singleton: true
// 运行时结果: 全局只加载 react 18.3.0 (最高版本)

// 如果版本不兼容:
// Host: react ^17.0.0, singleton: true
// Remote: react ^18.0.0, singleton: true
// 运行时: 加载各自的react版本,singleton被忽略,可能产生两个React实例

生产环境中所有应用应统一共享依赖的大版本号。通过在shared配置中设置eager: false(默认值)实现按需加载,仅当远程模块实际被引用时才加载共享依赖。如果设置eager: true,共享依赖会在入口文件加载时立即初始化,增加首屏加载时间。

模块联邦的部署需要配置正确的CORS策略。Remote应用的remoteEntry.js需要通过CDN或静态服务器提供,HTTP响应头必须包含Access-Control-Allow-Origin允许Host应用域名的跨域请求。Nginx配置示例:

# Nginx配置 (Remote应用静态资源服务)
location / {
    add_header 'Access-Control-Allow-Origin' 'https://host.yunthe.com';
    add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';
    add_header 'Cache-Control' 'public, max-age=31536000';
    try_files $uri $uri/ /index.html;
}

remoteEntry.js文件设置长缓存(1年),通过文件内容hash实现缓存更新。各远程模块的chunk文件以contenthash命名,部署新版本时remoteEntry.js内容更新指向新的chunk文件名,浏览器自动拉取最新代码。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/webpack5-mo-kuai-lian-bang-wei-qian-duan-jia-gou-yu/

(0)
小编小编
上一篇 19小时前
下一篇 19小时前

相关推荐