-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdevServer.js
More file actions
179 lines (157 loc) · 5.78 KB
/
Copy pathdevServer.js
File metadata and controls
179 lines (157 loc) · 5.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/* eslint-disable no-console,global-require */
const path = require('path');
const notifier = require('node-notifier');
const chokidar = require('chokidar');
const webpack = require('webpack');
const express = require('express');
const createWebpackMiddleware = require('webpack-dev-middleware');
const createWebpackHotMiddleware = require('webpack-hot-middleware');
const clientBundleConfig = require('./webpack.client.config')({ mode: 'development' });
const serverBundleConfig = require('./webpack.server.config')({ mode: 'development' });
function createNotification(subject, msg) {
const title = `${subject.toUpperCase()} DEVSERVER`;
console.log(`==> ${title} -> ${msg}`);
notifier.notify({
title,
message: msg,
});
}
/**
* -----------------------------------------------------------------------------
* Server Bundle Server
*/
let serverListener = null;
let lastConnectionKey = 0;
const connectionMap = {};
const serverBundleCompiler = webpack(serverBundleConfig);
const serverBundlePath = path.resolve(
serverBundleConfig.output.path, `${Object.keys(serverBundleConfig.entry)[0]}.js`
);
serverBundleCompiler.plugin('done', (stats) => {
if (stats.hasErrors()) {
createNotification('server', '😵 Build failed, check console for error');
console.log(stats.toString());
return;
}
createNotification('server', '✅ Bundle built');
// Make sure our newly built server and client bundles aren't in the module cache.
Object.keys(require.cache).forEach(modulePath => {
if (~modulePath.indexOf(serverBundleConfig.output.path)) {
delete require.cache[modulePath];
}
});
// We wrap the require and execution of our server bundle just in case it
// contains invalid code that throws an exception. Rather than taking down
// the devServer this gives us the opportunity to fix the error and have
// the server bundle start again automatically.
try {
// The server bundle will automatically start the web server just by
// requiring it.
serverListener = require(serverBundlePath).default;
// Track all connections to our server so that we can close them when needed.
serverListener.on('connection', connection => {
// Generate a new key to represent the connection
const connectionKey = ++lastConnectionKey;
// Add the connection to our map.
connectionMap[connectionKey] = connection;
// Remove the connection from our map when it closes.
connection.on('close', () => {
delete connectionMap[connectionKey];
});
});
createNotification('server', '✅ Running');
} catch (err) {
createNotification('server', '😵 Bundle invalid, check console for error');
console.log(err);
}
});
function serverListenerDispose(callback) {
// First we destroy any connections.
Object.keys(connectionMap).forEach((connectionKey) => {
connectionMap[connectionKey].destroy();
});
// Then we close the server.
if (serverListener) {
serverListener.close(() => {
if (callback) callback();
});
} else {
callback();
}
}
function compileServerBundle() {
// Shut down any existing running server if necessary before starting the
// compile, else just compile.
if (serverListener) {
serverListenerDispose(() => serverBundleCompiler.run(() => undefined));
} else {
serverBundleCompiler.run(() => undefined);
}
}
// Now we will configure `chokidar` to watch our server specific source folder.
// Any changes will cause a rebuild of the server bundle.
const watcher = chokidar.watch([path.resolve(__dirname, './src/server')]);
watcher.on('ready', () => {
watcher
.on('add', compileServerBundle)
.on('addDir', compileServerBundle)
.on('change', compileServerBundle)
.on('unlink', compileServerBundle)
.on('unlinkDir', compileServerBundle);
});
/**
* -----------------------------------------------------------------------------
* Client Bundle Server
*/
const clientBundleCompiler = webpack(clientBundleConfig);
clientBundleCompiler.plugin('done', (stats) => {
if (stats.hasErrors()) {
createNotification('client', '😵 Build failed, check console for error');
console.log(stats.toString());
} else {
createNotification('client', '✅ Built');
// We will also build the server bundle any time the client bundle has
// been built. We do this as the server bundle depends on the client bundle,
// so if the client switched from an invalid to a valid state we want our
// server bundle to restart too.
compileServerBundle();
}
});
const clientBundleServer = express();
const webpackDevMiddleware = createWebpackMiddleware(clientBundleCompiler, {
quiet: true,
noInfo: true,
headers: {
'Access-Control-Allow-Origin': '*',
},
// The path at which the client bundles are served from. Note: in this
// case as we are running a seperate dev server the public path should
// be absolute, i.e. including the "http://..."
publicPath: clientBundleConfig.output.publicPath,
});
clientBundleServer.use(webpackDevMiddleware);
clientBundleServer.use(createWebpackHotMiddleware(clientBundleCompiler));
const clientListener = clientBundleServer.listen(process.env.CLIENT_DEVSERVER_PORT);
/**
* -----------------------------------------------------------------------------
* Disposal
*/
function gracefulShutdown() {
// Shut down any existing running servers before quiting.
if (serverListener && clientListener) {
serverListenerDispose(() => {
clientListener.close(() => process.exit());
});
} else {
if (serverListener) {
serverListenerDispose(() => process.exit());
}
if (clientListener) {
clientListener.close(() => process.exit());
}
}
}
// listen for TERM signal .e.g. kill
process.on('SIGTERM', gracefulShutdown);
// listen for INT signal e.g. Ctrl-C
process.on('SIGINT', gracefulShutdown);