Runtime API

init

  • Type: init(options: InitOptions): void
  • 用于运行时动态注册 Vmok 模块
  • InitOptions:
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>;
};
  • 示例
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"
        }
    ],
});

loadRemote

  • Type: loadRemote(id: string)
  • 加载远程模块
  • 示例
import { init, loadRemote } from '@module-federation/enhanced/runtime';

init({
    name: "mf_host",
    remotes: [
        {
          name: "remote",
          // 远程模块别名,配置别名后可通过别名加载模块。请注意:alias 和 name 的前缀不能相等
          alias: "app1",
          // 指定以 `mf-manifest.json` 结尾的远程模块地址,该文件由 mf 插件生成
          entry: "http://localhost:2001/vmok-manifest.json"
        }
    ]
});

// remoteName + expose
loadRemote("remote/util").then((m)=> m.add(1,2,3));

// alias + expose
loadRemote("app1/util").then((m)=> m.add(1,2,3));

loadShare

  • Type: loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial<Shared>;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})

  • 获取 share 依赖,当全局环境有符合当前 hostshare 依赖时,将优先复用当前已存在且满足 share 条件的依赖,否则将加载自身的依赖并存入全局缓存

  • API 一般不由用户直接调用,用于构建插件转换自身依赖时使用

  • 示例

import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime';
import React from 'react';
import ReactDom from 'react-dom';

init({
    name: "mf_host",
    remotes: [],
    shared: {
        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())
});

如果设置了多个版本 shared,默认会返回已加载且最高版本的 shared 。可以通过设置 extraOptions.resolver 来改变这个行为:

import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime';

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',
        },
      },
      {
        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',
        },
      },
    ],
  },
});

loadShare('react', {
   resolver: (sharedOptions) => {
      return (
        sharedOptions.find((i) => i.version === '17.0.0') ?? sharedOptions[0]
      );
  },
 }).then((reactFactory) => {
  console.log(reactFactory()); // { version: '17.0.0)' }
});

preloadRemote

  • type
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 可以预加载哪些内容:

  • remoteremoteEntry
  • remoteexpose
  • remote 的同步资源还是异步资源
  • remote 依赖的 remote 资源
  • Example
import { init, preloadRemote } from '@module-federation/enhanced/runtime';
init({
    name: 'mf_host',
    remotes: [
        {
            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;
        },
        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
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
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
function registerPlugins(plugins: FederationRuntimePlugin[]) {}
  • Example
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;
    },
  }
]);