Hello,
I am running into troubles with SSR + multiple entry points. None of the issues already solved seem to have the same problem.
My issue is that on the server-side, when doing:
const nodeExtractor = new ChunkExtractor({
statsFile: './public/build/js/node/loadable-stats.json',
entrypoints: ['MyPage'],
});
const { default: App } = nodeExtractor.requireEntrypoint('MyPage');
App comes as undefined, even though loadable-stats.json does contain the name of the entrypoint as a key with the right file as a value. Even if I do not give an argument to requireEntrypoint, it comes as undefined - same if I don't give entrypoints to instantiate ChunkExtractor and keep the argument.
If I replace the last line with:
const App = nodeExtractor.requireEntrypoint('MyPage');
App does not come as a string, a class or a function but an object. React.createElement therefore fails.
My webpack configuration is as follow:
// ...
// ... define some variables that we'll use in the configuration below
// ...
// The object that'll hold webpack configuration
const getConfig = (target) => ({
entry: {
MyPage: ['MyPage'],
MyOtherPage: ['MyOtherPage'],
},
mode,
name: target,
target,
module: {
rules: [{
test: /\.jsx?$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
caller: { target },
},
},
}],
noParse: [/aws-sdk/],
},
resolve: {
extensions: ['.js', '.jsx'],
},
externals: target === 'node' ? ['@loadable/component', nodeExternals()] : undefined,
output: {
filename,
chunkFilename,
path: path.resolve(__dirname, 'public', 'build', 'js', target),
publicPath: `/build/js/${target}`,
libraryTarget: target === 'node' ? 'commonjs2' : undefined,
},
plugins: [
// Define global constants to be used in the JS files
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'local'),
},
}),
new webpack.NamedChunksPlugin((chunk) => {
if (chunk.name) {
return chunk.name;
}
const n = [];
chunk.forEachModule((m) => n.push(path.relative(m.context, m.request)));
return n.join('_');
}),
// Name all modules because adding a new page will bust all cache
new NameAllModulesPlugin(),
// Add versioning to files with a manifest files as basis
new Versioning({
manifestPath: path.join(__dirname, './public/build/rev-manifest.json'),
basePath: `js/${target}/`,
cleanup: false,
}),
new LoadablePlugin(),
],
devtool,
optimization: {
namedModules: true,
providedExports: true,
usedExports: true,
concatenateModules: true,
noEmitOnErrors,
minimizer,
splitChunks: {
cacheGroups: {
chunks: CHUNKS,
// No vendor chunk on the server-side
...target !== 'node' && {
vendors: {
test: (module) => !skip(module) && isExternal(module),
name: 'vendor',
filename: filenameVendor,
chunks: 'all',
minChunks: 3,
},
},
commons: {
test: (module) => !skip(module) && !isExternal(module),
name: 'common',
filename: filenameCommon,
chunks: 'initial',
minChunks: 3,
},
},
},
},
performance: {
hints: false,
},
...target !== 'node' && {
resolveLoader: {
modules: ['node_modules'],
},
},
// Bail out on the first error
bail: true,
watchOptions: {
aggregateTimeout: 100,
poll: 1000,
ignored: /node_modules/,
},
});
I can give more details if necessary, but I find myself completely stuck here. I followed the example we can find here.
I am running:
I got it working, mixing both the official documentation and the server-side rendering code example. Maybe the documentation should be updated? The solution was to do as follow:
const nodeExtractor = new ChunkExtractor({
statsFile: './public/build/js/node/loadable-stats.json',
entrypoints: [pageName],
});
const webExtractor = new ChunkExtractor({
statsFile: './public/build/js/web/loadable-stats.json',
entrypoints: [pageName],
});
// Render page
const html = ReactDOMServer.renderToString(
React.createElement(
ChunkExtractorManager,
{
extractor: nodeExtractor,
},
React.createElement(Page)
)
);
And return something like:
return `<div id="redux-page-container">${html}</div>${webExtractor.getScriptTags()}`;
So the node extractor is used for the server-side rendering, and the web extractor is used to get the browser-side compiled files.
Running into some other problems. Whenever I use
```javascript
const MyComponent = loadable(() => import('./MyComponent'));
````
the browser-side does not match the server-side for a short period of time and I have a Did not expect server HTML to contain a <div> in <div>. So the component disappears for a little time. Then, I can see a request to get MyComponent is done in the network tab, and when it is fetched, everything is back to normal.
It would be great to have full examples with the expected output of loadable-stats.json for example. Now I have no clue what the problem is. Maybe the problem is that window.__LOADABLE_REQUIRED_CHUNKS__ is an empty array, but should contain MyComponent? Maybe the chunk that is made for MyComponent should be a dependency of the main app in loadable-stats.json? Or most likely the problem comes from the optimization configuration of webpack? If someone can give help, that would be highly appreciated :-)
@killiansc it look like you hydration process is failing.
Could there be some sort of logic that makes server don't match client? like setState in componentDidMount? In my experience this kind of issues have nothing to do with loadable.
To help resolve problem
export default () => <div>test</div>.loadableReady on client entry getScriptTags on server side. Regarding loadable-stats.json it is basically webpack stats with something like
```
as example in my build I have `commons~agree~fullTerms` chunk in assetsByChunkName,
so on ssr page in page source I will see
Most helpful comment
I got it working, mixing both the official documentation and the server-side rendering code example. Maybe the documentation should be updated? The solution was to do as follow:
And return something like:
So the node extractor is used for the server-side rendering, and the web extractor is used to get the browser-side compiled files.