Open FezVrasta opened 6 years ago
Since the plugin only does prerendering of a source, the way to go here would be to create an instance of HtmlWebpackPlugin for each route.
You can see how preact-cli
does it here:
https://github.com/developit/preact-cli/blob/master/src/lib/webpack/render-html-plugin.js
In a nutshell, it's roughly:
const URLS = ['/', '/a', '/b'];
module.exports = {
// in a webpack config
plugins: [
].concat( URLS.map(url =>
new HtmlWebpackPlugin({
filename: url + '/index.html',
template: '!!prerender-loader?'+encodeURIComponent(JSON.stringify(
string: true,
params: { url }
))+'!index.html'
})
) )
}
Thanks! So the result would be several HTML files that should be then served somehow by my own http server?
yup! each with their own independent static HTML (and initial state, titles, etc that you might have injected during prerendering).
I just amended the example with a name
configuration value to HtmlWebpackPlugin to clarify how the files get written to disk.
Update: you can now also configure JSDOM to report custom URLs for location.href
, etc via the documentUrl
loader option.
This is cool 😎
I've tried with multiple HtmlWebpackPlugins
and I get a fair amount of errors. Is there a way to tell it to emit one ssr-bundle.js file?
ERROR in chunk contact [entry]
ssr-bundle.js
Conflict: Multiple chunks emit assets to the same filename ssr-bundle.js (chunks 1 and 3)
ERROR in chunk dangers-of-genservers [entry]
ssr-bundle.js
Conflict: Multiple chunks emit assets to the same filename ssr-bundle.js (chunks 1 and 4)
ERROR in chunk home [entry]
ssr-bundle.js
Conflict: Multiple chunks emit assets to the same filename ssr-bundle.js (chunks 1 and 5)
ERROR in chunk polyfill [entry]
ssr-bundle.js
Conflict: Multiple chunks emit assets to the same filename ssr-bundle.js (chunks 1 and 6)
ERROR in chunk process [entry]
ssr-bundle.js
Conflict: Multiple chunks emit assets to the same filename ssr-bundle.js (chunks 1 and 8)
ERROR in chunk quote [entry]
ssr-bundle.js
Conflict: Multiple chunks emit assets to the same filename ssr-bundle.js (chunks 1 and 9)
const getUrlPath = (url) => url.match('[^\/]+$')[0]
const prerenderParams = (url) => encodeURIComponent(JSON.stringify({string: true, params: {url}, documentUrl: getUrlPath(url)}))
new HtmlWebpackPlugin({
template: `!!prerender-loader?${prerenderParams(url)}!pug-loader!$./index.html`,
inject: true
excludeChunks: ...
})
Hmm - that wouldn't allow for setting parameters since each has a child build. Perhaps the ssr-bundle could be omitted from the parent compiler's assets..
For anyone arriving here after I did: I had to make a few additions and changes to the code above to make route rendering work for me (I'm using react-router-dom
)
// webpack.config.js
const urls = ['/', '/about/'];
webpack.plugins = webpack.plugins.concat(urls.map((url) => {
return new HtmlWebpackPlugin({
template: !!prerender-loader?${JSON.stringify({string: true, params: {url}})}!${path.join(__dirname, '/src/index.html')}
,
filename: path.join(__dirname, /dist${url}index.html
),
});
}))
2. I had to modify my index.js file to export a StaticRouter rendering so I could pass the url param as the location prop:
```javascript
// index.js
import * as ReactDOM from 'react-dom';
import { BrowserRouter, StaticRouter, Route } from 'react-router-dom';
import HomePage from './pages/home';
import AboutPage from './pages/about';
// This part is run in the browser, using Browser Router
ReactDOM.hydrate(
<BrowserRouter>
<React.Fragment>
<Route path='/' exact component={HomePage} />
<Route path='/about/' exact component={AboutPage} />
</React.Fragment>
</BrowserRouter>
, document.getElementById('app')
);
// I had to add the below to get html-webpack-plugin to output the correct markup for each route
// Params from the loader are sent to this function
// Note that this function returns `undefined`
export default (params) => {
ReactDOM.render(
<StaticRouter location={params.url} context={{}}>
<React.Fragment>
<Route path='/' exact component={HomePage} />
<Route path='/about/' exact component={AboutPage} />
</React.Fragment>
</StaticRouter>
, document.getElementById('app')
)
};
Hope this helps!
@MikaAK were you able to get this working in a Webpack config containing multiple entries? I've run into the same Multiple chunks emit assets to the same filename ssr-bundle.js
issues
@andybflynn Can you give us a walk through what we need to do to emulate your setup for my project? Why do you have a slash at the end of /about/
in your urls?
I used the same code for the webpack.config.js:
const urls = ['/', /*and other routers here*/ ];
webpack.plugins = webpack.plugins.concat(urls.map((url) => {
return new HtmlWebpackPlugin({
filename: path.join(__dirname, `/dist${url}index.html`),
template: `!!prerender-loader?${JSON.stringify({string: true, params: {url}})}!${path.join(__dirname, '/src/index.html')}`,
});
}))
And this is what my index file looks like (i'm using typescript)
import * as React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, StaticRouter } from 'react-router-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.hydrate(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root') as HTMLElement
);
export default (params: any) => {
ReactDOM.render(
<StaticRouter location={params.url} context={{}}>
<App />
</StaticRouter>,
document.getElementById('root')
);
};
registerServiceWorker();
Starting or building the app (and then serving it) doesn't seem to make any difference in how the app works in this config.
Package versions:
"dependencies": {
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-router-dom": "^4.3.1"
},
"devDependencies": {
"prerender-loader": "^1.2.0",
}
@borisyordanov The only reason I had the forward slash at the end of /about/
was so that I didn't have to put the slash in the filename, i.e.
filename: path.join(__dirname, `/dist${url}index.html`),
instead of
filename: path.join(__dirname, `/dist${url}/index.html`),
As long as your <App />
component contains the <Route>
components that you want to render then your setup appears to be the same as mine. Make sure the <Route>
paths match your urls that you are sending in the params.
I didn't have to do anything else to get it working. Adding the default export was the breakthrough moment for me. I'm using the same package versions as you.
For people who may have the same issue. I have created a successful working example with react and multi-entry(production only though) https://github.com/edwardfhsiao/prerender-loader-test-repo
$npm i
$npm run compile
How can you use this project to pre-render an app that provides different pages at different routes?