Skip to content
This repository has been archived by the owner on Mar 10, 2023. It is now read-only.

Commit

Permalink
refactor: koa 2 (#236)
Browse files Browse the repository at this point in the history
BREAKING: app middleware now requires koa 2
  • Loading branch information
christophehurpeau authored Feb 17, 2017
1 parent 03cedce commit fcf56c7
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 113 deletions.
4 changes: 3 additions & 1 deletion .eslintrc → .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"globals": {
"IS_CLIENT": false,
"IS_SERVER": false,
"window": false,
"window": false
},

"rules": {
Expand All @@ -25,6 +25,8 @@
"no-nested-ternary": 0,
// We use underscore dangle to underline globals
"no-underscore-dangle": 0,
// koa2 ctx needs to reassign properties
"no-param-reassign": 0,
// We use the react-require loader which require automatically React when using jsx
"react/react-in-jsx-scope": 0,
// PropType.Shape seems buggy
Expand Down
13 changes: 6 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,12 @@
"isomorphic-style-loader": "^1.0.0",
"js-string-escape": "^1.0.1",
"json-loader": "^0.5.4",
"koa": "^1.1.2",
"koa-compose": "^2.4.0",
"koa-conditional-get": "^1.0.3",
"koa-etag": "^2.1.1",
"koa-mount": "^1.3.0",
"koa-send": "^3.2.0",
"koa-static": "^2.0.0",
"koa": "2.0.0-alpha.8",
"koa-compose": "^3.2.1",
"koa-conditional-get": "^2.0.0",
"koa-etag": "^3.0.0",
"koa-mount": "^2.0.0",
"koa-static": "^3.0.0",
"lodash.mergewith": "^4.0.4",
"node-fetch": "^1.6.3",
"postcss-browser-reporter": "^0.5.0",
Expand Down
8 changes: 6 additions & 2 deletions src/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ import staticAssetsServer from './middlewares/staticAssetsServer';

export default compose([
// Enable Hot Reload when vitamin devServer url differs from app url (externalUrl)
process.env.NODE_ENV !== 'production' &&
function* setCORS(next) { this.set('Access-Control-Allow-Origin', '*'); yield next; },
process.env.NODE_ENV !== 'production' && (
(ctx, next) => {
ctx.set('Access-Control-Allow-Origin', '*');
return next();
}
),
conditional(),
etag(),
errorHandler(),
Expand Down
10 changes: 5 additions & 5 deletions src/server/middlewares/actionDispatcher.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import actionDispatcher from '__app_modules__server_actionDispatcher__';

export default () => function* actionDispatcherMiddleware(next) {
const { dispatch, getState } = this.state.store;
const dispatchResult = actionDispatcher(this.request, dispatch, getState);
export default () => async (ctx, next) => {
const { dispatch, getState } = ctx.state.store;
const dispatchResult = actionDispatcher(ctx.request, dispatch, getState);
if (dispatchResult) {
yield dispatchResult;
await dispatchResult;
}
yield next;
await next();
};
62 changes: 30 additions & 32 deletions src/server/middlewares/errorHandler.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,66 +37,64 @@ const renderErrorPage = (props) => {
});
};

const onError = (context) => {
const onError = (errorContext) => {
/* eslint-disable no-console */
console.error(
chalk.red(`Error ${context.HTTPStatus}:`),
`${context.request.method} ${context.request.url}`,
chalk.red(`Error ${errorContext.HTTPStatus}:`),
`${errorContext.request.method} ${errorContext.request.url}`,
);
if (context.HTTPStatus === 500) {
console.error(chalk.red.bold(context.error.message));
console.error(chalk.grey(context.error.stack));
if (errorContext.HTTPStatus === 500) {
console.error(chalk.red.bold(errorContext.error.message));
console.error(chalk.grey(errorContext.error.stack));
}
/* eslint-enable no-console */
try {
userOnError(context);
userOnError(errorContext);
} catch (err) {
console.error(chalk.red('An error occured while calling the onError function'));
console.error(err);
}
};

function getContext(error) {
return {
...(error ? { error } : {}),
...(this.state.store ? { state: this.state.store.getState() } : {}),
HTTPStatus: this.status,
request: this.request,
};
}
const errorToErrorContext = (ctx, error) => ({
...(error ? { error } : {}),
...(ctx.state.store ? { state: ctx.state.store.getState() } : {}),
HTTPStatus: ctx.status,
request: ctx.request,
});

function renderError(error) {
const renderError = (ctx, error) => {
try {
const context = getContext.call(this, error);
this.body = renderErrorPage(context);
onError(context);
const errorContext = errorToErrorContext(ctx, error);
this.body = renderErrorPage(errorContext);
onError(errorContext);
} catch (renderingError) {
this.body = renderRawError(this.status, renderingError);
onError(getContext.call(this, renderingError));
this.body = renderRawError(ctx.status, renderingError);
onError(errorToErrorContext(ctx, renderingError));
}
this.type = 'html';
}
};

export default () => function* errorHandlerMiddleware(next) {
export default () => async (ctx, next) => {
try {
yield next;
await next();
} catch (error) {
this.status = 500;
renderError.call(this, error);
ctx.status = 500;
renderError(ctx, error);
}
if (this.status === 404) {
if (this.state.store) {
renderError.call(this);
if (ctx.status === 404) {
if (ctx.state.store) {
renderError(ctx);
} else {
// If there is no store, it means that it is a middleware that put a 404, we don't
// print a vitaminjs 404
onError(getContext.call(this));
if (!process.env.NODE_ENV === 'production' && !this.body) {
onError(errorToErrorContext(ctx));
if (!process.env.NODE_ENV === 'production' && !ctx.body) {
// eslint-disable-next-line no-console
console.warn(chalk.yellow(`\
It seems that one of your custom koa middleware returned a 404 with no response body.
This might be intentional, or you might have forgot to yield next.
(see https://github.com/koajs/koa/blob/master/docs/guide.md#writing-middleware)`,
(see https://github.com/koajs/koa/blob/v2.x/docs/guide.md#writing-middleware)`,
));
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/server/middlewares/renderer.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* global ASSETS_BY_CHUNK_NAME */
import render from '../render';

export default () => function* rendererMiddleware() {
const { renderProps, store } = this.state;
const mainEntry =
// eslint-disable-next-line no-undef
(ASSETS_BY_CHUNK_NAME || this.res.locals.webpackStats.toJson().assetsByChunkName).main;
this.body =
yield render(renderProps, store, Array.isArray(mainEntry) ? mainEntry[0] : mainEntry);
export default () => async (ctx) => {
const { renderProps, store } = ctx.state;
let mainEntry =
(ASSETS_BY_CHUNK_NAME || ctx.res.locals.webpackStats.toJson().assetsByChunkName).main;
mainEntry = Array.isArray(mainEntry) ? mainEntry[0] : mainEntry;
ctx.body = await render(renderProps, store, mainEntry);
};
28 changes: 14 additions & 14 deletions src/server/middlewares/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,36 @@ import routes from '__app_modules__routes__';

const routesWithStore = typeof routes === 'function' ? store => routes(store) : () => routes;

export default () => function* routerMiddleware(next) {
const url = this.req.url;
const history = this.state.history;
export default () => async (ctx, next) => {
const url = ctx.req.url;
const history = ctx.state.history;

const appRoutes = yield Promise.resolve(routesWithStore(this.state.store));
const appRoutes = await routesWithStore(ctx.state.store);

yield new Promise((resolve) => {
await new Promise((resolve) => {
match({ routes: appRoutes, location: url, history },
(error, redirectLocation, renderProps) => {
if (error) {
this.status = 500;
this.body = error.message;
ctx.status = 500;
ctx.body = error.message;
} else if (redirectLocation) {
this.redirect(
ctx.redirect(
(redirectLocation.basename || '') +
redirectLocation.pathname + redirectLocation.search,
);
} else if (renderProps) {
this.status = 200;
this.state.renderProps = renderProps;
ctx.status = 200;
ctx.state.renderProps = renderProps;
} else {
this.status = 404;
this.body = 'Not found';
ctx.status = 404;
ctx.body = 'Not found';
}
resolve();
},
);
});

if (this.status === 200) {
yield next;
if (ctx.status === 200) {
await next();
}
};
10 changes: 5 additions & 5 deletions src/server/middlewares/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import middlewares from '__app_modules__redux_middlewares__';
import { create as createStore } from '../../shared/store';
import config from '../../../config';

export default () => function* storeMiddleware(next) {
export default () => (ctx, next) => {
const history = useQueries(useBasename(createMemoryHistory))({
basename: config.basePath,
entries: [this.req.url],
entries: [ctx.req.url],
});
this.state.history = history;
this.state.store = createStore(history, reducers.default || reducers, middlewares);
yield next;
ctx.state.history = history;
ctx.state.store = createStore(history, reducers.default || reducers, middlewares);
return next();
};
13 changes: 7 additions & 6 deletions src/server/server.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable global-require, no-console */

import { parse as parseUrl } from 'url';
import koa from 'koa';
import Koa from 'koa';
import express from 'express';
import chalk from 'chalk';
import fetch from 'node-fetch';
Expand All @@ -15,11 +15,12 @@ global.fetch = fetch;

let currentApp = app;
function appServer() {
const server = koa();
const appWrapper = function* appWrapper(next) {
yield currentApp.call(this, next);
};
server.use(appWrapper);
const server = new Koa();
server.use(
process.env.NODE_ENV === 'production' ? currentApp
// ecapsulate app for hot reload
: (ctx, next) => currentApp(ctx, next),
);
return server.callback();
}

Expand Down
Loading

0 comments on commit fcf56c7

Please sign in to comment.