loadable-components icon indicating copy to clipboard operation
loadable-components copied to clipboard

loadable not working in SSR with webpack module federation -

Open ajayjaggi opened this issue 5 years ago • 97 comments

🐛 Bug Report

loadable-components: failed to synchronously load component, which expected to be available { fileName: './src/shared/dedicated/index.js', chunkName: 'Dedicated', error: 'Cannot read property 'call' of undefined' } (node:7562) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'call' of undefined at webpack_require (/Users/ajay/Desktop/WebStrom/MicroFrontend/Edge/dist/server/main.js:394:42) at Module../src/shared/components/Footer/index.js (/Users/ajay/Desktop/WebStrom/MicroFrontend/Edge/dist/server/Dedicated.server.js:21:71) at webpack_require (/Users/ajay/Desktop/WebStrom/MicroFrontend/Edge/dist/server/main.js:394:42) at Module../src/shared/dedicated/index.js (/Users/ajay/Desktop/WebStrom/MicroFrontend/Edge/dist/server/Dedicated.server.js:60:76) at webpack_require (/Users/ajay/Desktop/WebStrom/MicroFrontend/Edge/dist/server/main.js:394:42) at Object.requireSync (/Users/ajay/Desktop/WebStrom/MicroFrontend/Edge/dist/server/src_server_render_js.server.js:225:14) at InnerLoadable.loadSync (/Users/ajay/Desktop/WebStrom/MicroFrontend/Edge/dist/server/vendors-node_modules_loadable_server_lib_index_js-node_modules_express_index_js-node_modules_-ee7ccd.js:420:35) at new InnerLoadable (/Users/ajay/Desktop/WebStrom/MicroFrontend/Edge/dist/server/vendors-node_modules_loadable_server_lib_index_js-node_modules_express_index_js-node_modules_-ee7ccd.js:315:17) at processChild (/Users/ajay/Desktop/WebStrom/MicroFrontend/Edge/dist/server/vendors-node_modules_loadable_server_lib_index_js-node_modules_express_index_js-node_modules_-ee7ccd.js:56603:14) at resolve (/Users/ajay/Desktop/WebStrom/MicroFrontend/Edge/dist/server/vendors-node_modules_loadable_server_lib_index_js-node_modules_express_index_js-node_modules_-ee7ccd.js:56568:5) (node:7562) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1) (node:7562) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

To Reproduce

routes.js -

import React from 'react' import loadable from '@loadable/component' // import Home from './home' // import Dedicated from './dedicated'

const Home = loadable(() => import(/* webpackChunkName: "Home" / './home')) const Dedicated = loadable( () => import(/ webpackChunkName: "Dedicated" */ './dedicated'))

const homeRoute = (path) => ({ path, exact: true, component: Home })

const dedicatedRoute = (path) => ({ path, exact: true, component: Dedicated })

export default () => [ homeRoute('/'), dedicatedRoute('/:player(messi)') ]

Expected behavior

A clear and concise description of what you expected to happen.

Link to repl or repo (highly encouraged)

https://github.com/ajayjaggi/MicroForntEnd-Basic-Structure

Issues without a reproduction link are likely to stall.

ajayjaggi avatar Oct 14 '20 13:10 ajayjaggi

Hey @ajayjaggi :wave:, Thank you for opening an issue. We'll get back to you as soon as we can. Please, consider supporting us on Open Collective. We give a special attention to issues opened by backers. If you use Loadable at work, you can also ask your company to sponsor us :heart:.

Hi. i saw this PR https://github.com/gregberge/loadable-components/pull/638/files and used these fixes to run loadable in my local along with webpack 5. But further in SSR, wrapping components with loadable breaks the flow...Error is published above. Also i have mentioned my repo link..

ajayjaggi avatar Oct 14 '20 13:10 ajayjaggi

Hello @ajayjaggi,

did you try to use correct key for chunkExtractor.js? this https://github.com/gregberge/loadable-components/pull/638/files#diff-ae8b2f8a1736c52b65efbc3c341977cb11ce566027e0befaf874569fe969dcadR208 instead of https://github.com/ajayjaggi/MicroForntEnd-Basic-Structure/blob/master/Edge/node_modules/%40loadable/server/lib/ChunkExtractor.js#L208

example

return {
-     cleanFilename,
+     filename: resolvedFilename,
...

StephaneRob avatar Oct 15 '20 06:10 StephaneRob

Thanks for this.I used the wrong key. Have corrected this but still getting the same error.

ajayjaggi avatar Oct 15 '20 06:10 ajayjaggi

Did you use webpack LoadablePlugin in your webpack config?

StephaneRob avatar Oct 15 '20 06:10 StephaneRob

yes for both the websites ie Edge and Demand i have used LoadablePlugin in the webpack config for client side. demand website - https://github.com/ajayjaggi/MicroForntEnd-BasicStructure/blob/master/Demand/config/webpack/client.js edge website - https://github.com/ajayjaggi/MicroForntEnd-Basic-Structure/blob/master/Edge/config/webpack/client.js

ajayjaggi avatar Oct 15 '20 06:10 ajayjaggi

https://github.com/ajayjaggi/MicroForntEnd-Basic-Structure - this is the PR for project. The error of 'Cannot read property 'call' of undefined' is occurring only in server side rendering.For client side everything is working fine

ajayjaggi avatar Oct 15 '20 06:10 ajayjaggi

It may take a while before it will start working properly, but 🤞

theKashey avatar Oct 15 '20 10:10 theKashey

Hi @StephaneRob / @theKashey ,can we connect over a call to discuss the issue ? We can try to find the solution together.

ajayjaggi avatar Oct 15 '20 14:10 ajayjaggi

@ScriptedAlchemy is the boss in MF lands. We all need his help, not my help. PS: https://github.com/gregberge/loadable-components/issues/635 sounds to be related. We need unique callbacks for every remote. Or we don't?

theKashey avatar Oct 15 '20 22:10 theKashey

Each host has a name. Webpack usually appends a name to the remote containers. So they get their own name.

Webpack accesses the runtime via window.app1.get. App1 is the name you call it in the config.

I'm not sure if jsonp matters because they all get namespaced. It would have to be tested but I use loadable and do not have problems. However I'm using MF not standard entry-point runtimes which execute differently.

ScriptedAlchemy avatar Oct 16 '20 02:10 ScriptedAlchemy

Quoting from a comment I left on recently merged PR:

If you have the remotes on the page immediately. Like hardcoded. All remote code should transport down in a single RTT.

If you want to SSR the chunks a remote requires:

loadable needs a new way to map names of remotes to federated chunk maps. I need to extend the MF api so remotes will export their chunk maps so loadable can extract them during render. Whenever you use loadable. Babel basically transforms the import into a object with a normal require statement and the module / chunk id. Then it's executed the HOC pushes these IDs into a scope that the server can access after the react render.

The only Id it will have is ./someExposedModule - and we would need to go over the remotes stats and find what the browser side script is for that module.

Same problem exists in next js. The loadable method straight crashes. No existing code split tool is capable of MF chunk coronations. I work around hydration issues with top level awaits. Which work on both client and server.

You could also use partial hydration and simply hydrate markup when visible.

I don't foresee this being hard to accomplish. Especially since I can extend MF apis at will.

We could even change the getter on the remote so whenever a server gets code from the remote, it's container will report what request was asked for from the remote. It actually could be done without and need for Babel at all, or even loadable HOC. The only part we would need to adapt is ChunkExtractor to read from the second scope the remote containers are accessing directly.

This mechanism would work exactly like react-universal does. Push right into a map, but we will be doing this inside the webpack runtime directly.

MF should not be a complex problem to render correctly on. You don't need to use loadable as a wrapper 👆 we only need to flush chunks into it from webpack runtime.

ScriptedAlchemy avatar Oct 16 '20 02:10 ScriptedAlchemy

@theKashey how can I import and push extra chunk names into your context.

How can we have loadable read the remote containers chunks. I can have webpack infer this from the plugin.

What we could do is provide a loadableFederationPlugin which passes the object to MF but we can read the info.

Then we would have the paths to all remotes and can require the chunk stats upfront.

I could write to a map you expose directly in webpack. As the runtime attaches, it sends context to the host. The "hard" part on the webpack side is emitting the stats and reducing it down to simple maps.

If I'm able to push chunk maps and the module as it's required at runtime. This would make it much simpler. Then I wouldn't need to provide stats upfront but could push them into loadable as the remote attaches on its own.

ScriptedAlchemy avatar Oct 16 '20 03:10 ScriptedAlchemy

Okay, I'm on my iPad to cant really code much. Here's what the startup code will look like. (Check module federation examples/startup-code) to see it configured in webpack.

This entrypoint would be part of webpack-imported, and its added to webpack with something like SingleEntryPlugin or EntryPlugin (or mutation of options, like u do in next) We can get app1 from webpack and use template strings, or we can compose variables via definePlugin, like you do to env vars.

Global.chunkMap = Map() // or whatever
const container = __webpack_require__('webpack/container/entry/app1');
// app1 could be process.env.CURRENT_HOST or something we can inject during build. 
Module.exports = {
  get(request) {Map.add(request); return container.get(request)}
  init() {return container.init(arguments)}
  ClientChunks: non_webpack_require('../clientStats.json') // or some injection to inline this after the fact. 
}

We can also use normal require and stuff in here, its just a entrypoint.

As i use federated import(scope/request), RemoteModule will perform global.[scope].get(request)

Since we own the getter, its going to push into a global map. We can then read config from MF plugin to get paths to remote container if we need.

ImportedFederatedPlugin({ Name:app1 Remotes: {otherRemote: path.resolve(pathToRemote)} })

Now you've got the options passed to MF. So we can infer name, and know how to find the container to get the exported client chunks off it.

You can also use, in the server. So chunkExtractor would go:

remoteChunks = webpack_require(require.resolve(remote)).ClientChunks

Alternatively, we could attach to scope during initialization

{init()=>{Object.assign(global.chunkMaps,ClientChunks)}}

Inside loadable, you'd pretty much want to prevent the typical babel transform. Instead we use webpack directly. We can use require if needed for server transform, then webpack will hoist to app boot time, i think.

Lastly, we could use the existing HOC to push ones actually rendered, depending on if the require function pull the chunks right away. Ideally if we can preloadAll or something it might be better.

There's likely some details to work out but this will work.

ScriptedAlchemy avatar Oct 18 '20 03:10 ScriptedAlchemy

hi @ScriptedAlchemy, i was very curious about finding the solution to the problem. If we could talk and you could guide me through, i would be very happy to contribute.

ajayjaggi avatar Oct 22 '20 06:10 ajayjaggi

There's other issues that block webpack support besides this :/

But I'll push a branch somewhere soon that you can look at

ScriptedAlchemy avatar Oct 22 '20 06:10 ScriptedAlchemy

Hi @ScriptedAlchemy , If you could share the branch it would be great help

ajayjaggi avatar Oct 30 '20 07:10 ajayjaggi

I'm building most of this capability into the module federation dashboard.

I'll backport so capabilities to standalone

ScriptedAlchemy avatar Oct 31 '20 20:10 ScriptedAlchemy

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.

stale[bot] avatar Dec 31 '20 00:12 stale[bot]

~@theKashey Can we re-open this guy as~ it's a blocker for users of this package from updating to Webpack 5? Also is there any idea of timing or any assistance we "the community" can help with? CC. @ScriptedAlchemy

visormatt avatar Jan 04 '21 21:01 visormatt

webpack 5 support was already PR-ed - #676, I am waiting for a little free time to get it merged.

theKashey avatar Jan 04 '21 23:01 theKashey

Amazing, I should have poked around the branches a bit more, I didn't see it linked here 🤷. Thanks again and thank you for all the fantastic work thats gone into this project!!

visormatt avatar Jan 04 '21 23:01 visormatt

webpack 5 support was already PR-ed - #676, I am waiting for a little free time to get it merged.

Any progress?

AlexSergey avatar Jan 15 '21 08:01 AlexSergey

Any progress on this? Seems to break razzle builds (hash mismatch on production build)

pauliusuza avatar Feb 25 '21 18:02 pauliusuza

Watching this issue as well.

I also created a reproduction repo. README has instructions on how to run locally. https://github.com/suhanw/wp5-modfed-ssr-loadable

A very simple setup where remote exposes TopNav component, and host consumes TopNav. https://github.com/suhanw/wp5-modfed-ssr-loadable/blob/main/host/client/components/App/index.js

const TopNav = loadable(() => import('remote/top-nav'), { ssr: true }); The line above throws the following error:

loadable-components: failed to synchronously load component, which expected to be available {
  fileName: 'webpack/container/remote/remote/top-nav',
  chunkName: 'remote-top-nav',
  error: '__webpack_modules__[moduleId] is not a function'
}
(node:62803) UnhandledPromiseRejectionWarning: TypeError: __webpack_modules__[moduleId] is not a function
    at __webpack_require__ (/Users/suhanwijaya/Desktop/temp/wp5-modfed-ssr-loadable/host/build/server/bundle.js:249:41)
    at Object.requireSync (/Users/suhanwijaya/Desktop/temp/wp5-modfed-ssr-loadable/host/build/server/server_renderer_js.bundle.js:60:14)
    at InnerLoadable.loadSync (/Users/suhanwijaya/Desktop/temp/wp5-modfed-ssr-loadable/host/node_modules/@loadable/component/dist/loadable.cjs.js:278:35)
    at new InnerLoadable (/Users/suhanwijaya/Desktop/temp/wp5-modfed-ssr-loadable/host/node_modules/@loadable/component/dist/loadable.cjs.js:173:17)
    at processChild (/Users/suhanwijaya/Desktop/temp/wp5-modfed-ssr-loadable/host/node_modules/react-dom/cjs/react-dom-server.node.development.js:3305:14)
    at resolve (/Users/suhanwijaya/Desktop/temp/wp5-modfed-ssr-loadable/host/node_modules/react-dom/cjs/react-dom-server.node.development.js:3270:5)
    at ReactDOMServerRenderer.render (/Users/suhanwijaya/Desktop/temp/wp5-modfed-ssr-loadable/host/node_modules/react-dom/cjs/react-dom-server.node.development.js:3753:22)
    at ReactDOMServerRenderer.read (/Users/suhanwijaya/Desktop/temp/wp5-modfed-ssr-loadable/host/node_modules/react-dom/cjs/react-dom-server.node.development.js:3690:29)
    at Object.renderToString (/Users/suhanwijaya/Desktop/temp/wp5-modfed-ssr-loadable/host/node_modules/react-dom/cjs/react-dom-server.node.development.js:4298:27)
    at _callee$ (/Users/suhanwijaya/Desktop/temp/wp5-modfed-ssr-loadable/host/build/server/server_renderer_js.bundle.js:134:76)

import TopNav from 'remote/top-nav'; This line above appears to work, but extractor.getStyleTags() returns empty string (this seems related to https://github.com/gregberge/loadable-components/issues/706)

@ajayjaggi curious if you found a workaround?

suhanw avatar Mar 12 '21 21:03 suhanw

Any progress?

I get the same error

loadable-components: failed to synchronously load component, which expected to be available {
  fileName: './src/components/Mfa_1_app.tsx',
  chunkName: 'components-Mfa_1_app',
  error: '__webpack_modules__[moduleId] is not a function'
}
(node:29690) UnhandledPromiseRejectionWarning: TypeError: __webpack_modules__[moduleId] is not a function
    at __webpack_require__ (/media/gitprodenv/Elements6/Coding/mfa/packages/container/dist-backend/webpack:/@microfrontend/container/webpack/bootstrap:19:1)
    at Object../src/components/Mfa_1_app.tsx (/media/gitprodenv/Elements6/Coding/mfa/packages/container/dist-backend/dev-server-bundle.js:3086:73)
....

GitProdEnv avatar Apr 19 '21 13:04 GitProdEnv

Same failed to synchronously load component, which expected to be available Any updates?

muehre avatar May 06 '21 19:05 muehre

There is a little problem with supporting module federation, and it short it's all about relying on a stats.json(loadable.json) to handle SSR case. One loadable, one LoadableWebpackPlugin, one stats, one application - federated pieces will be not visible for the ChunkExtractor and magic will not happen.

Without first class support from Federation, and corresponding changes to LoadablePlugin - it's not gonna work.

theKashey avatar May 08 '21 02:05 theKashey

Also you can check https://github.com/module-federation/module-federation-examples/issues/751#issuecomment-832885498 and https://github.com/jacob-ebey/ssr-react-streaming-example/blob/main/ssr_react_streaming_example_b/federation-stats-plugin.js

7rulnik avatar May 08 '21 02:05 7rulnik

Basically that's all - collect stats from every federated module and merge result together to ChunkExtractor. To something like FederatedChunkExtractor.

Some extra functionality might be required to keep server and client "choices" consistent, as well as to support orchestration, but the very essence of proper MF support - just get all stats together in one place.

theKashey avatar May 08 '21 02:05 theKashey