开始使用
在 Modern.js 中使用 Module Federation 我们推荐使用官方插件 @module-federation/modern-js
。
本章节将会介绍如何通过官方插件搭含生产者应用与消费者应用,我们首先根据 Modern.js 快速上手 创建两个应用。
安装插件
完成应用创建后,我们分别为两个项目安装插件:
npm add @module-federation/modern-js
注册插件
安装插件后,你需要在 modern.config.js
中注册插件:
import { appTools, defineConfig } from '@modern-js/app-tools';
import { moduleFederationPlugin } from '@module-federation/modern-js';
export default defineConfig({
runtime: {
router: true,
},
plugins: [
appTools({
bundler: 'rspack',
}),
moduleFederationPlugin(),
],
});
生产者导出模块
接下来,我们先修改生产者的代码,导出 Module Federation 模块。
我们创建 src/components/Button.tsx
文件,导出一个 Button 组件:
src/components/Button.tsx
import React from 'react';
export const Button = () => {
return <button type="button">Remote Button</button>;
};
随后,在项目根目录添加 module-federation.config.ts
,配置 Module Federation 模块的名称、共享依赖和导出内容:
module-federation.config.ts
import { createModuleFederationConfig } from '@module-federation/modern-js';
export default createModuleFederationConfig({
name: 'remote',
manifest: {
filePath: 'static',
},
filename: 'static/remoteEntry.js',
exposes: {
'./Button': './src/components/Button.tsx',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
});
TIP
在上述代码块中,我们为 Module Federation 导出的 manifest 和 remoteEntry.js 都设置了 static
前缀,这是因为 Modern.js 要求将所有需要暴露的资源都放在 static/
目录下,Modern.js 的服务器在生产环境时也只会托管 static/
目录。
另外,我们还需要修改 modern.config.ts
,为生产者提供一个开发环境的端口,让消费者可以通过此端口访问生产者的资源:
modern.config.ts
import { appTools, defineConfig } from '@modern-js/app-tools';
import { moduleFederationPlugin } from '@module-federation/modern-js';
export default defineConfig({
dev: {
port: 3051,
},
runtime: {
router: true,
},
plugins: [
appTools({
bundler: 'rspack',
}),
moduleFederationPlugin(),
],
});
消费者使用模块
现在,我们修改消费者的代码,使用生产者导出的模块。
在项目根目录添加 module-federation.config.ts
,配置 Module Federation 模块的名称、共享依赖和使用的远程模块:
module-federation.config.ts
import { createModuleFederationConfig } from '@module-federation/modern-js';
export default createModuleFederationConfig({
name: 'host',
remotes: {
remote: 'remote@http://localhost:3051/static/mf-manifest.json',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
});
mf-manifest.json
是生产者在打包后产出的文件,包含了生产者导出的所有模块信息。
我们创建新的路由文件 src/routes/remote/page.tsx
,引入生产者模块:
src/routes/remote/page.tsx
import React, { useState, Suspense } from 'react';
import { Button } from 'remote/Button';
const Index = (): JSX.Element => {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<Button />
</Suspense>
</div>
);
};
export default Index;
此时 remote/Button
引入会出现类型错误,这是因为本地没有远程模块的类型。Module Federation 2.0 提供了 类型提示 的能力,会在生产者构建时自动生成远程模块的类型定义,在消费者构建时自动下载。
为此我们需要在 tsconfig.json
中添加新的 path
,保证类型生效:
tsconfig.json
{
"compilerOptions": {
"paths": {
"*": ["./@mf-types/*"]
}
}
}
TIP
在消费者中,我们通过 remote/Button
来引用远程模块。这里简单介绍下这个路径具体代表了什么,可以先将它抽象成 [remoteAlias]/[remoteExpose]
。
第一段 remoteAlias
是生产者在消费者中的别名,它是消费者 module-federation.config.ts
中配置的 remotes
字段的 key
:
{
remotes: {
[remoteAlias]: '[remoteModuleName]@[URL_ADDRESS]',
}
}
这里我们也将远程地址抽象为 [remoteModuleName]@[URL_ADDRESS]
,@
前的部分必须对应生产者的模块名。
第二段 remoteExpose
是生产者在 module-federation.config.ts
中配置的 exposes
字段的 key
。
启动应用
现在,生产者应用和消费者应用都已经搭建完毕,我们可以在本地运行 modern dev
启动两个应用。
启动后,消费者中对生产者模块的引入,不会再出现抛错,类型已经下载到消费者应用中。
访问 http://localhost:8080/remote
,可以看到页面中已经包含了生产者的远程模块 Button
组件。
我们也可以在本地执行 modern serve
来模拟生产环境。
因为 Module Federation 插件会自动读取 Modern.js 的 output.assetPrefix
配置作为远程模块的访问地址,而该值在生产环境下构建后默认是 /
。如果不做特殊处理,消费者将从自己的域名下拉取远程模块的入口文件。我们可以添加如下配置:
import { appTools, defineConfig } from '@modern-js/app-tools';
import { moduleFederationPlugin } from '@module-federation/modern-js';
const isLocal = process.env.IS_LOCAL === 'true';
// https://modernjs.dev/en/configure/app/usage
export default defineConfig({
server: {
port: 3051,
},
runtime: {
router: true,
},
output: {
// Now this configuration is only used in the local when you run modern serve command.
// If you want to deploy the application to the platform, use your own domain name.
// Module federation will automatically write it to mf-manifest.json, which influences consumer to fetch remoteEntry.js.
assetPrefix: isLocal ? 'http://127.0.0.1:3051' : '/',
},
plugins: [
appTools({
bundler: 'rspack', // Set to 'webpack' to enable webpack
}),
moduleFederationPlugin(),
],
});
现在,在生产者中运行 IS_LOCAL=true modern build && modern serve
,在消费者中运行 modern build && modern serve
,即可在本地模拟生产环境,访问到远程模块。
上述用例可以参考:Modern.js & Module Federation 基础用法示例。
相关文档