1 | const chalk = require('chalk')
|
2 | const path = require('path')
|
3 | const { NODE_ENV } = process.env
|
4 | const isDevelopment = !NODE_ENV || NODE_ENV === 'development'
|
5 | const decache = require('decache')
|
6 | const { watch } = require('chokidar')
|
7 |
|
8 | function 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 |
|
24 | const onReload = (req, res, next) => {
|
25 | const module = require(folderPath)
|
26 |
|
27 |
|
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 |
|
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 |
|
79 |
|
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 |
|
105 | module.exports = {
|
106 | isDevelopment,
|
107 | createReloadable
|
108 | }
|