Skip to content

Commit 6141018

Browse files
knagaitsevevilebottnawi
authored andcommitted
feat(client): add clientMode option (#1977)
1 parent 8bbd08c commit 6141018

File tree

10 files changed

+255
-7
lines changed

10 files changed

+255
-7
lines changed

lib/Server.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ class Server {
5454

5555
validateOptions(schema, options, 'webpack Dev Server');
5656

57-
updateCompiler(compiler, options);
58-
5957
this.compiler = compiler;
6058
this.options = options;
6159

@@ -67,13 +65,24 @@ class Server {
6765

6866
this.log = _log || createLogger(options);
6967

68+
// set serverMode default
7069
if (this.options.serverMode === undefined) {
7170
this.options.serverMode = 'sockjs';
7271
} else {
7372
this.log.warn(
7473
'serverMode is an experimental option, meaning its usage could potentially change without warning'
7574
);
7675
}
76+
// set clientMode default
77+
if (this.options.clientMode === undefined) {
78+
this.options.clientMode = 'sockjs';
79+
} else {
80+
this.log.warn(
81+
'clientMode is an experimental option, meaning its usage could potentially change without warning'
82+
);
83+
}
84+
85+
updateCompiler(this.compiler, this.options);
7786

7887
// this.SocketServerImplementation is a class, so it must be instantiated before use
7988
this.socketServerImplementation = getSocketServerImplementation(

lib/options.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
"warning"
4949
]
5050
},
51+
"clientMode": {
52+
"type": "string"
53+
},
5154
"compress": {
5255
"type": "boolean"
5356
},
@@ -390,6 +393,7 @@
390393
"ca": "should be {String|Buffer}",
391394
"cert": "should be {String|Buffer}",
392395
"clientLogLevel": "should be {String} and equal to one of the allowed values\n\n [ 'none', 'silent', 'info', 'debug', 'trace', 'error', 'warning', 'warn' ]\n\n (https://webpack.js.org/configuration/dev-server/#devserverclientloglevel)",
396+
"clientMode": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserverclientmode)",
393397
"compress": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devservercompress)",
394398
"contentBase": "should be {Number|String|Array} (https://webpack.js.org/configuration/dev-server/#devservercontentbase)",
395399
"disableHostCheck": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverdisablehostcheck)",
@@ -430,7 +434,7 @@
430434
"reporter": "should be {Function} (https://github.com/webpack/webpack-dev-middleware#reporter)",
431435
"requestCert": "should be {Boolean}",
432436
"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-)",
437+
"serverMode": "should be {String|Function} (https://webpack.js.org/configuration/dev-server/#devserverservermode)",
434438
"serverSideRender": "should be {Boolean} (https://github.com/webpack/webpack-dev-middleware#serversiderender)",
435439
"setup": "should be {Function} (https://webpack.js.org/configuration/dev-server/#devserversetup)",
436440
"sockHost": "should be {String|Null} (https://webpack.js.org/configuration/dev-server/#devserversockhost)",

lib/utils/getSocketClientPath.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
3+
function getSocketClientPath(options) {
4+
let ClientImplementation;
5+
let clientImplFound = true;
6+
switch (typeof options.clientMode) {
7+
case 'string':
8+
// could be 'sockjs', in the future 'ws', or a path that should be required
9+
if (options.clientMode === 'sockjs') {
10+
// eslint-disable-next-line global-require
11+
ClientImplementation = require('../../client/clients/SockJSClient');
12+
} else {
13+
try {
14+
// eslint-disable-next-line global-require, import/no-dynamic-require
15+
ClientImplementation = require(options.clientMode);
16+
} catch (e) {
17+
clientImplFound = false;
18+
}
19+
}
20+
break;
21+
default:
22+
clientImplFound = false;
23+
}
24+
25+
if (!clientImplFound) {
26+
throw new Error(
27+
"clientMode must be a string denoting a default implementation (e.g. 'sockjs') or a full path to " +
28+
'a JS file which exports a class extending BaseClient (webpack-dev-server/client-src/clients/BaseClient) ' +
29+
'via require.resolve(...)'
30+
);
31+
}
32+
33+
return ClientImplementation.getClientPath(options);
34+
}
35+
36+
module.exports = getSocketClientPath;

lib/utils/updateCompiler.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77
const webpack = require('webpack');
88
const addEntries = require('./addEntries');
9+
const getSocketClientPath = require('./getSocketClientPath');
910

1011
function updateCompiler(compiler, options) {
1112
if (options.inline !== false) {
@@ -50,10 +51,7 @@ function updateCompiler(compiler, options) {
5051
compiler.hooks.entryOption.call(config.context, config.entry);
5152

5253
const providePlugin = new webpack.ProvidePlugin({
53-
// SockJSClient.getClientPath(options)
54-
__webpack_dev_server_client__: require.resolve(
55-
'../../client/clients/SockJSClient.js'
56-
),
54+
__webpack_dev_server_client__: getSocketClientPath(options),
5755
});
5856
providePlugin.apply(compiler);
5957
});

test/e2e/ClientMode.test.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
'use strict';
2+
3+
const testServer = require('../helpers/test-server');
4+
const config = require('../fixtures/client-config/webpack.config');
5+
const runBrowser = require('../helpers/run-browser');
6+
const port = require('../ports-map').ClientMode;
7+
8+
describe('clientMode', () => {
9+
describe('sockjs', () => {
10+
beforeAll((done) => {
11+
const options = {
12+
port,
13+
host: '0.0.0.0',
14+
inline: true,
15+
clientMode: 'sockjs',
16+
};
17+
testServer.startAwaitingCompilation(config, options, done);
18+
});
19+
20+
describe('on browser client', () => {
21+
it('logs as usual', (done) => {
22+
runBrowser().then(({ page, browser }) => {
23+
const res = [];
24+
page.goto(`http://localhost:${port}/main`);
25+
page.on('console', ({ _text }) => {
26+
res.push(_text);
27+
});
28+
29+
setTimeout(() => {
30+
testServer.close(() => {
31+
// make sure the client gets the close message
32+
setTimeout(() => {
33+
browser.close().then(() => {
34+
expect(res).toMatchSnapshot();
35+
done();
36+
});
37+
}, 1000);
38+
});
39+
}, 3000);
40+
});
41+
});
42+
});
43+
});
44+
45+
describe('custom client', () => {
46+
beforeAll((done) => {
47+
const options = {
48+
port,
49+
host: '0.0.0.0',
50+
inline: true,
51+
clientMode: require.resolve(
52+
'../fixtures/custom-client/CustomSockJSClient'
53+
),
54+
};
55+
testServer.startAwaitingCompilation(config, options, done);
56+
});
57+
58+
describe('on browser client', () => {
59+
it('logs additional messages to console', (done) => {
60+
runBrowser().then(({ page, browser }) => {
61+
const res = [];
62+
page.goto(`http://localhost:${port}/main`);
63+
page.on('console', ({ _text }) => {
64+
res.push(_text);
65+
});
66+
67+
setTimeout(() => {
68+
testServer.close(() => {
69+
// make sure the client gets the close message
70+
setTimeout(() => {
71+
browser.close().then(() => {
72+
expect(res).toMatchSnapshot();
73+
done();
74+
});
75+
}, 1000);
76+
});
77+
}, 3000);
78+
});
79+
});
80+
});
81+
});
82+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`clientMode custom client on browser client logs additional messages to console 1`] = `
4+
Array [
5+
"Hey.",
6+
"open",
7+
"liveReload",
8+
"[WDS] Live Reloading enabled.",
9+
"hash",
10+
"ok",
11+
"close",
12+
"[WDS] Disconnected!",
13+
]
14+
`;
15+
16+
exports[`clientMode sockjs on browser client logs as usual 1`] = `
17+
Array [
18+
"Hey.",
19+
"[WDS] Live Reloading enabled.",
20+
"[WDS] Disconnected!",
21+
]
22+
`;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
3+
/* eslint-disable
4+
no-unused-vars
5+
*/
6+
const SockJS = require('sockjs-client/dist/sockjs');
7+
const BaseClient = require('../../../client/clients/BaseClient');
8+
9+
module.exports = class SockJSClient extends BaseClient {
10+
constructor(url) {
11+
super();
12+
this.sock = new SockJS(url);
13+
}
14+
15+
static getClientPath(options) {
16+
return require.resolve('./CustomSockJSClient');
17+
}
18+
19+
onOpen(f) {
20+
this.sock.onopen = () => {
21+
console.log('open');
22+
f();
23+
};
24+
}
25+
26+
onClose(f) {
27+
this.sock.onclose = () => {
28+
console.log('close');
29+
f();
30+
};
31+
}
32+
33+
// call f with the message string as the first argument
34+
onMessage(f) {
35+
this.sock.onmessage = (e) => {
36+
const obj = JSON.parse(e.data);
37+
console.log(obj.type);
38+
f(e.data);
39+
};
40+
}
41+
};

test/options.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ describe('options', () => {
137137
],
138138
failure: ['whoops!'],
139139
},
140+
clientMode: {
141+
success: ['sockjs', require.resolve('../client/clients/SockJSClient')],
142+
failure: [false],
143+
},
140144
compress: {
141145
success: [true],
142146
failure: [''],

test/ports-map.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
// test-file-name: the number of ports
4+
// important: new port mappings must be added to the bottom of this list
45
const portsList = {
56
cli: 2,
67
sockJSClient: 1,
@@ -37,6 +38,7 @@ const portsList = {
3738
ProvidePlugin: 1,
3839
WebsocketClient: 1,
3940
WebsocketServer: 1,
41+
ClientMode: 1,
4042
};
4143

4244
let startPort = 8079;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const getSocketClientPath = require('../../../lib/utils/getSocketClientPath');
4+
// 'npm run prepare' must be done for this test to pass
5+
const sockjsClientPath = require.resolve(
6+
'../../../client/clients/SockJSClient'
7+
);
8+
const baseClientPath = require.resolve('../../../client/clients/BaseClient');
9+
10+
describe('getSocketClientPath', () => {
11+
it("should work with clientMode: 'sockjs'", () => {
12+
let result;
13+
14+
expect(() => {
15+
result = getSocketClientPath({
16+
clientMode: 'sockjs',
17+
});
18+
}).not.toThrow();
19+
20+
expect(result).toEqual(sockjsClientPath);
21+
});
22+
23+
it('should work with clientMode: SockJSClient full path', () => {
24+
let result;
25+
26+
expect(() => {
27+
result = getSocketClientPath({
28+
clientMode: sockjsClientPath,
29+
});
30+
}).not.toThrow();
31+
32+
expect(result).toEqual(sockjsClientPath);
33+
});
34+
35+
it('should throw with clientMode: bad path', () => {
36+
expect(() => {
37+
getSocketClientPath({
38+
clientMode: '/bad/path/to/implementation',
39+
});
40+
}).toThrow(/clientMode must be a string/);
41+
});
42+
43+
it('should throw with clientMode: unimplemented client', () => {
44+
expect(() => {
45+
getSocketClientPath({
46+
clientMode: baseClientPath,
47+
});
48+
}).toThrow('Client needs implementation');
49+
});
50+
});

0 commit comments

Comments
 (0)