UNPKG

2.65 kBJavaScriptView Raw
1const chalk = require('chalk')
2const path = require('path')
3const { NODE_ENV } = process.env
4const isDevelopment = !NODE_ENV || NODE_ENV === 'development'
5const decache = require('decache')
6const { watch } = require('chokidar')
7
8function createReloadable (app, require) {
9 return (folderPath, options = {}) => {
10 if (!isDevelopment) {
11 throw new Error(
12 '[lib/reloadable.js] NODE_ENV must be set to "development" to use ' +
13 'reloadable.js'
14 )
15 }
16
17 const {
18 watchModules = [],
19 mountPoint = '/',
20 recursive = false
21 } = options
22
23 // On new request re-require app files
24 const onReload = (req, res, next) => {
25 const module = require(folderPath)
26
27 // Check if ES6 default export
28 if (module.default) {
29 module.default(req, res, next)
30 } else {
31 module(req, res, next)
32 }
33 }
34
35 const rootPath = path.resolve(folderPath)
36
37 const watchPaths = watchModules
38 .map(module => path.dirname(require.resolve(module)))
39 .concat([rootPath])
40
41 // Watch a subset of files for changes
42 watchPaths.forEach(folder => {
43 const watcher = watch(folder)
44
45 watcher.on('ready', () => {
46 watcher.on('change', file => console.log(`[@artsy/express-reloadable] File ${chalk.grey(file)} has changed.`))
47
48 watcher.on('all', () => {
49 Object.keys(require.cache).forEach(id => {
50 if (id.startsWith(rootPath) || id.startsWith(folder)) {
51 if (recursive) {
52 decache(id)
53 } else {
54 delete require.cache[id]
55 }
56 }
57 })
58 })
59 })
60 })
61
62 let currentResponse = null
63 let currentNext = null
64
65 app.use((req, res, next) => {
66 currentResponse = res
67 currentNext = next
68
69 res.on('finish', () => {
70 currentResponse = null
71 currentNext = null
72 })
73
74 next()
75 })
76
77 /**
78 * In case of an uncaught exception show it to the user and proceed, rather
79 * than exiting the process.
80 */
81 process.on('uncaughtException', (error) => {
82 if (currentResponse) {
83 currentNext(error)
84 currentResponse = null
85 currentNext = null
86 } else {
87 console.log(error)
88 }
89 })
90
91 app.use(mountPoint, (req, res, next) => {
92 try {
93 onReload(req, res, next)
94 } catch (error) {
95 console.error(error)
96 next(error)
97 }
98 })
99
100 console.log(`\n\n[@artsy/express-reloadable] Mounting: \n${chalk.grey(watchPaths.join('\n'))}\n`)
101 return onReload
102 }
103}
104
105module.exports = {
106 isDevelopment,
107 createReloadable
108}