module-federation / core

Module Federation is a concept that allows developers to share code and resources across multiple JavaScript applications
https://module-federation.io/
MIT License
1.39k stars 206 forks source link

Support Modern.js SSR #2348

Closed 2heal1 closed 1 month ago

2heal1 commented 4 months ago
Changelog
2024-05-22:draft
Implementation PR  WIP...

Summary

Module Federation supports Modern.js SSR , includes stream ssr and string ssr two modes.

Basic example

// usage import Comp from 'remote/Content'


* Dynamic remote(register remotes in runtime)
```ts
import React, { Suspense } from 'react';
import Button from 'antd/lib/button';
import {
  loadRemote,
  registerRemotes,
} from '@module-federation/enhanced/runtime';

registerRemotes([
  {
    name: 'dynamic_remote',
    entry: 'http://localhost:3008/mf-stats.json',
  },
]);

const Comp = React.lazy(() =>
loadRemote('dynamic_remote/Image').then((m) => {
    return m;
  }),
);

export default (): JSX.Element => (
  <Suspense fallback={'loading'}>
    <Comp />
  </Suspense>
);

Motivation

Detailed design

Update stats/manifest/snapshot

Stats/Manifest

Add ssr related fields

// metaData
interface BasicStatsMetaData {
+ ssrRemoteEntry?: ResourceInfo;
}

Snapshot

Add ssr related fields

interface BasicProviderModuleInfo extends BasicModuleInfo {
  // ssrRemoteEntry/ssrRemoteEntryType only appear while manifest has serveSideRemoteEntry field
+ ssrRemoteEntry?: string;
+ ssrRemoteEntryType?: RemoteEntryType
}

render mode

only support stream ssr mode

CSS flickering issue

Leverage mf-stats.json , we can easily get the module css assets, and insert them to html

// encapsulation component for users
const Comp = React.lazy(() =>
  loadRemote('dynamic_remote/Image').then((m) => {
    return {
      default:()=><div>
        <link href='http://localhost:3008/static/css/async/__federation_expose_Image.css' rel="stylesheet" type="text/css" />
        <span>11</span>
        <m.default />
      </div>
    };
  }),
);

Cache Strategy

For static remote , there won't be any serious problems. But for dynamic remotes , it may add endless dynamic remotes , and cause memory crashes.

To avoid the issue , it needs to add LRU cache:

Data Fetch

Use Modernjs Data loader

Downgrade strategy

Provide two ways to help CSR cab work normally while SSR failed

  1. Use the runtime version plugin to downgrade the latest available modules
    • runtime version plugin: allows users downgrade specific fallback modules while the entry can not get normally
  2. Provide encapsulation component for users which provide fallback

Dev

LiveReload

fetch all loaded remotes before render host , and judge whether remoteEntry content hash has changed . If yes , flush chunks .

import type { Plugin } from '@modern-js/runtime';

export const mfPluginSSR = (): Plugin => ({
  name: '@module-federation/modern-js',

  setup: () => ({
    async init({ context }, next) {
      if (typeof window !== 'undefined') {
        return next({ context });
      }
      const nodeUtils = await import('@module-federation/node/utils');
      const shouldUpdate = await nodeUtils.revalidate();
      if (shouldUpdate) {
        console.log('should HMR', shouldUpdate);
      }
      return next({ context });
    },
  }),
});

Dynamic remote type hints

notify local broker server while calling loadRemotes , and then the broker server will have the full relationship .

import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const notifyDynamicRemotePlugin: () => FederationRuntimePlugin = () => ({
  name: 'notify-dynamic-remote-plugin',
  loadRemote(args) {
    if(isDynamicRemote(args)){
        notifyBrokerServer(args.remote);
    }
    return args;
  },
});
export default notifyDynamicRemotePlugin;

debug

Because node server can not access local env , so not support yet.

It will auto forceCSR when accepting the debug info:

Drawbacks

github-actions[bot] commented 1 month ago

Stale issue message