logologo
指南
实践
配置
插件
案例
博客
生态
Module Federation Examples
Practical Module Federation
Zephyr Cloud
Nx
简体中文
English
指南
实践
配置
插件
案例
博客
Module Federation Examples
Practical Module Federation
Zephyr Cloud
Nx
简体中文
English
logologo
概览

Bridge

Bridge 介绍

React Bridge

快速开始
导出应用
加载应用
加载模块
Vue Bridge

框架

框架概览

React

Basic CRA with Rsbuild
国际化 (i18n)

Modern.js

快速开始
动态加载生产者

Next.js

Basic Example
导入组件
路由和导入页面
使用 Express.js
预设

Angular

Angular CLI 设置
Micro-frontends with Angular
服务端渲染
使用 Service Workers
Authentication with Auth0
Authentication with Okta
拆分巨石应用
改造巨石应用
Edit this page on GitHub
Previous Page快速开始
Next PageBasic Example

#动态加载生产者

Modern.js 提供了 Data Loader 来帮助进行数据管理,Data Loader 仅在服务端执行,不会在客户端重复执行。

本章节将介绍如何利用 Data Loader 获取生产者信息并动态加载。

#创建生产者

创建一个新的生产者,用于动态加载。

#1. 创建配置文件

在项目根目录创建 module-federation.config.ts 文件,并写入下列内容:

module-federation.config.ts
import { createModuleFederationConfig } from '@module-federation/modern-js';

export default createModuleFederationConfig({
  name: 'dynamic_provider',
  filename: 'remoteEntry.js',
  exposes: {
    './Image': './src/components/Image.tsx',
  },
  shared: {
    react: { singleton: true },
    'react-dom': { singleton: true },
  },
});

#2. 应用插件

在 modern.config.ts 应用 @module-federation/modern-js:

modern.config.ts
import { appTools, defineConfig } from '@modern-js/app-tools';
import { moduleFederationPlugin } from '@module-federation/modern-js';

// https://modernjs.dev/en/configure/app/usage
export default defineConfig({
  runtime: {
    router: true,
  },
  server: {
    ssr: {
      mode: 'stream',
    },
    port: 3008,
  },
  plugins: [appTools(), moduleFederationPlugin()],
});

#3. 创建导出组件

创建文件 src/components/Image.tsx ,内容如下:

Image.tsx
import React from 'react';
import styles from './Image.module.css';

export default (): JSX.Element => (
  <div
    id="remote-components"
    style={{
      backgroundColor: '#c0e91e',
      color: 'lightgrey',
      padding: '1rem',
    }}
  >
    <h2>
      <strong>dynamic remote</strong>&nbsp;image
    </h2>
    <button
      id="dynamic-remote-components-button"
      style={{ marginBottom: '1rem' }}
      onClick={() => alert('[remote-components] Client side Javascript works!')}
    >
      Click me to test i'm interactive!
    </button>
    <img
      id="dynamic-remote-components-image"
      src="https://module-federation.io/module-federation-logo.svg"
      style={{ width: '100px' }}
      alt="serge"
    />
    <button className={styles['button']}>Button from dynamic remote</button>
  </div>
);

并创建对应的样式文件,内容如下:

Image.module.css
.button {
  background: red;
}

#创建 loader

在对应的路由文件创建同名的 .data 文件,以根目录 src/routes/page.tsx 为例,创建 src/routes/page.data.ts:

page.data.ts
import { LoaderFunctionArgs } from '@modern-js/runtime/router';

export type DataLoaderRes = {
    providerList: Array<{
        name: string,
        entry: string,
        id: string;
    }>
}

const fetchProviderList = async () => {
    const res = await new Promise(resolve => {
        setTimeout(() => {
            resolve([
                {
                    name: 'dynamic_provider',
                    entry: 'http://localhost:3008/mf-manifest.json',
                    id: 'dynamic_provider/Image'
                }
            ])
        }, 1000);
    });

    return res as DataLoaderRes['providerList']
}

export const loader = async ({ request }: LoaderFunctionArgs): Promise<DataLoaderRes> => {
    console.log('request params', request);
    const providerList = await fetchProviderList();
    return {
        providerList
    }
};

#加载动态生产者

消费 loader 数据,并动态加载对应的生产者:

import { loadRemote, registerRemotes, getInstance } from '@module-federation/modern-js/runtime';
import { createLazyComponent } from '@module-federation/modern-js/react';
// 使用 import type ,仅获取类型
import type { DataLoaderRes } from './page.data';
import { useLoaderData } from '@modern-js/runtime/router';

import './index.css';

const RemoteSSRComponent = createLazyComponent({
  instance: getInstance(),
  loader: () => import('remote/Image'),
  loading: 'loading...',
  export: 'default',
  fallback: ({ error }) => {
    if (error instanceof Error && error.message.includes('not exist')) {
      return <div>fallback - not existed id</div>;
    }
    return <div>fallback</div>;
  },
});

const Index = () => {
  // 获取 data loader 数据
  const dataLoader = useLoaderData() as DataLoaderRes;
  // 注册生产者信息
  registerRemotes(dataLoader.providerList);

  const DynamicRemoteSSRComponents = dataLoader.providerList.map(item => {
    const { id } = item;
    const Com = createLazyComponent({
      instance: getInstance(),
      loader: () => loadRemote(id),
      loading: 'loading...',
      fallback: ({ error }) => {
        if (error instanceof Error && error.message.includes('not exist')) {
          return <div>fallback - not existed id</div>;
        }
        return <div>fallback</div>;
      },
    });
    return <Com />
  })
  return (
    <div className="container-box">
      <RemoteSSRComponent />
      {DynamicRemoteSSRComponents}
    </div>
  );
}

export default Index;