-
-
Save ScriptedAlchemy/3a24008ef60adc47fad1af7d3299a063 to your computer and use it in GitHub Desktop.
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(), | |
}, | |
}; | |
} |
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
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
wowww😍, thank you very much for your tireless efforts.
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?
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 😢
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.
@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.
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
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?
It's a nice improvement🤩, much thanks💜💜💜!!! One more question, can we use
injectScript
directly through npm package now?