Skip to content

Commit 44a8cde

Browse files
knagaitsevevilebottnawi
authored andcommitted
feat(server): add serverMode option (#1937)
1 parent def98d8 commit 44a8cde

6 files changed

+260
-2
lines changed

lib/Server.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ const status = require('./utils/status');
3030
const createDomain = require('./utils/createDomain');
3131
const runBonjour = require('./utils/runBonjour');
3232
const routes = require('./utils/routes');
33+
const getSocketServerImplementation = require('./utils/getSocketServerImplementation');
3334
const schema = require('./options.json');
34-
const SockJSServer = require('./servers/SockJSServer');
3535

3636
// Workaround for node ^8.6.0, ^9.0.0
3737
// DEFAULT_ECDH_CURVE is default to prime256v1 in these version
@@ -67,6 +67,19 @@ class Server {
6767

6868
this.log = _log || createLogger(options);
6969

70+
if (this.options.serverMode === undefined) {
71+
this.options.serverMode = 'sockjs';
72+
} else {
73+
this.log.warn(
74+
'serverMode is an experimental option, meaning its usage could potentially change without warning'
75+
);
76+
}
77+
78+
// this.SocketServerImplementation is a class, so it must be instantiated before use
79+
this.socketServerImplementation = getSocketServerImplementation(
80+
this.options
81+
);
82+
7083
this.originalStats =
7184
this.options.stats && Object.keys(this.options.stats).length
7285
? this.options.stats
@@ -655,7 +668,8 @@ class Server {
655668
}
656669

657670
createSocketServer() {
658-
this.socketServer = new SockJSServer(this);
671+
const SocketServerImplementation = this.socketServerImplementation;
672+
this.socketServer = new SocketServerImplementation(this);
659673

660674
this.socketServer.onConnection((connection) => {
661675
if (!connection) {

lib/options.json

+11
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,16 @@
297297
"serveIndex": {
298298
"type": "boolean"
299299
},
300+
"serverMode": {
301+
"anyOf": [
302+
{
303+
"type": "string"
304+
},
305+
{
306+
"instanceof": "Function"
307+
}
308+
]
309+
},
300310
"serverSideRender": {
301311
"type": "boolean"
302312
},
@@ -420,6 +430,7 @@
420430
"reporter": "should be {Function} (https://github.com/webpack/webpack-dev-middleware#reporter)",
421431
"requestCert": "should be {Boolean}",
422432
"serveIndex": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverserveindex)",
433+
"serverMode": "should be {String|Function} (https://webpack.js.org/configuration/dev-server/#devserverservermode-)",
423434
"serverSideRender": "should be {Boolean} (https://github.com/webpack/webpack-dev-middleware#serversiderender)",
424435
"setup": "should be {Function} (https://webpack.js.org/configuration/dev-server/#devserversetup)",
425436
"sockHost": "should be {String|Null} (https://webpack.js.org/configuration/dev-server/#devserversockhost)",
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
3+
function getSocketServerImplementation(options) {
4+
let ServerImplementation;
5+
let serverImplFound = true;
6+
switch (typeof options.serverMode) {
7+
case 'string':
8+
// could be 'sockjs', in the future 'ws', or a path that should be required
9+
if (options.serverMode === 'sockjs') {
10+
// eslint-disable-next-line global-require
11+
ServerImplementation = require('../servers/SockJSServer');
12+
} else {
13+
try {
14+
// eslint-disable-next-line global-require, import/no-dynamic-require
15+
ServerImplementation = require(options.serverMode);
16+
} catch (e) {
17+
serverImplFound = false;
18+
}
19+
}
20+
break;
21+
case 'function':
22+
// potentially do more checks here to confirm that the user implemented this properlly
23+
// since errors could be difficult to understand
24+
ServerImplementation = options.serverMode;
25+
break;
26+
default:
27+
serverImplFound = false;
28+
}
29+
30+
if (!serverImplFound) {
31+
throw new Error(
32+
"serverMode must be a string denoting a default implementation (e.g. 'sockjs'), a full path to " +
33+
'a JS file which exports a class extending BaseServer (webpack-dev-server/lib/servers/BaseServer) ' +
34+
'via require.resolve(...), or the class itself which extends BaseServer'
35+
);
36+
}
37+
38+
return ServerImplementation;
39+
}
40+
41+
module.exports = getSocketServerImplementation;
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const getSocketServerImplementation = require('../lib/utils/getSocketServerImplementation');
4+
const SockJSServer = require('../lib/servers/SockJSServer');
5+
6+
describe('getSocketServerImplementation', () => {
7+
it("should work with serverMode: 'sockjs'", () => {
8+
let result;
9+
10+
expect(() => {
11+
result = getSocketServerImplementation({
12+
serverMode: 'sockjs',
13+
});
14+
}).not.toThrow();
15+
16+
expect(result).toEqual(SockJSServer);
17+
});
18+
19+
it('should work with serverMode: SockJSServer class', () => {
20+
let result;
21+
22+
expect(() => {
23+
result = getSocketServerImplementation({
24+
serverMode: SockJSServer,
25+
});
26+
}).not.toThrow();
27+
28+
expect(result).toEqual(SockJSServer);
29+
});
30+
31+
it('should work with serverMode: SockJSServer full path', () => {
32+
let result;
33+
34+
expect(() => {
35+
result = getSocketServerImplementation({
36+
serverMode: require.resolve('../lib/servers/SockJSServer'),
37+
});
38+
}).not.toThrow();
39+
40+
expect(result).toEqual(SockJSServer);
41+
});
42+
43+
it('should throw with serverMode: bad path', () => {
44+
expect(() => {
45+
getSocketServerImplementation({
46+
serverMode: '/bad/path/to/implementation',
47+
});
48+
}).toThrow(/serverMode must be a string/);
49+
});
50+
});

test/ServerMode.test.js

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
'use strict';
2+
3+
/* eslint-disable
4+
class-methods-use-this
5+
*/
6+
const request = require('supertest');
7+
const sockjs = require('sockjs');
8+
const SockJSServer = require('../lib/servers/SockJSServer');
9+
const config = require('./fixtures/simple-config/webpack.config');
10+
const testServer = require('./helpers/test-server');
11+
const BaseServer = require('./../lib/servers/BaseServer');
12+
13+
describe('serverMode', () => {
14+
let server;
15+
let req;
16+
17+
afterEach((done) => {
18+
testServer.close(done);
19+
req = null;
20+
server = null;
21+
});
22+
describe("supplying 'sockjs' as a string", () => {
23+
beforeEach((done) => {
24+
server = testServer.start(
25+
config,
26+
{
27+
serverMode: 'sockjs',
28+
},
29+
done
30+
);
31+
req = request('http://localhost:8080');
32+
});
33+
34+
it('sockjs path responds with a 200', (done) => {
35+
req.get('/sockjs-node').expect(200, done);
36+
});
37+
});
38+
39+
describe('supplying path to sockjs implementation', () => {
40+
beforeEach((done) => {
41+
server = testServer.start(
42+
config,
43+
{
44+
serverMode: require.resolve('../lib/servers/SockJSServer'),
45+
},
46+
done
47+
);
48+
req = request('http://localhost:8080');
49+
});
50+
51+
it('sockjs path responds with a 200', (done) => {
52+
req.get('/sockjs-node').expect(200, done);
53+
});
54+
});
55+
56+
describe('supplying sockjs implementation class', () => {
57+
beforeEach((done) => {
58+
server = testServer.start(
59+
config,
60+
{
61+
serverMode: SockJSServer,
62+
},
63+
done
64+
);
65+
req = request('http://localhost:8080');
66+
});
67+
68+
it('sockjs path responds with a 200', (done) => {
69+
req.get('/sockjs-node').expect(200, done);
70+
});
71+
});
72+
73+
describe('custom sockjs implementation', () => {
74+
it('uses supplied server implementation', (done) => {
75+
server = testServer.start(
76+
config,
77+
{
78+
sockPath: '/foo/test/bar/',
79+
serverMode: class MySockJSServer extends BaseServer {
80+
constructor(serv) {
81+
super(serv);
82+
this.socket = sockjs.createServer({
83+
// Use provided up-to-date sockjs-client
84+
sockjs_url: '/__webpack_dev_server__/sockjs.bundle.js',
85+
// Limit useless logs
86+
log: (severity, line) => {
87+
if (severity === 'error') {
88+
this.server.log.error(line);
89+
} else {
90+
this.server.log.debug(line);
91+
}
92+
},
93+
});
94+
95+
this.socket.installHandlers(this.server.listeningApp, {
96+
prefix: this.server.sockPath,
97+
});
98+
99+
expect(server.options.sockPath).toEqual('/foo/test/bar/');
100+
}
101+
102+
send(connection, message) {
103+
connection.write(message);
104+
}
105+
106+
close(connection) {
107+
connection.close();
108+
}
109+
110+
onConnection(f) {
111+
this.socket.on('connection', f);
112+
}
113+
},
114+
},
115+
done
116+
);
117+
});
118+
});
119+
120+
describe('supplying nonexistent path', () => {
121+
it('should throw an error', () => {
122+
expect(() => {
123+
server = testServer.start(
124+
config,
125+
{
126+
serverMode: '/bad/path/to/implementation',
127+
},
128+
() => {}
129+
);
130+
}).toThrow(/serverMode must be a string/);
131+
});
132+
});
133+
});

test/options.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const webpack = require('webpack');
66
const { createFsFromVolume, Volume } = require('memfs');
77
const Server = require('../lib/Server');
88
const options = require('../lib/options.json');
9+
const SockJSServer = require('../lib/servers/SockJSServer');
910
const config = require('./fixtures/simple-config/webpack.config');
1011

1112
describe('options', () => {
@@ -340,6 +341,14 @@ describe('options', () => {
340341
success: [true],
341342
failure: [''],
342343
},
344+
serverMode: {
345+
success: [
346+
'sockjs',
347+
require.resolve('../lib/servers/SockJSServer'),
348+
SockJSServer,
349+
],
350+
failure: ['', false],
351+
},
343352
serverSideRender: {
344353
success: [true],
345354
failure: [''],

0 commit comments

Comments
 (0)