Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How does this work with apps with routes? #6

Open
FezVrasta opened this issue May 31, 2018 · 13 comments
Open

How does this work with apps with routes? #6

FezVrasta opened this issue May 31, 2018 · 13 comments
Labels
question Further information is requested

Comments

@FezVrasta
Copy link

How can you use this project to pre-render an app that provides different pages at different routes?

@developit
Copy link
Collaborator

developit commented May 31, 2018

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'
    })
  ) )
}

@developit developit added the question Further information is requested label May 31, 2018
@FezVrasta
Copy link
Author

Thanks! So the result would be several HTML files that should be then served somehow by my own http server?

@developit
Copy link
Collaborator

developit commented Jun 1, 2018

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.

@developit
Copy link
Collaborator

Update: you can now also configure JSDOM to report custom URLs for location.href, etc via the documentUrl loader option.

@johnstew
Copy link

This is cool 😎

@MikaAK
Copy link

MikaAK commented Oct 31, 2018

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: ...
})

@developit
Copy link
Collaborator

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..

@andybflynn
Copy link

andybflynn commented Dec 18, 2018

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)

  1. I didn't have to URI encode the JSON params when inlining the loader:
// 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`),
  });
}))
  1. I had to modify my index.js file to export a StaticRouter rendering so I could pass the url param as the location prop:
// 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!

@mikefowler
Copy link

@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

@borisyordanov
Copy link

borisyordanov commented Dec 19, 2018

@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",
}

@andybflynn
Copy link

andybflynn commented Dec 19, 2018

@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.

@edwardfxiao
Copy link

edwardfxiao commented Feb 13, 2019

#29

@edwardfxiao
Copy link

edwardfxiao commented Apr 28, 2019

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 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

8 participants