gregberge / loadable-components

The recommended Code Splitting library for React ✂️✨
https://loadable-components.com
MIT License
7.7k stars 381 forks source link

Support async-node and Module Federation #939

Closed ScriptedAlchemy closed 1 year ago

ScriptedAlchemy commented 1 year ago

🚀 Feature Proposal

A clear and concise description of what the feature is.

Motivation

Async loads do not work with either target: "async-node" or when using module federation on the server (which uses the same mechanics as async-node does under the hood)

Example

import(federated/remote) or require(federated/remote) import(local/file)

both do not work with async targets, loadable fails to look up the module, likely because theres no chunk load global to patch into.

Ive tested this with the async-node example in this repo, without federation and have the same issue.

Pitch

Federation support would be nice.

Solution

In the other issue with bruno, we discussed adding an option to babel parser to flip require.resolveWeak over to require. That works, for the most part - but host files cannot be loaded due to all of chunk loading depending on the async-node model.

One solution that solves federation on server and async-node targets is a little loader:

// import { getOptions } from 'loader-utils';

function loader(source) {
  // const options = getOptions(this);

  const { plugins, target } = this._compiler.options;

  const moduleFederationPlugin = plugins.find((plugin) => ['NodeFederationPlugin', 'ModuleFederationPlugin', 'UniversalFederationPlugin'].includes(
    plugin.constructor.name,
  ));

  const regex = new RegExp('require.resolveWeak\\(([\'"])(.+?)\\1\\)');
  const foundWeak = regex.exec(source);
  if (foundWeak && (!target || target === 'async-node')) {
    return [`require("${foundWeak[2]}")`, source].join('\n');
  }

  if (!moduleFederationPlugin) {
    return source;
  }
  const remotes = Object.keys(moduleFederationPlugin.options.remotes);

  // const regex = new RegExp(`require.resolveWeak\\([\"'](${remotes.join('|')})\/(.+?)[\"']\\)`);

  // Replace instances of import() statements that start with "app2" with require()
  // return source.replace('require.resolveWeak','require')
    if (regex.test(source)) {
      const modifiedSource = source.replace(regex, 'require("$1/$2")');
      // return ['require("app2/PokemonList")', source].join('\n');

      console.log('[FEDERATION-LOADER]', modifiedSource);
      return modifiedSource;
    }
  return source;
}

export default loader;
ScriptedAlchemy commented 1 year ago

this loader would be applied after babel-loader, and would only transform the source of server/node based builds.

fivethreeo commented 1 year ago

Maybe add it to the loadable plugin? Hook into parse and use a constdependency?

theKashey commented 1 year ago

From this angle option #2 from https://github.com/gregberge/loadable-components/pull/938#issuecomment-1366966793 sounds more feasible - track all loadable definitions and allow user to pre-import all required components before server starts. That would require one command to added before "server.start", and no babel/webpack specific logic.

brunos3d commented 1 year ago

Just moving this discussion further with this new issue

stale[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.