Skip to content

Instantly share code, notes, and snippets.

@ScriptedAlchemy
Last active February 14, 2024 02:25
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save ScriptedAlchemy/3a24008ef60adc47fad1af7d3299a063 to your computer and use it in GitHub Desktop.
Save ScriptedAlchemy/3a24008ef60adc47fad1af7d3299a063 to your computer and use it in GitHub Desktop.
The Right Way to Load Dynamic Remotes
import { injectScript } from '@module-federation/utilities';
// example of dynamic remote import on server and client
const isServer = typeof window === 'undefined';
//could also use
// getModule({
// remoteContainer: {
// global: 'app2',
// url: 'http://localhost:3002/remoteEntry.js',
// },
// modulePath: './sample'
// }).then((sample) => {
// console.log(sample)
// });
const dynamicContainer = injectScript({
global: 'checkout',
url: `http://localhost:3002/_next/static/${
isServer ? 'ssr' : 'chunks'
}/remoteEntry.js`,
}).then((container) => {
return container.get('./CheckoutTitle').then((factory) => {
return factory();
});
});
// if you wanted to use it server side/client side in next.js
const DynamicComponent = React.lazy(() => dynamicContainer);
// eslint-disable-next-line react/display-name
export default (props) => {
return (
<>
<React.Suspense>
<DynamicComponent />
</React.Suspense>
<p>Code from GSSP:</p>
<pre>{props.code}</pre>
</>
);
};
export async function getServerSideProps() {
return {
props: {
code: (await dynamicContainer).default.toString(),
},
};
}
@ScriptedAlchemy
Copy link
Author

I export it as a utility so you can. I’ll be moving it to module-federation/utilities and pushing to npm in near future

@ScriptedAlchemy
Copy link
Author

its on npm as module-federation/utilities now, readme needs some love -- but its there if you want it.

Could use a PR or two as I've not tested it as a standalone util

@shirly-chen-awx
Copy link

wowww😍, thank you very much for your tireless efforts.

@shirly-chen-awx
Copy link

Hi, @ScriptedAlchemy I found that injectScript function works in dev env, but it will throw __webpack_require__.l is not a function error in prod env. do you have any thoughts?

@Hydrock
Copy link

Hydrock commented Oct 27, 2022

Hello. I need your help. I recorded a video (https://www.youtube.com/watch?v=CMA6WciiGso) where I show the problem.

The problem is the following.

I used the function from the post to create a "ComponentLoader" - a special component for loading remote components

export function ComponentLoader(props) {
    console.log('0 ComponentLoader - componentName:', props.componentName);
    const {
        componentName,
        ...restProps
    } = props;

    const system = {
        remote: 'Shared',
        url: 'http://localhost:8081/assets/remoteEntry.js',
        module: `./${componentName}`,
    }
    
    if (typeof window !== 'undefined') {
        const Component = React.lazy(loadComponent(system.remote, 'default', system.module, system.url));

        return (
            <ErrorBoundary errorResetFlag={ componentName } >
                <Suspense fallback="loading !">
                    <Component
                        widgetName={ componentName }
                        {...restProps}
                    />
                </Suspense>
            </ErrorBoundary>
        );
    }

    return null;
}
import { getOrLoadRemote } from './getOrLoadRemote';

export const loadComponent = (remote, sharedScope, module, url) => {
  return async () => {
    await getOrLoadRemote(remote, sharedScope, url);
    const container = window[remote];
    const factory = await container.get(module);
    const Module = factory();
    return Module;
  };
};
export const getOrLoadRemote = (remote, shareScope, remoteFallbackUrl = undefined) =>
 new Promise((resolve, reject) => {
   // check if remote exists on window
   if (!window[remote]) {
     // search dom to see if remote tag exists, but might still be loading (async)
     const existingRemote = document.querySelector(`[data-webpack="${remote}"]`);
     // when remote is loaded..
     const onload = originOnload => async () => {
       // check if it was initialized
       if (!window[remote].__initialized) {
         // if share scope doesnt exist (like in webpack 4) then expect shareScope to be a manual object
         if (typeof __webpack_share_scopes__ === 'undefined') {
           // use default share scope object passed in manually
           await window[remote].init(shareScope.default);
         } else {
           // otherwise, init share scope as usual
           await window[remote].init(__webpack_share_scopes__[shareScope]);
         }
         // mark remote as initialized
         window[remote].__initialized = true;
       }
       // resolve promise so marking remote as loaded
       resolve();
       originOnload && originOnload();
     };
     if (existingRemote) {
       // if existing remote but not loaded, hook into its onload and wait for it to be ready
       existingRemote.onload = onload(existingRemote.onload);
       existingRemote.onerror = reject;
       // check if remote fallback exists as param passed to function
       // TODO: should scan public config for a matching key if no override exists
     } else if (remoteFallbackUrl) {
       // inject remote if a fallback exists and call the same onload function
       var d = document,
         script = d.createElement('script');
       script.type = 'text/javascript';
       // mark as data-webpack so runtime can track it internally
       script.setAttribute('data-webpack', `${remote}`);
       script.async = true;
       script.onerror = reject;
       script.onload = onload(null);
       script.src = remoteFallbackUrl;
       d.getElementsByTagName('head')[0].appendChild(script);
     } else {
       // no remote and no fallback exist, reject
       reject(`Cannot Find Remote ${remote} to inject`);
     }
   } else {
     // remote already instantiated, resolve
     resolve();
   }
 });

I use it here:

const sharedComponentsNames = {
    ExampleSharedComponent1: 'ExampleSharedComponent1',
    ExampleSharedComponent2: 'ExampleSharedComponent2',
}

export function ExternalComponentExample(props) {
    console.log('-1 ExternalComponentExampleContainer:', props);

    const { ComponentLoader } = window;

    const dynamicComponentName = sharedComponentsNames.ExampleSharedComponent2;
    
    return (
        <div>
            <div>
                <h1>
                    { dynamicComponentName }
                </h1><br />
                <ComponentLoader
                    componentName={ dynamicComponentName }
                />
            </div>
        </div>
    );
}

everything works fine when I load components at the same level
but when a downloadable component also has a downloadable component - this leads to a lot of re-rendering
it is worth noting that re-rendering is not infinite and sooner or later we all still see the final result

this is how the downloadable components look like

export default function ExampleSharedComponent1(props) {
  console.log('1 ExampleSharedComponent1:', props);
  return (
    <div>
      ExampleSharedComponent_____________1
    </div>
  );
}
export default function ExampleSharedComponent2(props) {
  console.log('1 ExampleSharedComponent2:', props);

  const { ComponentLoader } = window;

  return (
    <div>
      ExampleSharedComponent________2

      <ComponentLoader
          componentName={ 'ExampleSharedComponent1' }
      />
    </div>
  );
}

I'm new to wmf - so I really hope for your help
@ScriptedAlchemy 😢

@Hydrock
Copy link

Hydrock commented Oct 30, 2022

I think, i found answer in this article - https://dev.to/omher/lets-dynamic-remote-modules-with-webpack-module-federation-2b9m
At the same time, there is no re-rendering with nested loading of modular components.

@oravecz
Copy link

oravecz commented Apr 27, 2023

@shirly-chen-awx Did you ever find a solution to the __webpack_require__.l is not a function? We are experiencing the same strange behavior.

@ScriptedAlchemy
Copy link
Author

ensure the file is getting bundled by webpack and not treated as external. @oravecz

that error happens when something is importing it outside webpacks scope

@oravecz
Copy link

oravecz commented Apr 28, 2023

Yes, it was a duplicate of module-federation/core#551

I'm surprised it isn't discussed more, especially on the @module-federation/utilities page. importRemote is the function that is calling __webpack_require__.l(), and I would expect any production build using that package would tree-shake away the function without that plugin.

Is this plugin available as an npm published webpack plugin at this time?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment