Sarkis Arutiunian Sarkis Arutiunian - 3 months ago 40
React JSX Question

Server side rendering issue with routes importing

I found a lot of information about SSR with React and all of them with completely different approach. So I found one example which looks more useful in my case (web-app on React/graphQL/Apollo/Express/Webpack), but I stuck on one issue. Below some examples:

server.js


...
import {router} from './client/App';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { match, RoutingContext, Route } from 'react-router';
...
function renderApp(props, res) {
const markup = renderToString(<RoutingContext {...props}/>);
const html = createPage(markup);
write(html, 'text/html', res);
}

app.get('*', (req, res, next) => {
const location = hist.createLocation(req.path);
match({routes: router, location: location}, (err, redirectLocation, renderProps) => {
if (err) {
writeError('ERROR!', res);
next(err);
} else if (redirectLocation) {
redirect(redirectLocation, res);
} else if (renderProps) {
renderApp(renderProps, res);
} else {
writeNotFound(res);
}
});
});
...


and
App.js
from where we import router:

...

import Login from './components/Login';
import Register from './components/Register';
...
let routes = (
<Route>
<Route path="login" component={Login}/>
<Route path="register" component={Register}/>
...
</Route>
);

export let router = [{
path: '/',
component: Layout,
indexRoute: {
component: View
},
getChildRoutes(location, cb) {
require.ensure([], () => cb(null, routes));
}
}];

match({router, location}, () => {
render(
<ApolloProvider client={client}>
<div>
<Router routes={router} onUpdate={() => window.scrollTo(0, 0)} history={browserHistory}/>
</div>
</ApolloProvider>,
document.getElementById('root')
);
});


I tried do everything like in this example but issue is when in
server.js
I try to import
router
from
App.js
server doesn't run and give me error related with React component (styles importing and etc. everything what we can do on client but can't on server).

So question is, what am I doing wrong? How else can I import routes without having this issue?
It's really annoying that I already waist that much time on this small task.

I'll be grateful for any help, thanks!

Answer

As you know, server side render is render your react component in your node server. but Node server doesn't support import css/png files.

And if you don't want to change your client code, you can try user webpack-isomorphic-tools, it will help you generate a assert.json file, which can make require('*.css') calls return a json objects and generated CSS class names maps like they do in webpack css-loader.

if you are interested, you can look this demo.

here is your webpack-isomorphic-tools.js

var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');


module.exports = {


  assets: {
    images: {
      extensions: [
        'jpeg',
        'jpg',
        'png',
        'gif'
      ],
      parser: WebpackIsomorphicToolsPlugin.url_loader_parser
    },
    fonts: {
      extensions: [
        'woff',
        'woff2',
        'ttf',
        'eot'
      ],
      parser: WebpackIsomorphicToolsPlugin.url_loader_parser
    },
    svg: {
      extension: 'svg',
      parser: WebpackIsomorphicToolsPlugin.url_loader_parser
    },

    bootstrap: {
      extension: 'js',
      include: ['./src/theme/bootstrap.config.js'],
      filter: function(module, regex, options, log) {
        function is_bootstrap_style(name) {
          return name.indexOf('./src/theme/bootstrap.config.js') >= 0;
        }
        if (options.development) {
          return is_bootstrap_style(module.name) && WebpackIsomorphicToolsPlugin.style_loader_filter(module, regex, options, log);
        }

      },

      path: WebpackIsomorphicToolsPlugin.style_loader_path_extractor,
      parser: WebpackIsomorphicToolsPlugin.css_loader_parser
    },
    style_modules: {
      extensions: ['less','scss'],
      filter: function(module, regex, options, log) {
        if (options.development) {
                      return WebpackIsomorphicToolsPlugin.style_loader_filter(module, regex, options, log);
        } else {
                      return regex.test(module.name);
        }
      },
      path: function(module, options, log) {
        if (options.development) {
                      return WebpackIsomorphicToolsPlugin.style_loader_path_extractor(module, options, log);
        } else {
                      return module.name;
        }
      },
      parser: function(module, options, log) {
        if (options.development) {
          return WebpackIsomorphicToolsPlugin.css_modules_loader_parser(module, options, log);
        } else {
              return module.source;
        }
      }
    }
  }
}

and your server.js should look like this

function renderFullPage (title, css, html, initialState) {
    return `
        <!DOCTYPE html>
            <html>
              <head>
                <title>${title}</title>
                <style type="text/css">${css}</style>
              </head>
              <body>
                <div id="app">${html}</div>

                <script>
                    window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
                </script>
                <script src="/assets/scripts/app.bundle.js"></script>
              </body>
            </html>
    `;
}

const asyncStore = (store, renderProps) => {
    let promise = Promise.all([
        store.dispatch(queryArtistList()),
        store.dispatch(queryAuth())
    ]);
    return promise;
}

const HomeCtrl = {
    index: async (req, res) => {
        // 补全同构应用运行时缺失的全局对象
        global.window = {
            navigator: {
                userAgent: req.get('User-Agent'),
            },
            location: {
                protocol: req.protocol + ':',
                hostname: req.hostname,
            },
        };  
        match({ routes, location: req.url }, async (err, redirectLocation, renderProps) => {
            if (err) {
                res.status(500).end(`Internal Server Error ${err}`);
            } else if (redirectLocation) {
                res.redirect(redirectLocation.pathname + redirectLocation.search + '/');
            } else if (renderProps) {
                let store   = configureStore();
                const state = store.getState();
                await asyncStore(store, renderProps);
                const components = (<Provider store={store}>
                                            <RouterContext {...renderProps} />
                                        </Provider>);
                const html = renderToStaticMarkup(components);

                res.end(renderFullPage('tokyo Artist', '', html, store.getState()));

            } else {
              res.status(404).end('Not found');
            }
        })
    }
}

and please make sure, start your server after you have generate your webpack-asserts.json;

so your app.js should look like this:

#!/usr/bin/env node
const path = require('path');
const rootDir = path.resolve(__dirname, '..');
const fs = require('fs');

const babelrc = fs.readFileSync(rootDir + '/.babelrc', 'utf-8');
var config;

try {
    config = JSON.parse(babelrc);
} catch (err) {
    console.error('==>     ERROR: Error parsing your .babelrc.');
    console.error(err);
}

require('babel-register')(config);

/**
 * Define isomorphic constants.
 */
global.__CLIENT__ = false;
global.__SERVER__ = true;
global.__DEVELOPMENT__ = process.env.NODE_ENV !== 'production';
global.__DEVTOOLS__ = __DEVELOPMENT__;


const WebpackIsomorphicTools = require('webpack-isomorphic-tools');
global.webpackIsomorphicTools = new WebpackIsomorphicTools(require('../webpack/webpack.isomorphic-tools'))
    .development(__DEVELOPMENT__)
    .server(__DEVELOPMENT__ ? __dirname : rootDir, function() {
        require('../server/app.js');
    });