import debug from 'debug'; import { renderToString } from 'react-dom/server'; import createMemoryHistory from 'history/createMemoryHistory'; import { NOT_FOUND } from 'redux-first-router'; import devtoolsEnhancer from 'remote-redux-devtools'; import { errorThrowerMiddleware } from '../utils/react.js'; import { createApp, provideStore, App } from '../../common/app'; import waitForEpics from '../../common/utils/wait-for-epics.js'; import { titleSelector } from '../../common/app/redux'; const log = debug('fcc:react-server'); const isDev = process.env.NODE_ENV !== 'production'; // add routes here as they slowly get reactified // remove their individual controllers const routes = [ '/challenges', '/challenges/*', '/map', '/settings', '/settings/*', '/portfolio/:username' ]; const devRoutes = []; const middlewares = isDev ? [errorThrowerMiddleware] : []; export default function reactSubRouter(app) { var router = app.loopback.Router(); router.get('/videos', (req, res) => res.redirect('/map')); router.get( '/videos/:dashedName', (req, res) => res.redirect(`/challenges/${req.params.dashedName}`) ); // These routes are in production routes.forEach((route) => { router.get(route, serveReactApp); }); if (process.env.NODE_ENV === 'development') { devRoutes.forEach(function(route) { router.get(route, serveReactApp); }); } app.use('/:lang', router); function serveReactApp(req, res, next) { const { lang } = req; const serviceOptions = { req }; createApp({ serviceOptions, middlewares, enhancers: [ devtoolsEnhancer({ name: 'server' }) ], history: createMemoryHistory({ initialEntries: [ req.originalUrl ] }), defaultState: { app: { lang } } }) .filter(({ location: { type, kind, pathname } = {} }) => { if (kind === 'redirect') { log('react found a redirect'); res.redirect(pathname); return false; } if (type === NOT_FOUND) { log(`react tried to find ${req.path} but got 404`); next(); return false; } return true; }) .flatMap(({ store, epic }) => { return waitForEpics(epic) .map(() => renderToString( provideStore(App, store) )) .map((markup) => ({ markup, store, epic })); }) .do(({ markup, store, epic }) => { log('react markup rendered, data fetched'); const state = store.getState(); const title = titleSelector(state); epic.dispose(); res.expose(state, 'data', { isJSON: true }); // note(berks): we render without express-flash dumping our messages // the app will query for these on load res.renderWithoutFlash('layout-react', { markup, title }); }) .subscribe(() => log('html rendered and sent'), next); } }