Skip to content

Commit 22f18eb

Browse files
fix: allow to use 80 port for dev server (#3487)
1 parent b559738 commit 22f18eb

20 files changed

+265
-854
lines changed

client-src/index.js

+6-33
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,20 @@ const sendMessage = require('./utils/sendMessage');
1212
const reloadApp = require('./utils/reloadApp');
1313
const createSocketURL = require('./utils/createSocketURL');
1414

15-
const status = {
16-
isUnloading: false,
17-
currentHash: '',
18-
};
19-
const defaultOptions = {
15+
const status = { isUnloading: false, currentHash: '' };
16+
const options = {
2017
hot: false,
21-
hotReload: true,
2218
liveReload: false,
2319
initial: true,
2420
progress: false,
2521
overlay: false,
2622
};
2723
const parsedResourceQuery = parseURL(__resourceQuery);
28-
const options = defaultOptions;
29-
30-
// Handle Node.js legacy format and `new URL()`
31-
if (parsedResourceQuery.query) {
32-
Object.assign(options, parsedResourceQuery.query);
33-
} else if (parsedResourceQuery.searchParams) {
34-
const paramsToObject = (entries) => {
35-
const result = {};
36-
37-
for (const [key, value] of entries) {
38-
result[key] = value;
39-
}
4024

41-
return result;
42-
};
43-
44-
Object.assign(
45-
options,
46-
paramsToObject(parsedResourceQuery.searchParams.entries())
47-
);
25+
if (parsedResourceQuery.logging) {
26+
options.logging = parsedResourceQuery.logging;
4827
}
4928

50-
const socketURL = createSocketURL(parsedResourceQuery);
51-
5229
function setAllLogLevel(level) {
5330
// This is needed because the HMR logger operate separately from dev server logger
5431
webpackHotLog.setLogLevel(
@@ -65,12 +42,6 @@ self.addEventListener('beforeunload', () => {
6542
status.isUnloading = true;
6643
});
6744

68-
if (typeof window !== 'undefined') {
69-
const qs = window.location.search.toLowerCase();
70-
71-
options.hotReload = qs.indexOf('hotreload=false') === -1;
72-
}
73-
7445
const onSocketMessage = {
7546
hot() {
7647
options.hot = true;
@@ -220,4 +191,6 @@ const onSocketMessage = {
220191
},
221192
};
222193

194+
const socketURL = createSocketURL(parsedResourceQuery);
195+
223196
socket(socketURL, onSocketMessage);

client-src/utils/createSocketURL.js

+22-22
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ const url = require('url');
55
// We handle legacy API that is Node.js specific, and a newer API that implements the same WHATWG URL Standard used by web browsers
66
// Please look at https://nodejs.org/api/url.html#url_url_strings_and_url_objects
77
function createSocketURL(parsedURL) {
8-
let { auth, hostname, protocol, port } = parsedURL;
8+
let { hostname } = parsedURL;
99

1010
// Node.js module parses it as `::`
1111
// `new URL(urlString, [baseURLstring])` parses it as '[::]'
1212
const isInAddrAny =
1313
hostname === '0.0.0.0' || hostname === '::' || hostname === '[::]';
1414

15-
// check ipv4 and ipv6 `all hostname`
1615
// why do we need this check?
1716
// hostname n/a for file protocol (example, when using electron, ionic)
1817
// see: https://github.com/webpack/webpack-dev-server/pull/384
@@ -24,54 +23,55 @@ function createSocketURL(parsedURL) {
2423
hostname = self.location.hostname;
2524
}
2625

27-
if (protocol === 'auto:') {
28-
protocol = self.location.protocol;
29-
}
26+
let socketURLProtocol = parsedURL.protocol || 'ws:';
3027

31-
// `hostname` can be empty when the script path is relative. In that case, specifying a protocol would result in an invalid URL.
3228
// When https is used in the app, secure web sockets are always necessary because the browser doesn't accept non-secure web sockets.
33-
if (hostname && isInAddrAny && self.location.protocol === 'https:') {
34-
protocol = self.location.protocol;
29+
if (
30+
socketURLProtocol === 'auto:' ||
31+
(hostname && isInAddrAny && self.location.protocol === 'https:')
32+
) {
33+
socketURLProtocol = self.location.protocol;
3534
}
3635

37-
const socketURLProtocol = protocol.replace(
36+
socketURLProtocol = socketURLProtocol.replace(
3837
/^(?:http|.+-extension|file)/i,
3938
'ws'
4039
);
4140

41+
let socketURLAuth = '';
42+
4243
// `new URL(urlString, [baseURLstring])` doesn't have `auth` property
4344
// Parse authentication credentials in case we need them
4445
if (parsedURL.username) {
45-
auth = parsedURL.username;
46+
socketURLAuth = parsedURL.username;
4647

4748
// Since HTTP basic authentication does not allow empty username,
4849
// we only include password if the username is not empty.
4950
if (parsedURL.password) {
5051
// Result: <username>:<password>
51-
auth = auth.concat(':', parsedURL.password);
52+
socketURLAuth = socketURLAuth.concat(':', parsedURL.password);
5253
}
5354
}
5455

55-
const socketURLAuth = auth;
56-
5756
// In case the host is a raw IPv6 address, it can be enclosed in
5857
// the brackets as the brackets are needed in the final URL string.
5958
// Need to remove those as url.format blindly adds its own set of brackets
6059
// if the host string contains colons. That would lead to non-working
6160
// double brackets (e.g. [[::]]) host
6261
//
63-
// All of these sock url params are optionally passed in through resourceQuery,
62+
// All of these web socket url params are optionally passed in through resourceQuery,
6463
// so we need to fall back to the default if they are not provided
65-
const socketURLHostname = (hostname || 'localhost').replace(
66-
/^\[(.*)\]$/,
67-
'$1'
68-
);
64+
const socketURLHostname = (
65+
hostname ||
66+
self.location.hostname ||
67+
'localhost'
68+
).replace(/^\[(.*)\]$/, '$1');
6969

70-
if (!port || port === '0') {
71-
port = self.location.port;
72-
}
70+
let socketURLPort = parsedURL.port;
7371

74-
const socketURLPort = port;
72+
if (!socketURLPort || socketURLPort === '0') {
73+
socketURLPort = self.location.port;
74+
}
7575

7676
// If path is provided it'll be passed in via the resourceQuery as a
7777
// query param so it has to be parsed out of the querystring in order for the

client-src/utils/parseURL.js

+8-16
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,16 @@ const url = require('url');
44
const getCurrentScriptSource = require('./getCurrentScriptSource');
55

66
function parseURL(resourceQuery) {
7-
let options;
7+
let options = {};
88

99
if (typeof resourceQuery === 'string' && resourceQuery !== '') {
10-
// If this bundle is inlined, use the resource query to get the correct url.
11-
// for backward compatibility we supports:
12-
// - ?ws://0.0.0.0:8096&3Flogging=info
13-
// - ?ws%3A%2F%2F192.168.0.5%3A8080%2F%3Flogging%3Dinfo
14-
// Also we support `http` and `https` for backward compatibility too
15-
options = url.parse(
16-
decodeURIComponent(
17-
resourceQuery
18-
// strip leading `?` from query string to get a valid URL
19-
.substr(1)
20-
// replace first `&` with `?` to have a valid query string
21-
.replace('&', '?')
22-
),
23-
true
24-
);
10+
const searchParams = resourceQuery.substr(1).split('&');
11+
12+
for (let i = 0; i < searchParams.length; i++) {
13+
const pair = searchParams[i].split('=');
14+
15+
options[pair[0]] = decodeURIComponent(pair[1]);
16+
}
2517
} else {
2618
// Else, get the url from the <script> this file was called with.
2719
const scriptSource = getCurrentScriptSource();

client-src/utils/reloadApp.js

+2-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22

33
const { log } = require('./log');
44

5-
function reloadApp(
6-
{ hotReload, hot, liveReload },
7-
{ isUnloading, currentHash }
8-
) {
9-
if (isUnloading || !hotReload) {
5+
function reloadApp({ hot, liveReload }, { isUnloading, currentHash }) {
6+
if (isUnloading) {
107
return;
118
}
129

lib/utils/DevServerPlugin.js

+73-37
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
'use strict';
22

3-
const ipaddr = require('ipaddr.js');
4-
const getSocketClientPath = require('./getSocketClientPath');
5-
63
/**
74
* An Entry, it can be of type string or string[] or Object<string | string[],string>
85
* @typedef {(string[] | string | Object<string | string[],string>)} Entry
@@ -16,11 +13,66 @@ class DevServerPlugin {
1613
this.options = options;
1714
}
1815

16+
getWebsocketTransport() {
17+
let ClientImplementation;
18+
let clientImplementationFound = true;
19+
20+
const isKnownWebSocketServerImplementation =
21+
typeof this.options.webSocketServer.type === 'string' &&
22+
(this.options.webSocketServer.type === 'ws' ||
23+
this.options.webSocketServer.type === 'sockjs');
24+
25+
let clientTransport;
26+
27+
if (typeof this.options.client.transport !== 'undefined') {
28+
clientTransport = this.options.client.transport;
29+
} else if (isKnownWebSocketServerImplementation) {
30+
clientTransport = this.options.webSocketServer.type;
31+
}
32+
33+
switch (typeof clientTransport) {
34+
case 'string':
35+
// could be 'sockjs', 'ws', or a path that should be required
36+
if (clientTransport === 'sockjs') {
37+
ClientImplementation = require.resolve(
38+
'../../client/clients/SockJSClient'
39+
);
40+
} else if (clientTransport === 'ws') {
41+
ClientImplementation = require.resolve(
42+
'../../client/clients/WebsocketClient'
43+
);
44+
} else {
45+
try {
46+
// eslint-disable-next-line import/no-dynamic-require
47+
ClientImplementation = require.resolve(clientTransport);
48+
} catch (e) {
49+
clientImplementationFound = false;
50+
}
51+
}
52+
break;
53+
default:
54+
clientImplementationFound = false;
55+
}
56+
57+
if (!clientImplementationFound) {
58+
throw new Error(
59+
`${
60+
!isKnownWebSocketServerImplementation
61+
? 'When you use custom web socket implementation you must explicitly specify client.transport. '
62+
: ''
63+
}client.transport must be a string denoting a default implementation (e.g. 'sockjs', 'ws') or a full path to a JS file via require.resolve(...) which exports a class `
64+
);
65+
}
66+
67+
return ClientImplementation;
68+
}
69+
1970
/**
2071
* @returns {string}
2172
*/
2273
getWebSocketURL() {
2374
const { options } = this;
75+
const searchParams = new URLSearchParams();
2476

2577
/** @type {"ws:" | "wss:" | "http:" | "https:" | "auto:"} */
2678
let protocol;
@@ -32,6 +84,16 @@ class DevServerPlugin {
3284
protocol = options.https ? 'wss:' : 'ws:';
3385
}
3486

87+
searchParams.set('protocol', protocol);
88+
89+
if (typeof options.client.webSocketURL.username !== 'undefined') {
90+
searchParams.set('username', options.client.webSocketURL.username);
91+
}
92+
93+
if (typeof options.client.webSocketURL.password !== 'undefined') {
94+
searchParams.set('password', options.client.webSocketURL.password);
95+
}
96+
3597
/** @type {string} */
3698
let hostname;
3799

@@ -59,6 +121,8 @@ class DevServerPlugin {
59121
hostname = '0.0.0.0';
60122
}
61123

124+
searchParams.set('hostname', hostname);
125+
62126
/** @type {number | string} */
63127
let port;
64128

@@ -83,17 +147,18 @@ class DevServerPlugin {
83147
}
84148
// The `port` option is not specified or set to `auto`
85149
else {
86-
port = 0;
150+
port = '0';
87151
}
88152

153+
searchParams.set('port', String(port));
154+
89155
/** @type {string} */
90156
let pathname = '';
91157

92158
// We are proxying dev server and need to specify custom `pathname`
93159
if (typeof options.client.webSocketURL.pathname !== 'undefined') {
94160
pathname = options.client.webSocketURL.pathname;
95161
}
96-
97162
// Web socket server works on custom `path`
98163
else if (
99164
typeof options.webSocketServer.options.prefix !== 'undefined' ||
@@ -104,42 +169,13 @@ class DevServerPlugin {
104169
options.webSocketServer.options.path;
105170
}
106171

107-
/** @type {string} */
108-
let username = '';
109-
110-
if (typeof options.client.webSocketURL.username !== 'undefined') {
111-
username = options.client.webSocketURL.username;
112-
}
113-
114-
/** @type {string} */
115-
let password = '';
116-
117-
if (typeof options.client.webSocketURL.password !== 'undefined') {
118-
password = options.client.webSocketURL.password;
119-
}
120-
121-
const searchParams = new URLSearchParams();
172+
searchParams.set('pathname', pathname);
122173

123174
if (typeof options.client.logging !== 'undefined') {
124175
searchParams.set('logging', options.client.logging);
125176
}
126177

127-
const searchParamsString = searchParams.toString();
128-
129-
return encodeURIComponent(
130-
new URL(
131-
`${protocol}//${username}${password ? `:${password}` : ''}${
132-
username || password ? `@` : ''
133-
}${ipaddr.IPv6.isIPv6(hostname) ? `[${hostname}]` : hostname}${
134-
port ? `:${port}` : ''
135-
}${pathname || '/'}${
136-
searchParamsString ? `?${searchParamsString}` : ''
137-
}`
138-
).toString()
139-
).replace(
140-
/[!'()*]/g,
141-
(character) => `%${character.charCodeAt(0).toString(16)}`
142-
);
178+
return searchParams.toString();
143179
}
144180

145181
/**
@@ -315,7 +351,7 @@ class DevServerPlugin {
315351
}
316352

317353
const providePlugin = new webpack.ProvidePlugin({
318-
__webpack_dev_server_client__: getSocketClientPath(this.options),
354+
__webpack_dev_server_client__: this.getWebsocketTransport(),
319355
});
320356

321357
providePlugin.apply(compiler);

lib/utils/getSocketClientPath.d.ts

-3
This file was deleted.

0 commit comments

Comments
 (0)