diff --git a/client-src/default/index.js b/client-src/default/index.js index db102fd7f6..27c07880e5 100644 --- a/client-src/default/index.js +++ b/client-src/default/index.js @@ -2,7 +2,7 @@ /* global __resourceQuery WorkerGlobalScope self */ /* eslint prefer-destructuring: off */ - +const querystring = require('querystring'); const url = require('url'); const stripAnsi = require('strip-ansi'); const log = require('loglevel').getLogger('webpack-dev-server'); @@ -196,7 +196,10 @@ const socketUrl = url.format({ auth: urlParts.auth, hostname, port: urlParts.port, - pathname: urlParts.path == null || urlParts.path === '/' ? '/sockjs-node' : urlParts.path + // 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) }); socket(socketUrl, onSocketMsg); diff --git a/lib/Server.js b/lib/Server.js index 4a23f7cf79..5c4cb42203 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -101,6 +101,8 @@ function Server (compiler, options = {}, _log) { this.watchOptions = options.watchOptions || {}; this.contentBaseWatchers = []; + // Replace leading and trailing slashes to normalize path + this.sockPath = `/${options.sockPath ? options.sockPath.replace(/^\/|\/$/g, '') : 'sockjs-node'}`; // Listening for events const invalidPlugin = () => { @@ -798,7 +800,7 @@ Server.prototype.listen = function (port, hostname, fn) { }); socket.installHandlers(this.listeningApp, { - prefix: '/sockjs-node' + prefix: this.sockPath }); if (fn) { diff --git a/lib/options.json b/lib/options.json index fd60b5aaa0..5d0fb1fd00 100644 --- a/lib/options.json +++ b/lib/options.json @@ -51,6 +51,9 @@ "socket": { "type": "string" }, + "sockPath": { + "type": "string" + }, "watchOptions": { "type": "object" }, @@ -321,6 +324,7 @@ "filename": "should be {String|RegExp|Function} (https://webpack.js.org/configuration/dev-server/#devserver-filename-)", "port": "should be {String|Number} (https://webpack.js.org/configuration/dev-server/#devserver-port)", "socket": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserver-socket)", + "sockPath": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserver-sockPath)", "watchOptions": "should be {Object} (https://webpack.js.org/configuration/dev-server/#devserver-watchoptions)", "writeToDisk": "should be {Boolean|Function} (https://github.com/webpack/webpack-dev-middleware#writetodisk)", "headers": "should be {Object} (https://webpack.js.org/configuration/dev-server/#devserver-headers-)", diff --git a/lib/utils/addEntries.js b/lib/utils/addEntries.js index dd3c5e5365..146667a68d 100644 --- a/lib/utils/addEntries.js +++ b/lib/utils/addEntries.js @@ -21,7 +21,8 @@ function addEntries (config, options, server) { }; const domain = createDomain(options, app); - const entries = [ `${require.resolve('../../client/')}?${domain}` ]; + const sockPath = options.sockPath ? `&sockPath=${options.sockPath}` : ''; + const entries = [ `${require.resolve('../../client/')}?${domain}${sockPath}` ]; if (options.hotOnly) { entries.push(require.resolve('webpack/hot/only-dev-server')); diff --git a/package-lock.json b/package-lock.json index daf6094295..b09ce9b570 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6194,8 +6194,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, - "optional": true + "dev": true }, "jsesc": { "version": "1.3.0", @@ -10940,9 +10939,9 @@ "dev": true }, "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", "dev": true, "optional": true, "requires": { @@ -11478,8 +11477,7 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, - "optional": true + "dev": true }, "type-check": { "version": "0.3.2", diff --git a/test/Socket.test.js b/test/Socket.test.js new file mode 100644 index 0000000000..49c4108e4c --- /dev/null +++ b/test/Socket.test.js @@ -0,0 +1,49 @@ +'use strict'; + +const assert = require('assert'); +const request = require('supertest'); +const config = require('./fixtures/simple-config/webpack.config'); +const helper = require('./helper'); + +describe('socket options', () => { + let server; + let req; + + afterEach((done) => { + helper.close(done); + req = null; + server = null; + }); + describe('default behavior', () => { + beforeEach((done) => { + server = helper.start(config, {}, done); + req = request('http://localhost:8080'); + }); + + it('defaults to a path', () => { + assert.ok(server.sockPath.match(/\/[a-z0-9\-/]+[^/]$/)); + }); + + it('responds with a 200', (done) => { + req.get('/sockjs-node').expect(200, done); + }); + }); + + describe('socksPath option', () => { + const path = '/foo/test/bar'; + beforeEach((done) => { + server = helper.start(config, { + sockPath: '/foo/test/bar/' + }, done); + req = request('http://localhost:8080'); + }); + + it('sets the sock path correctly and strips leading and trailing /s', () => { + assert.equal(server.sockPath, path); + }); + + it('responds with a 200 second', (done) => { + req.get(path).expect(200, done); + }); + }); +});