Module Federation on Node.js:从能用到易用

Module Federation 已经原生支持在 Node.js 环境中使用,无论是作为纯运行时的模块消费者,还是在 Webpack/Rspack 构建流程中,都能轻松集成。本文档将详细介绍在 Node.js 场景下使用 Module Federation 的不同方式。

概览

在 Node.js 服务端应用中,你可以通过 Module Federation 加载远程模块,这些模块可以是以 CommonJS 格式提供的本地文件,也可以是通过 HTTP 访问的远程服务。这为实现服务端的微服务架构、动态功能扩展和资源共享提供了极大的灵活性。

消费者

仅使用运行时 API

如果你在 Node.js 环境中仅作为消费者,并且不希望引入 Webpack/Rspack 等构建工具,那么你可以采用纯运行时的方式。这种方式的核心是无需任何构建插件,仅通过 @module-federation/runtime 提供的 API 即可实现。

操作步骤如下:

  1. 创建 MF 实例:使用 createInstance 方法创建一个 Module Federation 实例。
  2. 注册远程模块:在实例配置中,通过 remotes 数组定义你要消费的远程模块。
  3. 加载远程模块:调用实例方法 loadRemote 来加载并使用模块。

下面是一个示例,展示了如何加载一个通过 HTTP 暴露的远程模块:

import { createInstance } from '@module-federation/enhanced/runtime';

const mf = createInstance({
  name: 'node_host',
  remotes: [
    {
      name: 'node_remote',
      entry: 'http://localhost:3022/mf-manifest.json',
    },
  ],
});

async function handleHttpRequest(req: { query: any }, userId: string) {
  const remote = await mf.loadRemote('node_remote/api-handler');

  const result = await remote({
    method: req.method,
    path: req.path,
    query: req.query,
    body: req.body,
    user: { id: 'u_123' },
  });
  res.status(result.status).json(result.body);
}

使用构建插件 (Rspack/Webpack)

当你的 Node.js 应用本身使用 Webpack 或 Rspack 进行构建时,集成 Module Federation 会更加简单。你只需要在构建配置中添加相应的插件和配置即可。

对于消费者(Host)应用,关键是添加 @module-federation/node/runtimePlugin,并设置 remoteType: 'script'target: 'async-node',再进行相关配置。

以 Rspack 为例,以下是 rspack.config.js 中的核心配置(Webpack 配置基本一致):

rspack.config.js
const {
  ModuleFederationPlugin,
} = require('@module-federation/enhanced/rspack');

module.exports = {
  // ...
  target: 'async-node',
  plugins: [
    new ModuleFederationPlugin({
      name: 'node_host',
      runtimePlugins: [
        require.resolve('@module-federation/node/runtimePlugin'),
      ],
      remoteType: 'script',
      remotes: {
        node_remote: 'node_remote@http://localhost:3022/mf-manifest.json',
      },
    }),
  ],
};

配置完成后,你就可以在代码中直接 import 远程模块了:

main.js
import node_remote_test from 'node_remote/test';

console.log(node_remote_test);

生产者

生产者侧更推荐使用 Rslib 来打包:只需要使用 @module-federation/rsbuild-plugin 插件,并设置 target: 'async-node',即可产出可在 Node.js 中消费的远程模块。

以下是 apps/node-remote/rslib.config.ts 中的关键配置:

rslib.config.ts
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import { defineConfig } from '@rslib/core';
import mfConfig from './module-federation.config.ts';

export default defineConfig({
  plugins: [pluginModuleFederation(mfConfig, { target: 'node' })],
});

如果你的项目没有使用 Rslib,也可以使用 Rspack/Webpack 直接构建(两者配置基本一致)。核心点是:将 target 设为 async-node,并使用 commonjs-module 类型输出 remoteEntry.js

Rspack 示例:

rspack.config.js
const {
  ModuleFederationPlugin,
} = require('@module-federation/enhanced/rspack');

module.exports = {
  target: 'async-node',
  plugins: [
    new ModuleFederationPlugin({
      name: 'node_remote',
      library: { type: 'commonjs-module', name: 'node_remote' },
      filename: 'remoteEntry.js',
      runtimePlugins: [
        require.resolve('@module-federation/node/runtimePlugin'),
      ],
      exposes: {
        './test': './src/expose.js',
      },
    }),
  ],
};

Webpack 配置与上述 Rspack 示例基本一致。

常见问题

1. target: 'async-node' 有什么用?

target: 'async-node' 是 Webpack 的一个构建目标,它告诉 Webpack 将代码编译为适用于 Node.js 环境的异步 CommonJS 模块。这对于 Module Federation 的动态、异步加载能力至关重要,特别是当你需要顶层 await 来处理远程模块时。

2. 为什么需要设置 remoteType: 'script'

目前 MF bundler runtime 仅支持 script 这一种 Remote 加载格式,因此 Node.js 场景下加载生产者需要显式设置 remoteType: 'script'

参考链接