diff --git a/lib/options.json b/lib/options.json index 77adb76cae..554f8fe48d 100644 --- a/lib/options.json +++ b/lib/options.json @@ -296,7 +296,15 @@ "description": "Tells clients connected to devServer to use the provided host." }, "port": { - "type": "number", + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "minLength": 1 + } + ], "description": "Tells clients connected to devServer to use the provided port." }, "path": { diff --git a/lib/utils/normalizeOptions.js b/lib/utils/normalizeOptions.js index 98e1e7617e..35a1089696 100644 --- a/lib/utils/normalizeOptions.js +++ b/lib/utils/normalizeOptions.js @@ -74,6 +74,10 @@ function normalizeOptions(compiler, options, logger) { options.liveReload = typeof options.liveReload !== 'undefined' ? options.liveReload : true; + if (typeof options.port === 'string' && options.port !== 'auto') { + options.port = Number(options.port); + } + const defaultWebSocketServerType = 'ws'; const defaultWebSocketServerOptions = { path: '/ws' }; @@ -98,6 +102,12 @@ function normalizeOptions(compiler, options, logger) { ...options.webSocketServer.options, }, }; + + if (typeof options.webSocketServer.options.port === 'string') { + options.webSocketServer.options.port = Number( + options.webSocketServer.options.port + ); + } } if (!options.client) { @@ -111,9 +121,11 @@ function normalizeOptions(compiler, options, logger) { options.client.webSocketURL = { host: parsedURL.hostname, - port: parsedURL.port, + port: Number(parsedURL.port), path: parsedURL.pathname, }; + } else if (typeof options.client.webSocketURL.port === 'string') { + options.client.webSocketURL.port = Number(options.client.webSocketURL.port); } // Enable client overlay by default diff --git a/test/__snapshots__/validate-options.test.js.snap.webpack4 b/test/__snapshots__/validate-options.test.js.snap.webpack4 index 33b486a843..a69d352d81 100644 --- a/test/__snapshots__/validate-options.test.js.snap.webpack4 +++ b/test/__snapshots__/validate-options.test.js.snap.webpack4 @@ -150,14 +150,21 @@ exports[`options validate should throw an error on the "client" option with '{"w exports[`options validate should throw an error on the "client" option with '{"webSocketURL":{"port":""}}' value 1`] = ` "ValidationError: Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. - - configuration.client.webSocketURL.port should be a number. - -> Tells clients connected to devServer to use the provided port." + - configuration.client.webSocketURL.port should be an non-empty string." `; exports[`options validate should throw an error on the "client" option with '{"webSocketURL":{"port":true}}' value 1`] = ` "ValidationError: Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. - - configuration.client.webSocketURL.port should be a number. - -> Tells clients connected to devServer to use the provided port." + - configuration.client.webSocketURL should be one of these: + non-empty string | object { host?, port?, path? } + -> When using dev server and you're proxying dev-server, the client script does not always know where to connect to. + Details: + * configuration.client.webSocketURL.port should be one of these: + number | non-empty string + -> Tells clients connected to devServer to use the provided port. + Details: + * configuration.client.webSocketURL.port should be a number. + * configuration.client.webSocketURL.port should be a non-empty string." `; exports[`options validate should throw an error on the "client" option with 'whoops!' value 1`] = ` diff --git a/test/__snapshots__/validate-options.test.js.snap.webpack5 b/test/__snapshots__/validate-options.test.js.snap.webpack5 index 33b486a843..a69d352d81 100644 --- a/test/__snapshots__/validate-options.test.js.snap.webpack5 +++ b/test/__snapshots__/validate-options.test.js.snap.webpack5 @@ -150,14 +150,21 @@ exports[`options validate should throw an error on the "client" option with '{"w exports[`options validate should throw an error on the "client" option with '{"webSocketURL":{"port":""}}' value 1`] = ` "ValidationError: Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. - - configuration.client.webSocketURL.port should be a number. - -> Tells clients connected to devServer to use the provided port." + - configuration.client.webSocketURL.port should be an non-empty string." `; exports[`options validate should throw an error on the "client" option with '{"webSocketURL":{"port":true}}' value 1`] = ` "ValidationError: Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. - - configuration.client.webSocketURL.port should be a number. - -> Tells clients connected to devServer to use the provided port." + - configuration.client.webSocketURL should be one of these: + non-empty string | object { host?, port?, path? } + -> When using dev server and you're proxying dev-server, the client script does not always know where to connect to. + Details: + * configuration.client.webSocketURL.port should be one of these: + number | non-empty string + -> Tells clients connected to devServer to use the provided port. + Details: + * configuration.client.webSocketURL.port should be a number. + * configuration.client.webSocketURL.port should be a non-empty string." `; exports[`options validate should throw an error on the "client" option with 'whoops!' value 1`] = ` diff --git a/test/e2e/web-socket-server-and-url.test.js b/test/e2e/web-socket-server-and-url.test.js index d6fc80f379..2cc0d286f2 100644 --- a/test/e2e/web-socket-server-and-url.test.js +++ b/test/e2e/web-socket-server-and-url.test.js @@ -316,6 +316,82 @@ for (const webSocketServerType of webSocketServerTypes) { }); }); + describe('should work with custom client port as string', () => { + beforeAll((done) => { + const options = { + webSocketServer: webSocketServerType, + port: port2, + host: '0.0.0.0', + client: { + webSocketURL: { + port: `${port3}`, + }, + }, + }; + + testServer.startAwaitingCompilation(config, options, done); + }); + + afterAll(testServer.close); + + describe('browser client', () => { + it('uses correct port and path', (done) => { + runBrowser().then(({ page, browser }) => { + waitForTest(browser, page, /ws/, (websocketUrl) => { + expect(websocketUrl).toContain( + `${websocketUrlProtocol}://localhost:${port3}/ws` + ); + + done(); + }); + + page.goto(`http://localhost:${port2}/main`); + }); + }); + }); + }); + + describe('should work with custom client.webSocketURL.port and webSocketServer.options.port both as string', () => { + beforeAll((done) => { + const options = { + webSocketServer: { + type: webSocketServerType, + options: { + host: '0.0.0.0', + port: `${port2}`, + }, + }, + port: port2, + host: '0.0.0.0', + client: { + webSocketURL: { + port: `${port3}`, + }, + }, + }; + + testServer.startAwaitingCompilation(config, options, done); + }); + + afterAll(testServer.close); + + describe('browser client', () => { + it('uses correct port and path', (done) => { + runBrowser().then(({ page, browser }) => { + waitForTest(browser, page, /ws/, (websocketUrl) => { + expect(websocketUrl).toContain( + `${websocketUrlProtocol}://localhost:${port3}/ws` + ); + + done(); + }); + + page.goto(`http://localhost:${port2}/main`); + }); + }); + }); + }); + describe('should work with custom client host', () => { beforeAll((done) => { const options = { diff --git a/test/server/utils/__snapshots__/normalizeOptions.test.js.snap.webpack4 b/test/server/utils/__snapshots__/normalizeOptions.test.js.snap.webpack4 index 9f022c76f4..91a974cfcd 100644 --- a/test/server/utils/__snapshots__/normalizeOptions.test.js.snap.webpack4 +++ b/test/server/utils/__snapshots__/normalizeOptions.test.js.snap.webpack4 @@ -109,6 +109,44 @@ Object { } `; +exports[`normalizeOptions client host and string port should set correct options 1`] = ` +Object { + "allowedHosts": "auto", + "client": Object { + "hotEntry": true, + "overlay": true, + "webSocketURL": Object { + "host": "my.host", + "port": 9000, + }, + }, + "compress": true, + "devMiddleware": Object {}, + "hot": true, + "liveReload": true, + "setupExitSignals": true, + "static": Array [ + Object { + "directory": "CWD", + "publicPath": Array [ + "/", + ], + "serveIndex": Object { + "icons": true, + }, + "staticOptions": Object {}, + "watch": Object {}, + }, + ], + "webSocketServer": Object { + "options": Object { + "path": "/ws", + }, + "type": "ws", + }, +} +`; + exports[`normalizeOptions client path should set correct options 1`] = ` Object { "allowedHosts": "auto", @@ -219,6 +257,82 @@ Object { } `; +exports[`normalizeOptions client.transport ws string and webSocketServer object should set correct options 1`] = ` +Object { + "allowedHosts": "auto", + "client": Object { + "hotEntry": true, + "overlay": true, + "transport": "ws", + "webSocketURL": Object {}, + }, + "compress": true, + "devMiddleware": Object {}, + "hot": true, + "liveReload": true, + "setupExitSignals": true, + "static": Array [ + Object { + "directory": "CWD", + "publicPath": Array [ + "/", + ], + "serveIndex": Object { + "icons": true, + }, + "staticOptions": Object {}, + "watch": Object {}, + }, + ], + "webSocketServer": Object { + "options": Object { + "host": "myhost", + "path": "/ws", + "port": 8080, + }, + "type": "ws", + }, +} +`; + +exports[`normalizeOptions client.transport ws string and webSocketServer object with port as string should set correct options 1`] = ` +Object { + "allowedHosts": "auto", + "client": Object { + "hotEntry": true, + "overlay": true, + "transport": "ws", + "webSocketURL": Object {}, + }, + "compress": true, + "devMiddleware": Object {}, + "hot": true, + "liveReload": true, + "setupExitSignals": true, + "static": Array [ + Object { + "directory": "CWD", + "publicPath": Array [ + "/", + ], + "serveIndex": Object { + "icons": true, + }, + "staticOptions": Object {}, + "watch": Object {}, + }, + ], + "webSocketServer": Object { + "options": Object { + "host": "myhost", + "path": "/ws", + "port": 8080, + }, + "type": "ws", + }, +} +`; + exports[`normalizeOptions client.transport ws string and webSocketServer ws string should set correct options 1`] = ` Object { "allowedHosts": "auto", @@ -575,6 +689,42 @@ Object { } `; +exports[`normalizeOptions port string should set correct options 1`] = ` +Object { + "allowedHosts": "auto", + "client": Object { + "hotEntry": true, + "overlay": true, + "webSocketURL": Object {}, + }, + "compress": true, + "devMiddleware": Object {}, + "hot": true, + "liveReload": true, + "port": 9000, + "setupExitSignals": true, + "static": Array [ + Object { + "directory": "CWD", + "publicPath": Array [ + "/", + ], + "serveIndex": Object { + "icons": true, + }, + "staticOptions": Object {}, + "watch": Object {}, + }, + ], + "webSocketServer": Object { + "options": Object { + "path": "/ws", + }, + "type": "ws", + }, +} +`; + exports[`normalizeOptions single compiler watchOptions is object should set correct options 1`] = ` Object { "allowedHosts": "auto", diff --git a/test/server/utils/__snapshots__/normalizeOptions.test.js.snap.webpack5 b/test/server/utils/__snapshots__/normalizeOptions.test.js.snap.webpack5 index 9f022c76f4..91a974cfcd 100644 --- a/test/server/utils/__snapshots__/normalizeOptions.test.js.snap.webpack5 +++ b/test/server/utils/__snapshots__/normalizeOptions.test.js.snap.webpack5 @@ -109,6 +109,44 @@ Object { } `; +exports[`normalizeOptions client host and string port should set correct options 1`] = ` +Object { + "allowedHosts": "auto", + "client": Object { + "hotEntry": true, + "overlay": true, + "webSocketURL": Object { + "host": "my.host", + "port": 9000, + }, + }, + "compress": true, + "devMiddleware": Object {}, + "hot": true, + "liveReload": true, + "setupExitSignals": true, + "static": Array [ + Object { + "directory": "CWD", + "publicPath": Array [ + "/", + ], + "serveIndex": Object { + "icons": true, + }, + "staticOptions": Object {}, + "watch": Object {}, + }, + ], + "webSocketServer": Object { + "options": Object { + "path": "/ws", + }, + "type": "ws", + }, +} +`; + exports[`normalizeOptions client path should set correct options 1`] = ` Object { "allowedHosts": "auto", @@ -219,6 +257,82 @@ Object { } `; +exports[`normalizeOptions client.transport ws string and webSocketServer object should set correct options 1`] = ` +Object { + "allowedHosts": "auto", + "client": Object { + "hotEntry": true, + "overlay": true, + "transport": "ws", + "webSocketURL": Object {}, + }, + "compress": true, + "devMiddleware": Object {}, + "hot": true, + "liveReload": true, + "setupExitSignals": true, + "static": Array [ + Object { + "directory": "CWD", + "publicPath": Array [ + "/", + ], + "serveIndex": Object { + "icons": true, + }, + "staticOptions": Object {}, + "watch": Object {}, + }, + ], + "webSocketServer": Object { + "options": Object { + "host": "myhost", + "path": "/ws", + "port": 8080, + }, + "type": "ws", + }, +} +`; + +exports[`normalizeOptions client.transport ws string and webSocketServer object with port as string should set correct options 1`] = ` +Object { + "allowedHosts": "auto", + "client": Object { + "hotEntry": true, + "overlay": true, + "transport": "ws", + "webSocketURL": Object {}, + }, + "compress": true, + "devMiddleware": Object {}, + "hot": true, + "liveReload": true, + "setupExitSignals": true, + "static": Array [ + Object { + "directory": "CWD", + "publicPath": Array [ + "/", + ], + "serveIndex": Object { + "icons": true, + }, + "staticOptions": Object {}, + "watch": Object {}, + }, + ], + "webSocketServer": Object { + "options": Object { + "host": "myhost", + "path": "/ws", + "port": 8080, + }, + "type": "ws", + }, +} +`; + exports[`normalizeOptions client.transport ws string and webSocketServer ws string should set correct options 1`] = ` Object { "allowedHosts": "auto", @@ -575,6 +689,42 @@ Object { } `; +exports[`normalizeOptions port string should set correct options 1`] = ` +Object { + "allowedHosts": "auto", + "client": Object { + "hotEntry": true, + "overlay": true, + "webSocketURL": Object {}, + }, + "compress": true, + "devMiddleware": Object {}, + "hot": true, + "liveReload": true, + "port": 9000, + "setupExitSignals": true, + "static": Array [ + Object { + "directory": "CWD", + "publicPath": Array [ + "/", + ], + "serveIndex": Object { + "icons": true, + }, + "staticOptions": Object {}, + "watch": Object {}, + }, + ], + "webSocketServer": Object { + "options": Object { + "path": "/ws", + }, + "type": "ws", + }, +} +`; + exports[`normalizeOptions single compiler watchOptions is object should set correct options 1`] = ` Object { "allowedHosts": "auto", diff --git a/test/server/utils/normalizeOptions.test.js b/test/server/utils/normalizeOptions.test.js index 8142555917..28518a448f 100644 --- a/test/server/utils/normalizeOptions.test.js +++ b/test/server/utils/normalizeOptions.test.js @@ -12,6 +12,14 @@ describe('normalizeOptions', () => { options: {}, optionsResults: null, }, + { + title: 'port string', + multiCompiler: false, + options: { + port: '9000', + }, + optionsResults: null, + }, { title: 'client.transport sockjs string', multiCompiler: false, @@ -59,6 +67,43 @@ describe('normalizeOptions', () => { }, optionsResults: null, }, + { + title: 'client.transport ws string and webSocketServer object', + multiCompiler: false, + options: { + client: { + transport: 'ws', + }, + webSocketServer: { + type: 'ws', + options: { + host: 'myhost', + port: 8080, + path: '/ws', + }, + }, + }, + optionsResults: null, + }, + { + title: + 'client.transport ws string and webSocketServer object with port as string', + multiCompiler: false, + options: { + client: { + transport: 'ws', + }, + webSocketServer: { + type: 'ws', + options: { + host: 'myhost', + port: '8080', + path: '/ws', + }, + }, + }, + optionsResults: null, + }, { title: 'client custom transport path', multiCompiler: false, @@ -82,6 +127,19 @@ describe('normalizeOptions', () => { }, optionsResults: null, }, + { + title: 'client host and string port', + multiCompiler: false, + options: { + client: { + webSocketURL: { + host: 'my.host', + port: '9000', + }, + }, + }, + optionsResults: null, + }, { title: 'client path', multiCompiler: false, diff --git a/test/validate-options.test.js b/test/validate-options.test.js index 4d66f884b9..c5db1a006e 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -91,6 +91,9 @@ const tests = { { webSocketURL: { port: 8080 }, }, + { + webSocketURL: { port: '8080' }, + }, { webSocketURL: { path: '' }, },