Skip to content

Commit 6f3c6ba

Browse files
fix: respect the client.logging option for HMR logging (#3159)
1 parent 23331e2 commit 6f3c6ba

40 files changed

+980
-414
lines changed

babel.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = (api) => {
1414
},
1515
],
1616
],
17+
plugins: ['@babel/plugin-transform-object-assign'],
1718
env: {
1819
test: {
1920
plugins: ['@babel/plugin-transform-runtime'],

client-src/clients/SockJSClient.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ module.exports = class SockJSClient extends BaseClient {
88
constructor(url) {
99
super();
1010

11-
const sockUrl = url.replace(/^(?:chrome-extension|file)/i, 'http');
12-
13-
this.sock = new SockJS(sockUrl);
14-
this.sock.onerror = (err) => {
15-
log.error(err);
11+
// SockJS requires `http` and `https` protocols
12+
this.sock = new SockJS(
13+
url.replace(/^ws:/i, 'http:').replace(/^wss:/i, 'https:')
14+
);
15+
this.sock.onerror = (error) => {
16+
log.error(error);
1617
};
1718
}
1819

client-src/clients/WebsocketClient.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ module.exports = class WebsocketClient extends BaseClient {
77
constructor(url) {
88
super();
99

10-
const wsUrl = url.replace(/^(?:http|chrome-extension|file)/i, 'ws');
11-
12-
this.client = new WebSocket(wsUrl);
13-
this.client.onerror = (err) => {
14-
log.error(err);
10+
this.client = new WebSocket(url);
11+
this.client.onerror = (error) => {
12+
log.error(error);
1513
};
1614
}
1715

client-src/index.js

+40-15
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@
22

33
/* global __resourceQuery WorkerGlobalScope */
44

5+
const webpackHotLog = require('webpack/hot/log');
56
const stripAnsi = require('./modules/strip-ansi');
7+
const parseURL = require('./utils/parseURL');
68
const socket = require('./socket');
79
const overlay = require('./overlay');
810
const { log, setLogLevel } = require('./utils/log');
911
const sendMessage = require('./utils/sendMessage');
1012
const reloadApp = require('./utils/reloadApp');
11-
const createSocketUrl = require('./utils/createSocketUrl');
13+
const createSocketURL = require('./utils/createSocketURL');
1214

1315
const status = {
1416
isUnloading: false,
1517
currentHash: '',
1618
};
17-
const options = {
19+
const defaultOptions = {
1820
hot: false,
1921
hotReload: true,
2022
liveReload: false,
@@ -23,7 +25,40 @@ const options = {
2325
useErrorOverlay: false,
2426
useProgress: false,
2527
};
26-
const socketUrl = createSocketUrl(__resourceQuery);
28+
const parsedResourceQuery = parseURL(__resourceQuery);
29+
const options = defaultOptions;
30+
31+
// Handle Node.js legacy format and `new URL()`
32+
if (parsedResourceQuery.query) {
33+
Object.assign(options, parsedResourceQuery.query);
34+
} else if (parsedResourceQuery.searchParams) {
35+
const paramsToObject = (entries) => {
36+
const result = {};
37+
38+
for (const [key, value] of entries) {
39+
result[key] = value;
40+
}
41+
42+
return result;
43+
};
44+
45+
Object.assign(
46+
options,
47+
paramsToObject(parsedResourceQuery.searchParams.entries())
48+
);
49+
}
50+
51+
const socketURL = createSocketURL(parsedResourceQuery);
52+
53+
function setAllLogLevel(level) {
54+
// This is needed because the HMR logger operate separately from dev server logger
55+
webpackHotLog.setLogLevel(level);
56+
setLogLevel(level);
57+
}
58+
59+
if (options.logging) {
60+
setAllLogLevel(options.logging);
61+
}
2762

2863
self.addEventListener('beforeunload', () => {
2964
status.isUnloading = true;
@@ -68,17 +103,7 @@ const onSocketMessage = {
68103

69104
sendMessage('StillOk');
70105
},
71-
logging: function logging(level) {
72-
// this is needed because the HMR logger operate separately from
73-
// dev server logger
74-
const hotCtx = require.context('webpack/hot', false, /^\.\/log$/);
75-
76-
if (hotCtx.keys().indexOf('./log') !== -1) {
77-
hotCtx('./log').setLogLevel(level);
78-
}
79-
80-
setLogLevel(level);
81-
},
106+
logging: setAllLogLevel,
82107
overlay(value) {
83108
if (typeof document !== 'undefined') {
84109
if (typeof value === 'boolean') {
@@ -172,4 +197,4 @@ const onSocketMessage = {
172197
},
173198
};
174199

175-
socket(socketUrl, onSocketMessage);
200+
socket(socketURL, onSocketMessage);

client-src/utils/createSocketURL.js

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
'use strict';
2+
3+
const url = require('url');
4+
5+
// We handle legacy API that is Node.js specific, and a newer API that implements the same WHATWG URL Standard used by web browsers
6+
// Please look at https://nodejs.org/api/url.html#url_url_strings_and_url_objects
7+
function createSocketURL(parsedURL) {
8+
let { auth, hostname, protocol, port } = parsedURL;
9+
10+
const getURLSearchParam = (name) => {
11+
if (parsedURL.searchParams) {
12+
return parsedURL.searchParams.get(name);
13+
}
14+
15+
return parsedURL.query && parsedURL.query[name];
16+
};
17+
18+
// Node.js module parses it as `::`
19+
// `new URL(urlString, [baseURLstring])` parses it as '[::]'
20+
const isInAddrAny =
21+
hostname === '0.0.0.0' || hostname === '::' || hostname === '[::]';
22+
23+
// check ipv4 and ipv6 `all hostname`
24+
// why do we need this check?
25+
// hostname n/a for file protocol (example, when using electron, ionic)
26+
// see: https://github.com/webpack/webpack-dev-server/pull/384
27+
if (
28+
isInAddrAny &&
29+
self.location.hostname &&
30+
self.location.protocol.indexOf('http') === 0
31+
) {
32+
hostname = self.location.hostname;
33+
}
34+
35+
// `hostname` can be empty when the script path is relative. In that case, specifying a protocol would result in an invalid URL.
36+
// When https is used in the app, secure websockets are always necessary because the browser doesn't accept non-secure websockets.
37+
if (hostname && isInAddrAny && self.location.protocol === 'https:') {
38+
protocol = self.location.protocol;
39+
}
40+
41+
const socketURLProtocol = protocol.replace(
42+
/^(?:http|.+-extension|file)/i,
43+
'ws'
44+
);
45+
46+
// `new URL(urlString, [baseURLstring])` doesn't have `auth` property
47+
// Parse authentication credentials in case we need them
48+
if (parsedURL.username) {
49+
auth = parsedURL.username;
50+
51+
// Since HTTP basic authentication does not allow empty username,
52+
// we only include password if the username is not empty.
53+
if (parsedURL.password) {
54+
// Result: <username>:<password>
55+
auth = auth.concat(':', parsedURL.password);
56+
}
57+
}
58+
59+
const socketURLAuth = auth;
60+
61+
// In case the host is a raw IPv6 address, it can be enclosed in
62+
// the brackets as the brackets are needed in the final URL string.
63+
// Need to remove those as url.format blindly adds its own set of brackets
64+
// if the host string contains colons. That would lead to non-working
65+
// double brackets (e.g. [[::]]) host
66+
//
67+
// All of these sock url params are optionally passed in through resourceQuery,
68+
// so we need to fall back to the default if they are not provided
69+
const socketURLHostname = (
70+
getURLSearchParam('host') ||
71+
hostname ||
72+
'localhost'
73+
).replace(/^\[(.*)\]$/, '$1');
74+
75+
if (!port || port === '0') {
76+
port = self.location.port;
77+
}
78+
79+
const socketURLPort = getURLSearchParam('port') || port;
80+
81+
// If path is provided it'll be passed in via the resourceQuery as a
82+
// query param so it has to be parsed out of the querystring in order for the
83+
// client to open the socket to the correct location.
84+
const socketURLPathname = getURLSearchParam('path') || '/ws';
85+
86+
return url.format({
87+
protocol: socketURLProtocol,
88+
auth: socketURLAuth,
89+
hostname: socketURLHostname,
90+
port: socketURLPort,
91+
pathname: socketURLPathname,
92+
slashes: true,
93+
});
94+
}
95+
96+
module.exports = createSocketURL;

client-src/utils/createSocketUrl.js

-100
This file was deleted.

client-src/utils/getCurrentScriptSource.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,21 @@ function getCurrentScriptSource() {
66
if (document.currentScript) {
77
return document.currentScript.getAttribute('src');
88
}
9-
// Fall back to getting all scripts in the document.
9+
10+
// Fallback to getting all scripts running in the document.
1011
const scriptElements = document.scripts || [];
11-
const currentScript = scriptElements[scriptElements.length - 1];
12+
const scriptElementsWithSrc = Array.prototype.filter.call(
13+
scriptElements,
14+
(element) => element.getAttribute('src')
15+
);
16+
17+
if (scriptElementsWithSrc.length > 0) {
18+
const currentScript =
19+
scriptElementsWithSrc[scriptElementsWithSrc.length - 1];
1220

13-
if (currentScript) {
1421
return currentScript.getAttribute('src');
1522
}
23+
1624
// Fail as there was no script to use.
1725
throw new Error('[webpack-dev-server] Failed to get current script source.');
1826
}

0 commit comments

Comments
 (0)