From 9e65c24214666241334b89c9e070f4d03bb0f317 Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Wed, 2 Dec 2020 16:41:30 +0300 Subject: [PATCH] feat: improve host output and fix open (#2892) --- lib/Server.js | 89 +++++++++++++++++++++++-- lib/utils/createDomain.js | 10 +-- lib/utils/status.js | 47 ------------- test/cli/__snapshots__/cli.test.js.snap | 8 +-- test/cli/cli.test.js | 54 +++++++++++++-- test/server/open-option.test.js | 85 ++++++++++++++++++++++- 6 files changed, 224 insertions(+), 69 deletions(-) delete mode 100644 lib/utils/status.js diff --git a/lib/Server.js b/lib/Server.js index 84278ec44a..2810485665 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -6,6 +6,7 @@ const url = require('url'); const http = require('http'); const https = require('https'); const ip = require('ip'); +const internalIp = require('internal-ip'); const killable = require('killable'); const chokidar = require('chokidar'); const express = require('express'); @@ -21,8 +22,8 @@ const { validate } = require('schema-utils'); const normalizeOptions = require('./utils/normalizeOptions'); const updateCompiler = require('./utils/updateCompiler'); const getCertificate = require('./utils/getCertificate'); -const status = require('./utils/status'); -const createDomain = require('./utils/createDomain'); +const colors = require('./utils/colors'); +const runOpen = require('./utils/runOpen'); const runBonjour = require('./utils/runBonjour'); const routes = require('./utils/routes'); const getSocketServerImplementation = require('./utils/getSocketServerImplementation'); @@ -580,17 +581,91 @@ class Server { } showStatus() { - const suffix = '/'; - const uri = `${createDomain(this.options, this.server)}${suffix}`; + const useColor = getColorsOption(getCompilerConfigArray(this.compiler)); - const configArr = getCompilerConfigArray(this.compiler); - const colors = getColorsOption(configArr); + const protocol = this.options.https ? 'https' : 'http'; + const { hostname, port } = this; + const prettyPrintUrl = (newHostname) => + url.format({ protocol, hostname: newHostname, port, pathname: '/' }); + + let prettyHostname; + let lanUrlForTerminal; + + if (hostname === '0.0.0.0' || hostname === '::') { + prettyHostname = 'localhost'; + + const localIP = + hostname === '::' ? internalIp.v6.sync() : internalIp.v4.sync(); + + if (localIP) { + lanUrlForTerminal = prettyPrintUrl(localIP); + } + } else { + prettyHostname = hostname; + } - status(uri, this.options, this.logger, colors); + const localUrlForTerminal = prettyPrintUrl(prettyHostname); + + if (lanUrlForTerminal) { + this.logger.info('Project is running at:'); + this.logger.info(`Local: ${colors.info(useColor, localUrlForTerminal)}`); + this.logger.info( + `On Your Network: ${colors.info(useColor, lanUrlForTerminal)}` + ); + } else { + this.logger.info( + `Project is running at ${colors.info(useColor, localUrlForTerminal)}` + ); + } + + if ( + this.options.dev && + typeof this.options.dev.publicPath !== 'undefined' + ) { + this.options.info( + `webpack output is served from '${colors.info( + useColor, + this.options.dev.publicPath === 'auto' + ? '/' + : this.options.dev.publicPath + )}' URL` + ); + } + + if (this.options.static && this.options.static.length > 0) { + this.logger.info( + `Content not from webpack is served from '${colors.info( + useColor, + this.options.static + .map((staticOption) => staticOption.directory) + .join(', ') + )}' directory` + ); + } + + if (this.options.historyApiFallback) { + this.logger.info( + `404s will fallback to '${colors.info( + useColor, + this.options.historyApiFallback.index || '/index.html' + )}'` + ); + } + + if (this.options.bonjour) { + this.logger.info( + 'Broadcasting "http" with subtype of "webpack" via ZeroConf DNS (Bonjour)' + ); + } + + if (this.options.open) { + runOpen(localUrlForTerminal, this.options, this.logger); + } } listen(port, hostname, fn) { this.hostname = hostname; + if (typeof port !== 'undefined' && port !== this.options.port) { this.logger.warn( 'The port specified in options and the port passed as an argument is different.' diff --git a/lib/utils/createDomain.js b/lib/utils/createDomain.js index 7248e7ad4e..4a46bd2fc3 100644 --- a/lib/utils/createDomain.js +++ b/lib/utils/createDomain.js @@ -8,6 +8,7 @@ function createDomain(options, server) { // use location hostname and port by default in createSocketUrl // ipv6 detection is not required as 0.0.0.0 is just used as a placeholder let hostname; + if (options.useLocalIp) { hostname = ip.v4.sync() || '0.0.0.0'; } else if (server) { @@ -15,7 +16,9 @@ function createDomain(options, server) { } else { hostname = '0.0.0.0'; } + const port = server ? server.address().port : 0; + // use explicitly defined public url // (prefix with protocol if not explicitly given) if (options.public) { @@ -23,12 +26,9 @@ function createDomain(options, server) { ? `${options.public}` : `${protocol}://${options.public}`; } + // the formatted domain (url without path) of the webpack server - return url.format({ - protocol, - hostname, - port, - }); + return url.format({ protocol, hostname, port }); } module.exports = createDomain; diff --git a/lib/utils/status.js b/lib/utils/status.js deleted file mode 100644 index 18f0a9185b..0000000000 --- a/lib/utils/status.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -const colors = require('./colors'); -const runOpen = require('./runOpen'); - -function status(uri, options, logger, useColor) { - logger.info(`Project is running at ${colors.info(useColor, uri)}`); - - if (options.dev && options.dev.publicPath) { - logger.info( - `webpack output is served from ${colors.info( - useColor, - options.dev.publicPath - )}` - ); - } - - if (options.static && options.static.length > 0) { - logger.info( - `Content not from webpack is served from ${colors.info( - useColor, - options.static.map((staticOption) => staticOption.directory).join(', ') - )}` - ); - } - - if (options.historyApiFallback) { - logger.info( - `404s will fallback to ${colors.info( - useColor, - options.historyApiFallback.index || '/index.html' - )}` - ); - } - - if (options.bonjour) { - logger.info( - 'Broadcasting "http" with subtype of "webpack" via ZeroConf DNS (Bonjour)' - ); - } - - if (options.open) { - runOpen(uri, options, logger); - } -} - -module.exports = status; diff --git a/test/cli/__snapshots__/cli.test.js.snap b/test/cli/__snapshots__/cli.test.js.snap index c73d2cb1ae..2e31ea80d0 100644 --- a/test/cli/__snapshots__/cli.test.js.snap +++ b/test/cli/__snapshots__/cli.test.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`CLI --hot webpack 4 1`] = ` -" [webpack-dev-server] Project is running at http://127.0.0.1:8080/ +" [webpack-dev-server] Project is running at http://localhost:8080/ [webpack-dev-middleware] Hash: X Version: webpack x.x.x Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT @@ -28,7 +28,7 @@ exports[`CLI --hot webpack 4 1`] = ` `; exports[`CLI --hot webpack 5 1`] = ` -" [webpack-dev-server] Project is running at http://127.0.0.1:8080/ +" [webpack-dev-server] Project is running at http://localhost:8080/ [webpack-dev-middleware] asset main.js X KiB [emitted] (name: main) runtime modules X KiB 10 modules cacheable modules X KiB @@ -46,7 +46,7 @@ exports[`CLI --hot webpack 5 1`] = ` `; exports[`CLI --no-hot webpack 4 1`] = ` -" [webpack-dev-server] Project is running at http://127.0.0.1:8080/ +" [webpack-dev-server] Project is running at http://localhost:8080/ [webpack-dev-middleware] Hash: X Version: webpack x.x.x Time: Xms Built at: Thu Jan 01 1970 00:00:00 GMT @@ -73,7 +73,7 @@ exports[`CLI --no-hot webpack 4 1`] = ` `; exports[`CLI --no-hot webpack 5 1`] = ` -" [webpack-dev-server] Project is running at http://127.0.0.1:8080/ +" [webpack-dev-server] Project is running at http://localhost:8080/ [webpack-dev-middleware] asset main.js X KiB [emitted] (name: main) runtime modules X bytes 3 modules cacheable modules X KiB diff --git a/test/cli/cli.test.js b/test/cli/cli.test.js index 05746d19ce..7513da7a23 100644 --- a/test/cli/cli.test.js +++ b/test/cli/cli.test.js @@ -2,6 +2,7 @@ const { join, resolve } = require('path'); const execa = require('execa'); +const internalIp = require('internal-ip'); const testBin = require('../helpers/test-bin'); const isWebpack5 = require('../helpers/isWebpack5'); @@ -118,12 +119,57 @@ runCLITest('CLI', () => { .catch(done); }); - it('unspecified port', (done) => { + it('unspecified host and port', (done) => { testBin('') .then((output) => { - expect(/http:\/\/127\.0\.0\.1:[0-9]+/.test(output.stderr)).toEqual( - true - ); + expect(/http:\/\/localhost:[0-9]+/.test(output.stderr)).toEqual(true); + done(); + }) + .catch(done); + }); + + it('--host 0.0.0.0 (IPv4)', (done) => { + testBin('--host 0.0.0.0') + .then((output) => { + const localIP = internalIp.v4.sync(); + + expect(/http:\/\/localhost:[0-9]+/.test(output.stderr)).toEqual(true); + expect( + new RegExp(`http://${localIP}:[0-9]+/`).test(output.stderr) + ).toEqual(true); + done(); + }) + .catch(done); + }); + + // TODO search way how to tests it on github actions + it.skip('--host :: (IPv6)', (done) => { + testBin('--host ::') + .then((output) => { + const localIP = internalIp.v4.sync(); + + expect(/http:\/\/localhost:[0-9]+/.test(output.stderr)).toEqual(true); + expect( + new RegExp(`http://${localIP}:[0-9]+/`).test(output.stderr) + ).toEqual(true); + done(); + }) + .catch(done); + }); + + it('--host localhost', (done) => { + testBin('--host localhost') + .then((output) => { + expect(/http:\/\/localhost:[0-9]+/.test(output.stderr)).toEqual(true); + done(); + }) + .catch(done); + }); + + it('--port', (done) => { + testBin('--port 9999') + .then((output) => { + expect(/http:\/\/localhost:9999/.test(output.stderr)).toEqual(true); done(); }) .catch(done); diff --git a/test/server/open-option.test.js b/test/server/open-option.test.js index 7fd3a42229..1538b24f66 100644 --- a/test/server/open-option.test.js +++ b/test/server/open-option.test.js @@ -15,7 +15,7 @@ open.mockImplementation(() => { }); describe('open option', () => { - it('should open', (done) => { + it('should work with unspecified host', (done) => { const compiler = webpack(config); const server = new Server(compiler, { open: true, @@ -27,7 +27,7 @@ describe('open option', () => { server.close(() => { expect(open.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "http://127.0.0.1:8117/", + "http://localhost:8117/", Object { "wait": false, }, @@ -41,4 +41,85 @@ describe('open option', () => { compiler.run(() => {}); server.listen(port, 'localhost'); }); + + it('should work with "0.0.0.0" 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[0]).toMatchInlineSnapshot(` + Array [ + "http://localhost:8117/", + Object { + "wait": false, + }, + ] + `); + expect(open.mock.invocationCallOrder[0]).toEqual(1); + 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.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "http://localhost:8117/", + Object { + "wait": false, + }, + ] + `); + expect(open.mock.invocationCallOrder[0]).toEqual(1); + 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[0]).toMatchInlineSnapshot(` + Array [ + "http://localhost:8117/", + Object { + "wait": false, + }, + ] + `); + expect(open.mock.invocationCallOrder[0]).toEqual(1); + done(); + }); + }); + + compiler.run(() => {}); + server.listen(port, '::'); + }); });