docs/docs/docs/max/mf.en-US.md
Using Module Federation functionality in Umi projects.
:::warning{title=π¨}
Module Federation functionality requires browser support for Top Level Await feature. Please pay attention to browser support (Browser support status) when using it in production environment.
:::
@umijs/max projects
// .umirc.ts
import { defineConfig } from '@umijs/max';
const shared = {
react: {
singleton: true,
eager: true,
},
'react-dom': {
singleton: true,
eager: true,
},
};
export default defineConfig({
// Module Federation plugin is already built-in, just enable the configuration
mf: {
remotes: [
{
// Optional, if not configured, the current remotes[].name field is used
aliasName: 'mfNameAlias',
name: 'theMfName',
entry: 'https://to.the.remote.com/remote.js',
// OptionalοΌwill modify the publicPath of mf entry files to window[`mf_${name}EntryPath`]
runtimeEntryPath: {},
},
],
// Configure MF shared modules
shared,
},
});
Regular Umi projects
// .umirc.ts
import { defineConfig } from 'umi';
const shared = {
react: {
singleton: true,
eager: true,
},
'react-dom': {
singleton: true,
eager: true,
},
};
export default defineConfig({
plugins: ['@umijs/plugins/dist/mf'], // Import plugin
mf: {
remotes: [
{
// Optional, if not configured, the current remotes[].name field is used
aliasName: 'mfNameAlias',
name: 'theMfName',
entry: 'https://to.the.remote.com/remote.js',
},
],
// Configure MF shared modules
shared,
},
});
In the project, you can now use import XXX from 'mfNameAlias/XXXX' to use the content of remote modules.
If you need to decide the remote module's loading address at runtime (based on the running environment), you can configure it as follows:
// .umirc.ts
defineConfig({
mf: {
remotes: [
{
name: 'theMfName',
keyResolver: `(function(){
try {
return window.injectInfo.env || 'PROD'
} catch(e) {
return 'PROD'}
})()`,
entries: {
PRE: 'http://pre.mf.com/remote.js',
PROD: 'http://produ.mf.com/remote.js',
TEST: 'http://test.dev.mf.com/remote.js',
DEV: 'http://127.0.0.1:8000/remote.js',
},
},
],
shared,
},
});
remotes[]#entry, the plugin will prioritize this field.keyResolver is used to decide which key of entries to use at runtime; using an Immediately Invoked Function Expression is recommended and can implement more complex functionality in the function. Does not support asynchronous functions.keyResolver can also use static values, configured as keyResolver: '"PROD"'For the current project to provide remote modules, use the following configuration field for the module name
// .umirc.ts
// Extracting the variable is for consistent configuration with MFSU
const remoteMFName = 'remoteMFName';
defineConfig({
mf: {
name: remoteMFName,
// Optional, library type of remote module, if the module needs to be used in a Qiankun sub-application, the suggested value is recommended,
// Note that the name here must be consistent with the final MF module name
// library: { type: "window", name: "exportMFName" },
},
});
:::info{title=π¨} The configured module name must be a valid Javascript variable name! :::
The modules exported follow the convention, taking the directory names under src/exposes as the export items, and the export file is the index file under that directory, for example
src/exposes/
βββ Button
βΒ Β βββ index.jsx
βββ Head
βΒ Β βββ index.ts
βββ Form
βββ index.tsx
The corresponding Module Federation exposes are
{
'./Button': 'src/exposes/Button/index.jsx',
'./Button': 'src/exposes/Head/index.ts',
'./Form' : 'src/exposes/Form/index.tsx',
}
By default, when the user enables hash: true, the entry file in the MF product will automatically carry a hash, such as remote.123abc.js. You can disable it by setting remoteHash: false (resulting in remote.js), at which point you may need to modify nginx/CDN/gateway response header configuration to remove the cache for the remote.js file, otherwise the new build will not take effect.
Note: More harm without hash and recommended practices are detailed in issue #11711
mf: {
remoteHash: false
}
Configuring with import() can easily use the Module Federation functionality. However, consider using runtime API if you have the following needs:
A fail-safe remote module loading function, interface definition as follows:
safeMfImport(moduleSpecifier: string, fallback: any): Promise<any>
Combined with React.lazy can implement lazy loading of remote modules
import { safeMfImport } from '@umijs/max';
import React, { Suspense } from 'react';
const RemoteCounter = React.lazy(() => {
return safeMfImport('remoteCounter/Counter', { default: () => 'Fallback' });
});
export default function Page() {
return (
<Suspense fallback="loading">
<RemoteCounter />
</Suspense>
);
};
:::info{title=π¨}
default field to mimic a module.remoteCounter/Counter needs to correspond with the configuration.
:::This API is a higher-order component that encapsulates safeMfImport, interface definition as follows:
safeRemoteComponent<T extends React.ComponentType<any>>
(opts: {
moduleSpecifier:string;
fallbackComponent: React.ComponentType<any>; // Fallback component if remote component fails to load
loadingElement: React.ReactNode ; // Loading display for component loading
} ): T
Example:
const RemoteCounter = safeRemoteComponent<React.FC<{ init?: number }>>({
moduleSpecifier: 'remoteCounter/Counter',
fallbackComponent: () => 'fallbacked',
loadingElement: 'Loading',
});
export default function Page() {
return (
<div>
<RemoteCounter init={808} />
</div>
);
};
Loading remote modules, interface as follows.
rawMfImport(opts: {
entry: string;
remoteName: string;
moduleName: string;
}): Promise<any>
Example
const RemoteCounter = React.lazy(() => {
return rawMfImport({
entry: 'http://localhost:8001/remote.js',
moduleName: 'Counter',
remoteName: 'remoteCounter',
});
});
A higher-order component that encapsulates rawMfImport:
type RawRemoteComponentOpts ={
mfConfig:{
entry:string;
remoteName: string;
moduleName: string;
};
fallbackComponent: ComponentType<any>;
loadingElement: ReactNode;
}
safeRemoteComponentWithMfConfig<T extends ComponentType<any>>(opts: RawRemoteComponentOpts): T
Example
const RemoteCounter = safeRemoteComponentWithMfConfig<
React.FC<{ init?: number }>
>({
mfConfig: {
entry: 'http://localhost:8001/remote.js',
moduleName: 'Counter',
remoteName: 'remoteCounter',
},
fallbackComponent: () => 'raw Fallback',
loadingElement: 'raw Loading',
});
export default function Page() {
return <RemoteCounter />;
};
Dynamically registers Module Federation module remote configurations.
type MFModuleRegisterRequest = { entry: string; remoteName: string; aliasName?:string; }
registerMfRemote (opts: MFModuleRegisterRequest): void
When using safeMfImport or safeRemoteComponent, moduleSpecifier must be a configured remote module. While calling rawMfImport is somewhat verbose, registerMfRemote can be used to register first, then use the succinct safeMfImport and safeRemoteComponent.
registerMfRemote({
aliasName: 'registered',
remoteName: 'remoteCounter',
entry: 'http://127.0.0.1:8001/remote.js',
});
const RemoteCounter = React.lazy(() => {
return safeMfImport('registered/Counter', { default: null });
});
The Module Federation plugin automatically modifies MFSU's default configuration based on plugin configuration to allow both features to work normally during the development stage, the principle is as follows:
Assume we have the following mf plugin configuration
// .umirc.ts
export default defineConfig({
mf: {
name: 'remoteMFName',
remotes: [
{
name: 'remote1',
entry: 'https://to.the.remote.com/remote.js',
},
{
aliasName: 'aliasRemote',
name: 'remote2',
entry: 'https://to.the.remote.com/remote2.js',
},
],
shared: {
react: {
singleton: true,
eager: true,
},
'react-dom': {
singleton: true,
eager: true,
},
}
},
});
The corresponding final effective configuration is as follows
{
mfsu: {
// mf plugin automatically fills in the following default configurations compatible with MFSU
// MFSU can also debug MF modules in the DEV stage
remoteName: 'remoteMFName',
remoteAliases: ['remote1', 'aliasRemote'],
shared: {
react: {
singleton: true,
eager: true,
},
'react-dom': {
singleton: true,
eager: true,
},
}
},
mf: {
name: 'remoteMFName',
remotes: [
{
name: 'remote1',
entry: 'https://to.the.remote.com/remote.js',
},
{
aliasName: 'aliasRemote',
name: 'remote2',
entry: 'https://to.the.remote.com/remote2.js',
},
],
shared: {
react: {
singleton: true,
eager: true,
},
'react-dom': {
singleton: true,
eager: true,
},
},
},
}