UNPKG

3.77 kBJavaScriptView Raw
1import React from 'react';
2import ReactDOM from 'react-dom/server';
3
4import Koa from 'koa';
5import staticFiles from 'koa-static';
6import body from 'koa-bodyparser';
7import compress from 'koa-compress';
8import session from 'koa-session';
9import conditionalGet from 'koa-conditional-get';
10import favicon from 'koa-favicon';
11import etag from 'koa-etag';
12import csrf from 'koa-csrf';
13import uuid from 'node-uuid';
14import convert from 'koa-convert';
15
16import { App as Horse } from 'horse';
17import Chariot from './app';
18
19const App = Chariot(Horse);
20
21export function requestGUID () {
22 return async function (ctx, next) {
23 ctx.guid = uuid.v4();
24 await next();
25 };
26}
27
28export function setServerContextProps(server) {
29 return async function (ctx, next) {
30 ctx.synchronous = true;
31 ctx.includeLayout = true;
32
33 ctx.props = {
34 config: server.app.config,
35 };
36
37 await next();
38 };
39}
40
41export const MIDDLEWARE_MAP = {
42 staticFiles,
43 compress,
44 session,
45 conditionalGet,
46 favicon,
47 etag,
48 csrf,
49 requestGUID,
50 body,
51};
52
53export function injectBootstrap(ctx, format) {
54 let p = { ...ctx.props };
55
56 if (format) {
57 p = format({...ctx.props});
58 }
59
60 delete p.app;
61 delete p.api;
62 delete p.manifest;
63 delete p.dataPromises;
64
65 const bootstrap = safeStringify(p);
66
67 const body = ctx.body;
68
69 if (body && body.lastIndexOf) {
70 const bodyIndex = body.lastIndexOf('</body>');
71 const template = `<script>window.bootstrap=${bootstrap}</script>`;
72 ctx.body = body.slice(0, bodyIndex) + template + body.slice(bodyIndex);
73 }
74}
75
76export function safeStringify (obj) {
77 return JSON.stringify(obj)
78 .replace(/&/g, '\\u0026')
79 .replace(/</g, '\\u003C')
80 .replace(/>/g, '\\u003E');
81}
82
83const GeneratorFunction = Object.getPrototypeOf(eval("(function*(){})")).constructor;
84
85export default class Server {
86 constructor (serverConfig, appConfig) {
87 this.config = serverConfig;
88
89 this.middleware = serverConfig.middleware || [];
90 this.middleware.push(setServerContextProps(this));
91
92 this.app = new App(appConfig);
93
94 this.warn(serverConfig);
95
96 this.server = new Koa();
97 this.server.keys = this.config.keys;
98 }
99
100 warn (serverConfig) {
101 if (!serverConfig.keys) {
102 console.log('No `keys` passed into serverConfig; your sessions are insecure.');
103 }
104 }
105
106 enableMiddleware(middleware) {
107 this.middleware.push(middleware);
108 }
109
110 loadRoutes(routes) {
111 routes(this.app);
112 }
113
114 async render (ctx) {
115 //todo figure out html template
116 ctx.type = 'text/html; charset=utf-8';
117
118 try {
119 if (React.isValidElement(ctx.body)) {
120 let body = ReactDOM.renderToStaticMarkup(ctx.body);
121 ctx.body = body;//layout.replace(/!!CONTENT!!/, body);
122 }
123 } catch (e) {
124 ctx.props.app.error(e, ctx, ctx.props.app);
125 await this.render(ctx);
126 }
127 }
128
129 serverRender (app) {
130 return async (ctx, next) => {
131 ctx.timings = {};
132
133 if (ctx.accepts('html')) {
134 const routeStart = Date.now();
135 await app.route(ctx, next);
136 ctx.timings.route = Date.now() - routeStart;
137 }
138
139 const renderStart = Date.now();
140 await this.render(ctx);
141 ctx.timings.render = Date.now() - renderStart;
142
143 await injectBootstrap(ctx, this.config.formatBootstrap);
144 }
145 }
146
147 start() {
148 if (this.started) {
149 throw new Error('Attempted to run `start` twice on a server instance');
150 }
151
152 this.middleware.forEach((m) => {
153 let middleware = m;
154
155 if (m instanceof GeneratorFunction) {
156 middleware = convert(m);
157 }
158
159 this.server.use(middleware);
160 });
161
162 this.server.use(this.serverRender(this.app));
163 this.server.listen(this.config.port);
164
165 this.started = true;
166 console.log(`Server istening on ${this.config.port}`);
167 }
168}