From 959a2eb46f9c6303f6afdf8ab6b5640295a91f4a Mon Sep 17 00:00:00 2001 From: Kata Date: Mon, 25 Feb 2019 00:47:08 -0600 Subject: [PATCH] backend: router hot-reloading --- Roleypoly.js | 40 +++++++++++++++++++++++++++++++++------- api/index.js | 8 ++++++-- index.js | 11 +++-------- package.json | 14 +++++++++++--- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/Roleypoly.js b/Roleypoly.js index bf067ac..61b8f71 100644 --- a/Roleypoly.js +++ b/Roleypoly.js @@ -3,16 +3,17 @@ const Sequelize = require('sequelize') const fetchModels = require('./models') const fetchApis = require('./api') const Next = require('next') - +const betterRouter = require('koa-better-router') class Roleypoly { - constructor (router, io, app) { - this.router = router + constructor (io, app) { this.io = io this.ctx = {} this.ctx.config = { - appUrl: process.env.APP_URL + appUrl: process.env.APP_URL, + dev: process.env.NODE_ENV !== 'production', + hotReload: process.env.NO_HOT_RELOAD !== '1' } this.ctx.io = io @@ -52,10 +53,11 @@ class Roleypoly { this.ctx.P = new (require('./services/presentation'))(this.ctx) } - async mountRoutes () { + async loadRoutes (forceClear = false) { await this.ctx.ui.prepare() - fetchApis(this.router, this.ctx) + this.router = betterRouter().loadMethods() + fetchApis(this.router, this.ctx, { forceClear }) // after routing, add the * for ui handler this.router.get('*', async ctx => { @@ -63,7 +65,31 @@ class Roleypoly { ctx.respond = false }) - this.__app.use(this.router.middleware()) + return this.router.middleware() + } + + async mountRoutes () { + let mw = await this.loadRoutes() + + if (this.ctx.config.dev && this.ctx.config.hotReload) { + // hot-reloading system + log.info('API hot-reloading is active.') + const chokidar = require('chokidar') + let hotMiddleware = mw + + this.__apiWatcher = chokidar.watch('api/**') + this.__apiWatcher.on('all', async (path) => { + log.info('reloading APIs...', path) + hotMiddleware = await this.loadRoutes(true) + }) + + // custom passthrough so we use a specially scoped middleware. + mw = (ctx, next) => { + return hotMiddleware(ctx, next) + } + } + + this.__app.use(mw) } } diff --git a/api/index.js b/api/index.js index ca717b0..797a74a 100644 --- a/api/index.js +++ b/api/index.js @@ -3,7 +3,7 @@ const glob = require('glob') const PROD = process.env.NODE_ENV === 'production' -module.exports = async (router, ctx) => { +module.exports = async (router, ctx, { forceClear = false } = {}) => { const apis = glob.sync(`./api/**/!(index).js`) log.debug('found apis', apis) @@ -14,7 +14,11 @@ module.exports = async (router, ctx) => { } log.debug(`mounting ${a}`) try { - require(a.replace('api/', ''))(router, ctx) + const pathname = a.replace('api/', '') + if (forceClear) { + delete require.cache[require.resolve(pathname)] + } + require(pathname)(router, ctx) } catch (e) { log.error(`couldn't mount ${a}`, e) } diff --git a/index.js b/index.js index 676bef8..37e1228 100644 --- a/index.js +++ b/index.js @@ -1,13 +1,10 @@ -require('dotenv').config({silent: true}) +require('dotenv').config({ silent: true }) const log = new (require('./logger'))('index') const http = require('http') const Koa = require('koa') const app = new Koa() const _io = require('socket.io') -const fs = require('fs') -const path = require('path') -const router = require('koa-better-router')().loadMethods() const Roleypoly = require('./Roleypoly') const ksuid = require('ksuid') @@ -20,7 +17,7 @@ Array.prototype.areduce = async function (predicate, acc = []) { // eslint-disab return acc } -Array.prototype.filterNot = Array.prototype.filterNot || function (predicate) { +Array.prototype.filterNot = Array.prototype.filterNot || function (predicate) { // eslint-disable-line return this.filter(v => !predicate(v)) } @@ -28,9 +25,7 @@ Array.prototype.filterNot = Array.prototype.filterNot || function (predicate) { const server = http.createServer(app.callback()) const io = _io(server, { transports: ['websocket'], path: '/api/socket.io' }) - - -const M = new Roleypoly(router, io, app) // eslint-disable-line no-unused-vars +const M = new Roleypoly(io, app) // eslint-disable-line no-unused-vars app.keys = [ process.env.APP_KEY ] diff --git a/package.json b/package.json index 5192063..5f7c6e0 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,13 @@ "version": "2.0.0", "main": "index.js", "scripts": { - "start": "pm2 start index.js", - "dev": "pm2 start index.js --watch", - "build": "next build" + "start": "node index.js", + "dev": "node index.js", + "build": "next build ui" }, "dependencies": { "@discordjs/uws": "^11.149.1", + "@primer/components": "^10.0.1", "chalk": "^2.4.2", "discord.js": "^11.4.2", "dotenv": "^6.2.0", @@ -32,5 +33,12 @@ "socket.io": "^2.2.0", "superagent": "^4.1.0", "uuid": "^3.3.2" + }, + "devDependencies": { + "babel-eslint": "^10.0.1", + "babel-plugin-transform-flow-strip-types": "^6.22.0", + "chokidar": "^2.1.2", + "flow-type": "^1.0.1", + "standard": "^12.0.1" } }