Closed caoyi5225 closed 3 years ago
Can you please, provide the route, the webpack.config.js and webpack.config.js from remote?
Because, when i run the angular11-microfrontends, it doesn't load the remoteEntry, only when i access the url of the MFE.
Hi. I came across with similar issue... I'm trying to load each route of a main app from remotes, however when page loads all remoteEntry.js from all routes are fetched. Maybe it's not possible to lazy load remoteEntry.js, or probably I'm doing something wrong here :/
I have 4 apps:
In main app, that's how I've defined in webpack.config.js:
new ModuleFederationPlugin({
name: 'main',
filename: 'remoteEntry.js',
remotes: {
'app_a': 'app_a@/app-a/remoteEntry.js',
'app_b': 'app_b@/app-b/remoteEntry.js',
'homepage': 'homepage@/homepage/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
and the routes are defined as:
import React, {Suspense, lazy} from "react";
import {Switch, Route} from "react-router";
import {BrowserRouter, NavLink} from "react-router-dom";
const AppA = lazy(() => import('app_a/AppA'));
const AppB = lazy(() => import('app_b/AppB'));
const Homepage = lazy(() => import('homepage/Homepage'));
export const App = () => {
return (
<main>
<BrowserRouter>
<nav>
<div><NavLink to="/">Main</NavLink></div>
<div><NavLink to="/app-a">AppA</NavLink></div>
<div><NavLink to="/app-b">AppB</NavLink></div>
</nav>
<hr />
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/app-a"><AppA/></Route>
<Route path="/app-b"><AppB/></Route>
<Route><Homepage/></Route>
</Switch>
</Suspense>
</BrowserRouter>
</main>
);
};
export default App;
Then for each remote app, I have in webpack.config.js something like:
new ModuleFederationPlugin({
name: 'app_a',
library: { type: 'var', name: 'app_a' },
filename: 'remoteEntry.js',
exposes: {
'./AppA': './src/app',
},
shared: ['react', 'react-dom'],
}),
When main app launches, I see in network that all 3 remoteEntry.js (homepage, app-a and app-b) are fetched:
So following the original question, I would like to know if it's possible to have those remoteEntry.js files lazy loaded, and if yes if you could point out what I might be doing wrong.
Thanks in advance.
I don't know react, but your routes are lazyload?
https://linguinecode.com/post/code-splitting-react-router-with-react-lazy-and-react-suspense
Hey. Thanks for your answer.
Yes, I'm lazy loading all routes by using React.lazy
and Suspense
. In fact, the js of each one of the routes only gets downloaded when I navigate to the correspondent route. However, the removeEntry.js
of each one of the routes is fetched always.
I've checked and tried these two examples:
Both also use lazy loading routes, and both have the same issue I'm talking about... all remoteEntry.js
from all apps are fetched when host app loads.
I'm not sure if there's another example that I can look at? Or maybe it's not possible to archive lazy loading of remoteEntry.js
in react?
Thanks.
Look at dynamic system host or check my external-remotes-plugin under my git repo
Sorry for posting on a closed thread 🙏
I've tried the dynamic system example and used the external-remotes-plugin. However, at the end, I wasn't very satisfied with the end result:
Given the huge benefits of module federation and dynamic imports (lazy loading), is there anything planned in the roadmap to have this out of the box through configuration? Like it was suggested in the first post.
Thanks again for this awesome technology!
Why issues is close? Why not make it async for non-blocking main tread?
script.onerror = onScriptComplete.bind(null, script.onerror);
script.onload = onScriptComplete.bind(null, script.onload);
needAttach && document.head.appendChild(script);
If you're dynamic importing the remote element then webpack will only add the remote when used if you're using static imports then only the low level apis (dynamic system host) will work.
i use React.lazy, but webpack call remoteEntry when app is loading. Request to remoteEntry is blocking main tread.
Use promise new promise interface on the plugin and you can inject and resolve it however you wish
How are the promises? Which plugin? I just indicate the path to the remoteEntry.js in the webpack module federation and that's it. How to access the behavior of the plugin?
Why issues is close? Why not make it async for non-blocking main tread?
script.onerror = onScriptComplete.bind(null, script.onerror); script.onload = onScriptComplete.bind(null, script.onload); needAttach && document.head.appendChild(script);
this code from webpack
Hey. Thanks for your answer.
Yes, I'm lazy loading all routes by using
React.lazy
andSuspense
. In fact, the js of each one of the routes only gets downloaded when I navigate to the correspondent route. However, theremoveEntry.js
of each one of the routes is fetched always.I've checked and tried these two examples:
Both also use lazy loading routes, and both have the same issue I'm talking about... all
remoteEntry.js
from all apps are fetched when host app loads.I'm not sure if there's another example that I can look at? Or maybe it's not possible to archive lazy loading of
remoteEntry.js
in react?Thanks.
I am also seeing same issue where all remoteEntry files are getting loaded on page irrespective of page url. please suggest if there is any way to fix this
If you don't like how webpack injects remotes. Use promise new promise syntax and take over the whole injection and resolution mechanics.
I load my remotes upfront, asynchronously since they are only like 2-5kb
Compared to the 700kb tag manager it's a moot point personally.
If you reference the import and execute it in any way at all. It's going to load the remote. You'd have to do something like make a function that calls the import() so it only fires when called. Not when module is executed
Thanks @ScriptedAlchemy..I was able to achieve this using promise based script injection
Will try to further improve bundle size for remoteEntry file as not allowing this file to get stored in browser cache
Storing in browser cache does present a challenge. You'd need to know the version of the other remote and then you could add that to the remote module name. But it's still tricky.
You could make a api call to like a json file to get the remote name and then inject it with a path that you now know
Use promise new promise syntax and take over the whole injection and resolution mechanics.
Thanks @ScriptedAlchemy..I was able to achieve this using promise based script injection
Could you please provide an example / code snippet to illustrate how this works?
I have something like
const mfA = React.lazy(async () => import('a-mf/App'));
but even if a don't use mfA anywhere the remoteEntry is being called.
I'm already using the ExternalTemplateRemotesPlugin to load the MF from different endpoints per environment
remotes: {
'a-mf': 'a@[window.ENV.MF_A]/remoteEntry.js',
'b-mf': 'b@[window.ENV.MF_B]/remoteEntry.js'
}
Sounds like a config issue or you have another nested remote using this.
That externals plugin probably only supports trying to read and load the remote containers instantly. Use promise new promise.
Read the webpack docs
Sounds like a config issue or you have another nested remote using this.
That externals plugin probably only supports trying to read and load the remote containers instantly. Use promise new promise.
Read the webpack docs
Created a new repo with a simplified codebase but the remoteEntry.js files are always loaded ahead, even with the "promise new promise".
Code repo: https://github.com/skypyxis/react-ts-module-federation
Does anyone have a repo where the remoteEntry.js is lazy loaded?
Don't set them as eager shared. That might cause webpack to eagerly negotiate share scope between the remotes
@ScriptedAlchemy @skypyxis I have the same situation, two apps loading toghether here I have code source, I need your experience, Please can you help me?
Sounds like a config issue or you have another nested remote using this.
That externals plugin probably only supports trying to read and load the remote containers instantly. Use promise new promise.
Read the webpack docs
@ScriptedAlchemy Can you provide external
option example value for implement lazy loading of remote entry?
In this example repo (https://github.com/krutoo/module-federation) after i added the "shared" option, promise began to be called immediately at the start of the page
without specifying the "shared" option, the promise is only fired when a dynamic import is directly called in the code
Upon looking into the webpack core code. Webpack will try/want to initialize all remotes known to it upfront. If you want to prevent that. You can create a proxy trap with promise new promise, basically resolve nothing and perform init manually when get() is fired.
Upon looking into the webpack core code. Webpack will try/want to initialize all remotes known to it upfront. If you want to prevent that. You can create a proxy trap with promise new promise, basically resolve nothing and perform init manually when get() is fired.
@ScriptedAlchemy can you please provide reference example?
Upon looking into the webpack core code. Webpack will try/want to initialize all remotes known to it upfront. If you want to prevent that. You can create a proxy trap with promise new promise, basically resolve nothing and perform init manually when get() is fired.
Yeah, after I read more about this feature, I saw this is a feature, not a bug, and I remediate this feature exactly how you said. Thanks.
Yeah. It's to ensure shared module negotiations happen across the whole network that can only happen during boot up.
Upon looking into the webpack core code. Webpack will try/want to initialize all remotes known to it upfront. If you want to prevent that. You can create a proxy trap with promise new promise, basically resolve nothing and perform init manually when get() is fired.
@ScriptedAlchemy, Hey. Thanks for your answers. Proxy should be something like this?
new Promise(resolve => {
const scriptElement = document.createElement('script');
scriptElement.onload = () => {
const container = window['app_2_var'];
const proxy = {
shareScope: {},
get: async (request) => {
try {
await container.init(this.shareScope);
} catch (e) {
console.log('remote container already initialized');
}
return container.get(request);
},
init: (arg) => {
this.shareScope = arg; // save reference to share scope
}
}
resolve(proxy);
}
scriptElement.src = 'http://example.com/path/to/remoteEntry.js'';
scriptElement.async = true;
document.head.append(scriptElement);
})
Yeah, but - rather use something like this
https://github.com/module-federation/nextjs-mf/blob/main/src/utils.js
OR if you need it in webpack runtime code.
https://github.com/module-federation/nextjs-mf/blob/main/src/NextFederationPlugin.js - look at my remote template builder + you need the AddRuntimeRequirementsToPromiseExternal plugin that I add on here to make webpakc_Require accessible to promise new promise.
Dont write your own script loader, trrrrust me.
If you want, consider porting over these parts to module-federation/utilities and making a PR. Ill happily publish it to npm and point my own packages to depend on those utilities :)
Ill do it at some point, just don't have the time to extract the useful tools and move them to the other repo.
Hey @ScriptedAlchemy - would moving to one of the proposed solutions (e.g. https://github.com/module-federation/utilities) break the Dashboard Plugin and it's ability to know which MFE a consumer is dependent on? My understanding of the Dashboard Plugin is it reads the (remotes) configuration of the ModuleFederation Plugin.
Also, do you think this behaviour of loading remoteEntry.js
on startup/bootstrap of the Host will change to on-demand in ModuleFederation V2? It seems a little counter to load all remoteEntry.js
files on startup when the MFE in question might not be used until a later flow, or not at all. We use React.Lazy
combined with React.Suspense
to ensure all the actual components are "lazy" / loaded on-demand.
I realise remoteEntry.js
is small (so long as eager
is not enabled on deps) and static, so there shouldn't be too many issues, but we've noticed if a request for a remoteEntry,js
takes a long time to respond (server is available but responding very slowly/hanging) it can cause the Host app to stall. This can likely be resolved by using infra to cache remoteEntry.js
, but it would be good to have the on-demand loading behaviour be the default.
Yeah static assets show go to a CDN instead of serving from internal infra
I know this is closed, but I've encountered application delays caused by MFE's remoteEntries loading slowly even when they aren't used yet (lazy loaded) I've considered the option of dynamic remotes and got it working using this import logic:
export const MFEComponent = (url, name, path) => {
return lazy(() =>
new Promise((resolve, reject) => {
__webpack_require__.l(
url,
(event) => {
if (typeof window[name] !== 'undefined') return resolve(window[name])
var errorType =
event && (event.type === 'load' ? 'missing' : event.type)
var realSrc = event && event.target && event.target.src
reject({
message:
'Loading script failed.\n(' + errorType + ': ' + realSrc + ')',
name: 'ScriptExternalLoadError',
type: errorType,
request: realSrc,
})
},
name
)
}).then(async (remote) => {
await __webpack_init_sharing__('default')
await remote.init(__webpack_share_scopes__.default)
const factory = await remote.get(`./${path}`)
return factory()
})
)
}
However the remotes I am attempting to load are statically known, and supported by typescript, so a basic import of:
export const MFEComponent = (url, name, path) => {
return lazy(() =>
import(`MFE/ENTRYPOINT`)
)
}
Would enable typescript support for these remote components. Any chance we can change the code generation of static imports to generate something similar to the above?
I see remote app start loading after all the remoteEntry.js loaded if i put all the remotes to host's webpack config.
And in angular11-microfrontends, there is a method named
loadRemoteEntry
to load remoteEntry.js when I need it. But I should add the method to my project.Is there any original webpack config to enable lazy load remoteEntry.js when I import the remote app? Just like this:
Thanks for replay.😁