ice mf runtime
基于 Module Federation 的运行时工具,用于在 ice.js 应用中加载和管理远程模块。支持跨版本 React 组件加载
- 基于 Module Federation 2.0
- 支持跨版本 React 组件加载
- 内置冲突检测和降级渲染
``bash`
$ npm i @ice/mf-runtime --save
在应用入口处初始化 Module Federation 配置:
`tsx
import { init } from '@ice/mf-runtime';
init({
name: 'host_app', // 当前应用名称
remotes: [
{
name: 'remote_app', // 远程应用名称
entry: 'http://localhost:3000/remoteEntry.js', // 远程应用入口文件,也支持新的 mf-manifest.json 结构
}
]
});
`
使用 RemoteModule 组件加载远程模块:
`tsx
import { RemoteModule } from '@ice/mf-runtime';
function App() {
return (
$3
#### 自定义 Public Path
如果远程模块的静态资源需要使用特定的 CDN 地址,可以通过
publicPath 属性指定:`tsx
module="Widget"
scope="remote_app"
publicPath="https://cdn.example.com/remote-app/"
/>
`#### 远程模块高级配置
通过
init 方法的 extraInfo 配置,可以实现更多高级特性:##### 类型定义
`ts
interface ExtraInfo {
// 配置外部依赖
external?: {
react?: string; // 外部 React 全局变量名
'react-dom'?: string; // 外部 ReactDOM 全局变量名
};
// 是否为旧版本模块(用于兼容性处理)
legacy?: boolean;
}
`##### 1. 使用外部依赖
当远程模块需要使用 CDN 加载的 React 时,可以通过
external 配置指定:`tsx
init({
name: 'host_app',
remotes: [
{
name: 'remote_app',
entry: 'http://localhost:3000/remoteEntry.js',
extraInfo: {
external: {
react: 'React', // 使用全局的 React
'react-dom': 'ReactDOM' // 使用全局的 ReactDOM
}
}
}
]
});
`当使用模块的 host 应用没有启用 MF 规范时,你可能会遇到 React 多实例引起的问题,对于这类问题,推荐使用外部依赖配置来保证 host 应用和 remote 应用使用同一个环境的 React 实例。
此时需要保证环境中存在指定的全局 React。runtime 会自动处理全局多个 React 的兼容性,优先使用兼容的 React 版本渲染。
##### 2. 兼容旧版本模块
对于一些旧版本的远程模块,可能需要特殊的兼容性处理,此时可以启用
legacy 模式:目前 legacy 模式下,会做以下处理:
1. 兼容 remote 模块 expose name 不以
./ 开头的情况。`tsx
init({
name: 'host_app',
remotes: [
{
name: 'remote_app',
entry: 'http://localhost:3000/remoteEntry.js',
extraInfo: {
legacy: true // 启用兼容模式
}
}
]
});
`##### 3. 混合配置示例
在实际项目中,可能需要同时配置多个远程模块,每个模块使用不同的配置:
`tsx
init({
name: 'host_app',
remotes: [
{
name: 'remote_app',
entry: 'http://localhost:3000/remoteEntry.js',
extraInfo: {
external: {
react: 'React',
'react-dom': 'ReactDOM'
}
}
},
{
name: 'remote_app_2',
entry: 'http://localhost:3002/remoteEntry.js',
extraInfo: {
legacy: true
}
}
]
});
`#### 为某个模块指定 React 版本渲染
当远程模块使用不同版本的 React 时,可以通过
runtime 属性指定运行时:`tsx
import { RemoteModule } from '@ice/mf-runtime';
import * as React from 'react';
import * as ReactDOM from 'react-dom';function App() {
return (
module="Widget"
scope="remote_app"
runtime={{
react: React,
reactDOM: ReactDOM
}}
/>
);
}
`
API
$3
初始化配置项:
| 参数 | 说明 | 类型 | 默认值 |
|------|------|------|--------|
| name | 当前应用名称 |
string | - |
| remotes | 远程应用配置数组 | Array | [] |RemoteConfig 配置:
| 参数 | 说明 | 类型 | 默认值 |
|------|------|------|--------|
| name | 远程应用名称 |
string | - |
| entry | 远程应用入口文件地址 | string | - |
| extraInfo | 额外配置信息,详见远程模块高级配置 | ExtraInfo | - |$3
RemoteModule 组件支持泛型,可以用来约束远程组件的 props 类型:
`tsx
// 定义远程组件的 props 类型
interface RemoteButtonProps {
onClick: () => void;
text: string;
}// 使用带类型的 RemoteModule
module="Button"
scope="remote_app"
componentProps={{
onClick: () => console.log('clicked'),
text: '点击我'
}}
/>
`组件属性:
| 参数 | 说明 | 类型 | 默认值 |
|------|------|------|--------|
| module | 远程模块名称 |
string | - |
| scope | 远程应用名称 | string | - |
| runtime | React 运行时配置 | { react: React, reactDOM: ReactDOM } | - |
| publicPath | 远程模块静态资源地址 | string | - |
| LoadingComponent | 加载状态组件 | ReactNode | |
| ErrorComponent | 错误回退组件 | ReactNode | |
| onError | 错误处理回调函数 | (error: Error, info: { componentStack: string }) => void | - |
| componentProps | 传递给远程模块的属性 | T extends object | {} |
| children | 传递给远程模块的子元素 | ReactNode | null |类型定义:
`tsx
// RemoteModule 的类型定义
interface RemoteModuleOptions {
module: string;
scope: string;
runtime?: {
react: typeof import('react');
reactDOM: typeof import('react-dom');
};
publicPath?: string;
LoadingComponent?: React.ReactNode;
ErrorComponent?: React.ReactNode;
onError?: (error: Error, info: { componentStack: string }) => void;
componentProps?: T;
children?: React.ReactNode;
}// 组件声明
const RemoteModule = (props: RemoteModuleOptions) => { ... }
`
注意事项
1. host name 请保证全局唯一。
2. remote name 的 package.json name 请保证全局唯一,否则会出现意料之外的缓存问题。
使用示例
$3
当远程模块需要接收属性或子元素时,可以使用
componentProps 和 children:`tsx
import { RemoteModule } from '@ice/mf-runtime';interface UserProfileProps {
userId: string;
showAvatar: boolean;
onUserClick: (user: any) => void;
}
interface CardProps {
title: string;
children?: React.ReactNode;
}
function App() {
return (
{/ 传递属性示例 /}
module="UserProfile"
scope="remote_app"
componentProps={{
userId: "123",
showAvatar: true,
onUserClick: (user) => console.log('用户点击:', user)
}}
/> {/ 传递子元素示例 /}
module="Card"
scope="remote_app"
componentProps={{
title: "我的卡片"
}}
>
这是卡片内容
可以传入任意的子元素
);
}
`在远程模块中的使用方式:
`tsx
// 远程模块: UserProfile.tsx
interface UserProfileProps {
userId: string;
showAvatar: boolean;
onUserClick: (user: any) => void;
}export default function UserProfile(props: UserProfileProps) {
const { userId, showAvatar, onUserClick } = props;
// ... 组件实现
}
// 远程模块: Card.tsx
interface CardProps {
title: string;
children?: React.ReactNode;
}
export default function Card({ title, children }: CardProps) {
return (
{title}
{children}
);
}
``