Document how to create a SSR build with externals. On the server, modules should not be duplicated to avoid problem with context. More detail here: https://github.com/smooth-code/loadable-components/issues/193#issuecomment-461870911
An example of correct configuration using webpack: https://github.com/smooth-code/loadable-components/blob/master/examples/server-side-rendering/webpack.config.babel.js
See https://github.com/smooth-code/loadable-components/issues/193
Hey @neoziro! We're having this issue. Split our codebase into a core and site app, but it's not picking up correctly for SSR. If you have any pointers I'd be happy to help document the approach once it's puzzled out.
CC @bigwillch
For now, you can rely on the example: https://github.com/smooth-code/loadable-components/blob/master/examples/server-side-rendering/webpack.config.babel.js
Hey @neoziro, a million thanks for this amazing project :)
I am not so used to webpack's externals but I understand the theory of it.
I have an issue where I am running the server-side compiled app source code, which complains on some modules not being found.
What struggles me is that those modules are either supposed to be used for the client-side only, or part of the server-side code and therefore should be bundled together with the server-side chunks?
In my example, heavily inspired by https://github.com/smooth-code/loadable-components/tree/master/examples/server-side-rendering, except that I use ChunkExtractorManager.
In the app itself, I use among others apollo-client, react-router, @emotion/core (css-in-js).
The webpack config looks like:
module.exports = [
{
name: 'web',
entry: 'src/client/index.js',
// ...
},
{
name: 'node',
entry: 'src/client/App.js',
// ...,
externals: [nodeExternals()],
// ...
}
];
The server-side code looks like:
const nodeExtractor = new ChunkExtractor({
statsFiles: path.resolve(BUILD_FOLDER, 'loadable-stats.node.json')
});
const { default: App } = nodeExtractor.requireEntrypoint();
const webExtractor = new ChunkExtractor({
statsFiles: path.resolve(BUILD_FOLDER, 'loadable-stats.web.json')
});
const AppWrapper = (
<ChunkExtractorManager extractor={webExtractor}>
<ApolloProvider client={apolloClient}>
<StaticRouter location={request.url} context={context}>
<App />
</StaticRouter>
</ApolloProvider>
</ChunkExtractorManager>
);
try {
await getDataFromTree(AppWrapper);
} catch (error) {
console.warn('Unable to load initial data', error);
}
const html = ReactDOMServer.renderToString(AppWrapper);
When I build my app, webpack outputs:
// ...
Entrypoint main = 3d99d338.chunk.js
[0] external "react" 42 bytes {10} [built]
[2] external "@emotion/core" 42 bytes {10} [built]
[3] external "react-router-dom" 42 bytes {10} [built]
[5] external "prop-types" 42 bytes {10} [built]
// ...
[8] external "@babel/runtime/helpers/classCallCheck" 42 bytes {10} [built]
// ...
So when I try to run that server-side code, I received errors like: Error: cannot find module 'react-router-dom' or the same with @emotion/core or some others.
Any chance you would know which modules should be specified in "externals"?
I get the fact that those modules are not needed by the server-side rendering part of should then be excluded from the server-side bundles.
What I am not getting is why I face those 'cannot find module' errors then?
Hello @flosch-hb, I think I got your problem.
Externals defines modules that remains a require('xxx') in the final bundle generated by webpack. When you run this bundle it will look for xxx module in node_modules. In your case I think you miss these modules. Just npm install react-router-dom, etc... and it should work properly.
Hey @neoziro, those modules are actual dependencies (part of the package.json dependencies) since they are required by the client-side bundles.
This is what confuses me the most but I think I just do not fully understand the concept of externals itself, I will try to get my mind around it!
Or well, in my case, the package is bundled by https://github.com/serverless-heaven/serverless-webpack which uses webpack under the hood to build the bundles, then deploy them to our Cloud Provider... Maybe the issue is on this side, I need to figure that out :)
I'm still wrapping my head around this package but will give my two cents based on what's working for me.
@flosch-hb
Since you're already have two separate bundles for server and client (I'm doing the same), what if, instead of doing this:
const nodeExtractor = new ChunkExtractor({
statsFiles: path.resolve(BUILD_FOLDER, 'loadable-stats.node.json')
});
const { default: App } = nodeExtractor.requireEntrypoint();
You just do:
import App from "./wherever/it/is/App";
It works for me, no need to deal with confusing externals.
Does it make sense @neoziro ?
@acaua yeah it works, except in development, you have to restart the server at every change. Loadable Components reset require cache for you in development mode.
@neoziro Thanks for the reply.
That's what I've been doing. I use nodemon to watch for changes and restart my server.
How does it work on the SSR example? Does it support HMR?
Or is it just that there's no need to rebundle the server on every change, you keep the server running and only rebundle the client? Would the second cas require a full reload on the browser?
You keep the server running and you only rebundle the client. Then you reload the browser and it should be up to date.
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.
Most helpful comment
You keep the server running and you only rebundle the client. Then you reload the browser and it should be up to date.