diff --git a/bin/webpack-dev-server.js b/bin/webpack-dev-server.js index 4ab436d6a0..8001c4f57f 100755 --- a/bin/webpack-dev-server.js +++ b/bin/webpack-dev-server.js @@ -22,13 +22,10 @@ const Server = require('../lib/Server'); const colors = require('../lib/utils/colors'); const createConfig = require('../lib/utils/createConfig'); -const createDomain = require('../lib/utils/createDomain'); const createLogger = require('../lib/utils/createLogger'); const defaultTo = require('../lib/utils/defaultTo'); const findPort = require('../lib/utils/findPort'); const getVersions = require('../lib/utils/getVersions'); -const runBonjour = require('../lib/utils/runBonjour'); -const status = require('../lib/utils/status'); const tryParseInt = require('../lib/utils/tryParseInt'); let server; @@ -159,11 +156,6 @@ function startDevServer(config, options) { }).apply(compiler); } - const suffix = - options.inline !== false || options.lazy === true - ? '/' - : '/webpack-dev-server/'; - try { server = new Server(compiler, options, log); } catch (err) { @@ -200,52 +192,27 @@ function startDevServer(config, options) { } }); - server.listen(options.socket, options.host, (err) => { + runServer(); + } else if (options.port) { + runServer(); + } else { + // only run port finder if no port as been specified + findPort(server, DEFAULT_PORT, defaultPortRetry, (err, port) => { if (err) { throw err; } - // chmod 666 (rw rw rw) - const READ_WRITE = 438; - - fs.chmod(options.socket, READ_WRITE, (err) => { - if (err) { - throw err; - } - - const uri = createDomain(options, server.listeningApp) + suffix; - - status(uri, options, log, argv.color); - }); + options.port = port; + runServer(); }); - return; } - const startServer = () => { + function runServer() { server.listen(options.port, options.host, (err) => { if (err) { throw err; } - if (options.bonjour) { - runBonjour(options); - } - const uri = createDomain(options, server.listeningApp) + suffix; - status(uri, options, log, argv.color); }); - }; - - if (options.port) { - startServer(); - return; } - - // only run port finder if no port as been specified - findPort(server, DEFAULT_PORT, defaultPortRetry, (err, port) => { - if (err) { - throw err; - } - options.port = port; - startServer(); - }); } processOptions(config); diff --git a/client-src/default/index.js b/client-src/default/index.js index e231de5642..cf459fac4e 100644 --- a/client-src/default/index.js +++ b/client-src/default/index.js @@ -225,21 +225,35 @@ if ( ) { protocol = self.location.protocol; } + +// default values of the sock url if they are not provided +let sockHost = hostname; +let sockPath = '/sockjs-node'; +let sockPort = urlParts.port; +if ( + urlParts.path !== null && + // eslint-disable-next-line no-undefined + urlParts.path !== undefined && + urlParts.path !== '/' +) { + const parsedQuery = querystring.parse(urlParts.path); + // all of these sock url params are optionally passed in through + // __resourceQuery, so we need to fall back to the default if + // they are not provided + sockHost = parsedQuery.sockHost || sockHost; + sockPath = parsedQuery.sockPath || sockPath; + sockPort = parsedQuery.sockPort || sockPort; +} + const socketUrl = url.format({ protocol, auth: urlParts.auth, - hostname, - port: - urlParts.path == null || urlParts.path === '/' - ? urlParts.port - : querystring.parse(urlParts.path).sockPort || urlParts.port, + hostname: sockHost, + port: sockPort, // If sockPath 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 // client to open the socket to the correct location. - pathname: - urlParts.path == null || urlParts.path === '/' - ? '/sockjs-node' - : querystring.parse(urlParts.path).sockPath || urlParts.path, + pathname: sockPath, }); socket(socketUrl, onSocketMsg); diff --git a/examples/api/simple/server.js b/examples/api/simple/server.js index eed5090f45..0f0bc323b0 100644 --- a/examples/api/simple/server.js +++ b/examples/api/simple/server.js @@ -6,6 +6,7 @@ const webpackConfig = require('./webpack.config'); const compiler = Webpack(webpackConfig); const devServerOptions = Object.assign({}, webpackConfig.devServer, { + open: true, stats: { colors: true, }, diff --git a/jest.config.js b/jest.config.js index 0e3e92d900..322de40ff2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,7 +2,7 @@ module.exports = { testURL: 'http://localhost/', - collectCoverage: true, + collectCoverage: false, coveragePathIgnorePatterns: ['test'], moduleFileExtensions: ['js', 'json'], testMatch: ['**/test/**/*.test.js'], diff --git a/lib/Server.js b/lib/Server.js index 0d00acfa2a..34bc46f979 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -27,6 +27,9 @@ const validateOptions = require('schema-utils'); const updateCompiler = require('./utils/updateCompiler'); const createLogger = require('./utils/createLogger'); const getCertificate = require('./utils/getCertificate'); +const status = require('./utils/status'); +const createDomain = require('./utils/createDomain'); +const runBonjour = require('./utils/runBonjour'); const routes = require('./utils/routes'); const schema = require('./options.json'); @@ -813,6 +816,41 @@ class Server { prefix: this.sockPath, }); + if (this.options.bonjour) { + runBonjour(this.options); + } + + const showStatus = () => { + const suffix = + this.options.inline !== false || this.options.lazy === true + ? '/' + : '/webpack-dev-server/'; + + const uri = `${createDomain(this.options, this.listeningApp)}${suffix}`; + + status( + uri, + this.options, + this.log, + this.options.stats && this.options.stats.colors + ); + }; + + if (this.options.socket) { + // chmod 666 (rw rw rw) + const READ_WRITE = 438; + + fs.chmod(this.options.socket, READ_WRITE, (err) => { + if (err) { + throw err; + } + + showStatus(); + }); + } else { + showStatus(); + } + if (fn) { fn.call(this.listeningApp, err); } diff --git a/lib/options.json b/lib/options.json index dfabc5dd95..c1b7759e2b 100644 --- a/lib/options.json +++ b/lib/options.json @@ -291,6 +291,9 @@ "setup": { "instanceof": "Function" }, + "sockHost": { + "type": "string" + }, "sockPath": { "type": "string" }, @@ -399,8 +402,9 @@ "serveIndex": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverserveindex)", "serverSideRender": "should be {Boolean} (https://github.com/webpack/webpack-dev-middleware#serversiderender)", "setup": "should be {Function} (https://webpack.js.org/configuration/dev-server/#devserversetup)", + "sockHost": "should be {String|Null} (https://webpack.js.org/configuration/dev-server/#devserversockhost)", "sockPath": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserversockpath)", - "sockPort": "should be {Number|String|Null}", + "sockPort": "should be {Number|String|Null} (https://webpack.js.org/configuration/dev-server/#devserversockport)", "socket": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserversocket)", "staticOptions": "should be {Object} (https://webpack.js.org/configuration/dev-server/#devserverstaticoptions)", "stats": "should be {Object|Boolean} (https://webpack.js.org/configuration/dev-server/#devserverstats-)", diff --git a/lib/utils/addEntries.js b/lib/utils/addEntries.js index 9754fbeb00..aa9f621fe4 100644 --- a/lib/utils/addEntries.js +++ b/lib/utils/addEntries.js @@ -16,11 +16,12 @@ function addEntries(config, options, server) { }; const domain = createDomain(options, app); + const sockHost = options.sockHost ? `&sockHost=${options.sockHost}` : ''; const sockPath = options.sockPath ? `&sockPath=${options.sockPath}` : ''; const sockPort = options.sockPort ? `&sockPort=${options.sockPort}` : ''; const clientEntry = `${require.resolve( '../../client/' - )}?${domain}${sockPath}${sockPort}`; + )}?${domain}${sockHost}${sockPath}${sockPort}`; let hotEntry; if (options.hotOnly) { diff --git a/lib/utils/createConfig.js b/lib/utils/createConfig.js index 38d0d1a335..510b3f7307 100644 --- a/lib/utils/createConfig.js +++ b/lib/utils/createConfig.js @@ -30,6 +30,10 @@ function createConfig(config, argv, { port }) { options.socket = argv.socket; } + if (argv.sockHost) { + options.sockHost = argv.sockHost; + } + if (argv.sockPath) { options.sockPath = argv.sockPath; } diff --git a/lib/utils/status.js b/lib/utils/status.js index f9acb1aa6c..4c552a4639 100644 --- a/lib/utils/status.js +++ b/lib/utils/status.js @@ -3,6 +3,7 @@ const open = require('opn'); const colors = require('./colors'); +// TODO: don't emit logs when webpack-dev-server is used via Node.js API function status(uri, options, log, useColor) { const contentBase = Array.isArray(options.contentBase) ? options.contentBase.join(', ') @@ -51,7 +52,7 @@ function status(uri, options, log, useColor) { openMessage += `: ${options.open}`; } - open(uri + (options.openPage || ''), openOptions).catch(() => { + open(`${uri}${options.openPage || ''}`, openOptions).catch(() => { log.warn( `${openMessage}. If you are running in a headless environment, please do not use the --open flag` ); diff --git a/package-lock.json b/package-lock.json index bdcf838dc2..91ade8d6c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1708,15 +1708,15 @@ } }, "babel-loader": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.5.tgz", - "integrity": "sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", + "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", "dev": true, "requires": { "find-cache-dir": "^2.0.0", "loader-utils": "^1.0.2", "mkdirp": "^0.5.1", - "util.promisify": "^1.0.0" + "pify": "^4.0.1" } }, "babel-plugin-istanbul": { diff --git a/package.json b/package.json index 18b02f9b76..aecbd43e13 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,10 @@ "lint": "eslint bin lib test examples client-src", "pretty": "prettier --loglevel warn --write \"**/*.{js,css,md,json,yml}\"", "test:only": "jest --runInBand", - "test:watch": "jest --watch --runInBand", - "test:coverage": "jest --runInBand --collectCoverageFrom='src/**/*.js' --coverage", - "pretest": "npm run lint", + "test:coverage": "npm run test:only -- --coverage", + "test:watch": "npm run test:coverage --watch", "test": "npm run test:coverage", + "pretest": "npm run lint", "prepare": "rimraf ./ssl/*.pem && npm run -s transpile:index && npm run -s build:live && npm run -s build:index && npm run -s build:sockjs", "transpile:index": "babel client-src/default --out-dir client --ignore *.config.js", "build:index": "webpack ./client-src/default/index.js -o client/index.bundle.js --color --config client-src/default/webpack.config.js", @@ -65,7 +65,7 @@ "@babel/cli": "7.4.4", "@babel/core": "7.4.4", "@babel/preset-env": "7.4.4", - "babel-loader": "8.0.5", + "babel-loader": "8.0.6", "copy-webpack-plugin": "5.0.3", "css-loader": "2.1.1", "eslint": "5.16.0", diff --git a/test/Client.test.js b/test/Client.test.js index 2fa749856f..84292e11da 100644 --- a/test/Client.test.js +++ b/test/Client.test.js @@ -156,3 +156,76 @@ describe('Client complex inline script path with sockPort', () => { }); }); }); + +// previously, using sockPort without sockPath had the ability +// to alter the sockPath (based on a bug in client-src/index.js) +// so we need to make sure sockPath is not altered in this case +describe('Client complex inline script path with sockPort, no sockPath', () => { + beforeAll((done) => { + const options = { + port: 9000, + host: '0.0.0.0', + inline: true, + watchOptions: { + poll: true, + }, + sockPort: 8080, + }; + helper.startAwaitingCompilation(config, options, done); + }); + + afterAll(helper.close); + + describe('browser client', () => { + jest.setTimeout(30000); + + it('uses the correct sockPort and sockPath', (done) => { + runBrowser().then(({ page, browser }) => { + page + .waitForRequest((requestObj) => requestObj.url().match(/sockjs-node/)) + .then((requestObj) => { + expect(requestObj.url()).toMatch( + /^http:\/\/localhost:8080\/sockjs-node/ + ); + browser.close().then(done); + }); + page.goto('http://localhost:9000/main'); + }); + }); + }); +}); + +describe('Client complex inline script path with sockHost', () => { + beforeAll((done) => { + const options = { + port: 9000, + host: '0.0.0.0', + inline: true, + watchOptions: { + poll: true, + }, + sockHost: 'myhost.test', + }; + helper.startAwaitingCompilation(config, options, done); + }); + + afterAll(helper.close); + + describe('browser client', () => { + jest.setTimeout(30000); + + it('uses the correct sockHost', (done) => { + runBrowser().then(({ page, browser }) => { + page + .waitForRequest((requestObj) => requestObj.url().match(/sockjs-node/)) + .then((requestObj) => { + expect(requestObj.url()).toMatch( + /^http:\/\/myhost\.test:9000\/sockjs-node/ + ); + browser.close().then(done); + }); + page.goto('http://localhost:9000/main'); + }); + }); + }); +}); diff --git a/test/Server.test.js b/test/Server.test.js index 1c4a17bb78..bb87ba7994 100644 --- a/test/Server.test.js +++ b/test/Server.test.js @@ -3,6 +3,15 @@ const { relative, sep } = require('path'); const webpack = require('webpack'); const request = require('supertest'); +// Mock opn before loading Server +jest.mock('opn'); +// eslint-disable-next-line import/newline-after-import +const opn = require('opn'); +opn.mockImplementation(() => { + return { + catch: jest.fn(), + }; +}); const Server = require('../lib/Server'); const config = require('./fixtures/simple-config/webpack.config'); const helper = require('./helper'); @@ -189,6 +198,26 @@ describe('Server', () => { server.listen(8080, 'localhost'); }); }); + + it('should open', () => { + return new Promise((res) => { + const compiler = webpack(config); + const server = new Server(compiler, { + open: true, + }); + + compiler.hooks.done.tap('webpack-dev-server', () => { + expect(opn.mock.calls[0]).toEqual(['http://localhost:8080/', {}]); + expect(opn.mock.invocationCallOrder[0]).toEqual(1); + server.close(() => { + res(); + }); + }); + + compiler.run(() => {}); + server.listen(8080, 'localhost'); + }); + }); }); describe('host', () => { diff --git a/test/options.test.js b/test/options.test.js index 5bf520df6a..e659ffcddd 100644 --- a/test/options.test.js +++ b/test/options.test.js @@ -337,6 +337,10 @@ describe('options', () => { success: [''], failure: [false], }, + sockHost: { + success: [''], + failure: [false], + }, sockPath: { success: [''], failure: [false],