UNPKG

@r/platform

Version:

A set of tools to enable easy universal rendering and page navigation on a React + Redux stack

112 lines (95 loc) 3.25 kB
import { Thunker, PromiseWell } from '@r/middleware'; import Koa from 'koa'; import KoaRouter from 'koa-router'; import KoaBodyParser from 'koa-bodyparser'; import { combineReducers, createStore, applyMiddleware } from 'redux'; import { values } from 'lodash/object'; import { isEqual, isEmpty } from 'lodash/lang'; import navigationMiddleware from './navigationMiddleware'; import platform from './reducer'; import actions from './actions'; import { createQuery } from './pageUtils'; import { METHODS } from './router'; export default config => { const { port=8888, preRouteServerMiddleware=[], reduxMiddleware=[], reducers={}, routes=[], getServerRouter=() => {}, template=() => {}, dispatchBeforeNavigation=async () => {}, onHandlerComplete=() => {}, } = config; const server = new Koa(); const bodyparser = KoaBodyParser(); const router = new KoaRouter(); const handleRoute = async (ctx, next) => { const nav = navigationMiddleware.create(routes, true, onHandlerComplete); const well = PromiseWell.create(); const thunk = Thunker.create(); const r = combineReducers({ ...reducers, platform }); const store = createStore(r, {}, applyMiddleware( ...reduxMiddleware, nav, thunk, well.middleware, )); await store.dispatch(async (dispatch, getState, utils) => { await dispatchBeforeNavigation(ctx, dispatch, getState, utils); }); store.dispatch(actions.navigateToUrl( ctx.request.method.toLowerCase(), ctx.path, { queryParams: ctx.request.query, bodyParams: ctx.request.body, referrer: ctx.headers.referer, } )); await well.onComplete(); const state = store.getState(); // check for redirects const currentUrl = state.platform.currentPage.url; const currentQuery = state.platform.currentPage.queryParams; if (!isEqual(currentUrl, ctx.path) || !isEqual(currentQuery, ctx.request.query)) { if (currentUrl) { let newUrl = currentUrl; if (!isEmpty(currentQuery)) { newUrl += createQuery(currentQuery); } ctx.redirect(newUrl); } else { ctx.redirect('/'); } } else { // HEAD request must not have a response body but otherwise // are equivalent to GETs. // HEAD requests are converted into GETS by navigation middleware // if there isn't an explicit HEAD method on our handler. if (ctx.request.method.toLowerCase() !== METHODS.HEAD) { ctx.body = template(state, store); } ctx.status = state.platform.currentPage.status; } }; // set up server routes before setting up shared routes getServerRouter(router); for (let route of routes) { let [path, handler] = route; for (let method of values(METHODS)) { if (handler.prototype[method]) { router[method](path, handleRoute); } } } // hook up all the middleware to the koa instance preRouteServerMiddleware.forEach(m => server.use(m)); server.use(bodyparser); server.use(router.routes()); server.use(router.allowedMethods()); return () => { server.listen(port, () => { console.log(`App launching on port ${port}`); }); }; };