Webpack 5模块联邦微前端架构:跨应用共享依赖与独立部署实战

模块联邦架构设计与微前端场景分析

Webpack 5引入的模块联邦(Module Federation)能力允许多个独立构建的应用在运行时共享模块,无需重新打包即可加载远程组件。与single-spa等微前端方案相比,模块联邦不需要额外的框架依赖,直接利用Webpack原生能力实现跨应用模块共享。核心场景包括:多个独立团队开发的前端应用需要共享公共组件库、大型单体前端应用拆分为可独立部署的子应用、不同技术栈应用间的组件复用。模块联邦解决了npm包发版滞后和运行时按需加载的问题。

Host应用配置与远程模块加载

Host应用是消费远程模块的一方。通过ModuleFederationPlugin配置需要远程加载的模块及其来源。

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

module.exports = {
  entry: './src/index.ts',
  mode: 'production',
  output: {
    publicPath: 'auto',
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js'
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx']
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'host_app',
      remotes: {
        // 远程模块配置
        remote_dashboard: 'dashboard_app@https://dashboard.example.com/remoteEntry.js',
        remote_settings: 'settings_app@https://settings.example.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'] }
      }
    })
  ],
  devServer: {
    port: 3000,
    historyApiFallback: true
  }
};

在应用入口文件中,需要动态加载远程模块的入口文件,并等待共享依赖初始化完成后再渲染应用:

// host/src/index.ts
import('./bootstrap');

// host/src/bootstrap.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

const root = createRoot(document.getElementById('root')!);
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

入口文件拆分为index.ts和bootstrap.tsx是模块联邦的必要模式。Webpack需要先加载远程模块的依赖映射,再执行应用代码,import(‘./bootstrap’)确保了这一加载顺序。

Remote应用配置与模块导出

Remote应用是提供模块的一方。配置exposes字段声明哪些模块对外暴露:

// remote-dashboard/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;

module.exports = {
  output: {
    publicPath: 'auto',
    filename: '[name].[contenthash].js'
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'dashboard_app',
      filename: 'remoteEntry.js',
      exposes: {
        './Dashboard': './src/components/Dashboard',
        './ChartWidget': './src/components/ChartWidget',
        './DataPanel': './src/components/DataPanel'
      },
      shared: {
        react: { singleton: true, requiredVersion: deps.react },
        'react-dom': { singleton: true, requiredVersion: deps['react-dom'] }
      }
    })
  ]
};

在Host应用中使用React.lazy加载远程组件:

// host/src/App.tsx
import React, { Suspense, lazy } from 'react';

const Dashboard = lazy(() => import('remote_dashboard/Dashboard'));
const ChartWidget = lazy(() => import('remote_dashboard/ChartWidget'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>加载中...</div>}>
        <Dashboard />
      </Suspense>
      <Suspense fallback={<div>加载图表...</div>}>
        <ChartWidget />
      </Suspense>
    </div>
  );
}

export default App;

共享依赖版本协商与单例模式

shared配置是模块联邦的关键机制。singleton: true确保React等核心库在整个应用中只加载一个实例,避免Hooks失效和状态隔离问题。requiredVersion指定所需版本范围,Webpack在运行时协商使用满足所有应用需求的版本。

shared: {
  react: {
    singleton: true,
    requiredVersion: '^18.0.0',
    eager: false  // 非急切加载,运行时按需
  },
  'react-dom': {
    singleton: true,
    requiredVersion: '^18.0.0'
  },
  'react-router-dom': {
    singleton: true,
    requiredVersion: '^6.0.0'
  },
  // 非单例共享,允许不同版本共存
  lodash: {
    singleton: false,
    requiredVersion: '^4.0.0'
  },
  // 自定义共享库
  '@company/ui-components': {
    singleton: true,
    requiredVersion: '^2.0.0',
    import: false  // 仅共享不导入
  }
}

eager: true会将共享依赖打入主bundle,适用于首屏关键依赖。import: false表示该模块只声明共享但不引入,适合公共库不需要在当前应用中直接使用的情况。

路由级懒加载与独立部署方案

模块联邦天然支持路由级代码分割。配合React Router的动态路由配置,实现子应用按路由懒加载:

// host/src/routes.tsx
import { lazy } from 'react';

const routes = [
  {
    path: '/dashboard',
    element: lazy(() => import('remote_dashboard/Dashboard'))
  },
  {
    path: '/settings',
    element: lazy(() => import('remote_settings/SettingsPanel'))
  },
  {
    path: '/analytics',
    element: lazy(() => import('remote_dashboard/DataPanel'))
  }
];

每个Remote应用独立部署到各自的CDN路径,remoteEntry.js文件作为模块入口。部署时只需更新remoteEntry.js及其关联chunk文件,Host应用无需重新构建。版本管理策略:Remote应用在publicPath中使用版本号路径(如/v2.1.0/),实现灰度发布和快速回滚。

跨应用通信与状态共享方案

模块联邦不提供内置的状态共享机制。常用的跨应用通信方案有三种:基于CustomEvent的发布订阅、基于共享模块的Store注入、基于URL参数的状态传递。

// shared/event-bus.ts(作为shared模块被所有应用共享)
type EventHandler = (data: any) => void;

class EventBus {
  private handlers = new Map<string, Set<EventHandler>>();

  on(event: string, handler: EventHandler) {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, new Set());
    }
    this.handlers.get(event)!.add(handler);
    return () => this.handlers.get(event)?.delete(handler);
  }

  emit(event: string, data: any) {
    this.handlers.get(event)?.forEach(h => h(data));
  }
}

export const eventBus = new EventBus();

// Host应用发送事件
import { eventBus } from 'shared/event-bus';
eventBus.emit('user-login', { userId: '123', token: 'xxx' });

// Remote应用监听事件
import { eventBus } from 'shared/event-bus';
eventBus.on('user-login', (data) => {
  console.log('用户已登录:', data.userId);
});

eventBus作为shared模块被所有应用共享同一实例,实现跨应用通信。对于复杂状态管理场景,可以将Zustand或Redux store作为shared模块导出,Host应用创建store实例,Remote应用通过shared依赖获取同一store引用。生产环境构建时需要确保shared模块的版本一致性,避免因版本差异导致实例隔离。

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

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

相关推荐