Skip to content

Commit

Permalink
feat(roc-package-web-app): Upgrade package to use Koa@2
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Middlewares need to be defined as an async funcions instead of generators.
  • Loading branch information
pmrukot committed Jan 30, 2018
1 parent 7500042 commit 416895b
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 30 deletions.
26 changes: 13 additions & 13 deletions packages/roc-package-web-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,21 @@
"dependencies": {
"config": "~1.16.0",
"debug": "~2.2.0",
"koa": "~1.1.1",
"koa-accesslog": "~0.0.2",
"koa-add-trailing-slashes": "~1.1.0",
"koa-compressor": "~1.0.3",
"koa": "~2.4.1",
"koa-add-trailing-slashes": "~2.0.1",
"koa-compress": "~2.0.0",
"koa-conditional-get": "~1.0.3",
"koa-errors": "~1.0.1",
"koa-etag": "~2.0.0",
"koa-favicon": "~1.2.0",
"koa-helmet": "~0.3.0",
"koa-logger": "~1.3.0",
"koa-lowercase-path": "~1.0.0",
"koa-normalize-path": "~1.0.0",
"koa-remove-trailing-slashes": "~1.0.0",
"koa-static": "~2.0.0",
"koa-error": "~3.1.1",
"koa-etag": "~3.0.0",
"koa-favicon": "~2.0.0",
"koa-helmet": "~3.3.0",
"koa-logger": "~3.1.0",
"koa-lowercase-path": "~2.0.0",
"koa-normalize-path": "~2.0.0",
"koa-remove-trailing-slashes": "~2.0.0",
"koa-static": "~4.0.2",
"lodash": "4.13.1",
"moment": "~2.20.1",
"roc": "^1.0.0-rc.23",
"roc-package-webpack-node": "^1.0.0",
"roc-package-webpack-web": "^1.0.0"
Expand Down
8 changes: 4 additions & 4 deletions packages/roc-package-web-app/src/app/createServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import http from 'http';
import https from 'https';

import debug from 'debug';
import koa from 'koa';
import Koa from 'koa';
import serve from 'koa-static';
import addTrailingSlash from 'koa-add-trailing-slashes';
import normalizePath from 'koa-normalize-path';
import lowercasePath from 'koa-lowercase-path';
import removeTrailingSlash from 'koa-remove-trailing-slashes';
import { merge, getSettings, getAbsolutePath } from 'roc';

import basepathSupport from './basepathSupport';
import basepathSupport from './middlewares/basepathSupport';

/**
* Creates a server instance.
Expand Down Expand Up @@ -41,15 +41,15 @@ export default function createServer(options = {}, beforeUserMiddlewares = [], {
const logger = debug('roc:server');
logger.log = console.info.bind(console);

const server = koa();
const server = new Koa();
const runtimeSettings = merge(getSettings('runtime'), options);

// Add support for rocPath
server.use(basepathSupport(rocPath));

if (useDefaultKoaMiddlewares) {
// eslint-disable-next-line
const middlewares = require('./middlewares').default(runtimeSettings, { dev, dist });
const middlewares = require('./defaultKoaMiddlewares').default(runtimeSettings, { dev, dist });
middlewares.forEach((middleware) => server.use(middleware));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import koaErrors from 'koa-errors';
import helmet from 'koa-helmet';
import koaError from 'koa-error';
import koaHelmet from 'koa-helmet';
import koaEtag from 'koa-etag';
import koaCompressor from 'koa-compressor';
import koaCompress from 'koa-compress';
import koaFavicon from 'koa-favicon';
import koaAccesslog from 'koa-accesslog';
import koaLogger from 'koa-logger';

import accesslog from './middlewares/accesslog';

/**
* Returns the middlewares to be used.
*
Expand All @@ -16,17 +17,17 @@ export default function middlewares(config, { dev, dist }) {
const middlewaresList = [];

if (dev) {
middlewaresList.push(koaErrors());
middlewaresList.push(koaError());
}

// Security headers
middlewaresList.push(helmet());
middlewaresList.push(koaHelmet());

middlewaresList.push(koaEtag());

// We only enable gzip in dist
if (dist) {
middlewaresList.push(koaCompressor());
middlewaresList.push(koaCompress());
}

const favicon = config.favicon;
Expand All @@ -35,7 +36,7 @@ export default function middlewares(config, { dev, dist }) {
}

if (dist) {
middlewaresList.push(koaAccesslog());
middlewaresList.push(accesslog());
} else {
middlewaresList.push(koaLogger());
}
Expand Down
64 changes: 64 additions & 0 deletions packages/roc-package-web-app/src/app/defaultKoaMiddlewares.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import koaErrorMock from 'koa-error';
import koaHelmetMock from 'koa-helmet';
import koaEtagMock from 'koa-etag';
import koaLoggerMock from 'koa-logger';
import koaCompressMock from 'koa-compress';
import koaFaviconMock from 'koa-favicon';

import accessLogMock from './middlewares/accesslog';
import getKoaMiddlewares from './defaultKoaMiddlewares';

jest.mock('koa-error', () => jest.fn(() => function koaError() {}));
jest.mock('koa-helmet', () => jest.fn(() => function koaHelmet() {}));
jest.mock('koa-etag', () => jest.fn(() => function koaEtag() {}));
jest.mock('koa-logger', () => jest.fn(() => function koaLogger() {}));
jest.mock('koa-compress', () => jest.fn(() => function koaCompress() {}));
jest.mock('koa-favicon', () => jest.fn(() => function koaFavicon() {}));
jest.mock('./middlewares/accesslog', () => jest.fn(() => function accessLog() {}));


describe('defaultKoaMiddleware', () => {
afterEach(() => {
koaErrorMock.mockClear();
koaHelmetMock.mockClear();
koaEtagMock.mockClear();
koaLoggerMock.mockClear();
koaCompressMock.mockClear();
});

it('should return default array of middlewares even when all options are falsy', () => {
const middlewares = getKoaMiddlewares({}, { dev: false, dist: false });

expect(middlewares.length).toEqual(3);
expect(middlewares.map(f => f.name)).toEqual(['koaHelmet', 'koaEtag', 'koaLogger']);
expect(koaHelmetMock).toHaveBeenCalled();
expect(koaEtagMock).toHaveBeenCalled();
expect(koaLoggerMock).toHaveBeenCalled();
});

it('should include koa-error when dev is set to true', () => {
const middlewares = getKoaMiddlewares({}, { dev: true, dist: false });

expect(middlewares.length).toEqual(4);
expect(middlewares.map(f => f.name)).toEqual(['koaError', 'koaHelmet', 'koaEtag', 'koaLogger']);
expect(koaErrorMock).toHaveBeenCalled();
});

it('should include koa-compress and accessLog instead of koa-logger when dist is set to true', () => {
const middlewares = getKoaMiddlewares({}, { dev: false, dist: true });

expect(middlewares.length).toEqual(4);
expect(middlewares.map(f => f.name)).toEqual(['koaHelmet', 'koaEtag', 'koaCompress', 'accessLog']);
expect(koaCompressMock).toHaveBeenCalled();
expect(accessLogMock).toHaveBeenCalled();
});

it('should include koa-favicon when favicon is specified in config', () => {
const config = { favicon: './pathToFavicon.ico' };
const middlewares = getKoaMiddlewares(config, { dev: false, dist: false });

expect(middlewares.length).toEqual(4);
expect(middlewares.map(f => f.name)).toEqual(['koaHelmet', 'koaEtag', 'koaFavicon', 'koaLogger']);
expect(koaFaviconMock).toHaveBeenCalledWith(config.favicon);
});
});
18 changes: 18 additions & 0 deletions packages/roc-package-web-app/src/app/middlewares/accesslog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import util from 'util';

import moment from 'moment';

/*
Implementation based on koa-accesslog
*/
export default function accesslog(stream = process.stdout) {
return async function(ctx, next) {
await next();

const format = '%s - - [%s] "%s %s HTTP/1.X" %d %s\n';
const length = ctx.length ? ctx.length.toString() : '-';
const date = moment().format('D/MMM/YYYY:HH:mm:ss ZZ');

stream.write(util.format(format, ctx.ip, date, ctx.method, ctx.path, ctx.status, length));
};
}
52 changes: 52 additions & 0 deletions packages/roc-package-web-app/src/app/middlewares/accesslog.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { format as formatMock } from 'util';

import createAccessLogMiddleware from './accesslog';

jest.mock('util', () => ({
format: jest.fn(),
}));

describe('middlewares/accesslog', () => {
const next = async () => {};

afterEach(() => {
formatMock.mockClear();
});

it('should call write method of passed stream', async () => {
const streamWriteMock = jest.fn();
const streamMock = {
write: streamWriteMock,
};
const accessLogMiddleware = createAccessLogMiddleware(streamMock);
await accessLogMiddleware({}, next);

expect(streamWriteMock).toHaveBeenCalled();
});

it('should call util.format with certain ctx properties', async () => {
const streamWriteMock = jest.fn();
const streamMock = {
write: streamWriteMock,
};
const accessLogMiddleware = createAccessLogMiddleware(streamMock);
const ctx = {
length: 42,
ip: '127.0.0.1',
method: 'GET',
path: '/app',
status: 200,
};
await accessLogMiddleware(ctx, next);

expect(formatMock).toHaveBeenCalledWith(
'%s - - [%s] \"%s %s HTTP/1.X\" %d %s\n',
ctx.ip,
expect.anything(),
ctx.method,
ctx.path,
ctx.status,
ctx.length.toString(),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ export default function basepathSupport(basepath) {
return newPath;
}

return function* (next) {
return async function(ctx, next) {
// Do nothing if the basepath is /
if (basepath === '/') {
return yield next;
return await next();
}

const newPath = matcher(this.path);
const newPath = matcher(ctx.path);
if (newPath) {
this.path = newPath;
return yield next;
ctx.path = newPath;
return await next();
}

// If the path does not match Koa will render a default 404 Not Found page.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import createBasepathSupportMiddleware from './basepathSupport';

describe('middlewares/basepathSupport', () => {
const next = async () => {};

it('should throw an error when basepath does not start with "/"', () => {
const veryWrongBasepath = 'veryWrongBasepath';
expect(() => {
createBasepathSupportMiddleware(veryWrongBasepath);
}).toThrowError(`The basepath must start with "/", was ${veryWrongBasepath}`);
});

it('should set new ctx.path with eased basepath', async () => {
const basepath = '/basepath';
const ctx = { path: '/basepath/application' };
const basepathMiddleware = createBasepathSupportMiddleware(basepath);
await basepathMiddleware(ctx, next);

expect(ctx.path).toEqual('/application');
});

it('should set new ctx.path to "/" if its the same as basepath', async () => {
const basepath = '/basepath';
const ctx = { path: '/basepath' };
const basepathMiddleware = createBasepathSupportMiddleware(basepath);
await basepathMiddleware(ctx, next);

expect(ctx.path).toEqual('/');
});

it('should return an undefined when basepath is different than "/" and no new path is set', async () => {
const basepath = '/';
const ctx = { path: '/' };
const basepathMiddleware = createBasepathSupportMiddleware(basepath);
const result = await basepathMiddleware(ctx, next);

expect(result).toEqual(undefined);
});
});

0 comments on commit 416895b

Please sign in to comment.