From 7e525ebe35201996d047d14af05709b0b082ae7a Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 26 Mar 2021 22:02:22 +0300 Subject: [PATCH] fix: prefer to open the `host` option (#3115) --- lib/Server.js | 140 +++++++++--------- test/cli/__snapshots__/cli.test.js.snap | 34 +++-- test/cli/cli.test.js | 4 +- test/helpers/test-bin.js | 15 +- test/server/open-option.test.js | 187 +++++++++++++++++------- 5 files changed, 237 insertions(+), 143 deletions(-) diff --git a/lib/Server.js b/lib/Server.js index 2e024e3d5a..80f29db7ef 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -586,92 +586,93 @@ class Server { showStatus() { const useColor = getColorsOption(getCompilerConfigArray(this.compiler)); - const protocol = this.options.https ? 'https' : 'http'; - const { address: hostname, port } = this.server.address(); + const { address, port } = this.server.address(); const prettyPrintUrl = (newHostname) => url.format({ protocol, hostname: newHostname, port, pathname: '/' }); - let localhostForTerminal; - let networkUrlForTerminalIPv4; - let networkUrlForTerminalIPv6; + let server; + let localhost; + let loopbackIPv4; + let loopbackIPv6; + let networkUrlIPv4; + let networkUrlIPv6; + + if (this.hostname) { + if (this.hostname === 'localhost') { + localhost = prettyPrintUrl('localhost'); + } else { + let isIP; + + try { + isIP = ipaddr.parse(this.hostname); + } catch (error) { + // Ignore + } + + if (!isIP) { + server = prettyPrintUrl(this.hostname); + } + } + } - const parsedIP = ipaddr.parse(hostname); - const isUnspecified = parsedIP.range() === 'unspecified'; + const parsedIP = ipaddr.parse(address); - if (isUnspecified) { - localhostForTerminal = prettyPrintUrl('localhost'); + if (parsedIP.range() === 'unspecified') { + localhost = prettyPrintUrl('localhost'); const networkIPv4 = internalIp.v4.sync(); if (networkIPv4) { - networkUrlForTerminalIPv4 = prettyPrintUrl(networkIPv4); + networkUrlIPv4 = prettyPrintUrl(networkIPv4); } - if (hostname === '::') { - const networkIPv6 = internalIp.v6.sync(); + const networkIPv6 = internalIp.v6.sync(); - if (networkIPv6) { - networkUrlForTerminalIPv6 = prettyPrintUrl(networkIPv6); - } + if (networkIPv6) { + networkUrlIPv6 = prettyPrintUrl(networkIPv6); } - } else { - if (parsedIP.range() === 'loopback') { - localhostForTerminal = prettyPrintUrl('localhost'); + } else if (parsedIP.range() === 'loopback') { + if (parsedIP.kind() === 'ipv4') { + loopbackIPv4 = prettyPrintUrl(parsedIP.toString()); + } else if (parsedIP.kind() === 'ipv6') { + loopbackIPv6 = prettyPrintUrl(parsedIP.toString()); } + } else { + networkUrlIPv4 = + parsedIP.kind() === 'ipv6' && parsedIP.isIPv4MappedAddress() + ? prettyPrintUrl(parsedIP.toIPv4Address().toString()) + : prettyPrintUrl(address); if (parsedIP.kind() === 'ipv6') { - if (parsedIP.isIPv4MappedAddress()) { - networkUrlForTerminalIPv4 = prettyPrintUrl( - parsedIP.toIPv4Address().toString() - ); - } - } else { - networkUrlForTerminalIPv4 = prettyPrintUrl(hostname); + networkUrlIPv6 = prettyPrintUrl(address); } + } - if (parsedIP.kind() === 'ipv6') { - networkUrlForTerminalIPv6 = prettyPrintUrl(hostname); - } + this.logger.info('Project is running at:'); + + if (server) { + this.logger.info(`Server: ${colors.info(useColor, server)}`); } - if (isUnspecified) { - this.logger.info('Project is running at:'); - this.logger.info(`Local: ${colors.info(useColor, localhostForTerminal)}`); - - const networkUrlsForTerminal = [] - .concat( - networkUrlForTerminalIPv4 - ? [`${colors.info(useColor, networkUrlForTerminalIPv4)} (IPv4)`] - : [] - ) - .concat( - networkUrlForTerminalIPv6 - ? [`${colors.info(useColor, networkUrlForTerminalIPv6)} (IPv6)`] - : [] - ); + if (localhost || loopbackIPv4 || loopbackIPv6) { + const loopbacks = [] + .concat(localhost ? [colors.info(useColor, localhost)] : []) + .concat(loopbackIPv4 ? [colors.info(useColor, loopbackIPv4)] : []) + .concat(loopbackIPv6 ? [colors.info(useColor, loopbackIPv6)] : []); - this.logger.info(`On Your Network: ${networkUrlsForTerminal.join(', ')}`); - } else { - const networkUrlsForTerminal = [] - .concat( - localhostForTerminal - ? [`${colors.info(useColor, localhostForTerminal)}`] - : [] - ) - .concat( - networkUrlForTerminalIPv4 - ? [`${colors.info(useColor, networkUrlForTerminalIPv4)} (IPv4)`] - : [] - ) - .concat( - networkUrlForTerminalIPv6 - ? [`${colors.info(useColor, networkUrlForTerminalIPv6)} (IPv6)`] - : [] - ); + this.logger.info(`Loopback: ${loopbacks.join(', ')}`); + } + + if (networkUrlIPv4) { + this.logger.info( + `On Your Network (IPv4): ${colors.info(useColor, networkUrlIPv4)}` + ); + } + if (networkUrlIPv6) { this.logger.info( - `Project is running at ${networkUrlsForTerminal.join(', ')}` + `On Your Network (IPv6): ${colors.info(useColor, networkUrlIPv6)}` ); } @@ -716,12 +717,9 @@ class Server { } if (this.options.open || this.options.openPage) { - const uri = - localhostForTerminal || - networkUrlForTerminalIPv4 || - networkUrlForTerminalIPv6; + const openTarget = prettyPrintUrl(this.hostname || 'localhost'); - runOpen(uri, this.options, this.logger); + runOpen(openTarget, this.options, this.logger); } } @@ -740,7 +738,7 @@ class Server { .then((port) => { this.port = port; - return this.server.listen(port, hostname, (err) => { + return this.server.listen(port, hostname, (error) => { if (this.options.hot || this.options.liveReload) { this.createSocketServer(); } @@ -752,7 +750,7 @@ class Server { this.showStatus(); if (fn) { - fn.call(this.server, err); + fn.call(this.server, error); } if (typeof this.options.onListening === 'function') { @@ -760,9 +758,9 @@ class Server { } }); }) - .catch((err) => { + .catch((error) => { if (fn) { - fn.call(this.server, err); + fn.call(this.server, error); } }) ); diff --git a/test/cli/__snapshots__/cli.test.js.snap b/test/cli/__snapshots__/cli.test.js.snap index 8b0b68ce1a..5c02ceb695 100644 --- a/test/cli/__snapshots__/cli.test.js.snap +++ b/test/cli/__snapshots__/cli.test.js.snap @@ -2,51 +2,61 @@ exports[`CLI --host :: (IPv6): stderr 1`] = ` " [webpack-dev-server] Project is running at: - [webpack-dev-server] Local: http://localhost:/ - [webpack-dev-server] On Your Network: http://:/ (IPv4), http://[]:/ (IPv6) + [webpack-dev-server] Loopback: http://localhost:/ + [webpack-dev-server] On Your Network (IPv4): http://:/ + [webpack-dev-server] On Your Network (IPv6): http://[]:/ [webpack-dev-server] Content not from webpack is served from '/public' directory" `; exports[`CLI --host ::1 (IPv6): stderr 1`] = ` -" [webpack-dev-server] Project is running at http://localhost:/, http://[::1]:/ (IPv6) +" [webpack-dev-server] Project is running at: + [webpack-dev-server] Loopback: http://[::1]:/ [webpack-dev-server] Content not from webpack is served from '/public' directory" `; exports[`CLI --host : stderr 1`] = ` -" [webpack-dev-server] Project is running at http://:/ (IPv4) +" [webpack-dev-server] Project is running at: + [webpack-dev-server] On Your Network (IPv4): http://:/ [webpack-dev-server] Content not from webpack is served from '/public' directory" `; exports[`CLI --host 0.0.0.0 (IPv4): stderr 1`] = ` " [webpack-dev-server] Project is running at: - [webpack-dev-server] Local: http://localhost:/ - [webpack-dev-server] On Your Network: http://:/ (IPv4) + [webpack-dev-server] Loopback: http://localhost:/ + [webpack-dev-server] On Your Network (IPv4): http://:/ + [webpack-dev-server] On Your Network (IPv6): http://[]:/ [webpack-dev-server] Content not from webpack is served from '/public' directory" `; exports[`CLI --host 0:0:0:0:0:FFFF:7F00:0001 (IPv6): stderr 1`] = ` -" [webpack-dev-server] Project is running at http://127.0.0.1:/ (IPv4), http://[::ffff:127.0.0.1]:/ (IPv6) +" [webpack-dev-server] Project is running at: + [webpack-dev-server] On Your Network (IPv4): http://127.0.0.1:/ + [webpack-dev-server] On Your Network (IPv6): http://[::ffff:127.0.0.1]:/ [webpack-dev-server] Content not from webpack is served from '/public' directory" `; exports[`CLI --host 127.0.0.1 (IPv4): stderr 1`] = ` -" [webpack-dev-server] Project is running at http://localhost:/, http://127.0.0.1:/ (IPv4) +" [webpack-dev-server] Project is running at: + [webpack-dev-server] Loopback: http://127.0.0.1:/ [webpack-dev-server] Content not from webpack is served from '/public' directory" `; exports[`CLI --host and --port are unspecified: stderr 1`] = ` " [webpack-dev-server] Project is running at: - [webpack-dev-server] Local: http://localhost:/ - [webpack-dev-server] On Your Network: http://:/ (IPv4), http://[]:/ (IPv6) + [webpack-dev-server] Loopback: http://localhost:/ + [webpack-dev-server] On Your Network (IPv4): http://:/ + [webpack-dev-server] On Your Network (IPv6): http://[]:/ [webpack-dev-server] Content not from webpack is served from '/public' directory" `; exports[`CLI --host localhost --port 9999: stderr 1`] = ` -" [webpack-dev-server] Project is running at http://localhost:/, http://127.0.0.1:/ (IPv4) +" [webpack-dev-server] Project is running at: + [webpack-dev-server] Loopback: http://localhost:/, http://127.0.0.1:/ [webpack-dev-server] Content not from webpack is served from '/public' directory" `; exports[`CLI --host localhost: stderr 1`] = ` -" [webpack-dev-server] Project is running at http://localhost:/, http://127.0.0.1:/ (IPv4) +" [webpack-dev-server] Project is running at: + [webpack-dev-server] Loopback: http://localhost:/, http://127.0.0.1:/ [webpack-dev-server] Content not from webpack is served from '/public' directory" `; diff --git a/test/cli/cli.test.js b/test/cli/cli.test.js index a2c710b4f7..0e2f79af51 100644 --- a/test/cli/cli.test.js +++ b/test/cli/cli.test.js @@ -109,7 +109,9 @@ describe('CLI', () => { it('--host 0.0.0.0 (IPv4)', (done) => { testBin('--host 0.0.0.0') .then((output) => { - expect(normalizeStderr(output.stderr)).toMatchSnapshot('stderr'); + expect(normalizeStderr(output.stderr, { ipv6: true })).toMatchSnapshot( + 'stderr' + ); done(); }) diff --git a/test/helpers/test-bin.js b/test/helpers/test-bin.js index 1390b53f65..6cf6d70552 100644 --- a/test/helpers/test-bin.js +++ b/test/helpers/test-bin.js @@ -62,10 +62,19 @@ function normalizeStderr(stderr, options = {}) { if (options.ipv6 && !networkIPv6) { // Github Actions doesnt' support IPv6 on ubuntu in some cases - normalizedStderr = normalizedStderr.replace( - /\(IPv4\)/, - '(IPv4), http://[]:/ (IPv6)' + normalizedStderr = normalizedStderr.split('\n'); + + const ipv4MessageIndex = normalizedStderr.findIndex((item) => + /On Your Network \(IPv4\)/.test(item) ); + + normalizedStderr.splice( + ipv4MessageIndex + 1, + 0, + ' [webpack-dev-server] On Your Network (IPv6): http://[]:/' + ); + + normalizedStderr = normalizedStderr.join('\n'); } return normalizedStderr; diff --git a/test/server/open-option.test.js b/test/server/open-option.test.js index b993f2cf26..75a33032ae 100644 --- a/test/server/open-option.test.js +++ b/test/server/open-option.test.js @@ -1,20 +1,28 @@ 'use strict'; -jest.mock('open'); - +const internalIp = require('internal-ip'); const webpack = require('webpack'); const open = require('open'); const Server = require('../../lib/Server'); const config = require('../fixtures/simple-config/webpack.config'); const port = require('../ports-map')['open-option']; +jest.mock('open'); + open.mockImplementation(() => { return { catch: jest.fn(), }; }); -describe('open option', () => { +const internalIPv4 = internalIp.v4.sync(); +// const internalIPv6 = internalIp.v6.sync(); + +describe('"open" option', () => { + afterEach(() => { + open.mockClear(); + }); + it('should work with unspecified host', (done) => { const compiler = webpack(config); const server = new Server(compiler, { @@ -25,41 +33,76 @@ describe('open option', () => { compiler.hooks.done.tap('webpack-dev-server', () => { server.close(() => { - expect(open.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "http://localhost:8117/", - Object { - "wait": false, - }, - ] - `); - expect(open.mock.invocationCallOrder[0]).toEqual(1); + expect(open).toHaveBeenCalledWith('http://localhost:8117/', { + wait: false, + }); + done(); }); }); compiler.run(() => {}); - server.listen(port, 'localhost'); + server.listen(port); }); - it('should work with unspecified open option', (done) => { + it('should work with "0.0.0.0" host', (done) => { const compiler = webpack(config); const server = new Server(compiler, { - openPage: 'index.html', + open: true, + port, + static: false, + }); + + compiler.hooks.done.tap('webpack-dev-server', () => { + server.close(() => { + expect(open).toHaveBeenCalledWith('http://0.0.0.0:8117/', { + wait: false, + }); + + done(); + }); + }); + + compiler.run(() => {}); + server.listen(port, '0.0.0.0'); + }); + + it('should work with "::" host', (done) => { + const compiler = webpack(config); + const server = new Server(compiler, { + open: true, + port, + static: false, + }); + + compiler.hooks.done.tap('webpack-dev-server', () => { + server.close(() => { + expect(open).toHaveBeenCalledWith('http://[::]:8117/', { + wait: false, + }); + + done(); + }); + }); + + compiler.run(() => {}); + server.listen(port, '::'); + }); + + it('should work with "localhost" host', (done) => { + const compiler = webpack(config); + const server = new Server(compiler, { + open: true, port, static: false, }); compiler.hooks.done.tap('webpack-dev-server', () => { server.close(() => { - expect(open.mock.calls[1]).toMatchInlineSnapshot(` - Array [ - "http://localhost:8117/index.html", - Object { - "wait": false, - }, - ] - `); + expect(open).toHaveBeenCalledWith('http://localhost:8117/', { + wait: false, + }); + done(); }); }); @@ -68,7 +111,7 @@ describe('open option', () => { server.listen(port, 'localhost'); }); - it('should work with "0.0.0.0" host', (done) => { + it('should work with "127.0.0.1" host', (done) => { const compiler = webpack(config); const server = new Server(compiler, { open: true, @@ -78,24 +121,19 @@ describe('open option', () => { compiler.hooks.done.tap('webpack-dev-server', () => { server.close(() => { - expect(open.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "http://localhost:8117/", - Object { - "wait": false, - }, - ] - `); - expect(open.mock.invocationCallOrder[0]).toEqual(1); + expect(open).toHaveBeenCalledWith('http://127.0.0.1:8117/', { + wait: false, + }); + done(); }); }); compiler.run(() => {}); - server.listen(port, '0.0.0.0'); + server.listen(port, '127.0.0.1'); }); - it('should work with "::" host', (done) => { + it('should work with "::1" host', (done) => { const compiler = webpack(config); const server = new Server(compiler, { open: true, @@ -105,24 +143,19 @@ describe('open option', () => { compiler.hooks.done.tap('webpack-dev-server', () => { server.close(() => { - expect(open.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "http://localhost:8117/", - Object { - "wait": false, - }, - ] - `); - expect(open.mock.invocationCallOrder[0]).toEqual(1); + expect(open).toHaveBeenCalledWith('http://[::1]:8117/', { + wait: false, + }); + done(); }); }); compiler.run(() => {}); - server.listen(port, '::'); + server.listen(port, '::1'); }); - it('should work with "localhost" host', (done) => { + it(`should work with "${internalIPv4}" host`, (done) => { const compiler = webpack(config); const server = new Server(compiler, { open: true, @@ -132,20 +165,62 @@ describe('open option', () => { compiler.hooks.done.tap('webpack-dev-server', () => { server.close(() => { - expect(open.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "http://localhost:8117/", - Object { - "wait": false, - }, - ] - `); - expect(open.mock.invocationCallOrder[0]).toEqual(1); + expect(open).toHaveBeenCalledWith(`http://${internalIPv4}:8117/`, { + wait: false, + }); + done(); }); }); compiler.run(() => {}); - server.listen(port, '::'); + server.listen(port, internalIPv4); + }); + + // TODO need improve + // if (internalIPv6) { + // it(`should work with "${internalIPv6}" host`, (done) => { + // const compiler = webpack(config); + // const server = new Server(compiler, { + // open: true, + // port, + // static: false, + // }); + // + // compiler.hooks.done.tap('webpack-dev-server', () => { + // server.close(() => { + // expect(open).toHaveBeenCalledWith(`http://[${internalIPv6}]:8117/`, { + // wait: false, + // }); + // + // done(); + // }); + // }); + // + // compiler.run(() => {}); + // server.listen(port, internalIPv6); + // }); + // } + + it('should work with unspecified the `open` option and specified the `openPage` option', (done) => { + const compiler = webpack(config); + const server = new Server(compiler, { + openPage: 'index.html', + port, + static: false, + }); + + compiler.hooks.done.tap('webpack-dev-server', () => { + server.close(() => { + expect(open).toHaveBeenCalledWith('http://localhost:8117/index.html', { + wait: false, + }); + + done(); + }); + }); + + compiler.run(() => {}); + server.listen(port, 'localhost'); }); });