1 | import { Thunker, PromiseWell } from '@r/middleware';
|
2 | import Koa from 'koa';
|
3 | import KoaRouter from 'koa-router';
|
4 | import KoaBodyParser from 'koa-bodyparser';
|
5 | import { combineReducers, createStore, applyMiddleware } from 'redux';
|
6 | import { values } from 'lodash/object';
|
7 | import { isEqual, isEmpty } from 'lodash/lang';
|
8 |
|
9 | import navigationMiddleware from './navigationMiddleware';
|
10 | import platform from './reducer';
|
11 | import actions from './actions';
|
12 | import { createQuery } from './pageUtils';
|
13 | import { METHODS } from './router';
|
14 |
|
15 | export default config => {
|
16 | const {
|
17 | port=8888,
|
18 | preRouteServerMiddleware=[],
|
19 | reduxMiddleware=[],
|
20 | reducers={},
|
21 | routes=[],
|
22 | getServerRouter=() => {},
|
23 | template=() => {},
|
24 | dispatchBeforeNavigation=async () => {},
|
25 | onHandlerComplete=() => {},
|
26 | } = config;
|
27 |
|
28 | const server = new Koa();
|
29 | const bodyparser = KoaBodyParser();
|
30 | const router = new KoaRouter();
|
31 |
|
32 | const handleRoute = async (ctx, next) => {
|
33 | const nav = navigationMiddleware.create(routes, true, onHandlerComplete);
|
34 | const well = PromiseWell.create();
|
35 | const thunk = Thunker.create();
|
36 |
|
37 | const r = combineReducers({ ...reducers, platform });
|
38 |
|
39 | const store = createStore(r, {}, applyMiddleware(
|
40 | ...reduxMiddleware,
|
41 | nav,
|
42 | thunk,
|
43 | well.middleware,
|
44 | ));
|
45 |
|
46 | await store.dispatch(async (dispatch, getState, utils) => {
|
47 | await dispatchBeforeNavigation(ctx, dispatch, getState, utils);
|
48 | });
|
49 |
|
50 | store.dispatch(actions.navigateToUrl(
|
51 | ctx.request.method.toLowerCase(),
|
52 | ctx.path,
|
53 | {
|
54 | queryParams: ctx.request.query,
|
55 | bodyParams: ctx.request.body,
|
56 | referrer: ctx.headers.referer,
|
57 | }
|
58 | ));
|
59 |
|
60 | await well.onComplete();
|
61 | const state = store.getState();
|
62 |
|
63 |
|
64 | const currentUrl = state.platform.currentPage.url;
|
65 | const currentQuery = state.platform.currentPage.queryParams;
|
66 |
|
67 | if (!isEqual(currentUrl, ctx.path) || !isEqual(currentQuery, ctx.request.query)) {
|
68 | if (currentUrl) {
|
69 | let newUrl = currentUrl;
|
70 | if (!isEmpty(currentQuery)) { newUrl += createQuery(currentQuery); }
|
71 | ctx.redirect(newUrl);
|
72 | } else {
|
73 | ctx.redirect('/');
|
74 | }
|
75 | } else {
|
76 |
|
77 |
|
78 |
|
79 |
|
80 | if (ctx.request.method.toLowerCase() !== METHODS.HEAD) {
|
81 | ctx.body = template(state, store);
|
82 | }
|
83 |
|
84 | ctx.status = state.platform.currentPage.status;
|
85 | }
|
86 | };
|
87 |
|
88 |
|
89 | getServerRouter(router);
|
90 |
|
91 | for (let route of routes) {
|
92 | let [path, handler] = route;
|
93 | for (let method of values(METHODS)) {
|
94 | if (handler.prototype[method]) {
|
95 | router[method](path, handleRoute);
|
96 | }
|
97 | }
|
98 | }
|
99 |
|
100 |
|
101 | preRouteServerMiddleware.forEach(m => server.use(m));
|
102 | server.use(bodyparser);
|
103 | server.use(router.routes());
|
104 | server.use(router.allowedMethods());
|
105 |
|
106 | return () => {
|
107 | server.listen(port, () => {
|
108 | console.log(`App launching on port ${port}`);
|
109 | });
|
110 | };
|
111 | };
|