Skip to content

fix: allow to use 80 port for dev server #3487

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 6 additions & 33 deletions client-src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,20 @@ const sendMessage = require('./utils/sendMessage');
const reloadApp = require('./utils/reloadApp');
const createSocketURL = require('./utils/createSocketURL');

const status = {
isUnloading: false,
currentHash: '',
};
const defaultOptions = {
const status = { isUnloading: false, currentHash: '' };
const options = {
hot: false,
hotReload: true,
liveReload: false,
initial: true,
progress: false,
overlay: false,
};
const parsedResourceQuery = parseURL(__resourceQuery);
const options = defaultOptions;

// Handle Node.js legacy format and `new URL()`
if (parsedResourceQuery.query) {
Object.assign(options, parsedResourceQuery.query);
} else if (parsedResourceQuery.searchParams) {
const paramsToObject = (entries) => {
const result = {};

for (const [key, value] of entries) {
result[key] = value;
}

return result;
};

Object.assign(
options,
paramsToObject(parsedResourceQuery.searchParams.entries())
);
if (parsedResourceQuery.logging) {
options.logging = parsedResourceQuery.logging;
}

const socketURL = createSocketURL(parsedResourceQuery);

function setAllLogLevel(level) {
// This is needed because the HMR logger operate separately from dev server logger
webpackHotLog.setLogLevel(
Expand All @@ -65,12 +42,6 @@ self.addEventListener('beforeunload', () => {
status.isUnloading = true;
});

if (typeof window !== 'undefined') {
const qs = window.location.search.toLowerCase();

options.hotReload = qs.indexOf('hotreload=false') === -1;
}

const onSocketMessage = {
hot() {
options.hot = true;
Expand Down Expand Up @@ -220,4 +191,6 @@ const onSocketMessage = {
},
};

const socketURL = createSocketURL(parsedResourceQuery);

socket(socketURL, onSocketMessage);
44 changes: 22 additions & 22 deletions client-src/utils/createSocketURL.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ const url = require('url');
// We handle legacy API that is Node.js specific, and a newer API that implements the same WHATWG URL Standard used by web browsers
// Please look at https://nodejs.org/api/url.html#url_url_strings_and_url_objects
function createSocketURL(parsedURL) {
let { auth, hostname, protocol, port } = parsedURL;
let { hostname } = parsedURL;

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

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

if (protocol === 'auto:') {
protocol = self.location.protocol;
}
let socketURLProtocol = parsedURL.protocol || 'ws:';

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

const socketURLProtocol = protocol.replace(
socketURLProtocol = socketURLProtocol.replace(
/^(?:http|.+-extension|file)/i,
'ws'
);

let socketURLAuth = '';

// `new URL(urlString, [baseURLstring])` doesn't have `auth` property
// Parse authentication credentials in case we need them
if (parsedURL.username) {
auth = parsedURL.username;
socketURLAuth = parsedURL.username;

// Since HTTP basic authentication does not allow empty username,
// we only include password if the username is not empty.
if (parsedURL.password) {
// Result: <username>:<password>
auth = auth.concat(':', parsedURL.password);
socketURLAuth = socketURLAuth.concat(':', parsedURL.password);
}
}

const socketURLAuth = auth;

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

if (!port || port === '0') {
port = self.location.port;
}
let socketURLPort = parsedURL.port;

const socketURLPort = port;
if (!socketURLPort || socketURLPort === '0') {
socketURLPort = self.location.port;
}

// If path is provided it'll be passed in via the resourceQuery as a
// query param so it has to be parsed out of the querystring in order for the
Expand Down
24 changes: 8 additions & 16 deletions client-src/utils/parseURL.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,16 @@ const url = require('url');
const getCurrentScriptSource = require('./getCurrentScriptSource');

function parseURL(resourceQuery) {
let options;
let options = {};

if (typeof resourceQuery === 'string' && resourceQuery !== '') {
// If this bundle is inlined, use the resource query to get the correct url.
// for backward compatibility we supports:
// - ?ws://0.0.0.0:8096&3Flogging=info
// - ?ws%3A%2F%2F192.168.0.5%3A8080%2F%3Flogging%3Dinfo
// Also we support `http` and `https` for backward compatibility too
options = url.parse(
decodeURIComponent(
resourceQuery
// strip leading `?` from query string to get a valid URL
.substr(1)
// replace first `&` with `?` to have a valid query string
.replace('&', '?')
),
true
);
const searchParams = resourceQuery.substr(1).split('&');

for (let i = 0; i < searchParams.length; i++) {
const pair = searchParams[i].split('=');

options[pair[0]] = decodeURIComponent(pair[1]);
}
} else {
// Else, get the url from the <script> this file was called with.
const scriptSource = getCurrentScriptSource();
Expand Down
7 changes: 2 additions & 5 deletions client-src/utils/reloadApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

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

function reloadApp(
{ hotReload, hot, liveReload },
{ isUnloading, currentHash }
) {
if (isUnloading || !hotReload) {
function reloadApp({ hot, liveReload }, { isUnloading, currentHash }) {
if (isUnloading) {
return;
}

Expand Down
110 changes: 73 additions & 37 deletions lib/utils/DevServerPlugin.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
'use strict';

const ipaddr = require('ipaddr.js');
const getSocketClientPath = require('./getSocketClientPath');

/**
* An Entry, it can be of type string or string[] or Object<string | string[],string>
* @typedef {(string[] | string | Object<string | string[],string>)} Entry
Expand All @@ -16,11 +13,66 @@ class DevServerPlugin {
this.options = options;
}

getWebsocketTransport() {
let ClientImplementation;
let clientImplementationFound = true;

const isKnownWebSocketServerImplementation =
typeof this.options.webSocketServer.type === 'string' &&
(this.options.webSocketServer.type === 'ws' ||
this.options.webSocketServer.type === 'sockjs');

let clientTransport;

if (typeof this.options.client.transport !== 'undefined') {
clientTransport = this.options.client.transport;
} else if (isKnownWebSocketServerImplementation) {
clientTransport = this.options.webSocketServer.type;
}

switch (typeof clientTransport) {
case 'string':
// could be 'sockjs', 'ws', or a path that should be required
if (clientTransport === 'sockjs') {
ClientImplementation = require.resolve(
'../../client/clients/SockJSClient'
);
} else if (clientTransport === 'ws') {
ClientImplementation = require.resolve(
'../../client/clients/WebsocketClient'
);
} else {
try {
// eslint-disable-next-line import/no-dynamic-require
ClientImplementation = require.resolve(clientTransport);
} catch (e) {
clientImplementationFound = false;
}
}
break;
default:
clientImplementationFound = false;
}

if (!clientImplementationFound) {
throw new Error(
`${
!isKnownWebSocketServerImplementation
? 'When you use custom web socket implementation you must explicitly specify client.transport. '
: ''
}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 `
);
}

return ClientImplementation;
}

/**
* @returns {string}
*/
getWebSocketURL() {
const { options } = this;
const searchParams = new URLSearchParams();

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

searchParams.set('protocol', protocol);

if (typeof options.client.webSocketURL.username !== 'undefined') {
searchParams.set('username', options.client.webSocketURL.username);
}

if (typeof options.client.webSocketURL.password !== 'undefined') {
searchParams.set('password', options.client.webSocketURL.password);
}

/** @type {string} */
let hostname;

Expand Down Expand Up @@ -59,6 +121,8 @@ class DevServerPlugin {
hostname = '0.0.0.0';
}

searchParams.set('hostname', hostname);

/** @type {number | string} */
let port;

Expand All @@ -83,17 +147,18 @@ class DevServerPlugin {
}
// The `port` option is not specified or set to `auto`
else {
port = 0;
port = '0';
}

searchParams.set('port', String(port));

/** @type {string} */
let pathname = '';

// We are proxying dev server and need to specify custom `pathname`
if (typeof options.client.webSocketURL.pathname !== 'undefined') {
pathname = options.client.webSocketURL.pathname;
}

// Web socket server works on custom `path`
else if (
typeof options.webSocketServer.options.prefix !== 'undefined' ||
Expand All @@ -104,42 +169,13 @@ class DevServerPlugin {
options.webSocketServer.options.path;
}

/** @type {string} */
let username = '';

if (typeof options.client.webSocketURL.username !== 'undefined') {
username = options.client.webSocketURL.username;
}

/** @type {string} */
let password = '';

if (typeof options.client.webSocketURL.password !== 'undefined') {
password = options.client.webSocketURL.password;
}

const searchParams = new URLSearchParams();
searchParams.set('pathname', pathname);

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

const searchParamsString = searchParams.toString();

return encodeURIComponent(
new URL(
`${protocol}//${username}${password ? `:${password}` : ''}${
username || password ? `@` : ''
}${ipaddr.IPv6.isIPv6(hostname) ? `[${hostname}]` : hostname}${
port ? `:${port}` : ''
}${pathname || '/'}${
searchParamsString ? `?${searchParamsString}` : ''
}`
).toString()
).replace(
/[!'()*]/g,
(character) => `%${character.charCodeAt(0).toString(16)}`
);
return searchParams.toString();
}

/**
Expand Down Expand Up @@ -315,7 +351,7 @@ class DevServerPlugin {
}

const providePlugin = new webpack.ProvidePlugin({
__webpack_dev_server_client__: getSocketClientPath(this.options),
__webpack_dev_server_client__: this.getWebsocketTransport(),
});

providePlugin.apply(compiler);
Expand Down
3 changes: 0 additions & 3 deletions lib/utils/getSocketClientPath.d.ts

This file was deleted.

Loading