diff --git a/.changeset/cuddly-spoons-grin.md b/.changeset/cuddly-spoons-grin.md new file mode 100644 index 00000000000..277006d0256 --- /dev/null +++ b/.changeset/cuddly-spoons-grin.md @@ -0,0 +1,5 @@ +--- +'@module-federation/runtime': patch +--- + +chore(runtime): add RUNTIME-009 error code diff --git a/.changeset/few-games-obey.md b/.changeset/few-games-obey.md new file mode 100644 index 00000000000..06a98bf608b --- /dev/null +++ b/.changeset/few-games-obey.md @@ -0,0 +1,5 @@ +--- +'@module-federation/runtime': patch +--- + +refactor(runtime): rename FederationRuntimePlugin to ModuleFederationRuntimePlugin diff --git a/.changeset/good-apricots-own.md b/.changeset/good-apricots-own.md new file mode 100644 index 00000000000..6f356a3c5ba --- /dev/null +++ b/.changeset/good-apricots-own.md @@ -0,0 +1,5 @@ +--- +'@module-federation/runtime': minor +--- + +feat(runtime): add createInstance api and deprecate init diff --git a/.changeset/poor-pillows-study.md b/.changeset/poor-pillows-study.md new file mode 100644 index 00000000000..3bc8d64389e --- /dev/null +++ b/.changeset/poor-pillows-study.md @@ -0,0 +1,11 @@ +--- +'@module-federation/webpack-bundler-runtime': patch +'@module-federation/bridge-react': patch +'@module-federation/data-prefetch': patch +'@module-federation/runtime-core': patch +'@module-federation/modern-js': patch +'@module-federation/runtime': patch +'@module-federation/node': patch +--- + +chore: rename FederationHost to ModuleFederation diff --git a/apps/manifest-demo/webpack-host/runtimePlugin.ts b/apps/manifest-demo/webpack-host/runtimePlugin.ts index d318363e607..3b5a09cad06 100644 --- a/apps/manifest-demo/webpack-host/runtimePlugin.ts +++ b/apps/manifest-demo/webpack-host/runtimePlugin.ts @@ -1,6 +1,6 @@ -import { FederationRuntimePlugin } from '@module-federation/runtime/types'; +import { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; -export default function (): FederationRuntimePlugin { +export default function (): ModuleFederationRuntimePlugin { return { name: 'custom-plugin-build', beforeInit(args) { diff --git a/apps/manifest-demo/webpack-host/src/runtimePlugin.ts b/apps/manifest-demo/webpack-host/src/runtimePlugin.ts index 5aad68d793a..a4bbe28a2da 100644 --- a/apps/manifest-demo/webpack-host/src/runtimePlugin.ts +++ b/apps/manifest-demo/webpack-host/src/runtimePlugin.ts @@ -1,5 +1,5 @@ -import { FederationRuntimePlugin } from '@module-federation/runtime/types'; -export default function (): FederationRuntimePlugin { +import { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; +export default function (): ModuleFederationRuntimePlugin { return { name: 'custom-plugin', beforeInit(args) { diff --git a/apps/node-host/runtimePlugin.ts b/apps/node-host/runtimePlugin.ts index 00d2bc60c1e..644c388ce41 100644 --- a/apps/node-host/runtimePlugin.ts +++ b/apps/node-host/runtimePlugin.ts @@ -1,4 +1,4 @@ -import { FederationRuntimePlugin } from '@module-federation/runtime/types'; +import { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; export default function () { return { diff --git a/apps/router-demo/router-host-2000/src/App.tsx b/apps/router-demo/router-host-2000/src/App.tsx index 5412248dd30..a1e795da8c6 100644 --- a/apps/router-demo/router-host-2000/src/App.tsx +++ b/apps/router-demo/router-host-2000/src/App.tsx @@ -15,10 +15,10 @@ import './App.css'; import BridgeReactPlugin from '@module-federation/bridge-react/plugin'; import { ErrorBoundary } from 'react-error-boundary'; import Remote1AppNew from 'remote1/app'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; import { Spin } from 'antd'; -const fallbackPlugin: () => FederationRuntimePlugin = function () { +const fallbackPlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'fallback-plugin', errorLoadRemote(args) { diff --git a/apps/router-demo/router-host-2000/src/runtime-plugin/error-handling/lifecycle-based.ts b/apps/router-demo/router-host-2000/src/runtime-plugin/error-handling/lifecycle-based.ts index 60dad9e0056..246a5927644 100644 --- a/apps/router-demo/router-host-2000/src/runtime-plugin/error-handling/lifecycle-based.ts +++ b/apps/router-demo/router-host-2000/src/runtime-plugin/error-handling/lifecycle-based.ts @@ -9,7 +9,7 @@ * 2. Entry File Loading (afterResolve): Attempts to load from a backup service */ -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; interface LifecycleBasedConfig { backupEntryUrl?: string; @@ -18,7 +18,7 @@ interface LifecycleBasedConfig { export const createLifecycleBasedPlugin = ( config: LifecycleBasedConfig = {}, -): FederationRuntimePlugin => { +): ModuleFederationRuntimePlugin => { const { backupEntryUrl = 'http://localhost:2002/mf-manifest.json', errorMessage = 'Module loading failed, please try again later', diff --git a/apps/router-demo/router-host-2000/src/runtime-plugin/error-handling/simple.ts b/apps/router-demo/router-host-2000/src/runtime-plugin/error-handling/simple.ts index e20465a4bb1..f5ab081cd8e 100644 --- a/apps/router-demo/router-host-2000/src/runtime-plugin/error-handling/simple.ts +++ b/apps/router-demo/router-host-2000/src/runtime-plugin/error-handling/simple.ts @@ -12,7 +12,7 @@ * Use this when you don't need different handling strategies for different error types. */ -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; interface SimpleConfig { errorMessage?: string; @@ -20,7 +20,7 @@ interface SimpleConfig { export const createSimplePlugin = ( config: SimpleConfig = {}, -): FederationRuntimePlugin => { +): ModuleFederationRuntimePlugin => { const { errorMessage = 'Module loading failed, please try again later' } = config; diff --git a/apps/router-demo/router-host-2000/src/runtime-plugin/fallback.ts b/apps/router-demo/router-host-2000/src/runtime-plugin/fallback.ts index 25793eca079..927bfe48d34 100644 --- a/apps/router-demo/router-host-2000/src/runtime-plugin/fallback.ts +++ b/apps/router-demo/router-host-2000/src/runtime-plugin/fallback.ts @@ -1,4 +1,4 @@ -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; import { createLifecycleBasedPlugin, createSimplePlugin, @@ -15,7 +15,7 @@ interface FallbackConfig { const fallbackPlugin = ( config: FallbackConfig = {}, -): FederationRuntimePlugin => { +): ModuleFederationRuntimePlugin => { const { backupEntryUrl = 'http://localhost:2002/mf-manifest.json', errorMessage = 'Module loading failed, please try again later', diff --git a/apps/router-demo/router-host-2000/src/runtime-plugin/shared-strategy.ts b/apps/router-demo/router-host-2000/src/runtime-plugin/shared-strategy.ts index 678023199c4..ad5dfda029f 100644 --- a/apps/router-demo/router-host-2000/src/runtime-plugin/shared-strategy.ts +++ b/apps/router-demo/router-host-2000/src/runtime-plugin/shared-strategy.ts @@ -1,7 +1,7 @@ -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; // The default external policy is version-first: the version with a higher version is used preferentially, even if the version is not loaded. Internally, it must be compatible with the previous logic, mainly loading -const sharedStrategy: () => FederationRuntimePlugin = () => ({ +const sharedStrategy: () => ModuleFederationRuntimePlugin = () => ({ name: 'shared-strategy', beforeInit(args) { const { userOptions, shareInfo } = args; diff --git a/apps/router-demo/router-host-v5-2200/src/runtime-plugin/shared-strategy.ts b/apps/router-demo/router-host-v5-2200/src/runtime-plugin/shared-strategy.ts index 13b62e42737..f4890cb15f5 100644 --- a/apps/router-demo/router-host-v5-2200/src/runtime-plugin/shared-strategy.ts +++ b/apps/router-demo/router-host-v5-2200/src/runtime-plugin/shared-strategy.ts @@ -1,7 +1,7 @@ -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; // The default external policy is version-first: the version with a higher version is used preferentially, even if the version is not loaded. Internally, it must be compatible with the previous logic, mainly loading -const sharedStrategy: () => FederationRuntimePlugin = () => ({ +const sharedStrategy: () => ModuleFederationRuntimePlugin = () => ({ name: 'shared-strategy', beforeInit(args) { const { userOptions, shareInfo } = args; diff --git a/apps/runtime-demo/3005-runtime-host/src/runtimePlugin.ts b/apps/runtime-demo/3005-runtime-host/src/runtimePlugin.ts index e45bce96318..9708bc92dc0 100644 --- a/apps/runtime-demo/3005-runtime-host/src/runtimePlugin.ts +++ b/apps/runtime-demo/3005-runtime-host/src/runtimePlugin.ts @@ -1,5 +1,5 @@ -import { FederationRuntimePlugin } from '@module-federation/runtime/types'; -export default function (): FederationRuntimePlugin { +import { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; +export default function (): ModuleFederationRuntimePlugin { return { name: 'custom-plugin', beforeInit(args) { diff --git a/apps/website-new/docs/en/blog/error-load-remote.mdx b/apps/website-new/docs/en/blog/error-load-remote.mdx index 9ac02429296..c3d8e6a0c18 100644 --- a/apps/website-new/docs/en/blog/error-load-remote.mdx +++ b/apps/website-new/docs/en/blog/error-load-remote.mdx @@ -172,7 +172,7 @@ import React from 'react'; import { init, loadRemote } from '@module-federation/enhanced/runtime'; import { RetryPlugin } from '@module-federation/retry-plugin'; -+ const fallbackPlugin: () => FederationRuntimePlugin = function () { ++ const fallbackPlugin: () => ModuleFederationRuntimePlugin = function () { + return { + name: 'fallback-plugin', + errorLoadRemote(args) { @@ -263,7 +263,7 @@ export default defineConfig({ ```tsx // src/runtime-plugin/fallback.ts -import type { FederationRuntimePlugin } from '@module-federation/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; interface FallbackConfig { // Backup service address @@ -272,7 +272,7 @@ interface FallbackConfig { errorMessage?: string; } -const fallbackPlugin = (config: FallbackConfig = {}): FederationRuntimePlugin => { +const fallbackPlugin = (config: FallbackConfig = {}): ModuleFederationRuntimePlugin => { const { backupEntryUrl = 'http://localhost:2002/mf-manifest.json', errorMessage = 'Module loading failed, please try again later' @@ -435,9 +435,9 @@ return () => ({ If you don't need to distinguish between error types, you can use a generic error handling solution: ```ts - import type { FederationRuntimePlugin } from '@module-federation/runtime'; + import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; - const fallbackPlugin = (errorMessage = 'Module loading failed, please try again later'): FederationRuntimePlugin => { + const fallbackPlugin = (errorMessage = 'Module loading failed, please try again later'): ModuleFederationRuntimePlugin => { return { name: 'fallback-plugin', async errorLoadRemote() { diff --git a/apps/website-new/docs/en/configure/experiments.mdx b/apps/website-new/docs/en/configure/experiments.mdx index 8927f1cfc27..2eca65496b6 100644 --- a/apps/website-new/docs/en/configure/experiments.mdx +++ b/apps/website-new/docs/en/configure/experiments.mdx @@ -74,7 +74,7 @@ This object contains flags related to build-time optimizations that can affect t - **Required:** No - **Default:** `false` -When set to `true`, this option defines the `FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN` global constant as `true` during the build. In the `@module-federation/runtime-core`, this prevents the `snapshotPlugin()` and `generatePreloadAssetsPlugin()` from being included and initialized within the FederationHost. +When set to `true`, this option defines the `FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN` global constant as `true` during the build. In the `@module-federation/runtime-core`, this prevents the `snapshotPlugin()` and `generatePreloadAssetsPlugin()` from being included and initialized within the Module Federation instance. **Impact:** * **Benefit:** Can reduce the overall bundle size of the Module Federation runtime by excluding the code for these two plugins. diff --git a/apps/website-new/docs/en/configure/exposes.mdx b/apps/website-new/docs/en/configure/exposes.mdx index 3864929c17d..96e82462b39 100644 --- a/apps/website-new/docs/en/configure/exposes.mdx +++ b/apps/website-new/docs/en/configure/exposes.mdx @@ -32,7 +32,7 @@ Example: ```jsx module.exports = { plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ name: 'provider', exposes: { // Note: "./" is not supported. Exporting as `.` indicates a default export diff --git a/apps/website-new/docs/en/configure/getpublicpath.mdx b/apps/website-new/docs/en/configure/getpublicpath.mdx index c8431db1bea..ea9cb58db5b 100644 --- a/apps/website-new/docs/en/configure/getpublicpath.mdx +++ b/apps/website-new/docs/en/configure/getpublicpath.mdx @@ -20,7 +20,7 @@ If you're using module federation webpack plugin and want to set a dynamic publi ```ts title="rspack.config.ts" module.exports = { plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ name: 'provider', exposes: { './Button': './src/components/Button.tsx', diff --git a/apps/website-new/docs/en/configure/implementation.mdx b/apps/website-new/docs/en/configure/implementation.mdx index 1ba27a7ed87..c9e1a4244b3 100644 --- a/apps/website-new/docs/en/configure/implementation.mdx +++ b/apps/website-new/docs/en/configure/implementation.mdx @@ -16,7 +16,7 @@ For example, if you want to use a specific version of `@module-federation/runtim module.exports = { // ...other configurations plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ // ...other Module Federation options implementation: require.resolve('@module-federation/runtime-tools'), }), diff --git a/apps/website-new/docs/en/configure/remotes.mdx b/apps/website-new/docs/en/configure/remotes.mdx index 6728109cbb5..da6e2163168 100644 --- a/apps/website-new/docs/en/configure/remotes.mdx +++ b/apps/website-new/docs/en/configure/remotes.mdx @@ -31,7 +31,7 @@ interface PluginRemoteOptions { ```js module.exports = { plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ name: 'host', // The `remotes` below defines two remotes, named `manifest-provider` for the project started on port 3011 and `js-entry-provider` for the project started on port 3012 remotes: { diff --git a/apps/website-new/docs/en/configure/runtimeplugins.mdx b/apps/website-new/docs/en/configure/runtimeplugins.mdx index 9235630ca6a..a005ed1e7f3 100644 --- a/apps/website-new/docs/en/configure/runtimeplugins.mdx +++ b/apps/website-new/docs/en/configure/runtimeplugins.mdx @@ -13,9 +13,9 @@ Once set, runtime plugins will be automatically injected and used during the bui To create a runtime plugin file, you can name it `custom-runtime-plugin.ts`: ```ts title="custom-runtime-plugin.ts" -import { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -export default function (): FederationRuntimePlugin { +export default function (): ModuleFederationRuntimePlugin { return { name: 'custom-plugin-build', beforeInit(args) { @@ -36,7 +36,7 @@ Then, apply this plugin in your build configuration: const path = require('path'); module.exports = { plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ name: 'host', remotes: { 'manifest-provider': diff --git a/apps/website-new/docs/en/guide/basic/runtime/runtime-api.mdx b/apps/website-new/docs/en/guide/basic/runtime/runtime-api.mdx index 4beaf9e5b3a..52654f94247 100644 --- a/apps/website-new/docs/en/guide/basic/runtime/runtime-api.mdx +++ b/apps/website-new/docs/en/guide/basic/runtime/runtime-api.mdx @@ -1,11 +1,57 @@ # Runtime API -## init +import { Steps, Tab, Tabs, Badge, Aside } from '@theme'; +import Collapse from '@components/Collapse' +import Runtime from '@components/en/runtime'; + + + +## createInstance + +Used to create ModuleFederation instance. + +* When to use `createInstance`? + +To ensure the uniqueness of the ModuleFederation instance, after the build plugin creates an instance, it will be stored in memory. The exported APIs all first obtain the ModuleFederation instance from memory and then call the APIs of the ModuleFederation instance. This is also why APIs like loadRemote can be used directly from the `@module-federation/enhanced/runtime` package and inherently understand what application container they are attached to. + +This singleton pattern works for most scenarios, but in the following cases, you need to use `createInstance`: + +- No build plugin is used (Only use runtime) +- Multiple ModuleFederation instances need to be created with different configurations for each instance +- You want to use ModuleFederation's partitioning feature to encapsulate corresponding APIs for use in other projects + +```ts +import { createInstance } from '@module-federation/enhanced/runtime'; + +const mf = createInstance({ + name: 'host', + remotes: [ + { + name: 'sub1', + entry: 'http://localhost:8080/mf-manifest.json' + } + ] +}); + +mf.loadRemote('sub1/util').then((m) => m.add(1, 2, 3)); +``` + +## init Deprecated + +:::warning Warning + +This API will be deprecated. If you need to get an existing instance, you can use [getInstance](#getinstance) to retrieve the created instance. + +If you need to create a new instance, please use the [createInstance](#createinstance). + +::: - Type: `init(options: InitOptions): void` - Used for dynamically registering `Vmok` modules at runtime. - InitOptions: + + ```ts type InitOptions { // Name of the current host @@ -67,9 +113,11 @@ type Share = { }; ``` -- Example + + + -```js +```ts import { init, loadRemote } from '@module-federation/enhanced/runtime'; init({ @@ -85,112 +133,490 @@ init({ ], }); ``` + -## loadRemote +### How to Migrate -- Type: `loadRemote(id: string)` -- Loads a remote module. -- Example +#### Build Plugin(Use build plugin) -```javascript -import { init, loadRemote } from '@module-federation/enhanced/runtime'; +1. Remove calls to the `init` API +2. Use [registerShared](#registershared) instead of the `shared` configuration in `init` +3. Use [registerRemotes](#registerremotes) instead of the `remotes` configuration in `init` +4. Use [registerPlugins](#registerplugins) instead of the `plugins` configuration in `init` +5. Use [getInstance](#getinstance) to obtain the `ModuleFederation` instance created by the build plugin -init({ - name: "mf_host", - remotes: [ +```diff +- import { init } from '@module-federation/enhanced/runtime'; ++ import { registerShared, registerRemotes, registerPlugins, getInstance } from '@module-federation/enhanced/runtime'; +import React from 'react'; +import mfRuntimePlugin from 'mf-runtime-plugin'; + +- const instance = init({ ++ const instance = getInstance(); +- name: 'mf_host', +- remotes: [ +- { +- name: 'remote', +- entry: 'http://localhost:2001/mf-manifest.json', +- } +- ], ++ registerRemotes([ ++ { ++ name: 'remote', ++ entry: 'http://localhost:2001/mf-manifest.json', ++ } ++ ]); +- shared: { +- react: { +- version: "18.0.0", +- scope: "default", +- lib: ()=> React, +- shareConfig: { +- singleton: true, +- requiredVersion: "^18.0.0" +- } +- }, +- }, ++ registerShared({ ++ react: { ++ version: "18.0.0", ++ scope: "default", ++ lib: ()=> React, ++ shareConfig: { ++ singleton: true, ++ requiredVersion: "^18.0.0" ++ } ++ } ++ }); +- plugins: [mfRuntimePlugin()] ++ registerPlugins([mfRuntimePlugin()]); +-}); + +``` + +#### Pure Runtime(Not Use Build Plugin) + +```diff +- import { init } from '@module-federation/enhanced/runtime'; ++ import { createInstance } from '@module-federation/enhanced/runtime'; + +-const instance = init({ ++ const instance = createInstance({ +name: 'mf_host', +remotes: [ + { + name: 'remote', + entry: 'http://localhost:2001/mf-manifest.json', + } +], +shared: { + react: { + version: "18.0.0", + scope: "default", + lib: ()=> React, + shareConfig: { + singleton: true, + requiredVersion: "^18.0.0" + } + }, +}, +plugins: [mfRuntimePlugin()] +}); +``` + +If you are not using the build plugin, you can replace `init` with [createInstance](#createinstance), which has exactly the same parameters. + +## getInstance + +- Type: `getInstance(): ModuleFederation` +- Retrieves the `ModuleFederation` instance created by the build plugin + +When the build plugin is used, you can call `getInstance` to retrieve the `ModuleFederation` instance created by the build plugin. + +```ts +import { getInstance } from '@module-federation/enhanced/runtime'; + +const mfInstance = getInstance(); +mfInstance.loadRemote('remote/util'); +``` + +If the build plugin is not used, calling `getInstance` will throw an exception. In this case, you need to use the [createInstance](#createinstance) to create a new instance. + +## registerRemotes + + + +```typescript +function registerRemotes(remotes: Remote[], options?: { force?: boolean }) {} + +type Remote = (RemoteWithEntry | RemoteWithVersion) & RemoteInfoCommon; + +interface RemoteInfoCommon { + alias?: string; + shareScope?: string; + type?: RemoteEntryType; + entryGlobalName?: string; +} + +interface RemoteWithEntry { + name: string; + entry: string; +} + +interface RemoteWithVersion { + name: string; + version: string; +} +``` + + + +- Details + +**info**: Please set `force:true` with caution! + +If `force: true` is set, it will overwrite the registered (and loaded) modules, and automatically delete the cache of the loaded modules (if they have been loaded), and output a warning in the console to inform that this operation is risky. + + + + ```tsx + import { registerRemotes } from '@module-federation/enhanced/runtime'; + + // register new remote sub2 + registerRemotes([ + { + name: 'sub2', + entry: 'http://localhost:2002/mf-manifest.json', + } + ]); + + // override remote sub1 + registerRemotes([ + { + name: 'sub1', + entry: 'http://localhost:2003/mf-manifest.json', + } + ],{ force:true }); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ { - name: "remote", - // Remote module alias. After configuring an alias, the module can be loaded through the alias. Please note: the prefixes of alias and name cannot be the same. - alias: "app1", - // Specify the remote module address ending with `mf-manifest.json`, which is generated by the mf plugin. - entry: "http://localhost:2001/vmok-manifest.json" + name: 'sub1', + entry: 'http://localhost:2001/mf-manifest.json', } - ] -}); + ] + }); + + // register new remote sub2 + mf.registerRemotes([ + { + name: 'sub2', + entry: 'http://localhost:2002/mf-manifest.json', + } + ]); + + // override remote sub1 + mf.registerRemotes([ + { + name: 'sub1', + entry: 'http://localhost:2003/mf-manifest.json', + } + ],{ force:true }); + ``` + + -// remoteName + expose -loadRemote("remote/util").then((m)=> m.add(1,2,3)); +## registerPlugins + +- type -// alias + expose -loadRemote("app1/util").then((m)=> m.add(1,2,3)); +```typescript +function registerPlugins(plugins: ModuleFederationRuntimePlugin[]) {} ``` -## loadShare + + + ```tsx + import { registerPlugins } from '@module-federation/enhanced/runtime'; + import runtimePlugin from './custom-runtime-plugin'; -- Type: `loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})` -- Gets a `share` dependency. When there is a `share` dependency in the global environment that meets the requirements of the current `host`, the existing dependency that meets the `share` conditions will be reused first. Otherwise, its own dependency will be loaded and stored in the global cache. -- This `API` is **generally not called directly by the user, but is used by the build plugin to transform its own dependencies**. + // add new runtime plugin + registerPlugins([runtimePlugin()]); -- Example + registerPlugins([ + { + name: 'custom-plugin-runtime', + beforeInit(args) { + const { userOptions, origin } = args; + if (origin.options.name && origin.options.name !== userOptions.name) { + userOptions.name = origin.options.name; + } + console.log('[build time inject] beforeInit: ', args); + return args; + }, + beforeLoadShare(args) { + console.log('[build time inject] beforeLoadShare: ', args); + return args; + }, + createLink({ url }) { + const link = document.createElement('link'); + link.setAttribute('href', url); + link.setAttribute('rel', 'preload'); + link.setAttribute('as', 'script'); + link.setAttribute('crossorigin', 'anonymous'); + return link; + }, + } + ]); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + import runtimePlugin from './custom-runtime-plugin'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'sub1', + entry: 'http://localhost:2001/mf-manifest.json', + } + ] + }); -```js -import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime'; -import React from 'react'; -import ReactDom from 'react-dom'; + // add new runtime plugin + mf.registerPlugins([runtimePlugin()]); -init({ - name: "mf_host", - remotes: [], - shared: { - react: { - version: "17.0.0", + mf.registerPlugins([ + { + name: 'custom-plugin-runtime', + beforeInit(args) { + const { userOptions, origin } = args; + if (origin.options.name && origin.options.name !== userOptions.name) { + userOptions.name = origin.options.name; + } + console.log('[build time inject] beforeInit: ', args); + return args; + }, + beforeLoadShare(args) { + console.log('[build time inject] beforeLoadShare: ', args); + return args; + }, + createLink({ url }) { + const link = document.createElement('link'); + link.setAttribute('href', url); + link.setAttribute('rel', 'preload'); + link.setAttribute('as', 'script'); + link.setAttribute('crossorigin', 'anonymous'); + return link; + }, + } + ]); + ``` + + + +## registerShared + + + +```typescript +function registerShared(shared: Shared) {} + +type Shared = { + [pkgName: string]: ShareArgs | ShareArgs[]; +} +type ShareArgs = (SharedBaseArgs & { + get: SharedGetter; +}) | (SharedBaseArgs & { + lib: () => Module; +}) | SharedBaseArgs; +type SharedBaseArgs = { + version?: string; + shareConfig?: SharedConfig; + scope?: string | Array; + deps?: Array; + strategy?: 'version-first' | 'loaded-first'; + loaded?: boolean; +}; +interface SharedConfig { + singleton?: boolean; + requiredVersion: false | string; + eager?: boolean; + strictVersion?: boolean; + layer?: string | null; +} +type SharedGetter = (() => () => Module) | (() => Promise<() => Module>); +``` + + + + + + ```tsx + import { registerShared } from '@module-federation/enhanced/runtime'; + import React from 'react'; + import ReactDom from 'react-dom'; + + registerShared({ + react: { + version: "18.0.0", scope: "default", lib: ()=> React, shareConfig: { singleton: true, - requiredVersion: "^17.0.0" + requiredVersion: "^18.0.0" } }, "react-dom": { - version: "17.0.0", + version: "18.0.0", scope: "default", lib: ()=> ReactDom, shareConfig: { singleton: true, - requiredVersion: "^17.0.0" + requiredVersion: "^18.0.0" } } - } -}); - + }); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + import React from 'react'; + import ReactDom from 'react-dom'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'sub1', + entry: 'http://localhost:2001/mf-manifest.json', + } + ] + }); -loadShare("react").then((reactFactory)=>{ - console.log(reactFactory()) -}); -``` + registerShared({ + react: { + version: "18.0.0", + scope: "default", + lib: ()=> React, + shareConfig: { + singleton: true, + requiredVersion: "^18.0.0" + } + }, + "react-dom": { + version: "18.0.0", + scope: "default", + lib: ()=> ReactDom, + shareConfig: { + singleton: true, + requiredVersion: "^18.0.0" + } + } + }); + ``` + + -If multiple versions of shared are set, the loaded shared with the highest version will be returned by default. This behavior can be changed by setting `extraOptions.resolver`: +## loadShare -```js -import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime'; +- Type: `loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})` +- Gets a `share` dependency. When there is a `share` dependency in the global environment that meets the requirements of the current `host`, the existing dependency that meets the `share` conditions will be reused first. Otherwise, its own dependency will be loaded and stored in the global cache. +- This `API` is **generally not called directly by the user, but is used by the build plugin to transform its own dependencies**. -init({ - name: 'mf_host', - remotes: [], - shared: { - react: [ - { - version: '17.0.0', - scope: 'default', - get: async ()=>() => ({ version: '17.0.0' }), - shareConfig: { - singleton: true, - requiredVersion: '^17.0.0', - }, + + + ```tsx + import { registerShared, loadShare } from '@module-federation/enhanced/runtime'; + import React from 'react'; + import ReactDom from 'react-dom'; + + registerShared({ + react: { + version: "17.0.0", + scope: "default", + lib: ()=> React, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } }, - { - version: '18.0.0', - scope: 'default', - // pass lib means the shared has loaded - lib: () => ({ version: '18.0.0' }), - shareConfig: { - singleton: true, - requiredVersion: '^18.0.0', - }, + "react-dom": { + version: "17.0.0", + scope: "default", + lib: ()=> ReactDom, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } + } + }); + + + loadShare("react").then((reactFactory)=>{ + console.log(reactFactory()) + }); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + import React from 'react'; + import ReactDom from 'react-dom'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote', + entry: 'http://localhost:2001/mf-manifest.json', + alias: 'app1' + } + ] + }); + + mf.registerShared({ + react: { + version: "17.0.0", + scope: "default", + lib: ()=> React, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } }, - ], - }, -}); + "react-dom": { + version: "17.0.0", + scope: "default", + lib: ()=> ReactDom, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } + } + }); + + + mf.loadShare("react").then((reactFactory)=>{ + console.log(reactFactory()) + }); + ``` + + + +If multiple versions of shared are set, the loaded shared with the highest version will be returned by default. This behavior can be changed by setting `extraOptions.resolver`: + +```ts +// ... loadShare('react', { resolver: (sharedOptions) => { @@ -203,9 +629,51 @@ loadShare('react', { }); ``` +## loadRemote + +- Type: `loadRemote(id: string)` +- Loads a remote module. +- Example + + + + ```tsx + import { loadRemote } from '@module-federation/enhanced/runtime'; + + // remoteName + expose + loadRemote("remote/util").then((m)=> m.add(1,2,3)); + + // alias + expose + loadRemote("app1/util").then((m)=> m.add(1,2,3)); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote', + entry: 'http://localhost:2001/mf-manifest.json', + alias: 'app1' + } + ] + }); + + // remoteName + expose + mf.loadRemote("remote/util").then((m)=> m.add(1,2,3)); + + // alias + expose + mf.loadRemote("app1/util").then((m)=> m.add(1,2,3)); + ``` + + + ## preloadRemote -- type + ```typescript async function preloadRemote(preloadOptions: Array){} @@ -232,6 +700,8 @@ type PreloadRemoteArgs = { }; ``` + + - Details Through `preloadRemote`, you can start preloading module resources at an earlier stage to avoid waterfall requests. What can `preloadRemote` preload: @@ -240,180 +710,119 @@ Through `preloadRemote`, you can start preloading module resources at an earlier * `remote`'s `expose` * `remote`'s synchronous or asynchronous resources * `remote`'s dependent `remote` resources + + + ```tsx + import { registerRemotes, preloadRemote } from '@module-federation/enhanced/runtime'; -- Example + registerRemotes([ + { + name: 'sub1', + entry: "http://localhost:2001/mf-manifest.json" + }, + { + name: 'sub2', + entry: "http://localhost:2002/mf-manifest.json" + }, + { + name: 'sub3', + entry: "http://localhost:2003/mf-manifest.json" + }, + ]); -```ts -import { init, preloadRemote } from '@module-federation/enhanced/runtime'; -init({ - name: 'mf_host', - remotes: [ + // 预加载 sub1 模块 + // 过滤资源名称中携带 ignore 的资源信息 + // 只预加载子依赖的 sub1-button 模块 + preloadRemote([ { - name: 'sub1', - entry: "http://localhost:2001/mf-manifest.json" + nameOrAlias: 'sub1', + filter(assetUrl) { + return assetUrl.indexOf('ignore') === -1; + }, + depsRemote: [{ nameOrAlias: 'sub1-button' }], }, + ]); + + + // 预加载 sub2 模块 + // 预加载 sub2 下的所有 expose + // 预加载 sub2 的同步资源和异步资源 + preloadRemote([ { - name: 'sub2', - entry: "http://localhost:2002/mf-manifest.json" + nameOrAlias: 'sub2', + resourceCategory: 'all', }, + ]); + + // 预加载 sub3 模块的 add expose + preloadRemote([ { - name: 'sub3', - entry: "http://localhost:2003/mf-manifest.json" + nameOrAlias: 'sub3', + resourceCategory: 'all', + exposes: ['add'], }, - ], -}); + ]); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [] + }); + + mf.registerRemotes([ + { + name: 'sub1', + entry: "http://localhost:2001/mf-manifest.json" + }, + { + name: 'sub2', + entry: "http://localhost:2002/mf-manifest.json" + }, + { + name: 'sub3', + entry: "http://localhost:2003/mf-manifest.json" + }, + ]); -// Preload the @demo/sub1 module -// Filter resource information that contains 'ignore' in the resource name -// Only preload the sub-dependency @demo/sub1-button module -preloadRemote([ - { - nameOrAlias: 'sub1', - filter(assetUrl) { - return assetUrl.indexOf('ignore') === -1; + // preload sub1 + // Filter resource information with ignore in resource names + // Preload only sub-dependency sub1-button module + mf.preloadRemote([ + { + nameOrAlias: 'sub1', + filter(assetUrl) { + return assetUrl.indexOf('ignore') === -1; + }, + depsRemote: [{ nameOrAlias: 'sub1-button' }], }, - depsRemote: [{ nameOrAlias: 'sub1-button' }], - }, -]); - - -// Preload the @demo/sub2 module -// Preload all exposes under @demo/sub2 -// Preload the synchronous and asynchronous resources of @demo/sub2 -preloadRemote([ - { - nameOrAlias: 'sub2', - resourceCategory: 'all', - }, -]); - -// Preload the add expose of the @demo/sub3 module -preloadRemote([ - { - nameOrAlias: 'sub3', - resourceCategory: 'all', - exposes: ['add'], - }, -]); -``` + ]); -## registerRemotes - -- type - -```typescript -function registerRemotes(remotes: Remote[], options?: { force?: boolean }) {} - -type Remote = (RemoteWithEntry | RemoteWithVersion) & RemoteInfoCommon; - -interface RemoteInfoCommon { - alias?: string; - shareScope?: string; - type?: RemoteEntryType; - entryGlobalName?: string; -} - -interface RemoteWithEntry { - name: string; - entry: string; -} - -interface RemoteWithVersion { - name: string; - version: string; -} -``` - -- Details - -**info**: Please set `force:true` with caution! - -If `force: true` is set, it will overwrite the registered (and loaded) modules, and automatically delete the cache of the loaded modules (if they have been loaded), and output a warning in the console to inform that this operation is risky. - -* Example - -```ts -import { init, registerRemotes } from '@module-federation/enhanced/runtime'; - -init({ - name: 'mf_host', - remotes: [ - { - name: 'sub1', - entry: 'http://localhost:2001/mf-manifest.json', - } - ], -}); - -// Add a new remote @demo/sub2 -registerRemotes([ - { - name: 'sub2', - entry: 'http://localhost:2002/mf-manifest.json', - } -]); - -// Overwrite the previous remote @demo/sub1 -registerRemotes([ - { - name: 'sub1', - entry: 'http://localhost:2003/mf-manifest.json', - } -],{ force:true }); -``` - -## registerPlugins - -- type - -```typescript -function registerPlugins(plugins: FederationRuntimePlugin[]) {} -``` -* Example - -```ts -import { init, registerPlugins } from '@module-federation/enhanced/runtime'; -import runtimePlugin from './custom-runtime-plugin'; - -init({ - name: 'mf_host', - remotes: [ - { - name: 'sub1', - entry: 'http://localhost:2001/mf-manifest.json', - } - ], -}); - -// Add a new runtime plugin -registerPlugins([runtimePlugin()]); - -registerPlugins([ - { - name: 'custom-plugin-runtime', - beforeInit(args) { - const { userOptions, origin } = args; - if (origin.options.name && origin.options.name !== userOptions.name) { - userOptions.name = origin.options.name; - } - console.log('[build time inject] beforeInit: ', args); - return args; - }, - beforeLoadShare(args) { - console.log('[build time inject] beforeLoadShare: ', args); - return args; - }, - createLink({ url }) { - const link = document.createElement('link'); - link.setAttribute('href', url); - link.setAttribute('rel', 'preload'); - link.setAttribute('as', 'script'); - link.setAttribute('crossorigin', 'anonymous'); - return link; - }, - } -]); -``` + // preload sub2 + // Preload all exposes in sub2 + // Preload sub2's sync and async resources + mf.preloadRemote([ + { + nameOrAlias: 'sub2', + resourceCategory: 'all', + }, + ]); + // preload sub3 + // Preload all exposes in sub3 + // Preload sub3's sync and async resources + mf.preloadRemote([ + { + nameOrAlias: 'sub3', + resourceCategory: 'all', + exposes: ['add'], + }, + ]); + ``` + + diff --git a/apps/website-new/docs/en/guide/basic/runtime/runtime-hooks.mdx b/apps/website-new/docs/en/guide/basic/runtime/runtime-hooks.mdx index cd6b8f436a0..7eba04ad6e5 100644 --- a/apps/website-new/docs/en/guide/basic/runtime/runtime-hooks.mdx +++ b/apps/website-new/docs/en/guide/basic/runtime/runtime-hooks.mdx @@ -13,18 +13,18 @@ function beforeInit(args: BeforeInitOptions): BeforeInitOptions type BeforeInitOptions ={ userOptions: UserOptions; - options: FederationRuntimeOptions; - origin: FederationHost; + options: ModuleFederationRuntimeOptions; + origin: ModuleFederation; shareInfo: ShareInfos; } -interface FederationRuntimeOptions { +interface ModuleFederationRuntimeOptions { id?: string; name: string; version?: string; remotes: Array; shared: ShareInfos; - plugins: Array; + plugins: Array; inBrowser: boolean; } ``` @@ -41,8 +41,8 @@ Called after the MF instance is initialized. function init(args: InitOptions): void type InitOptions ={ - options: FederationRuntimeOptions; - origin: FederationHost; + options: ModuleFederationRuntimeOptions; + origin: ModuleFederation; } ``` @@ -59,8 +59,8 @@ async function beforeRequest(args: BeforeRequestOptions): Promise FederationRuntimePlugin = +const fallbackPlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'fallback-plugin', @@ -182,7 +182,7 @@ const fallbackPlugin: () => FederationRuntimePlugin = }; -init({ +const mf = createInstance({ name: 'mf_host', remotes: [ { @@ -194,7 +194,7 @@ init({ plugins: [fallbackPlugin()] }); -loadRemote('app1/un-existed-module').then(mod=>{ +mf.loadRemote('app1/un-existed-module').then(mod=>{ expect(mod).toEqual('fallback'); }) ``` @@ -214,7 +214,7 @@ type BeforeLoadShareOptions ={ pkgName: string; shareInfo?: Shared; shared: Options['shared']; - origin: FederationHost; + origin: ModuleFederation; } ``` @@ -242,11 +242,11 @@ type ResolveShareOptions ={ * example ```ts -import { init, loadRemote } from '@module-federation/enhanced/runtime' +import { ModuleFederation, loadRemote } from '@module-federation/enhanced/runtime' -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -const customSharedPlugin: () => FederationRuntimePlugin = +const customSharedPlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'custom-shared-plugin', @@ -269,7 +269,7 @@ const customSharedPlugin: () => FederationRuntimePlugin = }; -init({ +const mf = createInstance({ name: 'mf_host', shared: { react: { @@ -287,7 +287,7 @@ init({ window.React = ()=> 'Desired Shared'; -loadShare("react").then((reactFactory)=>{ +mf.loadShare("react").then((reactFactory)=>{ expect(reactFactory()).toEqual(window.React()); }); ``` @@ -306,7 +306,7 @@ async function beforePreloadRemote(args: BeforePreloadRemoteOptions): BeforePrel type BeforePreloadRemoteOptions ={ preloadOps: Array; options: Options; - origin: FederationHost; + origin: ModuleFederation; } ``` @@ -322,7 +322,7 @@ Used to control the generation of resources that need to be preloaded. async function generatePreloadAssets(args: GeneratePreloadAssetsOptions): Promise type GeneratePreloadAssetsOptions ={ - origin: FederationHost; + origin: ModuleFederation; preloadOptions: PreloadOptions[number]; remote: Remote; remoteInfo: RemoteInfo; @@ -358,10 +358,9 @@ type CreateScriptOptions ={ * example ```ts -import { init } from '@module-federation/enhanced/runtime' -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -const changeScriptAttributePlugin: () => FederationRuntimePlugin = +const changeScriptAttributePlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'change-script-attribute', diff --git a/apps/website-new/docs/en/guide/basic/runtime/runtime.mdx b/apps/website-new/docs/en/guide/basic/runtime/runtime.mdx index b6572241113..b8c4b73f0f7 100644 --- a/apps/website-new/docs/en/guide/basic/runtime/runtime.mdx +++ b/apps/website-new/docs/en/guide/basic/runtime/runtime.mdx @@ -33,6 +33,10 @@ The two modes are not conflicting and can be used together. You can flexibly cho |The loading process can be affected through the `runtime`'s `plugin` mechanism|Currently does not support providing `plugin` to affect the loading process| |Does not support remote type hints|Supports remote type hints| +import Runtime from '@components/en/runtime'; + + + ## Installation import { PackageManagerTabs } from '@theme'; @@ -59,98 +63,267 @@ import { PackageManagerTabs } from '@theme'; ## Module Registration -```ts -import { init, loadRemote } from '@module-federation/enhanced/runtime'; - -init({ - // The name of the consumer module, required. If the ModuleFederationPlugin plugin is also registered, the `name` should be consistent with the `name`[/configure/name.html] configured in the plugin, +import { Steps, Tab, Tabs } from '@theme'; + + + + ```tsx + // If use build plugin, you can use `registerRemotes` directly. + import { registerRemotes } from '@module-federation/enhanced/runtime'; + + registerRemotes([ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ]); + ``` + + + ```ts + // If not use build plugin, you can create new instance and register remote. + import { createInstance } from '@module-federation/enhanced/runtime'; + + const mf = createInstance({ name: 'mf_host', - // Remote module registration information, including module name, alias, version, etc. remotes: [ - { - name: "remote1", - alias: "remotes-1", - entry: "http://localhost:3001/mf-manifest.json" - } - ], -}); -``` + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ] + }); + + mf.registerRemotes([ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ]); + ``` + + ## Module Loading -```tsx -import { loadRemote } from '@module-federation/enhanced/runtime'; - -export default () => { - const MyButton = React.lazy(() => - loadRemote('remote1').then(({ MyButton }) => { - return { - default: MyButton - }; - }), - ); - - return ( - - - - ); -} -``` -### Loading Anonymous Modules - -```tsx -import React from 'react'; -import { loadRemote } from '@module-federation/enhanced/runtime'; + + + ```tsx + // If use build plugin, you can use `loadRemote` directly. + import { loadRemote } from '@module-federation/enhanced/runtime'; + import React from 'react'; + + export default () => { + const MyButton = React.lazy(() => + loadRemote('remote1').then(({ MyButton }) => { + return { + default: MyButton + }; + }), + ); + + return ( + + + + ); + } + ``` + + + ```ts + // If not use build plugin, you can create new instance and load remote. + import { createInstance } from '@module-federation/enhanced/runtime'; + import React from 'react'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ] + }); + + export default () => { + const MyButton = React.lazy(() => + mf.loadRemote('remote1').then(({ MyButton }) => { + return { + default: MyButton + }; + }), + ); + + return ( + + + + ); + } + ``` + + -const RemoteButton = React.lazy(() => loadRemote('provider/button')); -// Can also be loaded by module alias: -// const RemoteButton = React.lazy(() => loadRemote('remotes-1/button')); +### Loading Anonymous Modules -export default () => { - return ( - - - - ); -} -``` + + + ```tsx + // If use build plugin, you can use `loadRemote` directly. + import React from 'react'; + import { loadRemote } from '@module-federation/enhanced/runtime'; + + const RemoteButton = React.lazy(() => loadRemote('provider/button')); + // 也可通过模块别名加载: + // const RemoteButton = React.lazy(() => loadRemote('remotes-1/button')); + + export default () => { + return ( + + + + ); + } + ``` + + + ```tsx + // If not use build plugin, you can create new instance and load remote. + import { createInstance } from '@module-federation/enhanced/runtime'; + import React from 'react'; + + // 创建实例 + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ] + }); + + // Use instance api load remote. + const RemoteButton = React.lazy(() => mf.loadRemote('provider/button')); + // Also support use alias to load + // const RemoteButton = React.lazy(() => mf.loadRemote('remotes-1/button')); + + export default () => { + return ( + + + + ); + } + ``` + + ### Loading Named Modules -```tsx -import React from 'react'; -import { loadRemote } from '@module-federation/enhanced/runtime'; - -export default () => { - const RemoteButton = React.lazy(() => - loadRemote('remote1/button').then(({ RemoteButton }) => { - return { - default: RemoteButton - }; - }), - ); - return ( - - - - ); -} -``` + + + ```tsx + // If use build plugin, you can use `loadRemote` directly. + import React from 'react'; + import { loadRemote } from '@module-federation/enhanced/runtime'; + + export default () => { + const RemoteButton = React.lazy(() => + loadRemote('remote1/button').then(({ RemoteButton }) => { + return { + default: RemoteButton + }; + }), + ); + return ( + + + + ); + } + ``` + + + ```tsx + // If not use build plugin, you can create new instance and load remote. + import { createInstance } from '@module-federation/enhanced/runtime'; + import React from 'react'; + + // 创建实例 + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ] + }); + + export default () => { + const RemoteButton = React.lazy(() => + // Use instance api load remote. + mf.loadRemote('remote1/button').then(({ RemoteButton }) => { + return { + default: RemoteButton + }; + }), + ); + return ( + + + + ); + } + ``` + + ### Loading Utility Functions -```ts -import { loadRemote } from '@module-federation/enhanced/runtime'; - -// Load the util exposed by app2 -loadRemote<{add: (...args: Array)=> number }>("@demo/app2/util").then((md)=>{ - md.add(1,2); -}); - -// Load by alias: -// loadRemote<{add: (...args: Array)=> number }>("app3/util").then((md)=>{ -// md.add(1,2); -// }); -``` + + + ```tsx + // If use build plugin, you can use `loadRemote` directly. + import React from 'react'; + import { loadRemote } from '@module-federation/enhanced/runtime'; + + // 加载 remote1 expose 的 util + loadRemote<{add: (...args: Array)=> number }>("remote1/util").then((md)=>{ + md.add(1,2); + }); + ``` + + + ```tsx + // If not use build plugin, you can create new instance and load remote. + import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'; + import React from 'react'; + + // create instance + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ] + }); + + mf.loadRemote<{add: (...args: Array)=> number }>("remote1/util").then((md)=>{ + md.add(1,2); + }); + ``` + + diff --git a/apps/website-new/docs/en/guide/troubleshooting/other.mdx b/apps/website-new/docs/en/guide/troubleshooting/other.mdx index 090f3992c4c..3d60b59055c 100644 --- a/apps/website-new/docs/en/guide/troubleshooting/other.mdx +++ b/apps/website-new/docs/en/guide/troubleshooting/other.mdx @@ -104,9 +104,9 @@ Add a runtime plugin via [runtimePlugins](../../configure/runtimeplugins) and co For example, to modify the crossorigin attribute of the preloaded link to `anonymous`: ```ts title="runtimePlugin.ts -import { FederationRuntimePlugin } from '@module-federation/runtime/types'; +import { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; -export default function MFLinkPlugin(): FederationRuntimePlugin { +export default function MFLinkPlugin(): ModuleFederationRuntimePlugin { return { name: 'link-plugin', createLink({ url }) { diff --git a/apps/website-new/docs/en/guide/troubleshooting/runtime/RUNTIME-009.mdx b/apps/website-new/docs/en/guide/troubleshooting/runtime/RUNTIME-009.mdx new file mode 100644 index 00000000000..4edea660f72 --- /dev/null +++ b/apps/website-new/docs/en/guide/troubleshooting/runtime/RUNTIME-009.mdx @@ -0,0 +1,13 @@ +import ErrorCodeTitle from '@components/ErrorCodeTitle'; + + + +## Reason + +Not use build plugin, but directly call runtime api. + +## Solution + +import Runtime from '@components/en/runtime'; + + diff --git a/apps/website-new/docs/en/plugin/dev/index.mdx b/apps/website-new/docs/en/plugin/dev/index.mdx index f27151f3bda..eab3ce871ce 100644 --- a/apps/website-new/docs/en/plugin/dev/index.mdx +++ b/apps/website-new/docs/en/plugin/dev/index.mdx @@ -11,14 +11,14 @@ Plugins developed by developers can modify the default behavior of `Module Feder ## Developing Plugins -Plugins are provided in the form of a function similar to `() => FederationRuntimePlugin`. +Plugins are provided in the form of a function similar to `() => ModuleFederationRuntimePlugin`. ### Plugin Example ```typescript title="custom-runtime-plugin.ts" -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -const runtimePlugin: () => FederationRuntimePlugin = function () { +const runtimePlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'my-runtime-plugin', beforeInit(args) { @@ -57,7 +57,7 @@ Registering plugins (either method is acceptable): const path = require('path'); module.exports = { plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ // ... runtimePlugins: [path.resolve(__dirname, './custom-runtime-plugin.ts')], }), @@ -93,8 +93,8 @@ The naming conventions for plugins are as follows: Here is an example: ```typescript -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -const pluginFooBar = (): FederationRuntimePlugin => ({ +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +const pluginFooBar = (): ModuleFederationRuntimePlugin => ({ name: 'xxx-plugin', //... }); @@ -104,632 +104,4 @@ export default pluginFooBar; ## Hooks - -### beforeInit - -`SyncWaterfallHook` - -Updates the federated host configuration before the initialization process of the remote container. - -- Type - -```typescript -function beforeInit(args: BeforeInitOptions): BeforeInitOptions; - -type BeforeInitOptions = { - userOptions: UserOptions; - options: FederationRuntimeOptions; - origin: FederationHost; - shareInfo: ShareInfos; -}; - -interface FederationRuntimeOptions { - id?: string; - name: string; - version?: string; - remotes: Array; - shared: ShareInfos; - plugins: Array; - inBrowser: boolean; -} -``` - -### init - -`SyncHook` - -Called during the initialization of the remote container. - -- Type - -```typescript -function init(args: InitOptions): void; - -type InitOptions = { - options: FederationRuntimeOptions; - origin: FederationHost; -}; -``` - -### beforeRequest - -`AsyncWaterfallHook` - -Called before resolving the remote container, used to inject containers or update certain content before lookup. - -- Type - -```typescript -async function beforeRequest( - args: BeforeRequestOptions, -): Promise; - -type BeforeRequestOptions = { - id: string; - options: FederationRuntimeOptions; - origin: FederationHost; -}; -``` - -### afterResolve - -`AsyncWaterfallHook` - -Called after the container is resolved, allows for redirection or modification of the resolved information. - -- Type - -```typescript -async function afterResolve( - args: AfterResolveOptions, -): Promise; - -type AfterResolveOptions = { - id: string; - pkgNameOrAlias: string; - expose: string; - remote: Remote; - options: FederationRuntimeOptions; - origin: FederationHost; - remoteInfo: RemoteInfo; - remoteSnapshot?: ModuleInfo; -}; -``` - -### onLoad - -`AsyncHook` - -Triggered when the federated module is fully loaded, allowing access to and modification of the exported content of the loaded file. - -- Type - -```typescript -async function onLoad(args: OnLoadOptions): Promise; - -type OnLoadOptions = { - id: string; - expose: string; - pkgNameOrAlias: string; - remote: Remote; - options: ModuleOptions; - origin: FederationHost; - exposeModule: any; - exposeModuleFactory: any; - moduleInstance: Module; -}; - -type ModuleOptions = { - remoteInfo: RemoteInfo; - host: FederationHost; -}; - -interface RemoteInfo { - name: string; - version?: string; - buildVersion?: string; - entry: string; - type: RemoteEntryType; - entryGlobalName: string; - shareScope: string; -} -``` - -### handlePreloadModule - -`SyncHook` - -Handles the logic for preloading federated modules. - -- Type - -```typescript -function handlePreloadModule(args: HandlePreloadModuleOptions): void; - -type HandlePreloadModuleOptions = { - id: string; - name: string; - remoteSnapshot: ModuleInfo; - preloadConfig: PreloadRemoteArgs; -}; -``` - -### errorLoadRemote - -`AsyncHook` - -This hook is invoked when the loading of a federated module fails, enabling customized error handling strategies. - -It is designed to be triggered during various lifecycle stages of module loading in the event that any of the stages fail. - -Utilize `args.lifecycle` to identify the specific lifecycle stage that has initiated the call to `errorLoadRemote`, allowing for appropriate error handling or fallback mechanisms. - - -- Type - -```typescript -async function errorLoadRemote( - args: ErrorLoadRemoteOptions, -): Promise; - -type ErrorLoadRemoteOptions = { - id: string; - error: unknown; - options?: any; - from: 'build' | 'runtime'; - lifecycle: 'onLoad' | 'beforeRequest'; - origin: FederationHost; -}; -``` - -- Example - -```typescript -import { init, loadRemote } from '@module-federation/enhanced/runtime'; - -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const fallbackPlugin: () => FederationRuntimePlugin = function () { - return { - name: 'fallback-plugin', - errorLoadRemote(args) { - if(args.lifecycle === 'onLoad') { - const fallback = 'fallback'; - return fallback; - } else if (args.lifecycle === 'beforeRequest') { - return args - } - } - }; -}; - -init({ - name: '@demo/app-main', - remotes: [ - { - name: '@demo/app2', - entry: 'http://localhost:3006/remoteEntry.js', - alias: 'app2' - } - ], - plugins: [fallbackPlugin()] -}); - -loadRemote('app2/un-existed-module').then((mod) => { - expect(mod).toEqual('fallback'); -}); -``` - -### beforeLoadShare - -`AsyncWaterfallHook` - -Called before attempting to load or negotiate shared modules between federated applications. - -- Type - -```typescript -async function beforeLoadShare( - args: BeforeLoadShareOptions, -): Promise; - -type BeforeLoadShareOptions = { - pkgName: string; - shareInfo?: Shared; - shared: Options['shared']; - origin: FederationHost; -}; -``` - -### resolveShare - -`SyncWaterfallHook` - -Allows for manual resolution of shared module requests. - -- Type - -```typescript -function resolveShare(args: ResolveShareOptions): ResolveShareOptions; - -type ResolveShareOptions = { - shareScopeMap: ShareScopeMap; - scope: string; - pkgName: string; - version: string; - GlobalFederation: Federation; - resolver: () => Shared | undefined; -}; -``` - -- Example - -```typescript -import { init, loadRemote } from '@module-federation/enhanced/runtime'; - -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const customSharedPlugin: () => FederationRuntimePlugin = function () { - return { - name: 'custom-shared-plugin', - resolveShare(args) { - const { shareScopeMap, scope, pkgName, version, GlobalFederation } = args; - - if (pkgName !== 'react') { - return args; - } - - // set lib - args.resolver = function () { - shareScopeMap[scope][pkgName][version] = { - lib: ()=>window.React, - loaded:true, - loading: Promise.resolve(()=>window.React) - }; // Manually replace the local share scope with the desired module - return shareScopeMap[scope][pkgName][version]; - }; - - // set get - args.resolver = function () { - shareScopeMap[scope][pkgName][version] = { - get: async ()=>()=>window.React, - }; // Manually replace the local share scope with the desired module - return shareScopeMap[scope][pkgName][version]; - }; - return args; - }, - }; -}; - -init({ - name: '@demo/app-main', - shared: { - react: { - version: '17.0.0', - scope: 'default', - lib: () => React, - shareConfig: { - singleton: true, - requiredVersion: '^17.0.0', - }, - }, - }, - plugins: [customSharedPlugin()], -}); - -window.React = () => 'Desired Shared'; - -loadShare('react').then((reactFactory) => { - expect(reactFactory()).toEqual(window.React()); -}); -``` - -### beforePreloadRemote - -`AsyncHook` - -Called before the preload handler executes any preload logic. - -- Type - -```typescript -async function beforePreloadRemote( - args: BeforePreloadRemoteOptions, -): BeforePreloadRemoteOptions; - -type BeforePreloadRemoteOptions = { - preloadOps: Array; - options: Options; - origin: FederationHost; -}; -``` - -### generatePreloadAssets - -`AsyncHook` - -Generates preload assets based on configuration. - -- Type - -```typescript -async function generatePreloadAssets( - args: GeneratePreloadAssetsOptions, -): Promise; - -type GeneratePreloadAssetsOptions = { - origin: FederationHost; - preloadOptions: PreloadOptions[number]; - remote: Remote; - remoteInfo: RemoteInfo; - remoteSnapshot: ModuleInfo; - globalSnapshot: GlobalModuleInfo; -}; - -interface PreloadAssets { - cssAssets: Array; - jsAssetsWithoutEntry: Array; - entryAssets: Array; -} -``` - -### createScript - -`SyncHook` - -- Type - -```typescript -function createScript(args: CreateScriptOptions): HTMLScriptElement | {script?: HTMLScriptElement, timeout?: number } | void; - -type CreateScriptOptions = { - url: string; - attrs?: Record; -}; -``` - -- Example - -```typescript -import { init } from '@module-federation/enhanced/runtime'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () { - return { - name: 'change-script-attribute', - createScript({ url, attrs }) { - if (url === testRemoteEntry) { - let script = document.createElement('script'); - script.src = testRemoteEntry; - // can modify the attrs object - attrs['loader-hooks'] = 'isTrue'; - // or add them to the script - script.setAttribute('crossorigin', 'anonymous'); - return script; - } - }, - }; -}; -``` - -- Example with script timeout - -```typescript -import { init } from '@module-federation/enhanced/runtime'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () { - return { - name: 'change-script-attribute', - createScript({ url }) { - if (url === testRemoteEntry) { - let script = document.createElement('script'); - script.src = testRemoteEntry; - script.setAttribute('loader-hooks', 'isTrue'); - script.setAttribute('crossorigin', 'anonymous'); - return { script, timeout: 1000 } - } - }, - }; -}; -``` - - -### createLink - -`SyncHook` - -- **Type** - -```typescript -function createLink(args: CreateLinkOptions): HTMLLinkElement | void; - -type CreateScriptOptions = { - url: string; - attrs?: Record; -}; -``` - -- Example - -```typescript -import { init } from '@module-federation/enhanced/runtime'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const changeLinkAttributePlugin: () => FederationRuntimePlugin = function () { - return { - name: 'change-link-attribute', - createLink({ url }) { - link.setAttribute('href', url); - link.setAttribute('rel', 'preload'); - link.setAttribute('as', 'script'); - link.setAttribute('crossorigin', 'anonymous'); - return link; - }, - }; -}; -``` - -### fetch -The `fetch` function allows customizing the request that fetches the manifest JSON. A successful `Response` must yield a valid JSON. - -`AsyncHook` - -- **Type** - -```typescript -function fetch(manifestUrl: string, requestInit: RequestInit): Promise | void | false; -``` - -- Example for including the credentials when fetching the manifest JSON: - -```typescript -// fetch-manifest-with-credentials-plugin.ts -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -export default function (): FederationRuntimePlugin { - return { - name: 'fetch-manifest-with-credentials-plugin', - fetch(manifestUrl, requestInit) { - return fetch(manifestUrl, { - ...requestInit, - credentials: 'include' - }); - }, - } -}; -``` - -### loadEntry -The `loadEntry` function allows for full customization of remotes, enabling you to extend and create new remote types. The following two simple examples demonstrate loading JSON data and module delegation. - -`asyncHook` - -- **Type** - -```typescript -function loadEntry(args: LoadEntryOptions): RemoteEntryExports | void; - -type LoadEntryOptions = { - createScriptHook: SyncHook, - remoteEntryExports?: RemoteEntryExports, - remoteInfo: RemoteInfo -}; -interface RemoteInfo { - name: string; - version?: string; - buildVersion?: string; - entry: string; - type: RemoteEntryType; - entryGlobalName: string; - shareScope: string; -} -export type RemoteEntryExports = { - get: (id: string) => () => Promise; - init: ( - shareScope: ShareScopeMap[string], - initScope?: InitScope, - remoteEntryInitOPtions?: RemoteEntryInitOptions, - ) => void | Promise; -}; -``` - -- Example Loading JSON Data - -```typescript -// load-json-data-plugin.ts -import { init } from '@module-federation/enhanced/runtime'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () { - return { - name: 'load-json-data-plugin', - loadEntry({ remoteInfo }) { - if (remoteInfo.jsonA === "jsonA") { - return { - init(shareScope, initScope, remoteEntryInitOPtions) {}, - async get(path) { - const json = await fetch(remoteInfo.entry + ".json").then(res => res.json()) - return () => ({ - path, - json - }) - } - } - } - }, - }; -}; -``` -```ts -// module-federation-config -{ - remotes: { - jsonA: "jsonA@https://cdn.jsdelivr.net/npm/@module-federation/runtime/package" - } -} -``` -```ts -// src/bootstrap.js -import jsonA from "jsonA" -jsonA // {...json data} -``` - -- Exmaple Delegate Modules - -```typescript -// delegate-modules-plugin.ts -import { init } from '@module-federation/enhanced/runtime'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () { - return { - name: 'delegate-modules-plugin', - loadEntry({ remoteInfo }) { - if (remoteInfo.name === "delegateModulesA") { - return { - init(shareScope, initScope, remoteEntryInitOPtions) {}, - async get(path) { - path = path.replace("./", "") - const {[path]: factory} = await import("./delegateModulesA.js") - const result = await factory() - return () => result - } - } - } - }, - }; -}; -``` -```ts -// ./src/delegateModulesA.js -export async function test1() { - return new Promise(resolve => { - setTimeout(() => { - resolve("test1 value") - }, 3000) - }) -} -export async function test2() { - return new Promise(resolve => { - setTimeout(() => { - resolve("test2 value") - }, 3000) - }) -} -``` -```ts -// module-federation-config -{ - remotes: { - delegateModulesA: "delegateModulesA@https://delegateModulesA.js" - } -} -``` -```ts -// src/bootstrap.js -import test1 from "delegateModulesA/test1" -import test2 from "delegateModulesA/test2" -test1 // "test1 value" -test2 // "test2 value" -``` +Refer to [Runtime Hooks](../../guide/basic/runtime/runtime-hooks) diff --git a/apps/website-new/docs/en/plugin/plugins/retry-plugin.mdx b/apps/website-new/docs/en/plugin/plugins/retry-plugin.mdx index 9e03a9af0a8..f7d3f7e5a80 100644 --- a/apps/website-new/docs/en/plugin/plugins/retry-plugin.mdx +++ b/apps/website-new/docs/en/plugin/plugins/retry-plugin.mdx @@ -84,7 +84,7 @@ the resources that need to be retried are divided into two types: fetch type and ## Type ```ts -const RetryPlugin: (params: RetryPluginParams) => FederationRuntimePlugin; +const RetryPlugin: (params: RetryPluginParams) => ModuleFederationRuntimePlugin; type RetryPluginParams = { fetch?: FetchWithRetryOptions; // fetch retry options diff --git a/apps/website-new/docs/zh/blog/announcement.mdx b/apps/website-new/docs/zh/blog/announcement.mdx index b91cc23e561..17edc720f2c 100644 --- a/apps/website-new/docs/zh/blog/announcement.mdx +++ b/apps/website-new/docs/zh/blog/announcement.mdx @@ -36,9 +36,9 @@ Module Federation 作为 Webpack 5 推出的重磅功能,自发布以来已经 Module Federation 的新版本带来了显著的变化。在这个版本中,原本嵌入在 Webpack 中的 Module Federation Runtime 能力被提取出来,形成了一个独立的 SDK。这个变化意味着,现在我们可以不依赖于任何构建工具,就能够动态地注册和加载远程模块,以及注册共享模块。此外,模块的预加载和运行时插件的使用也得到了增强,从而为模块的加载过程提供了更为强大的控制能力。 ```ts -import { init, loadRemote } from '@module-federation/enhanced/runtime'; +import { createInstance } from '@module-federation/enhanced/runtime'; -init({ +const mf = createInstance({ name: '@demo/app-main', remotes: [ { @@ -54,7 +54,7 @@ init({ ], }); -loadRemote("app2/util").then((md)=>{ +mf.loadRemote("app2/util").then((md)=>{ md.add(1,2,3); }); ``` diff --git a/apps/website-new/docs/zh/blog/error-load-remote.mdx b/apps/website-new/docs/zh/blog/error-load-remote.mdx index 822108eba5e..a31b2ef7ae7 100644 --- a/apps/website-new/docs/zh/blog/error-load-remote.mdx +++ b/apps/website-new/docs/zh/blog/error-load-remote.mdx @@ -57,11 +57,11 @@ Module Federation 官方提供重试插件 [@module-federation/retry-plugin](htt ```diff import React from 'react'; -import { init, loadRemote } from '@module-federation/enhanced/runtime'; +import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'; + import { RetryPlugin } from '@module-federation/retry-plugin'; // 模块注册 -init({ +const mf = createInstance({ name: 'host', remotes: [ { @@ -79,7 +79,7 @@ init({ }); // 模块加载 -const Remote1Button = React.lazy(() => loadRemote('remote1/button')); +const Remote1Button = React.lazy(() => mf.loadRemote('remote1/button')); export default () => { return ( @@ -90,7 +90,7 @@ export default () => { } // 方法/函数加载 -loadRemote<{add: (...args: Array)=> number }>("remote1/util").then((md)=>{ +mf.loadRemote<{add: (...args: Array)=> number }>("remote1/util").then((md)=>{ md.add(1,2,3); }); @@ -171,15 +171,16 @@ export default retryPlugin; #### 纯运行时 + 动态 import + 纯运行时注册时,远程模块在注册后实际加载前才会请求资源。 ```tsx import React from 'react'; -import { init, loadRemote } from '@module-federation/enhanced/runtime'; +import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'; import { RetryPlugin } from '@module-federation/retry-plugin'; // 模块注册 -init({ +const mf = createInstance({ name: 'host', remotes: [ { @@ -218,10 +219,10 @@ loadRemote<{add: (...args: Array)=> number }>("remote1/util").then((md)= ```diff import React from 'react'; -import { init, loadRemote } from '@module-federation/enhanced/runtime'; +import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'; import { RetryPlugin } from '@module-federation/retry-plugin'; -+ const fallbackPlugin: () => FederationRuntimePlugin = function () { ++ const fallbackPlugin: () => ModuleFederationRuntimePlugin = function () { + return { + name: 'fallback-plugin', + errorLoadRemote(args) { @@ -231,7 +232,7 @@ import { RetryPlugin } from '@module-federation/retry-plugin'; + }; // 模块注册 -init({ +const mf = createInstance({ name: 'host', remotes: [ { @@ -250,7 +251,7 @@ init({ }); // 模块加载 -const Remote1Button = React.lazy(() => loadRemote('remote1/button')); +const Remote1Button = React.lazy(() => mf.loadRemote('remote1/button')); export default () => { return ( @@ -261,7 +262,7 @@ export default () => { } // 方法/函数加载 -loadRemote<{add: (...args: Array)=> number }>("remote1/util").then((md)=>{ +mf.loadRemote<{add: (...args: Array)=> number }>("remote1/util").then((md)=>{ md.add(1,2,3); }); @@ -320,7 +321,7 @@ export default defineConfig({ ```tsx // src/runtime-plugin/fallback.ts -import type { FederationRuntimePlugin } from '@module-federation/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; interface FallbackConfig { // 备用服务地址 @@ -329,7 +330,7 @@ interface FallbackConfig { errorMessage?: string; } -const fallbackPlugin = (config: FallbackConfig = {}): FederationRuntimePlugin => { +const fallbackPlugin = (config: FallbackConfig = {}): ModuleFederationRuntimePlugin => { const { backupEntryUrl = 'http://localhost:2002/mf-manifest.json', errorMessage = '模块加载失败,请稍后重试' @@ -494,9 +495,9 @@ export default fallbackPlugin; 如果不需要区分错误类型,也可以使用一个通用的错误处理方案: ```ts - import type { FederationRuntimePlugin } from '@module-federation/runtime'; + import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; - const fallbackPlugin = (errorMessage = '模块加载失败,请稍后重试'): FederationRuntimePlugin => { + const fallbackPlugin = (errorMessage = '模块加载失败,请稍后重试'): ModuleFederationRuntimePlugin => { return { name: 'fallback-plugin', async errorLoadRemote() { diff --git a/apps/website-new/docs/zh/configure/exposes.mdx b/apps/website-new/docs/zh/configure/exposes.mdx index 17f572a8ffc..0315bc6be39 100644 --- a/apps/website-new/docs/zh/configure/exposes.mdx +++ b/apps/website-new/docs/zh/configure/exposes.mdx @@ -33,7 +33,7 @@ interface ExposesConfig { ```jsx module.exports = { plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ name: 'provider', exposes: { // 注意: 不支持 "./",为 . 导出是表示为 default 默认导出 diff --git a/apps/website-new/docs/zh/configure/getpublicpath.mdx b/apps/website-new/docs/zh/configure/getpublicpath.mdx index a4b7b09070f..23c3e469bc2 100644 --- a/apps/website-new/docs/zh/configure/getpublicpath.mdx +++ b/apps/website-new/docs/zh/configure/getpublicpath.mdx @@ -12,7 +12,7 @@ ```ts title="rspack.config.ts" module.exports = { plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ name: 'provider', exposes: { './Button': './src/components/Button.tsx', diff --git a/apps/website-new/docs/zh/configure/implementation.mdx b/apps/website-new/docs/zh/configure/implementation.mdx index e8cd2a0a770..c3c7606199f 100644 --- a/apps/website-new/docs/zh/configure/implementation.mdx +++ b/apps/website-new/docs/zh/configure/implementation.mdx @@ -10,7 +10,7 @@ module.exports = { // ...other configurations plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ // ...other Module Federation options implementation: require.resolve('@module-federation/runtime-tools'), }), diff --git a/apps/website-new/docs/zh/configure/remotes.mdx b/apps/website-new/docs/zh/configure/remotes.mdx index ab8301b0250..380ab997a80 100644 --- a/apps/website-new/docs/zh/configure/remotes.mdx +++ b/apps/website-new/docs/zh/configure/remotes.mdx @@ -31,7 +31,7 @@ interface PluginRemoteOptions { ```js module.exports = { plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ name: 'host', // 下面的 remotes 中定义了两个 remote,分别是名称为:manifest_provider 在 3011 端口启动的项目、js_entry_provider 在 3012 端口启动的项目 remotes: { diff --git a/apps/website-new/docs/zh/configure/runtimeplugins.mdx b/apps/website-new/docs/zh/configure/runtimeplugins.mdx index a89c98a8c18..fc6fddcc807 100644 --- a/apps/website-new/docs/zh/configure/runtimeplugins.mdx +++ b/apps/website-new/docs/zh/configure/runtimeplugins.mdx @@ -13,9 +13,9 @@ 创建运行时插件文件: `custom-runtime-plugin.ts` ```ts title="custom-runtime-plugin.ts" -import { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -export default function (): FederationRuntimePlugin { +export default function (): ModuleFederationRuntimePlugin { return { name: 'custom-plugin-build', beforeInit(args) { @@ -37,7 +37,7 @@ export default function (): FederationRuntimePlugin { const path = require('path'); module.exports = { plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ name: 'host', remotes: { 'manifest-provider': diff --git a/apps/website-new/docs/zh/guide/basic/runtime/runtime-api.mdx b/apps/website-new/docs/zh/guide/basic/runtime/runtime-api.mdx index 0fba9c823d3..32c17b8b3f5 100644 --- a/apps/website-new/docs/zh/guide/basic/runtime/runtime-api.mdx +++ b/apps/website-new/docs/zh/guide/basic/runtime/runtime-api.mdx @@ -1,11 +1,57 @@ # Runtime API -## init +import { Steps, Tab, Tabs, Badge, Aside } from '@theme'; +import Collapse from '@components/Collapse' +import Runtime from '@components/zh/runtime'; + + + +## createInstance + +用于创建 ModuleFederation 实例。 + +* 什么时候使用 `createInstance` ? + +为了保证 `ModuleFederation` 实例的唯一性,我们在构建插件创建实例后,会将其存储到内存中,导出的 API 都是先从内存中获取 `ModuleFederation` 实例,然后再调用 `ModuleFederation` 实例的 API。这也是为什么 `loadRemote` 等 API 可以直接使用的原因。 + +这种单例模式适用于大多数场景,但如果在以下场景,你需要使用 `createInstance`: + +- 没有使用构建插件(纯运行时场景) +- 需要创建多个 ModuleFederation 实例,每个实例的配置不同 +- 希望使用 ModuleFederation 分治特性,封装相应的 API 提供给其他项目使用 + +```ts +import { createInstance } from '@module-federation/enhanced/runtime'; + +const mf = createInstance({ + name: 'host', + remotes: [ + { + name: 'sub1', + entry: 'http://localhost:8080/mf-manifest.json' + } + ] +}); + +mf.loadRemote('sub1/util').then((m) => m.add(1, 2, 3)); +``` + +## init 废弃 + +:::warning Warning + +此 API 将被废弃。如果需要获取已创建的实例,可以使用 [getInstance](#getinstance) 获取已创建的实例。 + +如果需要创建新的实例,请使用 [createInstance](#createinstance)。 + +::: - Type: `init(options: InitOptions): void` - 用于运行时动态注册 `Vmok` 模块 - InitOptions: + + ```ts type InitOptions { // 当前 host 的名称 @@ -67,9 +113,11 @@ type Share = { }; ``` -- 示例 + + + -```js +```ts import { init, loadRemote } from '@module-federation/enhanced/runtime'; init({ @@ -85,112 +133,492 @@ init({ ], }); ``` + -## loadRemote +### 如何迁移 -- Type: `loadRemote(id: string)` -- 加载远程模块 -- 示例 +#### Build Plugin(使用构建插件) -```javascript -import { init, loadRemote } from '@module-federation/enhanced/runtime'; +1. 移除 `init` API 的调用 +2. 使用 [registerShared](#registershared) 代替 `init` 中的 `shared` 配置 +3. 使用 [registerRemotes](#registerremotes) 代替 `init` 中的 `remotes` 配置 +4. 使用 [registerPlugins](#registerplugins) 代替 `init` 中的 `plugins` 配置 +5. 使用 [getInstance](#getinstance) 获取构建插件创建的 `ModuleFederation` 实例 -init({ - name: "mf_host", - remotes: [ +```diff +- import { init } from '@module-federation/enhanced/runtime'; ++ import { registerShared, registerRemotes, registerPlugins, getInstance } from '@module-federation/enhanced/runtime'; +import React from 'react'; +import mfRuntimePlugin from 'mf-runtime-plugin'; + +- const instance = init({ ++ const instance = getInstance(); +- name: 'mf_host', +- remotes: [ +- { +- name: 'remote', +- entry: 'http://localhost:2001/mf-manifest.json', +- } +- ], ++ registerRemotes([ ++ { ++ name: 'remote', ++ entry: 'http://localhost:2001/mf-manifest.json', ++ } ++ ]); +- shared: { +- react: { +- version: "18.0.0", +- scope: "default", +- lib: ()=> React, +- shareConfig: { +- singleton: true, +- requiredVersion: "^18.0.0" +- } +- }, +- }, ++ registerShared({ ++ react: { ++ version: "18.0.0", ++ scope: "default", ++ lib: ()=> React, ++ shareConfig: { ++ singleton: true, ++ requiredVersion: "^18.0.0" ++ } ++ } ++ }); +- plugins: [mfRuntimePlugin()] ++ registerPlugins([mfRuntimePlugin()]); +-}); + +``` + +#### Pure Runtime(未使用构建插件) + + +```diff +- import { init } from '@module-federation/enhanced/runtime'; ++ import { createInstance } from '@module-federation/enhanced/runtime'; + +-const instance = init({ ++ const instance = createInstance({ +name: 'mf_host', +remotes: [ + { + name: 'remote', + entry: 'http://localhost:2001/mf-manifest.json', + } +], +shared: { + react: { + version: "18.0.0", + scope: "default", + lib: ()=> React, + shareConfig: { + singleton: true, + requiredVersion: "^18.0.0" + } + }, +}, +plugins: [mfRuntimePlugin()] +}); +``` + + +如果没有使用构建插件,你可以使用 [createInstance](#createinstance)替换 `init` ,其参数完全一致。 + +## getInstance + +- Type: `getInstance(): ModuleFederation` +- 获取构建插件创建的 `ModuleFederation` 实例 + +当使用了构建插件后,可以调用 `getInstance` 获取构建插件创建的 `ModuleFederation` 实例。 + +```ts +import { getInstance } from '@module-federation/enhanced/runtime'; + +const mfInstance = getInstance(); +mfInstance.loadRemote('remote/util'); +``` + +如果没有使用构建插件,调用 `getInstance` 会抛出异常,此时你需要使用 [createInstance](#createinstance) 来创建一个新的实例。 + +## registerRemotes + + + +```typescript +function registerRemotes(remotes: Remote[], options?: { force?: boolean }) {} + +type Remote = (RemoteWithEntry | RemoteWithVersion) & RemoteInfoCommon; + +interface RemoteInfoCommon { + alias?: string; + shareScope?: string; + type?: RemoteEntryType; + entryGlobalName?: string; +} + +interface RemoteWithEntry { + name: string; + entry: string; +} + +interface RemoteWithVersion { + name: string; + version: string; +} +``` + + + +- Details + +**info**: 请谨慎设置 `force:true` ! + +如果设置 `force: true`, 这会覆盖已经注册(且加载的模块, 并且自动删除已经加载过的模块缓存(如果已经加载过),同时在控制台输出警告,告知这操作存在风险性。 + + + + ```tsx + import { registerRemotes } from '@module-federation/enhanced/runtime'; + + // 增加新的 remote sub2 + registerRemotes([ + { + name: 'sub2', + entry: 'http://localhost:2002/mf-manifest.json', + } + ]); + + // 覆盖之前的 remote sub1 + registerRemotes([ + { + name: 'sub1', + entry: 'http://localhost:2003/mf-manifest.json', + } + ],{ force:true }); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ { - name: "remote", - // 远程模块别名,配置别名后可通过别名加载模块。请注意:alias 和 name 的前缀不能相等 - alias: "app1", - // 指定以 `mf-manifest.json` 结尾的远程模块地址,该文件由 mf 插件生成 - entry: "http://localhost:2001/vmok-manifest.json" + name: 'sub1', + entry: 'http://localhost:2001/mf-manifest.json', } - ] -}); + ] + }); + + // 增加新的 remote sub2 + mf.registerRemotes([ + { + name: 'sub2', + entry: 'http://localhost:2002/mf-manifest.json', + } + ]); -// remoteName + expose -loadRemote("remote/util").then((m)=> m.add(1,2,3)); + // 覆盖之前的 remote sub1 + mf.registerRemotes([ + { + name: 'sub1', + entry: 'http://localhost:2003/mf-manifest.json', + } + ],{ force:true }); + ``` + + -// alias + expose -loadRemote("app1/util").then((m)=> m.add(1,2,3)); +## registerPlugins + +- type + +```typescript +function registerPlugins(plugins: ModuleFederationRuntimePlugin[]) {} ``` -## loadShare + + + ```tsx + import { registerPlugins } from '@module-federation/enhanced/runtime'; + import runtimePlugin from './custom-runtime-plugin'; -- Type: `loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})` -- 获取 `share` 依赖,当全局环境有符合当前 `host` 的 `share` 依赖时,将优先复用当前已存在且满足 `share` 条件的依赖,否则将加载自身的依赖并存入全局缓存 -- 该 `API` **一般不由用户直接调用,用于构建插件转换自身依赖时使用** + // 增加新的运行时插件 + registerPlugins([runtimePlugin()]); + + registerPlugins([ + { + name: 'custom-plugin-runtime', + beforeInit(args) { + const { userOptions, origin } = args; + if (origin.options.name && origin.options.name !== userOptions.name) { + userOptions.name = origin.options.name; + } + console.log('[build time inject] beforeInit: ', args); + return args; + }, + beforeLoadShare(args) { + console.log('[build time inject] beforeLoadShare: ', args); + return args; + }, + createLink({ url }) { + const link = document.createElement('link'); + link.setAttribute('href', url); + link.setAttribute('rel', 'preload'); + link.setAttribute('as', 'script'); + link.setAttribute('crossorigin', 'anonymous'); + return link; + }, + } + ]); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + import runtimePlugin from './custom-runtime-plugin'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'sub1', + entry: 'http://localhost:2001/mf-manifest.json', + } + ] + }); -- 示例 + // 增加新的运行时插件 + mf.registerPlugins([runtimePlugin()]); -```js -import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime'; -import React from 'react'; -import ReactDom from 'react-dom'; + mf.registerPlugins([ + { + name: 'custom-plugin-runtime', + beforeInit(args) { + const { userOptions, origin } = args; + if (origin.options.name && origin.options.name !== userOptions.name) { + userOptions.name = origin.options.name; + } + console.log('[build time inject] beforeInit: ', args); + return args; + }, + beforeLoadShare(args) { + console.log('[build time inject] beforeLoadShare: ', args); + return args; + }, + createLink({ url }) { + const link = document.createElement('link'); + link.setAttribute('href', url); + link.setAttribute('rel', 'preload'); + link.setAttribute('as', 'script'); + link.setAttribute('crossorigin', 'anonymous'); + return link; + }, + } + ]); + ``` + + -init({ - name: "mf_host", - remotes: [], - shared: { - react: { - version: "17.0.0", +## registerShared + + + +```typescript +function registerShared(shared: Shared) {} + +type Shared = { + [pkgName: string]: ShareArgs | ShareArgs[]; +} +type ShareArgs = (SharedBaseArgs & { + get: SharedGetter; +}) | (SharedBaseArgs & { + lib: () => Module; +}) | SharedBaseArgs; +type SharedBaseArgs = { + version?: string; + shareConfig?: SharedConfig; + scope?: string | Array; + deps?: Array; + strategy?: 'version-first' | 'loaded-first'; + loaded?: boolean; +}; +interface SharedConfig { + singleton?: boolean; + requiredVersion: false | string; + eager?: boolean; + strictVersion?: boolean; + layer?: string | null; +} +type SharedGetter = (() => () => Module) | (() => Promise<() => Module>); +``` + + + + + + ```tsx + import { registerShared } from '@module-federation/enhanced/runtime'; + import React from 'react'; + import ReactDom from 'react-dom'; + + registerShared({ + react: { + version: "18.0.0", scope: "default", lib: ()=> React, shareConfig: { singleton: true, - requiredVersion: "^17.0.0" + requiredVersion: "^18.0.0" } }, "react-dom": { - version: "17.0.0", + version: "18.0.0", scope: "default", lib: ()=> ReactDom, shareConfig: { singleton: true, - requiredVersion: "^17.0.0" + requiredVersion: "^18.0.0" } } - } -}); - + }); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + import React from 'react'; + import ReactDom from 'react-dom'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'sub1', + entry: 'http://localhost:2001/mf-manifest.json', + } + ] + }); -loadShare("react").then((reactFactory)=>{ - console.log(reactFactory()) -}); -``` + registerShared({ + react: { + version: "18.0.0", + scope: "default", + lib: ()=> React, + shareConfig: { + singleton: true, + requiredVersion: "^18.0.0" + } + }, + "react-dom": { + version: "18.0.0", + scope: "default", + lib: ()=> ReactDom, + shareConfig: { + singleton: true, + requiredVersion: "^18.0.0" + } + } + }); + ``` + + -如果设置了多个版本 shared,默认会返回已加载且最高版本的 shared 。可以通过设置 `extraOptions.resolver` 来改变这个行为: +## loadShare -```js -import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime'; +- Type: `loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})` +- 获取 `share` 依赖,当全局环境有符合当前 `host` 的 `share` 依赖时,将优先复用当前已存在且满足 `share` 条件的依赖,否则将加载自身的依赖并存入全局缓存 +- 该 `API` **一般不由用户直接调用,用于构建插件转换自身依赖时使用** -init({ - name: 'mf_host', - remotes: [], - shared: { - react: [ - { - version: '17.0.0', - scope: 'default', - get: async ()=>() => ({ version: '17.0.0' }), - shareConfig: { - singleton: true, - requiredVersion: '^17.0.0', - }, + + + ```tsx + import { registerShared, loadShare } from '@module-federation/enhanced/runtime'; + import React from 'react'; + import ReactDom from 'react-dom'; + + registerShared({ + react: { + version: "17.0.0", + scope: "default", + lib: ()=> React, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } }, - { - version: '18.0.0', - scope: 'default', - // pass lib means the shared has loaded - lib: () => ({ version: '18.0.0' }), - shareConfig: { - singleton: true, - requiredVersion: '^18.0.0', - }, + "react-dom": { + version: "17.0.0", + scope: "default", + lib: ()=> ReactDom, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } + } + }); + + + loadShare("react").then((reactFactory)=>{ + console.log(reactFactory()) + }); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + import React from 'react'; + import ReactDom from 'react-dom'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote', + entry: 'http://localhost:2001/mf-manifest.json', + alias: 'app1' + } + ] + }); + + mf.registerShared({ + react: { + version: "17.0.0", + scope: "default", + lib: ()=> React, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } }, - ], - }, -}); + "react-dom": { + version: "17.0.0", + scope: "default", + lib: ()=> ReactDom, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } + } + }); + + + mf.loadShare("react").then((reactFactory)=>{ + console.log(reactFactory()) + }); + ``` + + + +如果设置了多个版本 shared,默认会返回已加载且最高版本的 shared 。可以通过设置 `extraOptions.resolver` 来改变这个行为: + +```ts +// ... loadShare('react', { resolver: (sharedOptions) => { @@ -203,9 +631,50 @@ loadShare('react', { }); ``` +## loadRemote + +- Type: `loadRemote(id: string)` +- 加载远程模块 + + + + ```tsx + import { loadRemote } from '@module-federation/enhanced/runtime'; + + // remoteName + expose + loadRemote("remote/util").then((m)=> m.add(1,2,3)); + + // alias + expose + loadRemote("app1/util").then((m)=> m.add(1,2,3)); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote', + entry: 'http://localhost:2001/mf-manifest.json', + alias: 'app1' + } + ] + }); + + // remoteName + expose + mf.loadRemote("remote/util").then((m)=> m.add(1,2,3)); + + // alias + expose + mf.loadRemote("app1/util").then((m)=> m.add(1,2,3)); + ``` + + + ## preloadRemote -- type + ```typescript async function preloadRemote(preloadOptions: Array){} @@ -232,6 +701,8 @@ type PreloadRemoteArgs = { }; ``` + + - Details 通过 `preloadRemote` 可以在更早的阶段开始预加载模块资源,避免出现瀑布请求,`preloadRemote` 可以预加载哪些内容: @@ -241,179 +712,117 @@ type PreloadRemoteArgs = { * `remote` 的同步资源还是异步资源 * `remote` 依赖的 `remote` 资源 -- Example + + + ```tsx + import { registerRemotes, preloadRemote } from '@module-federation/enhanced/runtime'; -```ts -import { init, preloadRemote } from '@module-federation/enhanced/runtime'; -init({ - name: 'mf_host', - remotes: [ + registerRemotes([ + { + name: 'sub1', + entry: "http://localhost:2001/mf-manifest.json" + }, + { + name: 'sub2', + entry: "http://localhost:2002/mf-manifest.json" + }, + { + name: 'sub3', + entry: "http://localhost:2003/mf-manifest.json" + }, + ]); + + // 预加载 sub1 模块 + // 过滤资源名称中携带 ignore 的资源信息 + // 只预加载子依赖的 sub1-button 模块 + preloadRemote([ { - name: 'sub1', - entry: "http://localhost:2001/mf-manifest.json" + nameOrAlias: 'sub1', + filter(assetUrl) { + return assetUrl.indexOf('ignore') === -1; + }, + depsRemote: [{ nameOrAlias: 'sub1-button' }], }, + ]); + + + // 预加载 sub2 模块 + // 预加载 sub2 下的所有 expose + // 预加载 sub2 的同步资源和异步资源 + preloadRemote([ { - name: 'sub2', - entry: "http://localhost:2002/mf-manifest.json" + nameOrAlias: 'sub2', + resourceCategory: 'all', }, + ]); + + // 预加载 sub3 模块的 add expose + preloadRemote([ { - name: 'sub3', - entry: "http://localhost:2003/mf-manifest.json" + nameOrAlias: 'sub3', + resourceCategory: 'all', + exposes: ['add'], }, - ], -}); + ]); + ``` + + + ```ts + import { createInstance } from '@module-federation/enhanced/runtime'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [] + }); + + mf.registerRemotes([ + { + name: 'sub1', + entry: "http://localhost:2001/mf-manifest.json" + }, + { + name: 'sub2', + entry: "http://localhost:2002/mf-manifest.json" + }, + { + name: 'sub3', + entry: "http://localhost:2003/mf-manifest.json" + }, + ]); -// 预加载 @demo/sub1 模块 -// 过滤资源名称中携带 ignore 的资源信息 -// 只预加载子依赖的 @demo/sub1-button 模块 -preloadRemote([ - { - nameOrAlias: 'sub1', - filter(assetUrl) { - return assetUrl.indexOf('ignore') === -1; + // 预加载 sub1 模块 + // 过滤资源名称中携带 ignore 的资源信息 + // 只预加载子依赖的 sub1-button 模块 + mf.preloadRemote([ + { + nameOrAlias: 'sub1', + filter(assetUrl) { + return assetUrl.indexOf('ignore') === -1; + }, + depsRemote: [{ nameOrAlias: 'sub1-button' }], }, - depsRemote: [{ nameOrAlias: 'sub1-button' }], - }, -]); - - -// 预加载 @demo/sub2 模块 -// 预加载 @demo/sub2 下的所有 expose -// 预加载 @demo/sub2 的同步资源和异步资源 -preloadRemote([ - { - nameOrAlias: 'sub2', - resourceCategory: 'all', - }, -]); + ]); -// 预加载 @demo/sub3 模块的 add expose -preloadRemote([ - { - nameOrAlias: 'sub3', - resourceCategory: 'all', - exposes: ['add'], - }, -]); -``` - -## registerRemotes -- type - -```typescript -function registerRemotes(remotes: Remote[], options?: { force?: boolean }) {} - -type Remote = (RemoteWithEntry | RemoteWithVersion) & RemoteInfoCommon; - -interface RemoteInfoCommon { - alias?: string; - shareScope?: string; - type?: RemoteEntryType; - entryGlobalName?: string; -} - -interface RemoteWithEntry { - name: string; - entry: string; -} - -interface RemoteWithVersion { - name: string; - version: string; -} -``` - -- Details - -**info**: 请谨慎设置 `force:true` ! - -如果设置 `force: true`, 这会覆盖已经注册(且加载的模块, 并且自动删除已经加载过的模块缓存(如果已经加载过),同时在控制台输出警告,告知这操作存在风险性。 - -* Example - -```ts -import { init, registerRemotes } from '@module-federation/enhanced/runtime'; - -init({ - name: 'mf_host', - remotes: [ - { - name: 'sub1', - entry: 'http://localhost:2001/mf-manifest.json', - } - ], -}); - -// 增加新的 remote @demo/sub2 -registerRemotes([ - { - name: 'sub2', - entry: 'http://localhost:2002/mf-manifest.json', - } -]); - -// 覆盖之前的 remote @demo/sub1 -registerRemotes([ - { - name: 'sub1', - entry: 'http://localhost:2003/mf-manifest.json', - } -],{ force:true }); -``` - -## registerPlugins - -- type - -```typescript -function registerPlugins(plugins: FederationRuntimePlugin[]) {} -``` - -* Example - -```ts -import { init, registerPlugins } from '@module-federation/enhanced/runtime'; -import runtimePlugin from './custom-runtime-plugin'; - -init({ - name: 'mf_host', - remotes: [ - { - name: 'sub1', - entry: 'http://localhost:2001/mf-manifest.json', - } - ], -}); - -// 增加新的运行时插件 -registerPlugins([runtimePlugin()]); - -registerPlugins([ - { - name: 'custom-plugin-runtime', - beforeInit(args) { - const { userOptions, origin } = args; - if (origin.options.name && origin.options.name !== userOptions.name) { - userOptions.name = origin.options.name; - } - console.log('[build time inject] beforeInit: ', args); - return args; - }, - beforeLoadShare(args) { - console.log('[build time inject] beforeLoadShare: ', args); - return args; - }, - createLink({ url }) { - const link = document.createElement('link'); - link.setAttribute('href', url); - link.setAttribute('rel', 'preload'); - link.setAttribute('as', 'script'); - link.setAttribute('crossorigin', 'anonymous'); - return link; - }, - } -]); -``` + // 预加载 sub2 模块 + // 预加载 sub2 下的所有 expose + // 预加载 sub2 的同步资源和异步资源 + mf.preloadRemote([ + { + nameOrAlias: 'sub2', + resourceCategory: 'all', + }, + ]); + // 预加载 sub3 模块的 add expose + mf.preloadRemote([ + { + nameOrAlias: 'sub3', + resourceCategory: 'all', + exposes: ['add'], + }, + ]); + ``` + + diff --git a/apps/website-new/docs/zh/guide/basic/runtime/runtime-hooks.mdx b/apps/website-new/docs/zh/guide/basic/runtime/runtime-hooks.mdx index 7cb43306ede..796feb020f0 100644 --- a/apps/website-new/docs/zh/guide/basic/runtime/runtime-hooks.mdx +++ b/apps/website-new/docs/zh/guide/basic/runtime/runtime-hooks.mdx @@ -13,18 +13,18 @@ function beforeInit(args: BeforeInitOptions): BeforeInitOptions type BeforeInitOptions ={ userOptions: UserOptions; - options: FederationRuntimeOptions; - origin: FederationHost; + options: ModuleFederationRuntimeOptions; + origin: ModuleFederation; shareInfo: ShareInfos; } -interface FederationRuntimeOptions { +interface ModuleFederationRuntimeOptions { id?: string; name: string; version?: string; remotes: Array; shared: ShareInfos; - plugins: Array; + plugins: Array; inBrowser: boolean; } ``` @@ -41,8 +41,8 @@ interface FederationRuntimeOptions { function init(args: InitOptions): void type InitOptions ={ - options: FederationRuntimeOptions; - origin: FederationHost; + options: ModuleFederationRuntimeOptions; + origin: ModuleFederation; } ``` @@ -59,8 +59,8 @@ async function beforeRequest(args: BeforeRequestOptions): Promise FederationRuntimePlugin = +const fallbackPlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'fallback-plugin', @@ -184,7 +184,7 @@ const fallbackPlugin: () => FederationRuntimePlugin = }; -init({ +const mf = createInstance({ name: 'mf_host', remotes: [ { @@ -196,7 +196,7 @@ init({ plugins: [fallbackPlugin()] }); -loadRemote('app1/un-existed-module').then(mod=>{ +mf.loadRemote('app1/un-existed-module').then(mod=>{ expect(mod).toEqual('fallback'); }) ``` @@ -216,7 +216,7 @@ type BeforeLoadShareOptions ={ pkgName: string; shareInfo?: Shared; shared: Options['shared']; - origin: FederationHost; + origin: ModuleFederation; } ``` @@ -244,11 +244,11 @@ type ResolveShareOptions ={ * example ```ts -import { init, loadRemote } from '@module-federation/enhanced/runtime' +import { createInstance, loadRemote } from '@module-federation/enhanced/runtime' -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -const customSharedPlugin: () => FederationRuntimePlugin = +const customSharedPlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'custom-shared-plugin', @@ -271,7 +271,7 @@ const customSharedPlugin: () => FederationRuntimePlugin = }; -init({ +const mf = createInstance({ name: 'mf_host', shared: { react: { @@ -289,7 +289,7 @@ init({ window.React = ()=> 'Desired Shared'; -loadShare("react").then((reactFactory)=>{ +mf.loadShare("react").then((reactFactory)=>{ expect(reactFactory()).toEqual(window.React()); }); ``` @@ -308,7 +308,7 @@ async function beforePreloadRemote(args: BeforePreloadRemoteOptions): BeforePrel type BeforePreloadRemoteOptions ={ preloadOps: Array; options: Options; - origin: FederationHost; + origin: ModuleFederation; } ``` @@ -324,7 +324,7 @@ type BeforePreloadRemoteOptions ={ async function generatePreloadAssets(args: GeneratePreloadAssetsOptions): Promise type GeneratePreloadAssetsOptions ={ - origin: FederationHost; + origin: ModuleFederation; preloadOptions: PreloadOptions[number]; remote: Remote; remoteInfo: RemoteInfo; @@ -360,10 +360,9 @@ type CreateScriptOptions ={ * example ```ts -import { init } from '@module-federation/enhanced/runtime' -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -const changeScriptAttributePlugin: () => FederationRuntimePlugin = +const changeScriptAttributePlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'change-script-attribute', diff --git a/apps/website-new/docs/zh/guide/basic/runtime/runtime.mdx b/apps/website-new/docs/zh/guide/basic/runtime/runtime.mdx index 47b3ee7ab1c..59db264a717 100644 --- a/apps/website-new/docs/zh/guide/basic/runtime/runtime.mdx +++ b/apps/website-new/docs/zh/guide/basic/runtime/runtime.mdx @@ -9,7 +9,6 @@ ::: - 目前 `Module Federation` 提供了两种注册模块和加载模块的方式: - 一种是在构建插件中声明(一般是在 `module-federation.config.ts` 文件中声明) @@ -32,6 +31,10 @@ |可以通过 `runtime` 的 `plugin` 机制影响加载流程|目前不支持提供 `plugin` 影响加载流程| |不支持远程类型提示|支持远程类型提示| +import Runtime from '@components/zh/runtime'; + + + ## 安装 import { PackageManagerTabs } from '@theme'; @@ -55,101 +58,269 @@ import { PackageManagerTabs } from '@theme'; ::: - ## 模块注册 -```ts -import { init, loadRemote } from '@module-federation/enhanced/runtime'; - -init({ - // 消费者模块名称,必填。如同时注册了 ModuleFederationPlugin 插件,则 `name` 应与插件中配置的 `name`[/configure/name.html] 保持一致, +import { Steps, Tab, Tabs } from '@theme'; + + + + ```tsx + // 如果使用了构建插件,那么可以直接使用 `registerRemotes` 注册模块。 + import { registerRemotes } from '@module-federation/enhanced/runtime'; + + registerRemotes([ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ]); + ``` + + + ```ts + // 如果没有使用构建插件,那么可以创建新的实例,并注册模块 + import { createInstance } from '@module-federation/enhanced/runtime'; + + const mf = createInstance({ name: 'mf_host', - // 远程模块注册信息,包含模块名称、别名、版本等信息。 remotes: [ - { - name: "remote1", - alias: "remotes-1", - entry: "http://localhost:3001/mf-manifest.json" - } - ], -}); -``` + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ] + }); + + mf.registerRemotes([ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ]); + ``` + + ## 模块加载 -```tsx -import { loadRemote } from '@module-federation/enhanced/runtime'; - -export default () => { - const MyButton = React.lazy(() => - loadRemote('remote1').then(({ MyButton }) => { - return { - default: MyButton - }; - }), - ); - - return ( - - - - ); -} -``` + + + ```tsx + // 如果使用了构建插件,那么可以直接使用 `loadRemote` 加载模块。 + import { loadRemote } from '@module-federation/enhanced/runtime'; + import React from 'react'; + + export default () => { + const MyButton = React.lazy(() => + loadRemote('remote1').then(({ MyButton }) => { + return { + default: MyButton + }; + }), + ); + + return ( + + + + ); + } + ``` + + + ```ts + // 如果没有使用构建插件,那么可以创建新的实例,并注册模块 + import { createInstance } from '@module-federation/enhanced/runtime'; + import React from 'react'; + + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ] + }); + + export default () => { + const MyButton = React.lazy(() => + mf.loadRemote('remote1').then(({ MyButton }) => { + return { + default: MyButton + }; + }), + ); + + return ( + + + + ); + } + ``` + + ### 加载匿名模块 -```tsx -import React from 'react'; -import { loadRemote } from '@module-federation/enhanced/runtime'; - -const RemoteButton = React.lazy(() => loadRemote('provider/button')); -// 也可通过模块别名加载: -// const RemoteButton = React.lazy(() => loadRemote('remotes-1/button')); + + + ```tsx + // 如果使用了构建插件,那么可以直接使用 `loadRemote` 加载模块。 + import React from 'react'; + import { loadRemote } from '@module-federation/enhanced/runtime'; + + const RemoteButton = React.lazy(() => loadRemote('provider/button')); + // 也可通过模块别名加载: + // const RemoteButton = React.lazy(() => loadRemote('remotes-1/button')); + + export default () => { + return ( + + + + ); + } + ``` + + + ```tsx + // 如果没有使用构建插件,那么可以创建新的实例,并注册模块 + import { createInstance } from '@module-federation/enhanced/runtime'; + import React from 'react'; + + // 创建实例 + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ] + }); + + // 使用 实例 loadRemote API 加载模块 + const RemoteButton = React.lazy(() => mf.loadRemote('provider/button')); + // 也可通过模块别名加载: + // const RemoteButton = React.lazy(() => mf.loadRemote('remotes-1/button')); + + export default () => { + return ( + + + + ); + } + ``` + + -export default () => { - return ( - - - - ); -} -``` ### 加载具名模块 -```tsx -import React from 'react'; -import { loadRemote } from '@module-federation/enhanced/runtime'; - -export default () => { - const RemoteButton = React.lazy(() => - loadRemote('remote1/button').then(({ RemoteButton }) => { - return { - default: RemoteButton - }; - }), - ); - return ( - - - - ); -} -``` + + + ```tsx + // 如果使用了构建插件,那么可以直接使用 `loadRemote` 加载模块。 + import React from 'react'; + import { loadRemote } from '@module-federation/enhanced/runtime'; + + export default () => { + const RemoteButton = React.lazy(() => + loadRemote('remote1/button').then(({ RemoteButton }) => { + return { + default: RemoteButton + }; + }), + ); + return ( + + + + ); + } + ``` + + + ```tsx + // 如果没有使用构建插件,那么可以创建新的实例,并注册模块 + import { createInstance } from '@module-federation/enhanced/runtime'; + import React from 'react'; + + // 创建实例 + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ] + }); + + export default () => { + const RemoteButton = React.lazy(() => + // 使用 实例 loadRemote API 加载模块 + mf.loadRemote('remote1/button').then(({ RemoteButton }) => { + return { + default: RemoteButton + }; + }), + ); + return ( + + + + ); + } + ``` + + ### 加载工具函数 -```ts -import { loadRemote } from '@module-federation/enhanced/runtime'; - -// 加载 app2 expose 的 util -loadRemote<{add: (...args: Array)=> number }>("@demo/app2/util").then((md)=>{ - md.add(1,2); -}); - -// 通过别名加载: -// loadRemote<{add: (...args: Array)=> number }>("app3/util").then((md)=>{ -// md.add(1,2); -// }); -``` + + + ```tsx + // 如果使用了构建插件,那么可以直接使用 `loadRemote` 加载模块。 + import React from 'react'; + import { loadRemote } from '@module-federation/enhanced/runtime'; + + // 加载 remote1 expose 的 util + loadRemote<{add: (...args: Array)=> number }>("remote1/util").then((md)=>{ + md.add(1,2); + }); + ``` + + + ```tsx + // 如果没有使用构建插件,那么可以创建新的实例,并注册模块 + import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'; + import React from 'react'; + + // 创建实例 + const mf = createInstance({ + name: 'mf_host', + remotes: [ + { + name: 'remote1', + alias: 'remote-1', + entry: 'http://localhost:3001/mf-manifest.json', + } + ] + }); + + mf.loadRemote<{add: (...args: Array)=> number }>("remote1/util").then((md)=>{ + md.add(1,2); + }); + ``` + + diff --git a/apps/website-new/docs/zh/guide/troubleshooting/other.mdx b/apps/website-new/docs/zh/guide/troubleshooting/other.mdx index dfb0f5fe6cf..bdac2e03328 100644 --- a/apps/website-new/docs/zh/guide/troubleshooting/other.mdx +++ b/apps/website-new/docs/zh/guide/troubleshooting/other.mdx @@ -78,9 +78,9 @@ Uncaught TypeError: Cannot read properties on null (reading `useState`) 例如需要修改预加载 link 的 crossorigin 属性为 `anonymous`: ```ts title="runtimePlugin.ts -import { FederationRuntimePlugin } from '@module-federation/runtime/types'; +import { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; -export default function MFLinkPlugin(): FederationRuntimePlugin { +export default function MFLinkPlugin(): ModuleFederationRuntimePlugin { return { name: 'link-plugin', createLink({ url }) { diff --git a/apps/website-new/docs/zh/guide/troubleshooting/runtime/RUNTIME-009.mdx b/apps/website-new/docs/zh/guide/troubleshooting/runtime/RUNTIME-009.mdx new file mode 100644 index 00000000000..10f77d002b8 --- /dev/null +++ b/apps/website-new/docs/zh/guide/troubleshooting/runtime/RUNTIME-009.mdx @@ -0,0 +1,13 @@ +import ErrorCodeTitle from '@components/ErrorCodeTitle'; + + + +## 原因 + +未使用构建插件,但直接调用了 runtime api 。 + +## 解决方法 + +import Runtime from '@components/zh/runtime'; + + diff --git a/apps/website-new/docs/zh/plugin/dev/index.mdx b/apps/website-new/docs/zh/plugin/dev/index.mdx index aa7ee48e357..1ba2d9b65f0 100644 --- a/apps/website-new/docs/zh/plugin/dev/index.mdx +++ b/apps/website-new/docs/zh/plugin/dev/index.mdx @@ -11,14 +11,14 @@ Module Federation 提供了一套轻量的运行时插件系统,用以实现 ## 开发插件 -插件提供类似 `() => FederationRuntimePlugin` 的函数。 +插件提供类似 `() => ModuleFederationRuntimePlugin` 的函数。 ### 插件示例 ```ts title="custom-runtime-plugin.ts" -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -const runtimePlugin: () => FederationRuntimePlugin = function () { +const runtimePlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'my-runtime-plugin', beforeInit(args) { @@ -57,7 +57,7 @@ export default runtimePlugin; const path = require('path'); module.exports = { plugins: [ - new ModuleFederation({ + new ModuleFederationPlugin({ // ... runtimePlugins: [path.resolve(__dirname, './custom-runtime-plugin.ts')], }), @@ -93,8 +93,8 @@ registerPlugins([runtimePlugin()]); 下面是一个例子: ```ts -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -const pluginFooBar = (): FederationRuntimePlugin => ({ +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +const pluginFooBar = (): ModuleFederationRuntimePlugin => ({ name: 'xxx-plugin', //... }); @@ -104,632 +104,4 @@ export default pluginFooBar; ## hooks -当然,这里是上述内容的中文翻译: - -### beforeInit - -`SyncWaterfallHook` - -在远程容器的初始化过程之前更新 Federation 实例配置。 - -- 类型 - -```ts -function beforeInit(args: BeforeInitOptions): BeforeInitOptions; - -type BeforeInitOptions = { - userOptions: UserOptions; - options: FederationRuntimeOptions; - origin: FederationHost; - shareInfo: ShareInfos; -}; - -interface FederationRuntimeOptions { - id?: string; - name: string; - version?: string; - remotes: Array; - shared: ShareInfos; - plugins: Array; - inBrowser: boolean; -} -``` - -### init - -`SyncHook` - -在远程容器初始化期间调用。 - -- 类型 - -```ts -function init(args: InitOptions): void; - -type InitOptions = { - options: FederationRuntimeOptions; - origin: FederationHost; -}; -``` - -### beforeRequest - -`AsyncWaterfallHook` - -在解析远程容器之前调用,用于注入容器或在查找之前更新某些内容。 - -- 类型 - -```ts -async function beforeRequest( - args: BeforeRequestOptions, -): Promise; - -type BeforeRequestOptions = { - id: string; - options: FederationRuntimeOptions; - origin: FederationHost; -}; -``` - -### afterResolve - -`AsyncWaterfallHook` - -在解析容器后调用,允许重定向或修改已解析的信息。 - -- 类型 - -```ts -async function afterResolve( - args: AfterResolveOptions, -): Promise; - -type AfterResolveOptions = { - id: string; - pkgNameOrAlias: string; - expose: string; - remote: Remote; - options: FederationRuntimeOptions; - origin: FederationHost; - remoteInfo: RemoteInfo; - remoteSnapshot?: ModuleInfo; -}; -``` - -### onLoad - -`AsyncHook` - -远程模块加载完毕时触发,允许访问和修改已加载文件的导出内容。 - -- 类型 - -```ts -async function onLoad(args: OnLoadOptions): Promise; - -type OnLoadOptions = { - id: string; - expose: string; - pkgNameOrAlias: string; - remote: Remote; - options: ModuleOptions; - origin: FederationHost; - exposeModule: any; - exposeModuleFactory: any; - moduleInstance: Module; -}; - -type ModuleOptions = { - remoteInfo: RemoteInfo; - host: FederationHost; -}; - -interface RemoteInfo { - name: string; - version?: string; - buildVersion?: string; - entry: string; - type: RemoteEntryType; - entryGlobalName: string; - shareScope: string; -} -``` - -### handlePreloadModule - -`SyncHook` - -处理远程模块预加载逻辑。 - -- 类型 - -```ts -function handlePreloadModule(args: HandlePreloadModuleOptions): void; - -type HandlePreloadModuleOptions = { - id: string; - name: string; - remoteSnapshot: ModuleInfo; - preloadConfig: PreloadRemoteArgs; -}; -``` - -### errorLoadRemote - -`AsyncHook` - -当远程模块加载失败时,此钩子将被触发,允许自定义错误处理策略。 - -它被设计为在模块加载的各个生命周期阶段失败时触发。 - -利用 `args.lifecycle` 来识别调用 `errorLoadRemote` 的具体生命周期阶段,从而实现适当的错误处理或回退机制。 - - -- 类型 - -```typescript -async function errorLoadRemote( - args: ErrorLoadRemoteOptions, -): Promise; - -type ErrorLoadRemoteOptions = { - id: string; - error: unknown; - options?: any; - from: 'build' | 'runtime'; - lifecycle: 'onLoad' | 'beforeRequest'; - origin: FederationHost; -}; -``` - -- 示例 - -```typescript -import { init, loadRemote } from '@module-federation/enhanced/runtime'; - -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const fallbackPlugin: () => FederationRuntimePlugin = function () { - return { - name: 'fallback-plugin', - errorLoadRemote(args) { - if(args.lifecycle === 'onLoad') { - const fallback = 'fallback'; - return fallback; - } else if (args.lifecycle === 'beforeRequest') { - return args - } - } - }; -}; - -init({ - name: '@demo/app-main', - remotes: [ - { - name: '@demo/app2', - entry: 'http://localhost:3006/remoteEntry.js', - alias: 'app2' - } - ], - plugins: [fallbackPlugin()] -}); - -loadRemote('app2/un-existed-module').then((mod) => { - expect(mod).toEqual('fallback'); -}); -``` -### beforeLoadShare - -`AsyncWaterfallHook` - -在尝试加载或协商联合应用之间的共享模块之前调用。 - -- 类型 - -```ts -async function beforeLoadShare( - args: BeforeLoadShareOptions, -): Promise; - -type BeforeLoadShareOptions = { - pkgName: string; - shareInfo?: Shared; - shared: Options['shared']; - origin: FederationHost; -}; -``` - -### resolveShare - -`SyncWaterfallHook` - -允许手动解析共享模块请求。 - -- 类型 - -```ts -function resolveShare(args: ResolveShareOptions): ResolveShareOptions; - -type ResolveShareOptions = { - shareScopeMap: ShareScopeMap; - scope: string; - pkgName: string; - version: string; - GlobalFederation: Federation; - resolver: () => Shared | undefined; -}; -``` - -- 示例 - -```ts -import { init, loadRemote } from '@module-federation/enhanced/runtime'; - -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const customSharedPlugin: () => FederationRuntimePlugin = function () { - return { - name: 'custom-shared-plugin', - resolveShare(args) { - const { shareScopeMap, scope, pkgName, version, GlobalFederation } = args; - - if (pkgName !== 'react') { - return args; - } - - // set lib - args.resolver = function () { - shareScopeMap[scope][pkgName][version] = { - lib: ()=>window.React, - loaded:true, - loading: Promise.resolve(()=>window.React) - }; // Manually replace the local share scope with the desired module - return shareScopeMap[scope][pkgName][version]; - }; - - // set get - args.resolver = function () { - shareScopeMap[scope][pkgName][version] = { - get: async ()=>()=>window.React, - }; // Manually replace the local share scope with the desired module - return shareScopeMap[scope][pkgName][version]; - }; - return args; - }, - }; -}; - -init({ - name: '@demo/app-main', - shared: { - react: { - version: '17.0.0', - scope: 'default', - lib: () => React, - shareConfig: { - singleton: true, - requiredVersion: '^17.0.0', - }, - }, - }, - plugins: [customSharedPlugin()], -}); - -window.React = () => 'Desired Shared'; - -loadShare('react').then((reactFactory) => { - expect(reactFactory()).toEqual(window.React()); -}); -``` - -### beforePreloadRemote - -`AsyncHook` - -在预加载处理程序执行任何预加载逻辑之前调用。 - -- 类型 - -```ts -async function beforePreloadRemote( - args: BeforePreloadRemoteOptions, -): BeforePreloadRemoteOptions; - -type BeforePreloadRemoteOptions = { - preloadOps: Array; - options: Options; - origin: FederationHost; -}; -``` - -### generatePreloadAssets - -`AsyncHook` - -根据配置生成预加载资产。 - -- 类型 - -```ts -async function generatePreloadAssets( - args: GeneratePreloadAssetsOptions, -): Promise; - -type GeneratePreloadAssetsOptions = { - origin: FederationHost; - preloadOptions: PreloadOptions[number]; - remote: Remote; - remoteInfo: RemoteInfo; - remoteSnapshot: ModuleInfo; - globalSnapshot: GlobalModuleInfo; -}; - -interface PreloadAssets { - cssAssets: Array; - jsAssetsWithoutEntry: Array; - entryAssets: Array; -} -``` - -### createScript - -`SyncHook` - -- 类型 - -```typescript -function createScript(args: CreateScriptOptions): HTMLScriptElement | {script?: HTMLScriptElement, timeout?: number } | void; - -type CreateScriptOptions = { - url: string; - attrs?: Record; -}; -``` - -- 示例 - -```typescript -import { init } from '@module-federation/enhanced/runtime'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () { - return { - name: 'change-script-attribute', - createScript({ url }) { - if (url === testRemoteEntry) { - let script = document.createElement('script'); - script.src = testRemoteEntry; - script.setAttribute('loader-hooks', 'isTrue'); - script.setAttribute('crossorigin', 'anonymous'); - return script; - } - }, - }; -}; -``` - -- 示例带有脚本超时 - -```typescript -import { init } from '@module-federation/enhanced/runtime'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () { - return { - name: 'change-script-attribute', - createScript({ url }) { - if (url === testRemoteEntry) { - let script = document.createElement('script'); - script.src = testRemoteEntry; - script.setAttribute('loader-hooks', 'isTrue'); - script.setAttribute('crossorigin', 'anonymous'); - return { script, timeout: 1000 } - } - }, - }; -}; -``` - - -### createLink - -`SyncHook` - -- 类型 - -```typescript -function createLink(args: CreateLinkOptions): HTMLLinkElement | void; - -type CreateScriptOptions = { - url: string; - attrs?: Record; -}; -``` - -- 示例 - -```typescript -import { init } from '@module-federation/enhanced/runtime'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const changeLinkAttributePlugin: () => FederationRuntimePlugin = function () { - return { - name: 'change-link-attribute', - createLink({ url }) { - link.setAttribute('href', url); - link.setAttribute('rel', 'preload'); - link.setAttribute('as', 'script'); - link.setAttribute('crossorigin', 'anonymous'); - return link; - }, - }; -}; -``` - -### fetch - -`fetch` 函数用于自定义获取 Manifest 的请求。 - -`AsyncHook` - -- **类型** - -```typescript -function fetch(manifestUrl: string, requestInit: RequestInit): Promise | void | false; -``` - -- 示例:获取 manifest 时增加 credentials: - -```typescript -// fetch-manifest-with-credentials-plugin.ts -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -export default function (): FederationRuntimePlugin { - return { - name: 'fetch-manifest-with-credentials-plugin', - fetch(manifestUrl, requestInit) { - return fetch(manifestUrl, { - ...requestInit, - credentials: 'include' - }); - }, - } -}; -``` - -### loadEntry -可以完全自定义remote, 可以扩展新的remote类型。 -下面两个简单的例子分别实现了加载json数据和模块代理 - -`asyncHook` - -- 类型 - -```typescript -function createScript(args: LoadEntryOptions): HTMLScriptElement | {script?: HTMLScriptElement, timeout?: number } | void; - -type LoadEntryOptions = { - createScriptHook: SyncHook, - remoteEntryExports?: RemoteEntryExports, - remoteInfo: RemoteInfo -}; -interface RemoteInfo { - name: string; - version?: string; - buildVersion?: string; - entry: string; - type: RemoteEntryType; - entryGlobalName: string; - shareScope: string; -} -export type RemoteEntryExports = { - get: (id: string) => () => Promise; - init: ( - shareScope: ShareScopeMap[string], - initScope?: InitScope, - remoteEntryInitOPtions?: RemoteEntryInitOptions, - ) => void | Promise; -}; -``` - -- 示例(加载json数据) - -```typescript -// load-json-data-plugin.ts -import { init } from '@module-federation/enhanced/runtime'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () { - return { - name: 'load-json-data-plugin', - loadEntry({ remoteInfo }) { - if (remoteInfo.jsonA === "jsonA") { - return { - init(shareScope, initScope, remoteEntryInitOPtions) {}, - async get(path) { - const json = await fetch(remoteInfo.entry + ".json").then(res => res.json()) - return () => ({ - path, - json - }) - } - } - } - }, - }; -}; -``` -```ts -// module-federation-config -{ - remotes: { - jsonA: "jsonA@https://cdn.jsdelivr.net/npm/@module-federation/runtime/package" - } -} -``` -```ts -// src/bootstrap.js -import jsonA from "jsonA" -jsonA // {...json data} -``` - -- 示例(模块代理) - -```typescript -// delegate-modules-plugin.ts -import { init } from '@module-federation/enhanced/runtime'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; - -const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () { - return { - name: 'delegate-modules-plugin', - loadEntry({ remoteInfo }) { - if (remoteInfo.name === "delegateModulesA") { - return { - init(shareScope, initScope, remoteEntryInitOPtions) {}, - async get(path) { - path = path.replace("./", "") - const {[path]: factory} = await import("./delegateModulesA.js") - const result = await factory() - return () => result - } - } - } - }, - }; -}; -``` -```ts -// ./src/delegateModulesA.js -export async function test1() { - return new Promise(resolve => { - setTimeout(() => { - resolve("test1 value") - }, 3000) - }) -} -export async function test2() { - return new Promise(resolve => { - setTimeout(() => { - resolve("test2 value") - }, 3000) - }) -} -``` -```ts -// module-federation-config -{ - remotes: { - delegateModulesA: "delegateModulesA@https://delegateModulesA.js" - } -} -``` -```ts -// src/bootstrap.js -import test1 from "delegateModulesA/test1" -import test2 from "delegateModulesA/test2" -test1 // "test1 value" -test2 // "test2 value" -``` +参考 [Runtime Hooks](../../guide/basic/runtime/runtime-hooks) diff --git a/apps/website-new/docs/zh/plugin/plugins/retry-plugin.mdx b/apps/website-new/docs/zh/plugin/plugins/retry-plugin.mdx index 8ec322c700f..3cd5a9e79c3 100644 --- a/apps/website-new/docs/zh/plugin/plugins/retry-plugin.mdx +++ b/apps/website-new/docs/zh/plugin/plugins/retry-plugin.mdx @@ -63,10 +63,10 @@ export default retryPlugin; ### 方式二:运行时注册 ```ts -import { init, loadRemote } from '@module-federation/enhanced/runtime'; +import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'; import { RetryPlugin } from '@module-federation/retry-plugin'; -init({ +const mf = createInstance({ name: 'federation_consumer', remotes: [], plugins: [ @@ -83,7 +83,7 @@ init({ ## Type ```ts -const RetryPlugin: (params: RetryPluginParams) => FederationRuntimePlugin; +const RetryPlugin: (params: RetryPluginParams) => ModuleFederationRuntimePlugin; type RetryPluginParams = { fetch?: FetchWithRetryOptions; // fetch retry options @@ -173,10 +173,10 @@ type ScriptWithRetryOptions = { ```ts -import { init, loadRemote } from '@module-federation/enhanced/runtime'; +import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'; import { RetryPlugin } from '@module-federation/retry-plugin'; -init({ +const mf = createInstance({ name: 'federation_consumer', remotes: [], plugins: [ diff --git a/apps/website-new/docs/zh/practice/frameworks/next/dynamic-remotes.mdx b/apps/website-new/docs/zh/practice/frameworks/next/dynamic-remotes.mdx index 9a49b6b1cb3..086506c4d55 100644 --- a/apps/website-new/docs/zh/practice/frameworks/next/dynamic-remotes.mdx +++ b/apps/website-new/docs/zh/practice/frameworks/next/dynamic-remotes.mdx @@ -1,11 +1,11 @@ # 动态远程模块 ```js -import { loadRemote, init } from '@module-federation/runtime'; +import { loadRemote, createInstance } from '@module-federation/runtime'; // if i have remotes in my federation plugin, i can pass the name of the remote loadRemote('home/exposedModule') // if i want to load a custom remote not known at build time. -init({ +const mf = createInstance({ name: 'hostname', remotes: [ { @@ -15,5 +15,5 @@ init({ ], force: true // may be needed to sideload remotes after the fact. }) -loadRemote('home/exposedModule') +mf.loadRemote('home/exposedModule') ``` diff --git a/apps/website-new/src/components/en/runtime.mdx b/apps/website-new/src/components/en/runtime.mdx new file mode 100644 index 00000000000..acfc89f7f99 --- /dev/null +++ b/apps/website-new/src/components/en/runtime.mdx @@ -0,0 +1,31 @@ +If the build plugin is used, an MF instance will be automatically created and stored in memory when the project starts. You can directly call methods of the MF instance via the . + +```ts +import { loadRemote } from '@module-federation/enhanced/runtime'; + +loadRemote('remote1'); +``` + +If the build plugin is not used, you need to manually create an MF instance before calling the corresponding API. + +```ts +import { createInstance } from '@module-federation/enhanced/runtime'; + +const mf = createInstance({ + name: 'host', + remotes: [ + { + name: 'remote1', + entry: 'http://localhost:2001/vmok-manifest.json', + }, + ], +}); + +mf.loadRemote('remote1'); +``` + +- What is a `ModuleFederation` instance? + +A `ModuleFederation` instance is an instance of the `ModuleFederation` class, which contains all the functionality of the `ModuleFederation` runtime. + +> You can enter `__FEDERATION__.__INSTANCES__` in the console to view the created instances. diff --git a/apps/website-new/src/components/zh/runtime.mdx b/apps/website-new/src/components/zh/runtime.mdx new file mode 100644 index 00000000000..1abf83edb81 --- /dev/null +++ b/apps/website-new/src/components/zh/runtime.mdx @@ -0,0 +1,31 @@ +若使用构建插件,项目启动时将自动创建 `ModuleFederation` 实例并存储于内存中。此时可直接调用 API,API 会自动从内存中获取构建运行时创建的 `ModuleFederation` 实例。 + +```ts +import { loadRemote } from '@module-federation/enhanced/runtime'; + +loadRemote('remote1'); +``` + +若未使用构建插件,则需手动创建 `ModuleFederation` 实例,之后调用相应 API。 + +```ts +import { createInstance } from '@module-federation/enhanced/runtime'; + +const mf = createInstance({ + name: 'host', + remotes: [ + { + name: 'remote1', + entry: 'http://localhost:2001/vmok-manifest.json', + }, + ], +}); + +mf.loadRemote('remote1'); +``` + +- 什么是 `ModuleFederation` 实例 ? + +`ModuleFederation` 实例是 `ModuleFederation` 类的实例,它包含了 `ModuleFederation` 运行时的所有功能。 + +> 你可以在控制台输入 `__FEDERATION__.__INSTANCES__` 来查看已经创建好的实例。 diff --git a/packages/bridge/bridge-react/src/provider/plugin.ts b/packages/bridge/bridge-react/src/provider/plugin.ts index b754c917f0d..e58646e6739 100644 --- a/packages/bridge/bridge-react/src/provider/plugin.ts +++ b/packages/bridge/bridge-react/src/provider/plugin.ts @@ -1,15 +1,15 @@ import type { - FederationRuntimePlugin, - FederationHost, + ModuleFederationRuntimePlugin, + ModuleFederation, } from '@module-federation/runtime'; export type FederationRuntimeType = { - instance: FederationHost | null; + instance: ModuleFederation | null; }; export const federationRuntime: FederationRuntimeType = { instance: null }; -function BridgeReactPlugin(): FederationRuntimePlugin { +function BridgeReactPlugin(): ModuleFederationRuntimePlugin { return { name: 'bridge-react-plugin', beforeInit(args) { diff --git a/packages/chrome-devtools/src/utils/chrome/fast-refresh.ts b/packages/chrome-devtools/src/utils/chrome/fast-refresh.ts index 598b509a2db..df17cdccc86 100644 --- a/packages/chrome-devtools/src/utils/chrome/fast-refresh.ts +++ b/packages/chrome-devtools/src/utils/chrome/fast-refresh.ts @@ -1,5 +1,5 @@ import type { - FederationRuntimePlugin, + ModuleFederationRuntimePlugin, Shared, } from '@module-federation/runtime/types'; import { loadScript } from '@module-federation/sdk'; @@ -8,7 +8,7 @@ import { isObject, getUnpkgUrl } from '../index'; import { definePropertyGlobalVal } from '../sdk'; import { __FEDERATION_DEVTOOLS__ } from '../../template'; -const fastRefreshPlugin = (): FederationRuntimePlugin => { +const fastRefreshPlugin = (): ModuleFederationRuntimePlugin => { return { name: 'mf-fast-refresh-plugin', beforeInit({ userOptions, ...args }) { diff --git a/packages/chrome-devtools/src/utils/chrome/post-message.ts b/packages/chrome-devtools/src/utils/chrome/post-message.ts index 18b7a8fce6e..b2a3be34272 100644 --- a/packages/chrome-devtools/src/utils/chrome/post-message.ts +++ b/packages/chrome-devtools/src/utils/chrome/post-message.ts @@ -1,9 +1,9 @@ import helpers from '@module-federation/runtime/helpers'; -import type { FederationRuntimePlugin } from '@module-federation/runtime/types'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; import { definePropertyGlobalVal } from '../sdk'; -const getModuleInfo = (): FederationRuntimePlugin => { +const getModuleInfo = (): ModuleFederationRuntimePlugin => { return { name: 'mf-devtool-getModuleInfo-plugin', loadRemoteSnapshot({ options, moduleInfo, remoteSnapshot, ...res }) { diff --git a/packages/chrome-devtools/src/utils/chrome/snapshot-plugin.ts b/packages/chrome-devtools/src/utils/chrome/snapshot-plugin.ts index a8dc24ab877..6084b9e30a7 100644 --- a/packages/chrome-devtools/src/utils/chrome/snapshot-plugin.ts +++ b/packages/chrome-devtools/src/utils/chrome/snapshot-plugin.ts @@ -1,7 +1,7 @@ import { MODULE_DEVTOOL_IDENTIFIER } from '@module-federation/sdk'; import runtimeHelpers from '@module-federation/runtime/helpers'; -import type { FederationRuntimePlugin } from '@module-federation/runtime/types'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; import { definePropertyGlobalVal } from '../sdk'; @@ -10,7 +10,7 @@ declare global { var __INIT_VMOK_CHROME_DEVTOOL_PLUGIN__: boolean | undefined; } -const chromeDevtoolsPlugin: () => FederationRuntimePlugin = function () { +const chromeDevtoolsPlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'mf-chrome-devtools-inject-snapshot-plugin', beforeLoadRemoteSnapshot({ options }) { diff --git a/packages/data-prefetch/__tests__/react.spec.ts b/packages/data-prefetch/__tests__/react.spec.ts index 2e113609731..728ce4ba6af 100644 --- a/packages/data-prefetch/__tests__/react.spec.ts +++ b/packages/data-prefetch/__tests__/react.spec.ts @@ -1,4 +1,4 @@ -import { FederationHost, init } from '@module-federation/runtime'; +import { ModuleFederation, init } from '@module-federation/runtime'; import { renderHook, act } from '@testing-library/react-hooks'; import * as ModuleFederationSDK from '@module-federation/sdk'; import { usePrefetch } from '../src/react'; @@ -46,7 +46,7 @@ describe('usePrefetch', () => { Promise.resolve(params ? params : testData), ); - new FederationHost({ + new ModuleFederation({ name: options.name, remotes: [], }); diff --git a/packages/data-prefetch/src/plugin.ts b/packages/data-prefetch/src/plugin.ts index 6dfd3014a50..102ba1f6b77 100644 --- a/packages/data-prefetch/src/plugin.ts +++ b/packages/data-prefetch/src/plugin.ts @@ -1,4 +1,4 @@ -import type { FederationRuntimePlugin } from '@module-federation/runtime/types'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; import { ModuleInfo, getResourceUrl } from '@module-federation/sdk'; import { getSignalFromManifest } from './common/runtime-utils'; @@ -18,7 +18,7 @@ interface Loading { const loadingArray: Array = []; let sharedFlag = SHARED_STRATEGY; // eslint-disable-next-line max-lines-per-function -export const prefetchPlugin = (): FederationRuntimePlugin => ({ +export const prefetchPlugin = (): ModuleFederationRuntimePlugin => ({ name: 'data-prefetch-runtime-plugin', initContainer(options) { const { remoteSnapshot, remoteInfo, id, origin } = options; diff --git a/packages/data-prefetch/src/prefetch.ts b/packages/data-prefetch/src/prefetch.ts index 1dbd23ff006..11f2078ff04 100644 --- a/packages/data-prefetch/src/prefetch.ts +++ b/packages/data-prefetch/src/prefetch.ts @@ -1,5 +1,5 @@ import { - FederationHost, + ModuleFederation, getRemoteEntry, getRemoteInfo, } from '@module-federation/runtime'; @@ -26,7 +26,7 @@ type PrefetchExports = Record; export interface DataPrefetchOptions { name: string; remote?: Remote; - origin?: FederationHost; + origin?: ModuleFederation; remoteSnapshot?: ModuleInfo; } diff --git a/packages/dts-plugin/src/runtime-plugins/dynamic-remote-type-hints-plugin.ts b/packages/dts-plugin/src/runtime-plugins/dynamic-remote-type-hints-plugin.ts index 67f189ddeb2..543d8e5aa9b 100644 --- a/packages/dts-plugin/src/runtime-plugins/dynamic-remote-type-hints-plugin.ts +++ b/packages/dts-plugin/src/runtime-plugins/dynamic-remote-type-hints-plugin.ts @@ -1,4 +1,4 @@ -import type { FederationRuntimePlugin } from '@module-federation/runtime/types'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; import { createWebsocket } from '../server/createWebsocket'; import { AddDynamicRemoteAction, @@ -9,7 +9,7 @@ import { getIpFromEntry } from '../dev-worker/utils'; declare const FEDERATION_IPV4: string | undefined; const PLUGIN_NAME = 'dynamic-remote-type-hints-plugin'; -function dynamicRemoteTypeHintsPlugin(): FederationRuntimePlugin { +function dynamicRemoteTypeHintsPlugin(): ModuleFederationRuntimePlugin { let ws = createWebsocket(); let isConnected = false; ws.onopen = () => { diff --git a/packages/error-codes/src/desc.ts b/packages/error-codes/src/desc.ts index 9116f82d418..2d63679f5dc 100644 --- a/packages/error-codes/src/desc.ts +++ b/packages/error-codes/src/desc.ts @@ -7,6 +7,7 @@ import { RUNTIME_006, RUNTIME_007, RUNTIME_008, + RUNTIME_009, TYPE_001, BUILD_001, BUILD_002, @@ -21,6 +22,7 @@ export const runtimeDescMap = { [RUNTIME_006]: 'Invalid loadShareSync function call from runtime', [RUNTIME_007]: 'Failed to get remote snapshot.', [RUNTIME_008]: 'Failed to load script resources.', + [RUNTIME_009]: 'Please call createInstance first.', }; export const typeDescMap = { diff --git a/packages/error-codes/src/error-codes.ts b/packages/error-codes/src/error-codes.ts index aa2d5850b78..321f9e5ee63 100644 --- a/packages/error-codes/src/error-codes.ts +++ b/packages/error-codes/src/error-codes.ts @@ -6,6 +6,7 @@ export const RUNTIME_005 = 'RUNTIME-005'; export const RUNTIME_006 = 'RUNTIME-006'; export const RUNTIME_007 = 'RUNTIME-007'; export const RUNTIME_008 = 'RUNTIME-008'; +export const RUNTIME_009 = 'RUNTIME-009'; export const TYPE_001 = 'TYPE-001'; export const BUILD_001 = 'BUILD-001'; diff --git a/packages/modernjs/src/cli/index.ts b/packages/modernjs/src/cli/index.ts index 62de6e9cf28..06fad84b5f4 100644 --- a/packages/modernjs/src/cli/index.ts +++ b/packages/modernjs/src/cli/index.ts @@ -88,4 +88,4 @@ export default moduleFederationPlugin; export { createModuleFederationConfig } from '@module-federation/enhanced'; -export type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +export type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; diff --git a/packages/modernjs/src/cli/mfRuntimePlugins/auto-fetch-data.ts b/packages/modernjs/src/cli/mfRuntimePlugins/auto-fetch-data.ts index 259ee4c9142..b7683659377 100644 --- a/packages/modernjs/src/cli/mfRuntimePlugins/auto-fetch-data.ts +++ b/packages/modernjs/src/cli/mfRuntimePlugins/auto-fetch-data.ts @@ -15,9 +15,9 @@ import { MF_DATA_FETCH_TYPE, MF_DATA_FETCH_STATUS } from '../../constant'; import { DATA_FETCH_CLIENT_SUFFIX } from '@module-federation/rsbuild-plugin/constant'; import type { MF_DATA_FETCH_MAP_VALUE } from '../../interfaces/global'; -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -const autoFetchData: () => FederationRuntimePlugin = () => ({ +const autoFetchData: () => ModuleFederationRuntimePlugin = () => ({ name: 'auto-fetch-data-plugin', beforeInit(args) { initDataFetchMap(); diff --git a/packages/modernjs/src/cli/mfRuntimePlugins/inject-node-fetch.ts b/packages/modernjs/src/cli/mfRuntimePlugins/inject-node-fetch.ts index 630228e7d6c..7ea5c64bf32 100644 --- a/packages/modernjs/src/cli/mfRuntimePlugins/inject-node-fetch.ts +++ b/packages/modernjs/src/cli/mfRuntimePlugins/inject-node-fetch.ts @@ -1,7 +1,7 @@ -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; import nodeFetch from 'node-fetch'; -const injectNodeFetchPlugin: () => FederationRuntimePlugin = () => ({ +const injectNodeFetchPlugin: () => ModuleFederationRuntimePlugin = () => ({ name: 'inject-node-fetch-plugin', beforeInit(args) { if (!globalThis.fetch) { diff --git a/packages/modernjs/src/cli/mfRuntimePlugins/resolve-entry-ipv4.ts b/packages/modernjs/src/cli/mfRuntimePlugins/resolve-entry-ipv4.ts index 554b09b6aab..252eb5b6fb2 100644 --- a/packages/modernjs/src/cli/mfRuntimePlugins/resolve-entry-ipv4.ts +++ b/packages/modernjs/src/cli/mfRuntimePlugins/resolve-entry-ipv4.ts @@ -1,4 +1,4 @@ -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; import { LOCALHOST } from '../../constant'; declare const FEDERATION_IPV4: string | undefined; @@ -26,7 +26,7 @@ function replaceLocalhost(url: string): string { return url.replace(LOCALHOST, ipv4); } -const resolveEntryIpv4Plugin: () => FederationRuntimePlugin = () => ({ +const resolveEntryIpv4Plugin: () => ModuleFederationRuntimePlugin = () => ({ name: 'resolve-entry-ipv4', beforeRegisterRemote(args) { diff --git a/packages/modernjs/src/cli/mfRuntimePlugins/shared-strategy.ts b/packages/modernjs/src/cli/mfRuntimePlugins/shared-strategy.ts index 9207b57a3b0..d7365727fc5 100644 --- a/packages/modernjs/src/cli/mfRuntimePlugins/shared-strategy.ts +++ b/packages/modernjs/src/cli/mfRuntimePlugins/shared-strategy.ts @@ -1,6 +1,6 @@ -import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime'; -const sharedStrategy: () => FederationRuntimePlugin = () => ({ +const sharedStrategy: () => ModuleFederationRuntimePlugin = () => ({ name: 'shared-strategy-plugin', beforeInit(args) { const { userOptions } = args; diff --git a/packages/modernjs/src/utils/dataFetch.ts b/packages/modernjs/src/utils/dataFetch.ts index bca456a7300..6a409329510 100644 --- a/packages/modernjs/src/utils/dataFetch.ts +++ b/packages/modernjs/src/utils/dataFetch.ts @@ -18,7 +18,7 @@ import type { MF_DATA_FETCH_MAP, NoSSRRemoteInfo, } from '../interfaces/global'; -import type { FederationHost } from '@module-federation/enhanced/runtime'; +import type { ModuleFederation } from '@module-federation/enhanced/runtime'; export const getDataFetchInfo = ({ name, @@ -195,7 +195,7 @@ export function getDataFetchMapKey( } export async function loadDataFetchModule( - instance: FederationHost, + instance: ModuleFederation, id: string, ) { return instance.loadRemote(id).then((m) => { diff --git a/packages/modernjs/src/utils/index.ts b/packages/modernjs/src/utils/index.ts index 9cb30ab3514..543a1a58149 100644 --- a/packages/modernjs/src/utils/index.ts +++ b/packages/modernjs/src/utils/index.ts @@ -1,8 +1,8 @@ -import type { FederationHost } from '@module-federation/enhanced/runtime'; +import type { ModuleFederation } from '@module-federation/enhanced/runtime'; export function getLoadedRemoteInfos( id: string, - instance: FederationHost | null, + instance: ModuleFederation | null, ) { if (!instance) { return; diff --git a/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts b/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts index 558dbcc0bb1..960770725c3 100644 --- a/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts +++ b/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts @@ -1,6 +1,6 @@ -import { FederationRuntimePlugin } from '@module-federation/runtime/types'; +import { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; -export default function (): FederationRuntimePlugin { +export default function (): ModuleFederationRuntimePlugin { return { name: 'next-internal-plugin', createScript: function (args: { diff --git a/packages/node/src/__tests__/runtimePlugin.test.ts b/packages/node/src/__tests__/runtimePlugin.test.ts index c0418b0151c..1af6962b39b 100644 --- a/packages/node/src/__tests__/runtimePlugin.test.ts +++ b/packages/node/src/__tests__/runtimePlugin.test.ts @@ -13,8 +13,8 @@ import runtimePlugin, { setupWebpackRequirePatching, } from '../runtimePlugin'; import type { - FederationRuntimePlugin, - FederationHost, + ModuleFederationRuntimePlugin, + ModuleFederation, Federation, } from '@module-federation/runtime'; import * as runtimePluginModule from '../runtimePlugin'; @@ -94,7 +94,7 @@ const mockModule = { }, inited: false, lib: {}, - host: {} as FederationHost, + host: {} as ModuleFederation, getEntry: () => 'http://localhost:3001/remoteEntry.js', init: () => Promise.resolve({}), get: () => Promise.resolve({}), @@ -125,7 +125,7 @@ const mockModule = { } as any; describe('runtimePlugin', () => { - let plugin: FederationRuntimePlugin; + let plugin: ModuleFederationRuntimePlugin; beforeEach(() => { jest.clearAllMocks(); @@ -829,7 +829,7 @@ describe('runtimePlugin', () => { }, }, }, - } as unknown as FederationHost, + } as unknown as ModuleFederation, } as any; if (plugin.beforeInit) { @@ -854,7 +854,7 @@ describe('runtimePlugin', () => { }, }, }, - } as unknown as FederationHost, + } as unknown as ModuleFederation, } as any; if (plugin.beforeInit) { @@ -903,7 +903,7 @@ describe('runtimePlugin', () => { }, }, }, - } as unknown as FederationHost, + } as unknown as ModuleFederation, } as any; if (plugin.beforeInit) { @@ -986,7 +986,7 @@ describe('runtimePlugin', () => { }, }, }, - } as unknown as FederationHost, + } as unknown as ModuleFederation, } as any; if (plugin.beforeInit) { @@ -1041,7 +1041,7 @@ describe('runtimePlugin', () => { }, }, }, - } as unknown as FederationHost, + } as unknown as ModuleFederation, } as any; if (plugin.beforeInit) { @@ -1087,7 +1087,7 @@ describe('runtimePlugin', () => { }, }, }, - } as unknown as FederationHost, + } as unknown as ModuleFederation, } as any; if (plugin.beforeInit) { @@ -1145,7 +1145,7 @@ describe('runtimePlugin', () => { }, }, }, - } as unknown as FederationHost, + } as unknown as ModuleFederation, } as any; if (plugin.beforeInit) { diff --git a/packages/node/src/recordDynamicRemoteEntryHashPlugin.ts b/packages/node/src/recordDynamicRemoteEntryHashPlugin.ts index 2edba24d7e7..0b0f9fe53f3 100644 --- a/packages/node/src/recordDynamicRemoteEntryHashPlugin.ts +++ b/packages/node/src/recordDynamicRemoteEntryHashPlugin.ts @@ -1,6 +1,6 @@ -import type { FederationRuntimePlugin } from '@module-federation/runtime'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime'; -const recordDynamicRemoteEntryHashPlugin: () => FederationRuntimePlugin = +const recordDynamicRemoteEntryHashPlugin: () => ModuleFederationRuntimePlugin = () => ({ name: 'record-dynamic-remote-entry-hash-plugin', beforeInit(args) { diff --git a/packages/node/src/runtimePlugin.ts b/packages/node/src/runtimePlugin.ts index fbacd099ddc..8c65c98ec3d 100644 --- a/packages/node/src/runtimePlugin.ts +++ b/packages/node/src/runtimePlugin.ts @@ -1,6 +1,6 @@ import type { - FederationRuntimePlugin, - FederationHost, + ModuleFederationRuntimePlugin, + ModuleFederation, } from '@module-federation/runtime'; type WebpackRequire = { (id: string): any; @@ -22,7 +22,7 @@ type WebpackRequire = { options: { attrs: { globalName: string } }, ) => Promise; }; - instance: FederationHost; + instance: ModuleFederation; chunkMatcher?: (chunkId: string) => boolean; rootOutputDir?: string; initOptions: { @@ -360,7 +360,7 @@ export const setupWebpackRequirePatching = ( } }; -export default function (): FederationRuntimePlugin { +export default function (): ModuleFederationRuntimePlugin { return { name: 'node-federation-plugin', beforeInit(args) { diff --git a/packages/retry-plugin/src/index.ts b/packages/retry-plugin/src/index.ts index c1bef77f65d..ab36f045f1c 100644 --- a/packages/retry-plugin/src/index.ts +++ b/packages/retry-plugin/src/index.ts @@ -1,9 +1,11 @@ -import { FederationRuntimePlugin } from '@module-federation/runtime/types'; +import { ModuleFederationRuntimePlugin } from '@module-federation/runtime/types'; import { fetchWithRetry } from './fetch-retry'; import type { RetryPluginParams } from './types'; import { scriptCommonRetry } from './util'; -const RetryPlugin: (params: RetryPluginParams) => FederationRuntimePlugin = ({ +const RetryPlugin: ( + params: RetryPluginParams, +) => ModuleFederationRuntimePlugin = ({ fetch: fetchOption, script: scriptOption, }) => ({ diff --git a/packages/runtime-core/CHANGELOG.md b/packages/runtime-core/CHANGELOG.md index 6acfc9cbacf..322f2ddb175 100644 --- a/packages/runtime-core/CHANGELOG.md +++ b/packages/runtime-core/CHANGELOG.md @@ -265,19 +265,19 @@ - 9e32644: Added comprehensive integration tests for the API synchronization and enhanced the embedded module proxy implementation. - Added detailed integration tests for API consistency between embedded and index modules. - - Tests include export comparison and method consistency for `FederationHost` and `Module` classes. + - Tests include export comparison and method consistency for `ModuleFederation` and `Module` classes. - Introduced and updated the `embedded.ts` file to dynamically access the runtime modules at runtime. - Included detailed implementations for accessing and wrapping existing runtime functions. - - Exposed the previously private `formatOptions` method in the `FederationHost` class publicly. + - Exposed the previously private `formatOptions` method in the `ModuleFederation` class publicly. - Enhanced error handling for uninstantiated or unregistered runtime access. - 9e32644: - Refactor `embedded.ts` to use a proxy pattern for better runtime compatibility: - - Implement FederationHost and Module classes that delegate to the actual runtime implementation + - Implement ModuleFederation and Module classes that delegate to the actual runtime implementation - Expose all public methods and properties from the original classes - Use a lazy initialization approach to ensure proper runtime loading - Add comprehensive test suite for API synchronization between embedded.ts and index.ts - Introduce new test file `sync.spec.ts` with extensive tests for API compatibility - - Ensure FederationHost and Module classes have the same methods in both files + - Ensure ModuleFederation and Module classes have the same methods in both files - Test various scenarios including remote loading, manifest handling, and circular dependencies - Modify `core.ts` to make `formatOptions` method public - Updated dependencies [9e32644] diff --git a/packages/runtime-core/__tests__/hooks.spec.ts b/packages/runtime-core/__tests__/hooks.spec.ts index 0f9ec6dd240..23952bb1758 100644 --- a/packages/runtime-core/__tests__/hooks.spec.ts +++ b/packages/runtime-core/__tests__/hooks.spec.ts @@ -1,6 +1,6 @@ import { assert, describe, test, it } from 'vitest'; -import { FederationHost } from '../src/core'; -import { FederationRuntimePlugin } from '../src/type/plugin'; +import { ModuleFederation } from '../src/core'; +import { ModuleFederationRuntimePlugin } from '../src/type/plugin'; import { mockStaticServer, removeScriptTags } from './mock/utils'; import { addGlobalSnapshot } from '../src/global'; @@ -20,7 +20,7 @@ describe('hooks', () => { initArgs: any, beforeLoadRemoteArgs, loadRemoteArgs; - const testPlugin: () => FederationRuntimePlugin = () => ({ + const testPlugin: () => ModuleFederationRuntimePlugin = () => ({ name: 'testPlugin', beforeInit(args) { beforeInitArgs = args; @@ -59,7 +59,7 @@ describe('hooks', () => { ], plugins: [testPlugin()], }; - const GM = new FederationHost(options); + const GM = new ModuleFederation(options); assert(beforeInitArgs, "beforeInitArgs can't be undefined"); expect(beforeInitArgs).toMatchObject({ options: { @@ -149,7 +149,7 @@ describe('hooks', () => { }, }); - const INSTANCE = new FederationHost({ + const INSTANCE = new ModuleFederation({ name: '@loader-hooks/globalinfo', remotes: [ { @@ -234,7 +234,7 @@ describe('hooks', () => { statusText: 'OK', headers: { 'Content-Type': 'application/json' }, }); - const fetchPlugin: () => FederationRuntimePlugin = () => ({ + const fetchPlugin: () => ModuleFederationRuntimePlugin = () => ({ name: 'fetch-plugin', fetch(url, options) { if (url === 'http://mockxxx.com/loader-fetch-hooks-mf-manifest.json') { @@ -242,7 +242,7 @@ describe('hooks', () => { } }, }); - const INSTANCE = new FederationHost({ + const INSTANCE = new ModuleFederation({ name: '@loader-hooks/fetch', remotes: [ { @@ -292,7 +292,7 @@ describe('hooks', () => { headers: { 'Content-Type': 'application/json' }, }); - const fetchPlugin: () => FederationRuntimePlugin = function () { + const fetchPlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'fetch-plugin', fetch(url, options) { @@ -304,7 +304,7 @@ describe('hooks', () => { }, }; }; - const loadEntryPlugin = function (): FederationRuntimePlugin { + const loadEntryPlugin = function (): ModuleFederationRuntimePlugin { return { name: 'load-entry-plugin', loadEntry({ remoteInfo }) { @@ -320,7 +320,7 @@ describe('hooks', () => { } as any; }; - const INSTANCE = new FederationHost({ + const INSTANCE = new ModuleFederation({ name: '@loader-hooks/fetch', remotes: [ { diff --git a/packages/runtime-core/__tests__/instance.spec.ts b/packages/runtime-core/__tests__/instance.spec.ts index 185c285dc3e..7867f40e15b 100644 --- a/packages/runtime-core/__tests__/instance.spec.ts +++ b/packages/runtime-core/__tests__/instance.spec.ts @@ -1,9 +1,9 @@ import { assert, describe, test, it } from 'vitest'; -import { FederationHost } from '../src/index'; +import { ModuleFederation } from '../src/index'; -describe('FederationHost', () => { +describe('ModuleFederation', () => { it('should initialize with provided arguments', () => { - const GM = new FederationHost({ + const GM = new ModuleFederation({ name: '@federation/instance', version: '1.0.1', remotes: [], diff --git a/packages/runtime-core/__tests__/mock/utils.ts b/packages/runtime-core/__tests__/mock/utils.ts index 4ad4f3eafab..7cc926bfd96 100644 --- a/packages/runtime-core/__tests__/mock/utils.ts +++ b/packages/runtime-core/__tests__/mock/utils.ts @@ -90,7 +90,7 @@ export function mockStaticServer({ }); } -import { FederationRuntimePlugin } from '../../src/type'; +import { ModuleFederationRuntimePlugin } from '../../src/type'; import { ProviderModuleInfo } from '@module-federation/sdk'; export const mockRemoteSnapshot: ( @@ -98,7 +98,7 @@ export const mockRemoteSnapshot: ( remoteSnapshots: { [name: string]: ProviderModuleInfo; }, -) => FederationRuntimePlugin = function (uniqueId, remoteSnapshots) { +) => ModuleFederationRuntimePlugin = function (uniqueId, remoteSnapshots) { return { name: `mock-snapshot-${uniqueId}`, loadSnapshot({ moduleInfo, ...info }) { diff --git a/packages/runtime-core/__tests__/register-remotes.spec.ts b/packages/runtime-core/__tests__/register-remotes.spec.ts index b8295c8dbd2..174d914c491 100644 --- a/packages/runtime-core/__tests__/register-remotes.spec.ts +++ b/packages/runtime-core/__tests__/register-remotes.spec.ts @@ -1,9 +1,9 @@ import { assert, describe, it, expect } from 'vitest'; -import { FederationHost } from '../src/index'; +import { ModuleFederation } from '../src/index'; -describe('FederationHost', () => { +describe('ModuleFederation', () => { it('registers new remotes and loads them correctly', async () => { - const FM = new FederationHost({ + const FM = new ModuleFederation({ name: '@federation/instance', version: '1.0.1', remotes: [ @@ -37,7 +37,7 @@ describe('FederationHost', () => { expect(res).toBe('hello app2'); }); it('does not merge loaded remote by default', async () => { - const FM = new FederationHost({ + const FM = new ModuleFederation({ name: '@federation/instance', version: '1.0.1', remotes: [ @@ -65,7 +65,7 @@ describe('FederationHost', () => { expect(app1Res).toBe('hello app1 entry1'); }); it('merges loaded remote by setting "force: true"', async () => { - const FM = new FederationHost({ + const FM = new ModuleFederation({ name: '@federation/instance', version: '1.0.1', remotes: [ diff --git a/packages/runtime-core/__tests__/snapshot.spec.ts b/packages/runtime-core/__tests__/snapshot.spec.ts index 64e31c00512..ef2bfb04496 100644 --- a/packages/runtime-core/__tests__/snapshot.spec.ts +++ b/packages/runtime-core/__tests__/snapshot.spec.ts @@ -1,5 +1,5 @@ import { assert, describe, it } from 'vitest'; -import { FederationHost } from '../src'; +import { ModuleFederation } from '../src'; import { getGlobalSnapshot, resetFederationGlobalInfo } from '../src/global'; describe('snapshot', () => { @@ -12,7 +12,7 @@ describe('snapshot', () => { 'http://localhost:1111/resources/snapshot/remote1/federation-manifest.json'; const Remote2Entry = 'http://localhost:1111/resources/snapshot/remote2/federation-manifest.json'; - const FM1 = new FederationHost({ + const FM1 = new ModuleFederation({ name: '@snapshot/host', version: '0.0.3', remotes: [ diff --git a/packages/runtime-core/src/core.ts b/packages/runtime-core/src/core.ts index ae9451f8444..1915ba0dbf7 100644 --- a/packages/runtime-core/src/core.ts +++ b/packages/runtime-core/src/core.ts @@ -44,20 +44,20 @@ const USE_SNAPSHOT = ? !FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN : true; // Default to true (use snapshot) when not explicitly defined -export class FederationHost { +export class ModuleFederation { options: Options; hooks = new PluginSystem({ beforeInit: new SyncWaterfallHook<{ userOptions: UserOptions; options: Options; - origin: FederationHost; + origin: ModuleFederation; shareInfo: ShareInfos; }>('beforeInit'), init: new SyncHook< [ { options: Options; - origin: FederationHost; + origin: ModuleFederation; }, ], void @@ -68,7 +68,7 @@ export class FederationHost { initScope: InitScope; remoteEntryInitOptions: RemoteEntryInitOptions; remoteInfo: RemoteInfo; - origin: FederationHost; + origin: ModuleFederation; }>('beforeInitContainer'), // maybe will change, temporarily for internal use only initContainer: new AsyncWaterfallHook<{ @@ -77,7 +77,7 @@ export class FederationHost { remoteEntryInitOptions: RemoteEntryInitOptions; remoteInfo: RemoteInfo; remoteEntryExports: RemoteEntryExports; - origin: FederationHost; + origin: ModuleFederation; id: string; remoteSnapshot?: ModuleInfo; }>('initContainer'), @@ -126,7 +126,7 @@ export class FederationHost { [ { getRemoteEntry: typeof getRemoteEntry; - origin: FederationHost; + origin: ModuleFederation; remoteInfo: RemoteInfo; remoteEntryExports?: RemoteEntryExports | undefined; globalLoading: Record< @@ -343,4 +343,11 @@ export class FederationHost { registerRemotes(remotes: Remote[], options?: { force?: boolean }): void { return this.remoteHandler.registerRemotes(remotes, options); } + + registerShared(shared: UserOptions['shared']) { + this.sharedHandler.registerShared(this.options, { + ...this.options, + shared, + }); + } } diff --git a/packages/runtime-core/src/global.ts b/packages/runtime-core/src/global.ts index 85f3d9a9dbd..d55534192f3 100644 --- a/packages/runtime-core/src/global.ts +++ b/packages/runtime-core/src/global.ts @@ -1,4 +1,4 @@ -import { FederationHost } from './core'; +import { ModuleFederation } from './core'; import { RemoteEntryExports, GlobalShareScopeMap, @@ -12,14 +12,14 @@ import { isDebugMode, } from '@module-federation/sdk'; import { warn } from './utils/logger'; -import { FederationRuntimePlugin } from './type/plugin'; +import { ModuleFederationRuntimePlugin } from './type/plugin'; export interface Federation { - __GLOBAL_PLUGIN__: Array; + __GLOBAL_PLUGIN__: Array; __DEBUG_CONSTRUCTOR_VERSION__?: string; moduleInfo: GlobalModuleInfo; - __DEBUG_CONSTRUCTOR__?: typeof FederationHost; - __INSTANCES__: Array; + __DEBUG_CONSTRUCTOR__?: typeof ModuleFederation; + __INSTANCES__: Array; __SHARE__: GlobalShareScopeMap; __MANIFEST_LOADING__: Record>; __PRELOADED_MAP__: Map; @@ -119,19 +119,19 @@ export function resetFederationGlobalInfo(): void { } export function setGlobalFederationInstance( - FederationInstance: FederationHost, + FederationInstance: ModuleFederation, ): void { CurrentGlobal.__FEDERATION__.__INSTANCES__.push(FederationInstance); } export function getGlobalFederationConstructor(): - | typeof FederationHost + | typeof ModuleFederation | undefined { return CurrentGlobal.__FEDERATION__.__DEBUG_CONSTRUCTOR__; } export function setGlobalFederationConstructor( - FederationConstructor: typeof FederationHost | undefined, + FederationConstructor: typeof ModuleFederation | undefined, isDebug = isDebugMode(), ): void { if (isDebug) { @@ -269,7 +269,7 @@ export const getRemoteEntryExports = ( // If a plugin is not registered, it is added to the global plugins. // If a plugin is already registered, a warning message is logged. export const registerGlobalPlugins = ( - plugins: Array, + plugins: Array, ): void => { const { __GLOBAL_PLUGIN__ } = nativeGlobal.__FEDERATION__; @@ -282,7 +282,7 @@ export const registerGlobalPlugins = ( }); }; -export const getGlobalHostPlugins = (): Array => +export const getGlobalHostPlugins = (): Array => nativeGlobal.__FEDERATION__.__GLOBAL_PLUGIN__; export const getPreloaded = (id: string) => diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 400f85d47cc..9345ce4eef0 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -1,5 +1,5 @@ import helpers, { type IGlobalUtils, type IShareUtils } from './helpers'; -export { FederationHost } from './core'; +export { ModuleFederation } from './core'; export { type Federation, CurrentGlobal, @@ -12,7 +12,7 @@ export { getGlobalSnapshot, getInfoWithoutType, } from './global'; -export type { UserOptions, FederationRuntimePlugin } from './type'; +export type { UserOptions, ModuleFederationRuntimePlugin } from './type'; export { assert } from './utils/logger'; export { registerGlobalPlugins } from './global'; export { diff --git a/packages/runtime-core/src/module/index.ts b/packages/runtime-core/src/module/index.ts index 8d700707d82..9d670b3c7bb 100644 --- a/packages/runtime-core/src/module/index.ts +++ b/packages/runtime-core/src/module/index.ts @@ -6,7 +6,7 @@ import { runtimeDescMap, } from '@module-federation/error-codes'; import { getRemoteEntry, getRemoteEntryUniqueKey } from '../utils/load'; -import { FederationHost } from '../core'; +import { ModuleFederation } from '../core'; import { RemoteEntryExports, RemoteInfo, InitScope } from '../type'; import { globalLoading } from '../global'; @@ -17,14 +17,14 @@ class Module { inited = false; remoteEntryExports?: RemoteEntryExports; lib: RemoteEntryExports | undefined = undefined; - host: FederationHost; + host: ModuleFederation; constructor({ remoteInfo, host, }: { remoteInfo: RemoteInfo; - host: FederationHost; + host: ModuleFederation; }) { this.remoteInfo = remoteInfo; this.host = host; diff --git a/packages/runtime-core/src/plugins/generate-preload-assets.ts b/packages/runtime-core/src/plugins/generate-preload-assets.ts index 22f15509f8d..8f98a03de57 100644 --- a/packages/runtime-core/src/plugins/generate-preload-assets.ts +++ b/packages/runtime-core/src/plugins/generate-preload-assets.ts @@ -8,7 +8,7 @@ import { } from '@module-federation/sdk'; import { EntryAssets, - FederationRuntimePlugin, + ModuleFederationRuntimePlugin, PreloadAssets, PreloadConfig, PreloadOptions, @@ -18,7 +18,7 @@ import { } from '../type'; import { assignRemoteInfo } from './snapshot'; import { getInfoWithoutType, getPreloaded, setPreloaded } from '../global'; -import { FederationHost } from '../core'; +import { ModuleFederation } from '../core'; import { defaultPreloadArgs, normalizePreloadExposes } from '../utils/preload'; import { getRegisteredShare } from '../utils/share'; import { @@ -110,7 +110,7 @@ const isExisted = (type: 'link' | 'script', url: string) => { // eslint-disable-next-line max-lines-per-function export function generatePreloadAssets( - origin: FederationHost, + origin: ModuleFederation, preloadOptions: PreloadOptions[number], remote: RemoteInfoOptionalVersion, globalSnapshot: GlobalModuleInfo, @@ -310,7 +310,7 @@ export function generatePreloadAssets( }; } -export const generatePreloadAssetsPlugin: () => FederationRuntimePlugin = +export const generatePreloadAssetsPlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'generate-preload-assets-plugin', diff --git a/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts b/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts index b996c4ebdfe..fe470edcf4d 100644 --- a/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts +++ b/packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts @@ -23,12 +23,12 @@ import { getInfoWithoutType, } from '../../global'; import { PluginSystem, AsyncHook, AsyncWaterfallHook } from '../../utils/hooks'; -import { FederationHost } from '../../core'; +import { ModuleFederation } from '../../core'; import { assert } from '../../utils/logger'; export function getGlobalRemoteInfo( moduleInfo: Remote, - origin: FederationHost, + origin: ModuleFederation, ): { hostGlobalSnapshot: ModuleInfo | undefined; globalSnapshot: ReturnType; @@ -69,7 +69,7 @@ export function getGlobalRemoteInfo( export class SnapshotHandler { loadingHostSnapshot: Promise | null = null; - HostInstance: FederationHost; + HostInstance: ModuleFederation; manifestCache: Map = new Map(); hooks = new PluginSystem({ beforeLoadRemoteSnapshot: new AsyncHook< @@ -98,17 +98,17 @@ export class SnapshotHandler { }>('loadRemoteSnapshot'), afterLoadSnapshot: new AsyncWaterfallHook<{ id?: string; - host: FederationHost; + host: ModuleFederation; options: Options; moduleInfo: Remote; remoteSnapshot: ModuleInfo; }>('afterLoadSnapshot'), }); - loaderHook: FederationHost['loaderHook']; + loaderHook: ModuleFederation['loaderHook']; manifestLoading: Record> = Global.__FEDERATION__.__MANIFEST_LOADING__; - constructor(HostInstance: FederationHost) { + constructor(HostInstance: ModuleFederation) { this.HostInstance = HostInstance; this.loaderHook = HostInstance.loaderHook; } diff --git a/packages/runtime-core/src/plugins/snapshot/index.ts b/packages/runtime-core/src/plugins/snapshot/index.ts index b40be0b1f66..fde0f973576 100644 --- a/packages/runtime-core/src/plugins/snapshot/index.ts +++ b/packages/runtime-core/src/plugins/snapshot/index.ts @@ -3,7 +3,7 @@ import { getResourceUrl, isBrowserEnv, } from '@module-federation/sdk'; -import { FederationRuntimePlugin } from '../../type/plugin'; +import { ModuleFederationRuntimePlugin } from '../../type/plugin'; import { error, isPureRemoteEntry, @@ -37,7 +37,7 @@ export function assignRemoteInfo( remoteInfo.buildVersion = remoteSnapshot.buildVersion; } -export function snapshotPlugin(): FederationRuntimePlugin { +export function snapshotPlugin(): ModuleFederationRuntimePlugin { return { name: 'snapshot-plugin', async afterResolve(args) { diff --git a/packages/runtime-core/src/remote/index.ts b/packages/runtime-core/src/remote/index.ts index 3b2de0e2b75..9b4ccfb9bbb 100644 --- a/packages/runtime-core/src/remote/index.ts +++ b/packages/runtime-core/src/remote/index.ts @@ -27,7 +27,7 @@ import { RemoteEntryExports, CallFrom, } from '../type'; -import { FederationHost } from '../core'; +import { ModuleFederation } from '../core'; import { PluginSystem, AsyncHook, @@ -54,28 +54,28 @@ export interface LoadRemoteMatch { expose: string; remote: Remote; options: Options; - origin: FederationHost; + origin: ModuleFederation; remoteInfo: RemoteInfo; remoteSnapshot?: ModuleInfo; } export class RemoteHandler { - host: FederationHost; + host: ModuleFederation; idToRemoteMap: Record; hooks = new PluginSystem({ beforeRegisterRemote: new SyncWaterfallHook<{ remote: Remote; - origin: FederationHost; + origin: ModuleFederation; }>('beforeRegisterRemote'), registerRemote: new SyncWaterfallHook<{ remote: Remote; - origin: FederationHost; + origin: ModuleFederation; }>('registerRemote'), beforeRequest: new AsyncWaterfallHook<{ id: string; options: Options; - origin: FederationHost; + origin: ModuleFederation; }>('beforeRequest'), onLoad: new AsyncHook< [ @@ -85,7 +85,7 @@ export class RemoteHandler { pkgNameOrAlias: string; remote: Remote; options: ModuleOptions; - origin: FederationHost; + origin: ModuleFederation; exposeModule: any; exposeModuleFactory: any; moduleInstance: Module; @@ -101,7 +101,7 @@ export class RemoteHandler { remote: Remote; remoteSnapshot: ModuleInfo; preloadConfig: PreloadRemoteArgs; - origin: FederationHost; + origin: ModuleFederation; }, ], void @@ -118,7 +118,7 @@ export class RemoteHandler { | 'beforeLoadShare' | 'afterResolve' | 'onLoad'; - origin: FederationHost; + origin: ModuleFederation; }, ], void | unknown @@ -128,14 +128,14 @@ export class RemoteHandler { { preloadOps: Array; options: Options; - origin: FederationHost; + origin: ModuleFederation; }, ] >('beforePreloadRemote'), generatePreloadAssets: new AsyncHook< [ { - origin: FederationHost; + origin: ModuleFederation; preloadOptions: PreloadOptions[number]; remote: Remote; remoteInfo: RemoteInfo; @@ -149,12 +149,12 @@ export class RemoteHandler { afterPreloadRemote: new AsyncHook<{ preloadOps: Array; options: Options; - origin: FederationHost; + origin: ModuleFederation; }>(), loadEntry: new AsyncHook< [ { - loaderHook: FederationHost['loaderHook']; + loaderHook: ModuleFederation['loaderHook']; remoteInfo: RemoteInfo; remoteEntryExports?: RemoteEntryExports; }, @@ -163,7 +163,7 @@ export class RemoteHandler { >(), }); - constructor(host: FederationHost) { + constructor(host: ModuleFederation) { this.host = host; this.idToRemoteMap = {}; } @@ -342,7 +342,7 @@ export class RemoteHandler { })) as { id: string; options: Options; - origin: FederationHost; + origin: ModuleFederation; }; if (!loadRemoteArgs) { diff --git a/packages/runtime-core/src/shared/index.ts b/packages/runtime-core/src/shared/index.ts index c1d6728af66..f2c742fffa3 100644 --- a/packages/runtime-core/src/shared/index.ts +++ b/packages/runtime-core/src/shared/index.ts @@ -17,7 +17,7 @@ import { InitTokens, CallFrom, } from '../type'; -import { FederationHost } from '../core'; +import { ModuleFederation } from '../core'; import { PluginSystem, AsyncHook, @@ -35,7 +35,7 @@ import { DEFAULT_SCOPE } from '../constant'; import { LoadRemoteMatch } from '../remote'; export class SharedHandler { - host: FederationHost; + host: ModuleFederation; shareScopeMap: ShareScopeMap; hooks = new PluginSystem({ afterResolve: new AsyncWaterfallHook('afterResolve'), @@ -43,10 +43,10 @@ export class SharedHandler { pkgName: string; shareInfo?: Shared; shared: Options['shared']; - origin: FederationHost; + origin: ModuleFederation; }>('beforeLoadShare'), // not used yet - loadShare: new AsyncHook<[FederationHost, string, ShareInfos]>(), + loadShare: new AsyncHook<[ModuleFederation, string, ShareInfos]>(), resolveShare: new SyncWaterfallHook<{ shareScopeMap: ShareScopeMap; scope: string; @@ -59,13 +59,13 @@ export class SharedHandler { initContainerShareScopeMap: new SyncWaterfallHook<{ shareScope: ShareScopeMap[string]; options: Options; - origin: FederationHost; + origin: ModuleFederation; scopeName: string; hostShareScopeMap?: ShareScopeMap; }>('initContainerShareScopeMap'), }); initTokens: InitTokens; - constructor(host: FederationHost) { + constructor(host: ModuleFederation) { this.host = host; this.shareScopeMap = {}; this.initTokens = {}; diff --git a/packages/runtime-core/src/type/config.ts b/packages/runtime-core/src/type/config.ts index 7e24d614dc5..cf2ccec40ce 100644 --- a/packages/runtime-core/src/type/config.ts +++ b/packages/runtime-core/src/type/config.ts @@ -4,7 +4,7 @@ import type { Module, RemoteEntryType, } from '@module-federation/sdk'; -import { FederationRuntimePlugin } from './plugin'; +import { ModuleFederationRuntimePlugin } from './plugin'; export type Optional = Omit & Partial; export type PartialOptional = Omit & { @@ -111,7 +111,7 @@ export interface Options { version?: string; remotes: Array; shared: ShareInfos; - plugins: Array; + plugins: Array; inBrowser: boolean; shareStrategy?: ShareStrategy; } diff --git a/packages/runtime-core/src/type/plugin.ts b/packages/runtime-core/src/type/plugin.ts index 2d57568220b..bba4e421f77 100644 --- a/packages/runtime-core/src/type/plugin.ts +++ b/packages/runtime-core/src/type/plugin.ts @@ -1,10 +1,10 @@ -import { FederationHost } from '../core'; +import { ModuleFederation } from '../core'; import { Module } from '../module'; import { SnapshotHandler } from '../plugins/snapshot/SnapshotHandler'; import { SharedHandler } from '../shared'; import { RemoteHandler } from '../remote'; -type CoreLifeCycle = FederationHost['hooks']['lifecycle']; +type CoreLifeCycle = ModuleFederation['hooks']['lifecycle']; type CoreLifeCyclePartial = Partial<{ [k in keyof CoreLifeCycle]: Parameters[0]; }>; @@ -36,7 +36,7 @@ type RemoteLifeCycleCyclePartial = Partial<{ [k in keyof RemoteLifeCycle]: Parameters[0]; }>; -export type FederationRuntimePlugin = CoreLifeCyclePartial & +export type ModuleFederationRuntimePlugin = CoreLifeCyclePartial & SnapshotLifeCycleCyclePartial & SharedLifeCycleCyclePartial & RemoteLifeCycleCyclePartial & diff --git a/packages/runtime-core/src/utils/load.ts b/packages/runtime-core/src/utils/load.ts index 50cc67bb236..ebdeff2b13e 100644 --- a/packages/runtime-core/src/utils/load.ts +++ b/packages/runtime-core/src/utils/load.ts @@ -5,7 +5,7 @@ import { isBrowserEnv, } from '@module-federation/sdk'; import { DEFAULT_REMOTE_TYPE, DEFAULT_SCOPE } from '../constant'; -import { FederationHost } from '../core'; +import { ModuleFederation } from '../core'; import { globalLoading, getRemoteEntryExports } from '../global'; import { Remote, RemoteEntryExports, RemoteInfo } from '../type'; import { assert } from './logger'; @@ -109,7 +109,7 @@ async function loadEntryScript({ name: string; globalName: string; entry: string; - loaderHook: FederationHost['loaderHook']; + loaderHook: ModuleFederation['loaderHook']; }): Promise { const { entryExports: remoteEntryExports } = getRemoteEntryExports( name, @@ -160,7 +160,7 @@ async function loadEntryDom({ }: { remoteInfo: RemoteInfo; remoteEntryExports?: RemoteEntryExports; - loaderHook: FederationHost['loaderHook']; + loaderHook: ModuleFederation['loaderHook']; }) { const { entry, entryGlobalName: globalName, name, type } = remoteInfo; switch (type) { @@ -179,7 +179,7 @@ async function loadEntryNode({ loaderHook, }: { remoteInfo: RemoteInfo; - loaderHook: FederationHost['loaderHook']; + loaderHook: ModuleFederation['loaderHook']; }) { const { entry, entryGlobalName: globalName, name, type } = remoteInfo; const { entryExports: remoteEntryExports } = getRemoteEntryExports( @@ -225,7 +225,7 @@ export async function getRemoteEntry({ remoteEntryExports, remoteInfo, }: { - origin: FederationHost; + origin: ModuleFederation; remoteInfo: RemoteInfo; remoteEntryExports?: RemoteEntryExports | undefined; }): Promise { diff --git a/packages/runtime-core/src/utils/plugin.ts b/packages/runtime-core/src/utils/plugin.ts index 36f84a26ad7..8dca108499c 100644 --- a/packages/runtime-core/src/utils/plugin.ts +++ b/packages/runtime-core/src/utils/plugin.ts @@ -1,4 +1,4 @@ -import { FederationHost } from '../core'; +import { ModuleFederation } from '../core'; import { UserOptions } from '../type'; import { Module } from '../module'; import { getGlobalHostPlugins } from '../global'; @@ -6,10 +6,10 @@ import { getGlobalHostPlugins } from '../global'; export function registerPlugins( plugins: UserOptions['plugins'], hookInstances: Array< - | FederationHost['hooks'] - | FederationHost['snapshotHandler']['hooks'] - | FederationHost['sharedHandler']['hooks'] - | FederationHost['remoteHandler']['hooks'] + | ModuleFederation['hooks'] + | ModuleFederation['snapshotHandler']['hooks'] + | ModuleFederation['sharedHandler']['hooks'] + | ModuleFederation['remoteHandler']['hooks'] | Module['host']['loaderHook'] | Module['host']['bridgeHook'] >, diff --git a/packages/runtime-core/src/utils/preload.ts b/packages/runtime-core/src/utils/preload.ts index ed99ee6c3bf..cadca6251c8 100644 --- a/packages/runtime-core/src/utils/preload.ts +++ b/packages/runtime-core/src/utils/preload.ts @@ -10,7 +10,7 @@ import { } from '../type'; import { matchRemote } from './manifest'; import { assert } from './logger'; -import { FederationHost } from '../core'; +import { ModuleFederation } from '../core'; import { getRemoteEntry } from './load'; export function defaultPreloadArgs( @@ -66,7 +66,7 @@ export function normalizePreloadExposes(exposes?: string[]): string[] { export function preloadAssets( remoteInfo: RemoteInfo, - host: FederationHost, + host: ModuleFederation, assets: PreloadAssets, // It is used to distinguish preload from load remote parallel loading useLinkPreload = true, diff --git a/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts b/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts index 995b238bf2d..e574325d0f6 100644 --- a/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts +++ b/packages/runtime-plugins/inject-external-runtime-core-plugin/src/index.ts @@ -1,6 +1,6 @@ import * as runtimeCore from '@module-federation/runtime-tools/runtime-core'; -import type { FederationRuntimePlugin } from '@module-federation/runtime-tools/runtime-core'; +import type { ModuleFederationRuntimePlugin } from '@module-federation/runtime-tools/runtime-core'; declare global { var __VERSION__: string; var _FEDERATION_RUNTIME_CORE: typeof runtimeCore; @@ -10,7 +10,7 @@ declare global { }; } -function injectExternalRuntimeCorePlugin(): FederationRuntimePlugin { +function injectExternalRuntimeCorePlugin(): ModuleFederationRuntimePlugin { return { name: 'inject-external-runtime-core-plugin', version: __VERSION__, diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index c9ffa4d39ae..7e7663290e0 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -424,19 +424,19 @@ - 9e32644: Added comprehensive integration tests for the API synchronization and enhanced the embedded module proxy implementation. - Added detailed integration tests for API consistency between embedded and index modules. - - Tests include export comparison and method consistency for `FederationHost` and `Module` classes. + - Tests include export comparison and method consistency for `ModuleFederation` and `Module` classes. - Introduced and updated the `embedded.ts` file to dynamically access the runtime modules at runtime. - Included detailed implementations for accessing and wrapping existing runtime functions. - - Exposed the previously private `formatOptions` method in the `FederationHost` class publicly. + - Exposed the previously private `formatOptions` method in the `ModuleFederation` class publicly. - Enhanced error handling for uninstantiated or unregistered runtime access. - 9e32644: - Refactor `embedded.ts` to use a proxy pattern for better runtime compatibility: - - Implement FederationHost and Module classes that delegate to the actual runtime implementation + - Implement ModuleFederation and Module classes that delegate to the actual runtime implementation - Expose all public methods and properties from the original classes - Use a lazy initialization approach to ensure proper runtime loading - Add comprehensive test suite for API synchronization between embedded.ts and index.ts - Introduce new test file `sync.spec.ts` with extensive tests for API compatibility - - Ensure FederationHost and Module classes have the same methods in both files + - Ensure ModuleFederation and Module classes have the same methods in both files - Test various scenarios including remote loading, manifest handling, and circular dependencies - Modify `core.ts` to make `formatOptions` method public - Updated dependencies [9e32644] diff --git a/packages/runtime/__tests__/hooks.spec.ts b/packages/runtime/__tests__/hooks.spec.ts index a322b7e7f6b..03034d36a3c 100644 --- a/packages/runtime/__tests__/hooks.spec.ts +++ b/packages/runtime/__tests__/hooks.spec.ts @@ -1,9 +1,9 @@ import { assert, describe, test, it } from 'vitest'; import { - FederationHost, + ModuleFederation, addGlobalSnapshot, } from '@module-federation/runtime-core'; -import { FederationRuntimePlugin } from '@module-federation/runtime-core/types'; +import { ModuleFederationRuntimePlugin } from '@module-federation/runtime-core/types'; import { mockStaticServer, removeScriptTags } from './mock/utils'; // eslint-disable-next-line max-lines-per-function @@ -22,7 +22,7 @@ describe('hooks', () => { initArgs: any, beforeLoadRemoteArgs, loadRemoteArgs; - const testPlugin: () => FederationRuntimePlugin = () => ({ + const testPlugin: () => ModuleFederationRuntimePlugin = () => ({ name: 'testPlugin', beforeInit(args) { beforeInitArgs = args; @@ -61,7 +61,7 @@ describe('hooks', () => { ], plugins: [testPlugin()], }; - const GM = new FederationHost(options); + const GM = new ModuleFederation(options); assert(beforeInitArgs, "beforeInitArgs can't be undefined"); expect(beforeInitArgs).toMatchObject({ options: { @@ -151,7 +151,7 @@ describe('hooks', () => { }, }); - const INSTANCE = new FederationHost({ + const INSTANCE = new ModuleFederation({ name: '@loader-hooks/globalinfo', remotes: [ { @@ -236,7 +236,7 @@ describe('hooks', () => { statusText: 'OK', headers: { 'Content-Type': 'application/json' }, }); - const fetchPlugin: () => FederationRuntimePlugin = () => ({ + const fetchPlugin: () => ModuleFederationRuntimePlugin = () => ({ name: 'fetch-plugin', fetch(url, options) { if (url === 'http://mockxxx.com/loader-fetch-hooks-mf-manifest.json') { @@ -244,7 +244,7 @@ describe('hooks', () => { } }, }); - const INSTANCE = new FederationHost({ + const INSTANCE = new ModuleFederation({ name: '@loader-hooks/fetch', remotes: [ { @@ -294,7 +294,7 @@ describe('hooks', () => { headers: { 'Content-Type': 'application/json' }, }); - const fetchPlugin: () => FederationRuntimePlugin = function () { + const fetchPlugin: () => ModuleFederationRuntimePlugin = function () { return { name: 'fetch-plugin', fetch(url, options) { @@ -306,7 +306,7 @@ describe('hooks', () => { }, }; }; - const loadEntryPlugin = function (): FederationRuntimePlugin { + const loadEntryPlugin = function (): ModuleFederationRuntimePlugin { return { name: 'load-entry-plugin', loadEntry({ remoteInfo }) { @@ -322,7 +322,7 @@ describe('hooks', () => { } as any; }; - const INSTANCE = new FederationHost({ + const INSTANCE = new ModuleFederation({ name: '@loader-hooks/fetch', remotes: [ { diff --git a/packages/runtime/__tests__/instance.spec.ts b/packages/runtime/__tests__/instance.spec.ts index 185c285dc3e..7867f40e15b 100644 --- a/packages/runtime/__tests__/instance.spec.ts +++ b/packages/runtime/__tests__/instance.spec.ts @@ -1,9 +1,9 @@ import { assert, describe, test, it } from 'vitest'; -import { FederationHost } from '../src/index'; +import { ModuleFederation } from '../src/index'; -describe('FederationHost', () => { +describe('ModuleFederation', () => { it('should initialize with provided arguments', () => { - const GM = new FederationHost({ + const GM = new ModuleFederation({ name: '@federation/instance', version: '1.0.1', remotes: [], diff --git a/packages/runtime/__tests__/load-remote.spec.ts b/packages/runtime/__tests__/load-remote.spec.ts index b072baccd6d..6801cd1dc64 100644 --- a/packages/runtime/__tests__/load-remote.spec.ts +++ b/packages/runtime/__tests__/load-remote.spec.ts @@ -1,6 +1,6 @@ import 'whatwg-fetch'; import { assert, describe, it } from 'vitest'; -import { FederationHost, init } from '../src/index'; +import { ModuleFederation, init } from '../src/index'; import { mockRemoteSnapshot } from './mock/utils'; import { matchRemoteWithNameAndExpose } from '@module-federation/runtime-core'; import { @@ -109,7 +109,7 @@ describe('matchRemote', () => { // eslint-disable-next-line max-lines-per-function describe('loadRemote', () => { it('api functionality', () => { - const FederationInstance = new FederationHost({ + const FederationInstance = new ModuleFederation({ name: '@federation-test/loadRemote-api', remotes: [], }); @@ -149,7 +149,7 @@ describe('loadRemote', () => { }, }); - const FederationInstance = new FederationHost({ + const FederationInstance = new ModuleFederation({ name: '@federation-test/globalinfo', remotes: [ { @@ -195,7 +195,7 @@ describe('loadRemote', () => { }, }); - const FM = new FederationHost({ + const FM = new ModuleFederation({ name: 'xxxxx', remotes: [ { @@ -251,7 +251,7 @@ describe('loadRemote', () => { }, }); - const FederationInstance = new FederationHost({ + const FederationInstance = new ModuleFederation({ name: '@federation-test/compatible', remotes: [ { @@ -268,7 +268,7 @@ describe('loadRemote', () => { reset(); }); it('handles remote entry URL with query', async () => { - const FederationInstance = new FederationHost({ + const FederationInstance = new ModuleFederation({ name: '@federation-test/compatible', remotes: [ { @@ -333,11 +333,11 @@ describe('loadRemote', () => { }, ], }; - const FM = new FederationHost({ + const FM = new ModuleFederation({ name: '@module-federation/load-remote-different-instance', ...vmOptions, }); - const FM2 = new FederationHost({ + const FM2 = new ModuleFederation({ name: '@module-federation/load-remote-different-instance2', ...vmOptions, }); @@ -355,7 +355,7 @@ describe('loadRemote', () => { describe('loadRemote with manifest.json', () => { it('handles duplicate request to manifest.json', async () => { - const FM = new FederationHost({ + const FM = new ModuleFederation({ name: '@demo/host', remotes: [ { @@ -366,7 +366,7 @@ describe('loadRemote with manifest.json', () => { ], }); - const FM2 = new FederationHost({ + const FM2 = new ModuleFederation({ name: '@demo/host2', remotes: [ { @@ -393,7 +393,7 @@ describe('loadRemote with manifest.json', () => { ).toBe(1); }); it('handles circular dependencies', async () => { - setGlobalFederationConstructor(FederationHost, true); + setGlobalFederationConstructor(ModuleFederation, true); const FM = init({ name: '@circulate-deps/app1', remotes: [ @@ -416,7 +416,7 @@ describe('loadRemote with manifest.json', () => { setGlobalFederationConstructor(undefined, true); }); it('handles manifest.json with query', async () => { - const FM = new FederationHost({ + const FM = new ModuleFederation({ name: '@demo/host', remotes: [ { @@ -463,7 +463,7 @@ describe('lazy loadRemote and add remote into snapshot', () => { remoteEntry: 'federation-remote-entry.js', }, }); - const federationInstance = new FederationHost({ + const federationInstance = new ModuleFederation({ name: '@demo/app1', remotes: [ { @@ -505,7 +505,7 @@ describe('lazy loadRemote and add remote into snapshot', () => { }, }); - const federationInstance = new FederationHost({ + const federationInstance = new ModuleFederation({ name: '@demo/app1', remotes: [ { @@ -583,7 +583,7 @@ describe('loadRemote', () => { }, }); - const FederationInstance = new FederationHost({ + const FederationInstance = new ModuleFederation({ name: '@federation-test/globalinfo', remotes: [ { diff --git a/packages/runtime/__tests__/mock/utils.ts b/packages/runtime/__tests__/mock/utils.ts index 4ad4f3eafab..7cc926bfd96 100644 --- a/packages/runtime/__tests__/mock/utils.ts +++ b/packages/runtime/__tests__/mock/utils.ts @@ -90,7 +90,7 @@ export function mockStaticServer({ }); } -import { FederationRuntimePlugin } from '../../src/type'; +import { ModuleFederationRuntimePlugin } from '../../src/type'; import { ProviderModuleInfo } from '@module-federation/sdk'; export const mockRemoteSnapshot: ( @@ -98,7 +98,7 @@ export const mockRemoteSnapshot: ( remoteSnapshots: { [name: string]: ProviderModuleInfo; }, -) => FederationRuntimePlugin = function (uniqueId, remoteSnapshots) { +) => ModuleFederationRuntimePlugin = function (uniqueId, remoteSnapshots) { return { name: `mock-snapshot-${uniqueId}`, loadSnapshot({ moduleInfo, ...info }) { diff --git a/packages/runtime/__tests__/register-remotes.spec.ts b/packages/runtime/__tests__/register-remotes.spec.ts index b8295c8dbd2..174d914c491 100644 --- a/packages/runtime/__tests__/register-remotes.spec.ts +++ b/packages/runtime/__tests__/register-remotes.spec.ts @@ -1,9 +1,9 @@ import { assert, describe, it, expect } from 'vitest'; -import { FederationHost } from '../src/index'; +import { ModuleFederation } from '../src/index'; -describe('FederationHost', () => { +describe('ModuleFederation', () => { it('registers new remotes and loads them correctly', async () => { - const FM = new FederationHost({ + const FM = new ModuleFederation({ name: '@federation/instance', version: '1.0.1', remotes: [ @@ -37,7 +37,7 @@ describe('FederationHost', () => { expect(res).toBe('hello app2'); }); it('does not merge loaded remote by default', async () => { - const FM = new FederationHost({ + const FM = new ModuleFederation({ name: '@federation/instance', version: '1.0.1', remotes: [ @@ -65,7 +65,7 @@ describe('FederationHost', () => { expect(app1Res).toBe('hello app1 entry1'); }); it('merges loaded remote by setting "force: true"', async () => { - const FM = new FederationHost({ + const FM = new ModuleFederation({ name: '@federation/instance', version: '1.0.1', remotes: [ diff --git a/packages/runtime/__tests__/shares.spec.ts b/packages/runtime/__tests__/shares.spec.ts index 7194bee6e55..f67eb017248 100644 --- a/packages/runtime/__tests__/shares.spec.ts +++ b/packages/runtime/__tests__/shares.spec.ts @@ -11,7 +11,7 @@ import { shareInfoWithoutLibAndGetProvider, } from './share'; // import { assert } from '../src/utils/logger'; -import { FederationHost } from '@module-federation/runtime-core'; +import { ModuleFederation } from '@module-federation/runtime-core'; import { UserOptions, ShareScopeMap, @@ -108,8 +108,8 @@ describe('shared', () => { }, }; - const FederationInstance = new FederationHost(gmConfig1); - const FederationInstance2 = new FederationHost(gmConfig2); + const FederationInstance = new ModuleFederation(gmConfig1); + const FederationInstance2 = new ModuleFederation(gmConfig2); const reactInstance = await FederationInstance.loadShare<{ version: string; @@ -156,7 +156,7 @@ describe('shared', () => { }, }; - const FederationInstance = new FederationHost(gmConfig1); + const FederationInstance = new ModuleFederation(gmConfig1); const [reactInstance1, reactInstance2] = await Promise.all([ FederationInstance.loadShare<{ @@ -214,7 +214,7 @@ describe('shared', () => { }, }; - const GM1 = new FederationHost(gmConfig1); + const GM1 = new ModuleFederation(gmConfig1); GM1.initOptions({ name: '@federation/runtime-deps', remotes: [], @@ -226,7 +226,7 @@ describe('shared', () => { }, }); await GM1.loadShare<{ from: string; version: string }>('runtime-react'); - const FederationInstance2 = new FederationHost(gmConfig2); + const FederationInstance2 = new ModuleFederation(gmConfig2); const shared = await FederationInstance2.loadShare<{ from: string; version: string; @@ -238,7 +238,7 @@ describe('shared', () => { }); // it('share deps', async () => { - // const GM1 = new FederationHost({ + // const GM1 = new ModuleFederation({ // name: '@federation/load-deps', // remotes: [], // shared: { @@ -334,12 +334,12 @@ describe('single shared', () => { }, }; - const FM1 = new FederationHost(vmConfig1); + const FM1 = new ModuleFederation(vmConfig1); await FM1.loadShare<{ from: string; version: string }>('runtime-react'); - const FM3 = new FederationHost(vmConfig3); + const FM3 = new ModuleFederation(vmConfig3); await FM3.loadShare<{ from: string; version: string }>('runtime-react'); - const FM2 = new FederationHost(vmConfig2); + const FM2 = new ModuleFederation(vmConfig2); const shared = await FM2.loadShare<{ from: string; version: string }>( 'runtime-react', ); @@ -394,8 +394,8 @@ describe('eager shared', () => { }, }; - const FM = new FederationHost(federationConfig1); - const FM2 = new FederationHost(federationConfig2); + const FM = new ModuleFederation(federationConfig1); + const FM2 = new ModuleFederation(federationConfig2); const reactInstanceFactory = FM.loadShareSync<{ version: string; @@ -454,8 +454,8 @@ describe('eager shared', () => { }, }; - const FM = new FederationHost(federationConfig1); - const FM2 = new FederationHost(federationConfig2); + const FM = new ModuleFederation(federationConfig1); + const FM2 = new ModuleFederation(federationConfig2); const reactInstance2 = FM2.loadShareSync<{ version: string; @@ -485,7 +485,7 @@ describe('eager shared', () => { }, }, }; - const FM = new FederationHost(federationConfig1); + const FM = new ModuleFederation(federationConfig1); expect(function () { FM.loadShareSync<{ @@ -530,8 +530,8 @@ describe('strictVersion shared', () => { }, }; - const FM1 = new FederationHost(federationConfig1); - const FM2 = new FederationHost(federationConfig2); + const FM1 = new ModuleFederation(federationConfig1); + const FM2 = new ModuleFederation(federationConfig2); await FM1.loadShare<{ from: string; version: string }>('runtime-react'); FM2.initShareScopeMap('default', FM1.shareScopeMap['default']); @@ -577,8 +577,8 @@ describe('strictVersion shared', () => { }, }; - const FM1 = new FederationHost(federationConfig1); - const FM2 = new FederationHost(federationConfig2); + const FM1 = new ModuleFederation(federationConfig1); + const FM2 = new ModuleFederation(federationConfig2); await FM1.loadShare<{ from: string; version: string }>('runtime-react'); FM2.initShareScopeMap('default', FM1.shareScopeMap['default']); @@ -660,7 +660,7 @@ describe('with shareScope shared', () => { }, }; - const FM1 = new FederationHost(federationConfig1); + const FM1 = new ModuleFederation(federationConfig1); // initShareScopeMap will be called while container.init execute FM1.initShareScopeMap('default', existedShareScopeMap['default']); @@ -740,7 +740,7 @@ describe('with shareScope shared', () => { }, }; - const FM1 = new FederationHost(federationConfig1); + const FM1 = new ModuleFederation(federationConfig1); // initShareScopeMap will be called while container.init execute FM1.initShareScopeMap('old', existedShareScopeMap['old']); @@ -771,7 +771,7 @@ describe('load share with customize consume info', () => { }, }; - const FM1 = new FederationHost(federationConfig1); + const FM1 = new ModuleFederation(federationConfig1); const shared = await FM1.loadShare<{ from: string }>('runtime-react'); assert(shared, "shared can't be null"); const sharedRes = shared(); @@ -793,7 +793,7 @@ describe('load share with customize consume info', () => { describe('load share with different strategy', () => { it('register all shared to shareScopeMap while strategy is "version-first"', async () => { - setGlobalFederationConstructor(FederationHost, true); + setGlobalFederationConstructor(ModuleFederation, true); const federationConfig1: UserOptions = { name: '@shared-test/app1', @@ -817,7 +817,7 @@ describe('load share with different strategy', () => { }, }; - const FM1 = new FederationHost(federationConfig1); + const FM1 = new ModuleFederation(federationConfig1); const shared = await FM1.loadShare<{ from: string }>('runtime-react'); // should register remote shared to share scope map @@ -835,7 +835,7 @@ describe('load share with different strategy', () => { }); it('register only self shared to shareScopeMap while strategy is "loaded-first"', async () => { - setGlobalFederationConstructor(FederationHost, true); + setGlobalFederationConstructor(ModuleFederation, true); const federationConfig1: UserOptions = { name: '@shared-test/app1', @@ -859,7 +859,7 @@ describe('load share with different strategy', () => { }, }; - const FM1 = new FederationHost(federationConfig1); + const FM1 = new ModuleFederation(federationConfig1); const shared = await FM1.loadShare<{ from: string }>('runtime-react'); // should not register remote shared to share scope map @@ -903,7 +903,7 @@ describe('load share while shared has multiple versions', () => { }, }; - const FM1 = new FederationHost(federationConfig1); + const FM1 = new ModuleFederation(federationConfig1); const shared = await FM1.loadShare<{ version: string }>('runtime-react'); assert(shared, "shared can't be null"); const sharedRes = shared(); @@ -936,7 +936,7 @@ describe('load share while shared has multiple versions', () => { }, }; - const FM1 = new FederationHost(federationConfig1); + const FM1 = new ModuleFederation(federationConfig1); const shared = await FM1.loadShare<{ version: string }>('runtime-react', { resolver: (sharedOptions) => { return ( diff --git a/packages/runtime/__tests__/snapshot.spec.ts b/packages/runtime/__tests__/snapshot.spec.ts index 483140c1888..8b798022a67 100644 --- a/packages/runtime/__tests__/snapshot.spec.ts +++ b/packages/runtime/__tests__/snapshot.spec.ts @@ -1,5 +1,5 @@ import { assert, describe, it } from 'vitest'; -import { FederationHost } from '../src'; +import { ModuleFederation } from '../src'; import { getGlobalSnapshot, resetFederationGlobalInfo, @@ -15,7 +15,7 @@ describe('snapshot', () => { 'http://localhost:1111/resources/snapshot/remote1/federation-manifest.json'; const Remote2Entry = 'http://localhost:1111/resources/snapshot/remote2/federation-manifest.json'; - const FM1 = new FederationHost({ + const FM1 = new ModuleFederation({ name: '@snapshot/host', version: '0.0.3', remotes: [ diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 4b23d6b2e39..f9e473c534f 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -1,11 +1,16 @@ import { - FederationHost, + ModuleFederation, type UserOptions, getGlobalFederationConstructor, setGlobalFederationInstance, assert, setGlobalFederationConstructor, } from '@module-federation/runtime-core'; +import { + runtimeDescMap, + getShortErrorMsg, + RUNTIME_009, +} from '@module-federation/error-codes'; import { getGlobalFederationInstance } from './utils'; export { @@ -15,21 +20,28 @@ export { getRemoteEntry, getRemoteInfo, registerGlobalPlugins, - type FederationRuntimePlugin, + type ModuleFederationRuntimePlugin, type Federation, } from '@module-federation/runtime-core'; -export { FederationHost }; +export { ModuleFederation }; + +export function createInstance(options: UserOptions) { + // Retrieve debug constructor + const ModuleFederationConstructor = + getGlobalFederationConstructor() || ModuleFederation; + return new ModuleFederationConstructor(options); +} -let FederationInstance: FederationHost | null = null; -export function init(options: UserOptions): FederationHost { +let FederationInstance: ModuleFederation | null = null; +/** + * @deprecated Use createInstance or getInstance instead + */ +export function init(options: UserOptions): ModuleFederation { // Retrieve the same instance with the same name const instance = getGlobalFederationInstance(options.name, options.version); if (!instance) { - // Retrieve debug constructor - const FederationConstructor = - getGlobalFederationConstructor() || FederationHost; - FederationInstance = new FederationConstructor(options); + FederationInstance = createInstance(options); setGlobalFederationInstance(FederationInstance); return FederationInstance; } else { @@ -43,9 +55,9 @@ export function init(options: UserOptions): FederationHost { } export function loadRemote( - ...args: Parameters + ...args: Parameters ): Promise { - assert(FederationInstance, 'Please call init first'); + assert(FederationInstance, getShortErrorMsg(RUNTIME_009, runtimeDescMap)); const loadRemote: typeof FederationInstance.loadRemote = FederationInstance.loadRemote; // eslint-disable-next-line prefer-spread @@ -53,9 +65,9 @@ export function loadRemote( } export function loadShare( - ...args: Parameters + ...args: Parameters ): Promise T | undefined)> { - assert(FederationInstance, 'Please call init first'); + assert(FederationInstance, getShortErrorMsg(RUNTIME_009, runtimeDescMap)); // eslint-disable-next-line prefer-spread const loadShare: typeof FederationInstance.loadShare = FederationInstance.loadShare; @@ -63,9 +75,9 @@ export function loadShare( } export function loadShareSync( - ...args: Parameters + ...args: Parameters ): () => T | never { - assert(FederationInstance, 'Please call init first'); + assert(FederationInstance, getShortErrorMsg(RUNTIME_009, runtimeDescMap)); const loadShareSync: typeof FederationInstance.loadShareSync = FederationInstance.loadShareSync; // eslint-disable-next-line prefer-spread @@ -73,25 +85,25 @@ export function loadShareSync( } export function preloadRemote( - ...args: Parameters -): ReturnType { - assert(FederationInstance, 'Please call init first'); + ...args: Parameters +): ReturnType { + assert(FederationInstance, getShortErrorMsg(RUNTIME_009, runtimeDescMap)); // eslint-disable-next-line prefer-spread return FederationInstance.preloadRemote.apply(FederationInstance, args); } export function registerRemotes( - ...args: Parameters -): ReturnType { - assert(FederationInstance, 'Please call init first'); + ...args: Parameters +): ReturnType { + assert(FederationInstance, getShortErrorMsg(RUNTIME_009, runtimeDescMap)); // eslint-disable-next-line prefer-spread return FederationInstance.registerRemotes.apply(FederationInstance, args); } export function registerPlugins( - ...args: Parameters -): ReturnType { - assert(FederationInstance, 'Please call init first'); + ...args: Parameters +): ReturnType { + assert(FederationInstance, getShortErrorMsg(RUNTIME_009, runtimeDescMap)); // eslint-disable-next-line prefer-spread return FederationInstance.registerPlugins.apply(FederationInstance, args); } @@ -100,5 +112,13 @@ export function getInstance() { return FederationInstance; } +export function registerShared( + ...args: Parameters +): ReturnType { + assert(FederationInstance, getShortErrorMsg(RUNTIME_009, runtimeDescMap)); + // eslint-disable-next-line prefer-spread + return FederationInstance.registerShared.apply(FederationInstance, args); +} + // Inject for debug -setGlobalFederationConstructor(FederationHost); +setGlobalFederationConstructor(ModuleFederation); diff --git a/packages/runtime/src/utils.ts b/packages/runtime/src/utils.ts index 0dd3fefb120..01338aee94d 100644 --- a/packages/runtime/src/utils.ts +++ b/packages/runtime/src/utils.ts @@ -1,4 +1,4 @@ -import { FederationHost } from '@module-federation/runtime-core'; +import { ModuleFederation } from '@module-federation/runtime-core'; import { CurrentGlobal } from '@module-federation/runtime-core'; // injected by bundler, so it can not use runtime-core stuff @@ -13,7 +13,7 @@ export function getBuilderId(): string { export function getGlobalFederationInstance( name: string, version: string | undefined, -): FederationHost | undefined { +): ModuleFederation | undefined { const buildId = getBuilderId(); return CurrentGlobal.__FEDERATION__.__INSTANCES__.find((GMInstance) => { if (buildId && GMInstance.options.id === getBuilderId()) { diff --git a/packages/webpack-bundler-runtime/src/types.ts b/packages/webpack-bundler-runtime/src/types.ts index 110089f0434..4905a1806b9 100644 --- a/packages/webpack-bundler-runtime/src/types.ts +++ b/packages/webpack-bundler-runtime/src/types.ts @@ -15,10 +15,10 @@ type NonUndefined = ExcludeUndefined; type InitOptions = Parameters[0]; -type ModuleCache = runtime.FederationHost['moduleCache']; +type ModuleCache = runtime.ModuleFederation['moduleCache']; type InferModule = T extends Map ? U : never; type InferredModule = InferModule; -export type ShareScopeMap = runtime.FederationHost['shareScopeMap']; +export type ShareScopeMap = runtime.ModuleFederation['shareScopeMap']; type InitToken = Record>; @@ -128,7 +128,7 @@ export interface InitContainerEntryOptions { export interface Federation { runtime?: typeof runtime; - instance?: runtime.FederationHost; + instance?: runtime.ModuleFederation; initOptions?: InitOptions; installInitialConsumes?: (options: InstallInitialConsumesOptions) => any; bundlerRuntime?: {