本章介绍如何在 Rslib 中构建 模块联邦 产物。
模块联邦有一些典型的使用场景,包括:
模块联邦可以帮助你:
首先安装 Module Federation Rsbuild Plugin.
npm add @module-federation/rsbuild-plugin -D然后在 rslib.config.ts 中注册插件:
import { const pluginModuleFederation: (moduleFederationOptions: ModuleFederationOptions, rsbuildOptions?: RSBUILD_PLUGIN_OPTIONS) => RsbuildPlugin pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import { const pluginReact: (options?: PluginReactOptions) => RsbuildPlugin pluginReact } from '@rsbuild/plugin-react';
import { function defineConfig(config: RslibConfig): RslibConfig (+3 overloads)This function helps you to autocomplete configuration types.
It accepts a Rslib config object, or a function that returns a config.
defineConfig } from '@rslib/core';
export default function defineConfig(config: RslibConfig): RslibConfig (+3 overloads)This function helps you to autocomplete configuration types.
It accepts a Rslib config object, or a function that returns a config.
defineConfig ({
RslibConfig.lib: LibConfig[] lib : [
// ... 其他 format
{
LibConfig.format?: Format | undefinedOutput format for the generated JavaScript files.
format : 'mf',
LibConfig.output?: RslibOutputConfig | undefinedOptions for build outputs.
output : {
OutputConfig.distPath?: string | DistPathConfig | undefinedSet the directory of the output files.
Rsbuild will emit files to the specified subdirectory according to the file type.
string: Set the root output directory to a specific path, equivalent to distPath.root.
object: Set the output directory for each file type.
distPath : './dist/mf',
// production 时, 在这里使用线上 assetPrefix
OutputConfig.assetPrefix?: LiteralUnion<"auto", string> | undefinedWhen using CDN in the production,
you can use this option to set the URL prefix of static assets,
similar to the output.publicPath config of Rspack.
auto means use a relative path based on file location.
assetPrefix : 'http://localhost:3001/mf',
},
// Storybook 在 dev 下使用
EnvironmentConfig.dev?: Pick<DevConfig, AllowedEnvironmentDevKeys> | undefinedOptions for local development.
dev : {
assetPrefix?: boolean | LiteralUnion<"auto", string> | undefinedSet the URL prefix of static assets in development mode,
similar to the output.publicPath
config of Rspack.
assetPrefix : 'http://localhost:3001/mf',
},
EnvironmentConfig.plugins?: RsbuildPlugins | undefinedConfigure Rsbuild plugins.
plugins : [
function pluginModuleFederation(moduleFederationOptions: ModuleFederationOptions, rsbuildOptions?: RSBUILD_PLUGIN_OPTIONS): RsbuildPlugin pluginModuleFederation (
{
ModuleFederationPluginOptions.name?: string | undefinedThe name of the container.
name : 'rslib_provider',
ModuleFederationPluginOptions.exposes?: Exposes | undefinedModules that should be exposed by this container. When provided, property name is used as public name, otherwise public name is automatically inferred from request.
exposes : {
// 这里添加 expose
},
// 此处无法添加 "remote",因为你可能会在一次构建中构建 "esm" 或 "cjs" 产物。
// 如果你希望 Rslib 包使用远程模块,请参考下面。
ModuleFederationPluginOptions.shared?: Shared | undefinedModules that should be shared in the share scope. When provided, property names are used to match requested modules in this compilation.
shared : {
react: {
singleton: true;
}
react : {
SharedConfig.singleton?: boolean | undefinedAllow only a single version of the shared module in share scope (disabled by default).
singleton : true,
},
'react-dom': {
SharedConfig.singleton?: boolean | undefinedAllow only a single version of the shared module in share scope (disabled by default).
singleton : true,
},
},
},
{},
),
],
},
],
// Storybook 在 dev 下使用
RsbuildConfig.server?: ServerConfig | undefinedOptions for the Rsbuild server,
will take effect during local development and preview.
server : {
ServerConfig.port?: number | undefinedSpecify a port number for Rsbuild server to listen.
port : 3001,
},
RslibConfig.output?: RslibOutputConfig | undefinedOptions for build outputs.
output : {
RslibOutputConfig.target?: RsbuildTarget | undefinedSetting the build target for Rsbuild.
target : 'web',
},
EnvironmentConfig.plugins?: RsbuildPlugins | undefinedConfigure Rsbuild plugins.
plugins : [function pluginReact(options?: PluginReactOptions): RsbuildPlugin pluginReact ()],
});这样,我们就完成了对 Rslib Module 生产者的集成。构建完成后,我们可以看到产物中已经添加了 mf 目录,消费者可以直接消费这个包。
在上面的例子中,我们添加了一个新的 format: 'mf' ,它将添加一个额外的模块联邦产物,同时还配置了 cjs 和 esm 的格式,它们是不冲突的。
但是,如果你希望此 Rslib 模块同时消费其他生产者,请不要使用构建配置 remote 参数,因为在其他格式下,这可能会导致错误,请参考下面使用 Module Federation 运行时的示例。
Rslib 支持宿主应用和 Rslib 模块联邦项目同时开发。
rslib mf-dev 命令添加 dev 命令在 package.json 文件:
{
"scripts": {
"dev": "rslib mf-dev"
}
}然后运行 dev 命令即可启动模块联邦开发模式,可被宿主应用消费,
同时具有模块热更新(HMR)功能。
npm run dev设置宿主应用消费 Rslib 的模块联邦库。查看 @module-federation/rsbuild-plugin 获取更多信息。
import { const pluginModuleFederation: (moduleFederationOptions: ModuleFederationOptions, rsbuildOptions?: RSBUILD_PLUGIN_OPTIONS) => RsbuildPlugin pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import { function defineConfig(config: RsbuildConfig): RsbuildConfig (+3 overloads)This function helps you to autocomplete configuration types.
It accepts a Rsbuild config object, or a function that returns a config.
defineConfig } from '@rsbuild/core';
import { const pluginReact: (options?: PluginReactOptions) => RsbuildPlugin pluginReact } from '@rsbuild/plugin-react';
export default function defineConfig(config: RsbuildConfig): RsbuildConfig (+3 overloads)This function helps you to autocomplete configuration types.
It accepts a Rsbuild config object, or a function that returns a config.
defineConfig ({
EnvironmentConfig.plugins?: RsbuildPlugins | undefinedConfigure Rsbuild plugins.
plugins : [
function pluginReact(options?: PluginReactOptions): RsbuildPlugin pluginReact (),
function pluginModuleFederation(moduleFederationOptions: ModuleFederationOptions, rsbuildOptions?: RSBUILD_PLUGIN_OPTIONS): RsbuildPlugin pluginModuleFederation (
{
ModuleFederationPluginOptions.name?: string | undefinedThe name of the container.
name : 'rsbuild_host',
ModuleFederationPluginOptions.remotes?: Remotes | undefinedContainer locations and request scopes from which modules should be resolved and loaded at runtime. When provided, property name is used as request scope, otherwise request scope is automatically inferred from container location.
remotes : {
rslib: string rslib : 'rslib@http://localhost:3001/mf/mf-manifest.json',
},
ModuleFederationPluginOptions.shared?: Shared | undefinedModules that should be shared in the share scope. When provided, property names are used to match requested modules in this compilation.
shared : {
react: {
singleton: true;
}
react : {
SharedConfig.singleton?: boolean | undefinedAllow only a single version of the shared module in share scope (disabled by default).
singleton : true,
},
'react-dom': {
SharedConfig.singleton?: boolean | undefinedAllow only a single version of the shared module in share scope (disabled by default).
singleton : true,
},
},
// 开启这个当 Rslib 产物为 'production' 模式, 但是宿主应用是 'development' 模式。
// 参考链接: https://rslib.rs/guide/advanced/module-federation#faqs
ModuleFederationPluginOptions.shareStrategy?: SharedStrategy | undefinedload shared strategy(defaults to 'version-first').
shareStrategy : 'loaded-first',
},
{},
),
],
});然后通过 rsbuild dev 启动宿主应用。
Rslib 支持使用 Storybook 开发 Rslib 模块联邦项目。
rslib mf-dev 命令添加 dev 命令在 package.json 文件:
{
"scripts": {
"dev": "rslib mf-dev"
}
}然后运行 dev 命令即可启动模块联邦开发模式,可被 Storybook 消费,
同时具有模块热更新(HMR)功能。
npm run dev首先,在 Rslib 项目中配置 Storybook。你可以参考 Storybook 章节来了解如何执行此操作。在本章中,我们将使用 React 框架作为示例。
安装以下 Storybook addon,让 Storybook 与 Rslib 模块联邦一起使用:
npm add storybook-addon-rslib @module-federation/storybook-addon -D然后创建 Storybook 配置文件 .storybook/main.ts,指定 stories 和 addons,并设置 framework 和相应的 framework 集成。
import { dirname, join } from 'node:path';
import type { StorybookConfig } from 'storybook-react-rsbuild';
function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, 'package.json')));
}
const config: StorybookConfig = {
stories: [
'../stories/**/*.mdx',
'../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)',
],
framework: {
name: getAbsolutePath('storybook-react-rsbuild'),
options: {},
},
addons: [
{
name: getAbsolutePath('storybook-addon-rslib'),
options: {
rslib: {
include: ['**/stories/**'],
},
},
},
{
name: '@module-federation/storybook-addon/preset',
options: {
// 在添加 rslib module manifest 给 storybook dev
// 我们在上面已经设置了 dev.assetPrefix 和 server.port 到 3001 在 rslib.config.ts
remotes: {
'rslib-module':
//还可以在这里添加 storybook 的 shared
// shared: {}
'rslib-module@http://localhost:3001/mf/mf-manifest.json',
},
},
},
],
};
export default config;从远程模块引入组件
import React from 'react';
// 在这里加载远程模块,Storybook 相当于宿主应用.
import { Counter } from 'rslib-module';
const Component = () => <Counter />;
export default {
title: 'App Component',
component: Component,
};
export const Primary = {};tsconfig.json 中添加模块联邦类型和 stories 文件{
"compilerOptions": {
// ...
"paths": {
"*": ["./@mf-types/*"]
}
},
"include": ["src/**/*", ".storybook/**/*", "stories/**/*"]
}大功告成,启动 Storybook npx storybook dev。
由于 Rslib 中有多种格式,如果在构建时配置 remote 参数来消耗其他模块,则可能无法在所有格式下正常工作。建议通过以下方式访问 Module Federation Runtime
首先安装运行时依赖
npm add @module-federation/enhanced -D然后在运行时使用其他模块联邦模块,例如
import { init, loadRemote } from '@module-federation/enhanced/runtime';
import { Suspense, createElement, lazy } from 'react';
init({
name: 'rslib_provider',
remotes: [
{
name: 'mf_remote',
entry: 'http://localhost:3002/mf-manifest.json',
},
],
});
export const Counter: React.FC = () => {
return (
<div>
<Suspense fallback={<div>loading</div>}>
{createElement(
lazy(
() =>
loadRemote('mf_remote') as Promise<{
default: React.FC;
}>,
),
)}
</Suspense>
</div>
);
};这确保了模块可以按预期以多种格式加载。
如果 Rslib 生产者是用 build 构建的, 这意味着生产者中的 process.env.NODE_ENV 是 production 。如果这时消费者是使用的开发模式启动,由于模块联邦默认使用共享的加载策略,可能会有 react 和 react-dom 加载模式不一致的问题 (比如 react 在 development mode, react-dom 在 production mode)。
你可以在消费者设置 shareStrategy 来解决这个问题,这需要你确保已经完全理解了这个配置。
pluginModuleFederation({
// ...
shareStrategy: 'loaded-first',
}, {}),如果你希望 Rslib 生产者的模块联邦产物生成 ES Modules 的导出,可以额外配置如下:
export default defineConfig({
lib: [
{
format: 'mf',
// ...
tools: {
rspack(config) {
config.experiments = {
outputModule: true,
};
},
},
},
],
});你可以在 Rslib 模块联邦示例 中查看相关项目。
mf-host: Rsbuild App 消费者mf-react-component: Rslib Module, 同时是消费者和生产者, 作为生产者向 mf-host 提供模块, 并消费 mf-remotemf-remote: Rsbuild App 生产者