# Runtime API 若使用构建插件,项目启动时将自动创建 `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__` 来查看已经创建好的实例。 ## 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:
  Type declaration```ts type InitOptions { // 当前 host 的名称 name: string; // 依赖远程模块的列表 // tip: 在运行时配置的 remotes 和构建插件传入的类型和数据并不完全一致 remotes: Array<RemoteInfo>; // 当前 host 需要共享出去的依赖列表 // 在使用构建插件时,用户可以在构建插件中配置需要共享的依赖,构建插件将会将需要共享的依赖注入到运行时的 shared 配置中 // shared 在运行时传入时必须要要手动传入版本实例引用,因为在运行时无法直接 shared?: { [pkgName: string]: ShareArgs | ShareArgs[]; }; }; type ShareArgs = | (SharedBaseArgs & { get: SharedGetter }) | (SharedBaseArgs & { lib: () => Module }); type SharedBaseArgs = { version: string; shareConfig?: SharedConfig; scope?: string | Array<string>; deps?: Array<string>; strategy?: 'version-first' | 'loaded-first'; }; type SharedGetter = (() => () => Module) | (() => Promise<() => Module>); type RemoteInfo = { alias?: string; }; interface RemotesWithEntry { name: string; entry: string; } type ShareInfos = { // 依赖的包名和依赖的基础信息、共享策略 [pkgName: string]: Share; }; type Share = { // 共享依赖的版本 version: string; // 当前的共享依赖被哪些模块消费了 useIn?: Array<string>; // 共享的依赖来自哪个模块 from?: string; // 获取共享依赖的实例的工厂函数,当无法加载缓存的共享实例时将加载自身的共享依赖 lib: () => Module; // 共享策略,将以一个什么策略来决定共享依赖的复用 shareConfig?: SharedConfig; // share 间的依赖 deps?: Array<string>; // 当前共享依赖放在什么 scope 下面,默认为 default scope?: string | Array<string>; }; ```
  示例```ts import { init, loadRemote } from '@module-federation/enhanced/runtime'; init({ name: "mf_host", remotes: [ { name: "remote", // 配置别名后可直接通过别名加载 alias: "app1", // 通过指定模块的 manifest.json 文件地址来决定加载的模块 entry: "http://localhost:2001/mf-manifest.json" } ], }); ```
### 如何迁移 #### Build Plugin(使用构建插件) 1. 移除 `init` API 的调用 2. 使用 [registerShared](#registershared) 代替 `init` 中的 `shared` 配置 3. 使用 [registerRemotes](#registerremotes) 代替 `init` 中的 `remotes` 配置 4. 使用 [registerPlugins](#registerplugins) 代替 `init` 中的 `plugins` 配置 5. 使用 [getInstance](#getinstance) 获取构建插件创建的 `ModuleFederation` 实例 ```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
  Type declaration```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`, 这会覆盖已经注册(且加载的模块, 并且自动删除已经加载过的模块缓存(如果已经加载过),同时在控制台输出警告,告知这操作存在风险性。 **Build Plugin(使用构建插件)** ```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 }); ``` **Pure Runtime(未使用构建插件)** ```ts import { createInstance } from '@module-federation/enhanced/runtime'; const mf = createInstance({ name: 'mf_host', remotes: [ { name: 'sub1', entry: 'http://localhost:2001/mf-manifest.json', } ] }); // 增加新的 remote sub2 mf.registerRemotes([ { name: 'sub2', entry: 'http://localhost:2002/mf-manifest.json', } ]); // 覆盖之前的 remote sub1 mf.registerRemotes([ { name: 'sub1', entry: 'http://localhost:2003/mf-manifest.json', } ],{ force:true }); ``` ## registerPlugins - type ```typescript function registerPlugins(plugins: ModuleFederationRuntimePlugin[]) {} ``` **Build Plugin(使用构建插件)** ```tsx import { registerPlugins } from '@module-federation/enhanced/runtime'; import runtimePlugin from './custom-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; }, } ]); ``` **Pure Runtime(未使用构建插件)** ```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()]); 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
  Type declaration```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<string>; deps?: Array<string>; 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>); ```
**Build Plugin(使用构建插件)** ```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: "^18.0.0" } }, "react-dom": { version: "18.0.0", scope: "default", lib: ()=> ReactDom, shareConfig: { singleton: true, requiredVersion: "^18.0.0" } } }); ``` **Pure Runtime(未使用构建插件)** ```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', } ] }); 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" } } }); ``` ## loadShare - Type: `loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial<Shared>;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})` - 获取 `share` 依赖,当全局环境有符合当前 `host` 的 `share` 依赖时,将优先复用当前已存在且满足 `share` 条件的依赖,否则将加载自身的依赖并存入全局缓存 - 该 `API` **一般不由用户直接调用,用于构建插件转换自身依赖时使用** **Build Plugin(使用构建插件)** ```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" } }, "react-dom": { version: "17.0.0", scope: "default", lib: ()=> ReactDom, shareConfig: { singleton: true, requiredVersion: "^17.0.0" } } }); loadShare("react").then((reactFactory)=>{ console.log(reactFactory()) }); ``` **Pure Runtime(未使用构建插件)** ```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) => { return ( sharedOptions.find((i) => i.version === '17.0.0') ?? sharedOptions[0] ); }, }).then((reactFactory) => { console.log(reactFactory()); // { version: '17.0.0)' } }); ``` ## loadRemote - Type: `loadRemote(id: string)` - 加载远程模块 **Build Plugin(使用构建插件)** ```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)); ``` **Pure Runtime(未使用构建插件)** ```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 declaration```typescript async function preloadRemote(preloadOptions: Array<PreloadRemoteArgs>){} type depsPreloadArg = Omit<PreloadRemoteArgs, 'depsRemote'>; type PreloadRemoteArgs = { // 预加载 remote 的名称和别名 nameOrAlias: string; // 是否需要预加载模块的接口,默认值为 false,具体参考<性能优化>中<Interface Prefetch 接口预请求>章节,@vmok/kit 版本需要大于 1.7.6 prefetchInterface?: boolean; // 需要预加载的 expose // 默认预加载所有的 expose // 提供了 expose 时只会加载所需要的 expose exposes?: Array<string>; // 默认请求 // 默认为 sync,只会加载 expose 中引用的同步代码 // 设置为 all 时将加载同步引用和异步引用 resourceCategory?: 'all' | 'sync'; // 未配置值时默认加载所有的依赖 // 配置了依赖后仅会加载配置选项 depsRemote?: boolean | Array<depsPreloadArg>; // 未配置时不过滤资源 // 配置了后将会过滤不需要的资源 filter?: (assetUrl: string) => boolean; }; ```
- Details 通过 `preloadRemote` 可以在更早的阶段开始预加载模块资源,避免出现瀑布请求,`preloadRemote` 可以预加载哪些内容: - `remote` 的 `remoteEntry` - `remote` 的 `expose` - `remote` 的同步资源还是异步资源 - `remote` 依赖的 `remote` 资源 **Build Plugin(使用构建插件)** ```tsx import { registerRemotes, preloadRemote } from '@module-federation/enhanced/runtime'; 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([ { nameOrAlias: 'sub1', filter(assetUrl) { return assetUrl.indexOf('ignore') === -1; }, depsRemote: [{ nameOrAlias: 'sub1-button' }], }, ]); // 预加载 sub2 模块 // 预加载 sub2 下的所有 expose // 预加载 sub2 的同步资源和异步资源 preloadRemote([ { nameOrAlias: 'sub2', resourceCategory: 'all', }, ]); // 预加载 sub3 模块的 add expose preloadRemote([ { nameOrAlias: 'sub3', resourceCategory: 'all', exposes: ['add'], }, ]); ``` **Pure Runtime(未使用构建插件)** ```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" }, ]); // 预加载 sub1 模块 // 过滤资源名称中携带 ignore 的资源信息 // 只预加载子依赖的 sub1-button 模块 mf.preloadRemote([ { nameOrAlias: 'sub1', filter(assetUrl) { return assetUrl.indexOf('ignore') === -1; }, depsRemote: [{ nameOrAlias: 'sub1-button' }], }, ]); // 预加载 sub2 模块 // 预加载 sub2 下的所有 expose // 预加载 sub2 的同步资源和异步资源 mf.preloadRemote([ { nameOrAlias: 'sub2', resourceCategory: 'all', }, ]); // 预加载 sub3 模块的 add expose mf.preloadRemote([ { nameOrAlias: 'sub3', resourceCategory: 'all', exposes: ['add'], }, ]); ```