@artsy/express-reloadable
Version:
Development tool that enables hot-swapping Express server code without a restart
101 lines (83 loc) • 2.56 kB
JavaScript
const path = require('path')
const { NODE_ENV } = process.env
const isDevelopment = !NODE_ENV || NODE_ENV === 'development'
function createReloadable (app, require) {
return (folderPath, options = {}) => {
if (isDevelopment) {
const {
watchModules = [],
mountPoint = '/'
} = options
// On new request re-require app files
const onReload = (req, res, next) => {
const module = require(folderPath)
// Check if ES6 default export
if (module.default) {
module.default(req, res, next)
} else {
module(req, res, next)
}
}
const rootPath = path.resolve(folderPath)
const watchPaths = watchModules
.map(module => path.dirname(require.resolve(module)))
.concat([rootPath])
// Watch a subset of files for changes
watchPaths.forEach(folder => {
const watcher = require('chokidar').watch(folder)
watcher.on('ready', () => {
watcher.on('change', file => console.log(`[@artsy/express-reloadable] File ${file} has changed.`))
watcher.on('all', () => {
Object.keys(require.cache).forEach(id => {
if (id.startsWith(rootPath)) {
delete require.cache[id]
}
if (id.startsWith(folder)) {
delete require.cache[id]
}
})
})
})
})
let currentResponse = null
let currentNext = null
app.use((req, res, next) => {
currentResponse = res
currentNext = next
res.on('finish', () => {
currentResponse = null
currentNext = null
})
next()
})
/**
* In case of an uncaught exception show it to the user and proceed, rather
* than exiting the process.
*/
process.on('uncaughtException', (error) => {
if (currentResponse) {
currentNext(error)
currentResponse = null
currentNext = null
} else {
process.abort()
}
})
app.use(mountPoint, (req, res, next) => {
onReload(req, res, next)
})
console.log(`[@artsy/express-reloadable] Mounting: ${watchPaths.join(', ')}`)
return onReload
// Node env not 'development', exit
} else {
throw new Error(
'[lib/reloadable.js] NODE_ENV must be set to "development" to use ' +
'reloadable.js'
)
}
}
}
module.exports = {
isDevelopment,
createReloadable
}