Runtime API

init

  • Type: init(options: InitOptions): void
  • Used for dynamically registering Vmok modules at runtime.
  • InitOptions:
type InitOptions {
    // Name of the current host
    name: string;
    // List of dependent remote modules
    // tip: The remotes configured at runtime are not completely consistent in type and data with those passed in by the build plugin.
    remotes: Array<RemoteInfo>;
    // List of dependencies that the current host needs to share
    // When using the build plugin, users can configure the dependencies to be shared in the build plugin, and the build plugin will inject the dependencies to be shared into the shared configuration at runtime.
    // When shared is passed in at runtime, the version instance reference must be passed in manually, because it cannot be directly obtained at runtime.
    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 = {
  // Package name and basic information of the dependency, sharing strategy
  [pkgName: string]: Share;
};

type Share = {
  // Version of the shared dependency
  version: string;
  // Which modules consume the current shared dependency
  useIn?: Array<string>;
  // Which module the shared dependency comes from
  from?: string;
  // Factory function to get the instance of the shared dependency. When the cached shared instance cannot be loaded, it will load its own shared dependency.
  lib: () => Module;
  // Sharing strategy, which strategy will be used to determine the reuse of shared dependencies
  shareConfig?: SharedConfig;
  // Dependencies between shares
  deps?: Array<string>;
  // Under which scope the current shared dependency is placed, the default is default
  scope?: string | Array<string>;
};
  • Example
import { init, loadRemote } from '@module-federation/enhanced/runtime';

init({
    name: "mf_host",
    remotes: [
        {
            name: "remote",
            // After configuring an alias, it can be loaded directly through the alias
            alias: "app1",
            // Decide which module to load by specifying the address of the module's manifest.json file
            entry: "http://localhost:2001/mf-manifest.json"
        }
    ],
});

loadRemote

  • Type: loadRemote(id: string)
  • Loads a remote module.
  • Example
import { init, loadRemote } from '@module-federation/enhanced/runtime';

init({
    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"
        }
    ]
});

// 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;})

  • 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.

  • Example

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())
});

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:

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 = {
  // Name or alias of the remote to be preloaded
  nameOrAlias: string;
  // Whether to preload the module's interface, the default value is false. For details, please refer to the <Interface Prefetch> chapter in <Performance Optimization>. The @vmok/kit version needs to be greater than 1.7.6.
  prefetchInterface?: boolean;
  // The exposes to be preloaded
  // By default, all exposes are preloaded
  // When exposes are provided, only the required exposes will be loaded
  exposes?: Array<string>; // Default request
  // The default is sync, which only loads the synchronous code referenced in expose
  // When set to all, both synchronous and asynchronous references will be loaded
  resourceCategory?: 'all' | 'sync';
  // When no value is configured, all dependencies are loaded by default
  // After configuring dependencies, only the configuration options will be loaded
  depsRemote?: boolean | Array<depsPreloadArg>;
  // When not configured, resources are not filtered
  // After configuration, unnecessary resources will be filtered
  filter?: (assetUrl: string) => boolean;
};
  • Details

Through preloadRemote, you can start preloading module resources at an earlier stage to avoid waterfall requests. What can preloadRemote preload:

  • remote's remoteEntry
  • remote's expose
  • remote's synchronous or asynchronous resources
  • remote's dependent remote resources
  • 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"
        },
    ],
});

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

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