-
-
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(), | |
}, | |
}; | |
} |
It's a nice improvement🤩, much thanks💜💜💜!!! One more question, can we use injectScript
directly through npm package now?
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?
Don’t do manual script injection. Webpack has its own script loading function you can access. Check injectScript function in nextjs-MF repo.