From 2b80cbb0cec1e092751d61c4a8348c273023471b Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Mon, 1 Jul 2019 21:20:48 -0500 Subject: [PATCH 1/2] feat(client): ws client mode --- lib/utils/getSocketClientPath.js | 7 +- test/e2e/Client.test.js | 82 ++++++++++++++++++- test/e2e/ClientMode.test.js | 37 +++++++++ test/e2e/ProvidePlugin.test.js | 41 +++++++++- .../e2e/__snapshots__/ClientMode.test.js.snap | 8 ++ test/fixtures/provide-plugin-ws-config/foo.js | 8 ++ .../webpack.config.js | 11 +++ 7 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/provide-plugin-ws-config/foo.js create mode 100644 test/fixtures/provide-plugin-ws-config/webpack.config.js diff --git a/lib/utils/getSocketClientPath.js b/lib/utils/getSocketClientPath.js index 950d07802a..6a2274c2d9 100644 --- a/lib/utils/getSocketClientPath.js +++ b/lib/utils/getSocketClientPath.js @@ -5,10 +5,13 @@ function getSocketClientPath(options) { let clientImplFound = true; switch (typeof options.clientMode) { case 'string': - // could be 'sockjs', in the future 'ws', or a path that should be required + // could be 'sockjs', 'ws', or a path that should be required if (options.clientMode === 'sockjs') { // eslint-disable-next-line global-require ClientImplementation = require('../../client/clients/SockJSClient'); + } else if (options.clientMode === 'ws') { + // eslint-disable-next-line global-require + ClientImplementation = require('../../client/clients/WebsocketClient'); } else { try { // eslint-disable-next-line global-require, import/no-dynamic-require @@ -24,7 +27,7 @@ function getSocketClientPath(options) { if (!clientImplFound) { throw new Error( - "clientMode must be a string denoting a default implementation (e.g. 'sockjs') or a full path to " + + "clientMode must be a string denoting a default implementation (e.g. 'sockjs', 'ws') or a full path to " + 'a JS file which exports a class extending BaseClient (webpack-dev-server/client-src/clients/BaseClient) ' + 'via require.resolve(...)' ); diff --git a/test/e2e/Client.test.js b/test/e2e/Client.test.js index 90eb2ffe5a..e970994f46 100644 --- a/test/e2e/Client.test.js +++ b/test/e2e/Client.test.js @@ -13,7 +13,7 @@ const port = require('../ports-map').Client; const cssFilePath = resolve(__dirname, '../fixtures/reload-config/main.css'); describe('reload', () => { - describe('hot', () => { + describe('hot with default clientMode (sockjs)', () => { beforeAll((done) => { fs.writeFileSync( cssFilePath, @@ -91,6 +91,86 @@ describe('reload', () => { }); }); + describe('hot with clientMode ws', () => { + beforeAll((done) => { + fs.writeFileSync( + cssFilePath, + 'body { background-color: rgb(0, 0, 255); }' + ); + const options = { + port, + host: '0.0.0.0', + inline: true, + hot: true, + clientMode: 'ws', + serverMode: require.resolve('../../lib/servers/WebsocketServer'), + watchOptions: { + poll: 500, + }, + }; + testServer.startAwaitingCompilation(reloadConfig, options, done); + }); + + afterAll((done) => { + fs.unlinkSync(cssFilePath); + testServer.close(done); + }); + + describe('on browser client', () => { + it('should hot reload without page refresh', (done) => { + runBrowser().then(({ page, browser }) => { + let refreshed = false; + page.waitForNavigation({ waitUntil: 'load' }).then(() => { + page + .evaluate(() => { + const body = document.body; + const bgColor = getComputedStyle(body)['background-color']; + return bgColor; + }) + .then((color) => { + page.setRequestInterception(true).then(() => { + page.on('request', (req) => { + if ( + req.isNavigationRequest() && + req.frame() === page.mainFrame() && + req.url() === `http://localhost:${port}/main` + ) { + refreshed = true; + } + req.continue(); + }); + fs.writeFileSync( + cssFilePath, + 'body { background-color: rgb(255, 0, 0); }' + ); + page.waitFor(10000).then(() => { + page + .evaluate(() => { + const body = document.body; + const bgColor = getComputedStyle(body)[ + 'background-color' + ]; + return bgColor; + }) + .then((color2) => { + browser.close().then(() => { + expect(color).toEqual('rgb(0, 0, 255)'); + expect(color2).toEqual('rgb(255, 0, 0)'); + expect(refreshed).toBeFalsy(); + done(); + }); + }); + }); + }); + }); + }); + + page.goto(`http://localhost:${port}/main`); + }); + }); + }); + }); + describe('inline', () => { beforeAll((done) => { fs.writeFileSync( diff --git a/test/e2e/ClientMode.test.js b/test/e2e/ClientMode.test.js index 8f7b5d7a54..0f755006af 100644 --- a/test/e2e/ClientMode.test.js +++ b/test/e2e/ClientMode.test.js @@ -42,6 +42,43 @@ describe('clientMode', () => { }); }); + describe('ws', () => { + beforeAll((done) => { + const options = { + port, + host: '0.0.0.0', + inline: true, + clientMode: 'ws', + serverMode: require.resolve('../../lib/servers/WebsocketServer'), + }; + testServer.startAwaitingCompilation(config, options, done); + }); + + describe('on browser client', () => { + it('logs as usual', (done) => { + runBrowser().then(({ page, browser }) => { + const res = []; + page.goto(`http://localhost:${port}/main`); + page.on('console', ({ _text }) => { + res.push(_text); + }); + + setTimeout(() => { + testServer.close(() => { + // make sure the client gets the close message + setTimeout(() => { + browser.close().then(() => { + expect(res).toMatchSnapshot(); + done(); + }); + }, 1000); + }); + }, 3000); + }); + }); + }); + }); + describe('custom client', () => { beforeAll((done) => { const options = { diff --git a/test/e2e/ProvidePlugin.test.js b/test/e2e/ProvidePlugin.test.js index 99d44cf910..d7022167a3 100644 --- a/test/e2e/ProvidePlugin.test.js +++ b/test/e2e/ProvidePlugin.test.js @@ -2,11 +2,12 @@ const testServer = require('../helpers/test-server'); const config = require('../fixtures/provide-plugin-config/webpack.config'); +const wsConfig = require('../fixtures/provide-plugin-ws-config/webpack.config'); const runBrowser = require('../helpers/run-browser'); const port = require('../ports-map').ProvidePlugin; describe('ProvidePlugin', () => { - describe('inline', () => { + describe('inline with default clientMode (sockjs)', () => { beforeAll((done) => { const options = { port, @@ -42,6 +43,44 @@ describe('ProvidePlugin', () => { }); }); + describe('inline with clientMode ws', () => { + beforeAll((done) => { + const options = { + port, + host: '0.0.0.0', + inline: true, + clientMode: 'ws', + serverMode: require.resolve('../../lib/servers/WebsocketServer'), + watchOptions: { + poll: true, + }, + }; + testServer.startAwaitingCompilation(wsConfig, options, done); + }); + + afterAll(testServer.close); + + describe('on browser client', () => { + it('should inject ws client implementation', (done) => { + runBrowser().then(({ page, browser }) => { + page.waitForNavigation({ waitUntil: 'load' }).then(() => { + page + .evaluate(() => { + return window.injectedClient === window.expectedClient; + }) + .then((isCorrectClient) => { + browser.close().then(() => { + expect(isCorrectClient).toBeTruthy(); + done(); + }); + }); + }); + page.goto(`http://localhost:${port}/main`); + }); + }); + }); + }); + describe('not inline', () => { beforeAll((done) => { const options = { diff --git a/test/e2e/__snapshots__/ClientMode.test.js.snap b/test/e2e/__snapshots__/ClientMode.test.js.snap index e7517d8b16..bd0977d6d3 100644 --- a/test/e2e/__snapshots__/ClientMode.test.js.snap +++ b/test/e2e/__snapshots__/ClientMode.test.js.snap @@ -20,3 +20,11 @@ Array [ "[WDS] Disconnected!", ] `; + +exports[`clientMode ws on browser client logs as usual 1`] = ` +Array [ + "Hey.", + "[WDS] Live Reloading enabled.", + "[WDS] Disconnected!", +] +`; diff --git a/test/fixtures/provide-plugin-ws-config/foo.js b/test/fixtures/provide-plugin-ws-config/foo.js new file mode 100644 index 0000000000..dbc1f86da8 --- /dev/null +++ b/test/fixtures/provide-plugin-ws-config/foo.js @@ -0,0 +1,8 @@ +'use strict'; + +// 'npm run prepare' must be run for this to work during testing +const WebsocketClient = require('../../../client/clients/WebsocketClient'); + +window.expectedClient = WebsocketClient; +// eslint-disable-next-line camelcase, no-undef +window.injectedClient = __webpack_dev_server_client__; diff --git a/test/fixtures/provide-plugin-ws-config/webpack.config.js b/test/fixtures/provide-plugin-ws-config/webpack.config.js new file mode 100644 index 0000000000..50d700c09f --- /dev/null +++ b/test/fixtures/provide-plugin-ws-config/webpack.config.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = { + mode: 'development', + context: __dirname, + entry: './foo.js', + output: { + path: '/', + }, + node: false, +}; From 66948723aad7768b9be977415634578577ad9844 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Mon, 1 Jul 2019 21:49:32 -0500 Subject: [PATCH 2/2] test(client): changed client and client mode tests to use arrays --- test/e2e/Client.test.js | 313 ++++++------------ test/e2e/ClientMode.test.js | 148 +++------ .../e2e/__snapshots__/ClientMode.test.js.snap | 6 +- 3 files changed, 148 insertions(+), 319 deletions(-) diff --git a/test/e2e/Client.test.js b/test/e2e/Client.test.js index e970994f46..6f85049cf5 100644 --- a/test/e2e/Client.test.js +++ b/test/e2e/Client.test.js @@ -13,237 +13,112 @@ const port = require('../ports-map').Client; const cssFilePath = resolve(__dirname, '../fixtures/reload-config/main.css'); describe('reload', () => { - describe('hot with default clientMode (sockjs)', () => { - beforeAll((done) => { - fs.writeFileSync( - cssFilePath, - 'body { background-color: rgb(0, 0, 255); }' - ); - const options = { - port, - host: '0.0.0.0', - inline: true, + const modes = [ + { + title: 'hot with default clientMode (sockjs)', + options: { hot: true, - watchOptions: { - poll: 500, - }, - }; - testServer.startAwaitingCompilation(reloadConfig, options, done); - }); - - afterAll((done) => { - fs.unlinkSync(cssFilePath); - testServer.close(done); - }); - - describe('on browser client', () => { - it('should hot reload without page refresh', (done) => { - runBrowser().then(({ page, browser }) => { - let refreshed = false; - page.waitForNavigation({ waitUntil: 'load' }).then(() => { - page - .evaluate(() => { - const body = document.body; - const bgColor = getComputedStyle(body)['background-color']; - return bgColor; - }) - .then((color) => { - page.setRequestInterception(true).then(() => { - page.on('request', (req) => { - if ( - req.isNavigationRequest() && - req.frame() === page.mainFrame() && - req.url() === `http://localhost:${port}/main` - ) { - refreshed = true; - } - req.continue(); - }); - fs.writeFileSync( - cssFilePath, - 'body { background-color: rgb(255, 0, 0); }' - ); - page.waitFor(10000).then(() => { - page - .evaluate(() => { - const body = document.body; - const bgColor = getComputedStyle(body)[ - 'background-color' - ]; - return bgColor; - }) - .then((color2) => { - browser.close().then(() => { - expect(color).toEqual('rgb(0, 0, 255)'); - expect(color2).toEqual('rgb(255, 0, 0)'); - expect(refreshed).toBeFalsy(); - done(); - }); - }); - }); - }); - }); - }); - - page.goto(`http://localhost:${port}/main`); - }); - }); - }); - }); - - describe('hot with clientMode ws', () => { - beforeAll((done) => { - fs.writeFileSync( - cssFilePath, - 'body { background-color: rgb(0, 0, 255); }' - ); - const options = { - port, - host: '0.0.0.0', - inline: true, + }, + shouldRefresh: false, + }, + { + title: 'hot with clientMode ws', + options: { hot: true, clientMode: 'ws', serverMode: require.resolve('../../lib/servers/WebsocketServer'), - watchOptions: { - poll: 500, - }, - }; - testServer.startAwaitingCompilation(reloadConfig, options, done); - }); - - afterAll((done) => { - fs.unlinkSync(cssFilePath); - testServer.close(done); - }); - - describe('on browser client', () => { - it('should hot reload without page refresh', (done) => { - runBrowser().then(({ page, browser }) => { - let refreshed = false; - page.waitForNavigation({ waitUntil: 'load' }).then(() => { - page - .evaluate(() => { - const body = document.body; - const bgColor = getComputedStyle(body)['background-color']; - return bgColor; - }) - .then((color) => { - page.setRequestInterception(true).then(() => { - page.on('request', (req) => { - if ( - req.isNavigationRequest() && - req.frame() === page.mainFrame() && - req.url() === `http://localhost:${port}/main` - ) { - refreshed = true; - } - req.continue(); - }); - fs.writeFileSync( - cssFilePath, - 'body { background-color: rgb(255, 0, 0); }' - ); - page.waitFor(10000).then(() => { - page - .evaluate(() => { - const body = document.body; - const bgColor = getComputedStyle(body)[ - 'background-color' - ]; - return bgColor; - }) - .then((color2) => { - browser.close().then(() => { - expect(color).toEqual('rgb(0, 0, 255)'); - expect(color2).toEqual('rgb(255, 0, 0)'); - expect(refreshed).toBeFalsy(); - done(); - }); - }); - }); - }); - }); - }); + }, + shouldRefresh: false, + }, + { + title: 'inline', + options: { + hot: false, + }, + shouldRefresh: true, + }, + ]; - page.goto(`http://localhost:${port}/main`); - }); + modes.forEach((mode) => { + describe(mode.title, () => { + beforeAll((done) => { + fs.writeFileSync( + cssFilePath, + 'body { background-color: rgb(0, 0, 255); }' + ); + const options = Object.assign( + {}, + { + port, + host: '0.0.0.0', + inline: true, + watchOptions: { + poll: 500, + }, + }, + mode.options + ); + testServer.startAwaitingCompilation(reloadConfig, options, done); }); - }); - }); - describe('inline', () => { - beforeAll((done) => { - fs.writeFileSync( - cssFilePath, - 'body { background-color: rgb(0, 0, 255); }' - ); - const options = { - port, - host: '0.0.0.0', - inline: true, - hot: false, - watchOptions: { - poll: 500, - }, - }; - testServer.startAwaitingCompilation(reloadConfig, options, done); - }); - - afterAll((done) => { - fs.unlinkSync(cssFilePath); - testServer.close(done); - }); + afterAll((done) => { + fs.unlinkSync(cssFilePath); + testServer.close(done); + }); - describe('on browser client', () => { - it('should reload with page refresh', (done) => { - runBrowser().then(({ page, browser }) => { - let refreshed = false; - page.waitForNavigation({ waitUntil: 'load' }).then(() => { - page - .evaluate(() => { - const body = document.body; - const bgColor = getComputedStyle(body)['background-color']; - return bgColor; - }) - .then((color) => { - page.setRequestInterception(true).then(() => { - page.on('request', (req) => { - if ( - req.isNavigationRequest() && - req.frame() === page.mainFrame() && - req.url() === `http://localhost:${port}/main` - ) { - refreshed = true; - } - req.continue(); - }); - fs.writeFileSync( - cssFilePath, - 'body { background-color: rgb(255, 0, 0); }' - ); - page.waitFor(10000).then(() => { - page - .evaluate(() => { - const body = document.body; - const bgColor = getComputedStyle(body)[ - 'background-color' - ]; - return bgColor; - }) - .then((color2) => { - browser.close().then(() => { - expect(color).toEqual('rgb(0, 0, 255)'); - expect(color2).toEqual('rgb(255, 0, 0)'); - expect(refreshed).toBeTruthy(); - done(); + describe('on browser client', () => { + it(`should reload ${ + mode.shouldRefresh ? 'with' : 'without' + } page refresh`, (done) => { + runBrowser().then(({ page, browser }) => { + let refreshed = false; + page.waitForNavigation({ waitUntil: 'load' }).then(() => { + page + .evaluate(() => { + const body = document.body; + const bgColor = getComputedStyle(body)['background-color']; + return bgColor; + }) + .then((color) => { + page.setRequestInterception(true).then(() => { + page.on('request', (req) => { + if ( + req.isNavigationRequest() && + req.frame() === page.mainFrame() && + req.url() === `http://localhost:${port}/main` + ) { + refreshed = true; + } + req.continue(); + }); + fs.writeFileSync( + cssFilePath, + 'body { background-color: rgb(255, 0, 0); }' + ); + page.waitFor(10000).then(() => { + page + .evaluate(() => { + const body = document.body; + const bgColor = getComputedStyle(body)[ + 'background-color' + ]; + return bgColor; + }) + .then((color2) => { + browser.close().then(() => { + expect(color).toEqual('rgb(0, 0, 255)'); + expect(color2).toEqual('rgb(255, 0, 0)'); + expect(refreshed).toEqual(mode.shouldRefresh); + done(); + }); }); - }); + }); }); }); - }); - }); + }); - page.goto(`http://localhost:${port}/main`); + page.goto(`http://localhost:${port}/main`); + }); }); }); }); diff --git a/test/e2e/ClientMode.test.js b/test/e2e/ClientMode.test.js index 0f755006af..7c9a1b16c9 100644 --- a/test/e2e/ClientMode.test.js +++ b/test/e2e/ClientMode.test.js @@ -6,112 +6,66 @@ const runBrowser = require('../helpers/run-browser'); const port = require('../ports-map').ClientMode; describe('clientMode', () => { - describe('sockjs', () => { - beforeAll((done) => { - const options = { - port, - host: '0.0.0.0', - inline: true, + const modes = [ + { + title: 'sockjs', + options: { clientMode: 'sockjs', - }; - testServer.startAwaitingCompilation(config, options, done); - }); - - describe('on browser client', () => { - it('logs as usual', (done) => { - runBrowser().then(({ page, browser }) => { - const res = []; - page.goto(`http://localhost:${port}/main`); - page.on('console', ({ _text }) => { - res.push(_text); - }); - - setTimeout(() => { - testServer.close(() => { - // make sure the client gets the close message - setTimeout(() => { - browser.close().then(() => { - expect(res).toMatchSnapshot(); - done(); - }); - }, 1000); - }); - }, 3000); - }); - }); - }); - }); - - describe('ws', () => { - beforeAll((done) => { - const options = { - port, - host: '0.0.0.0', - inline: true, + }, + }, + { + title: 'ws', + options: { clientMode: 'ws', serverMode: require.resolve('../../lib/servers/WebsocketServer'), - }; - testServer.startAwaitingCompilation(config, options, done); - }); - - describe('on browser client', () => { - it('logs as usual', (done) => { - runBrowser().then(({ page, browser }) => { - const res = []; - page.goto(`http://localhost:${port}/main`); - page.on('console', ({ _text }) => { - res.push(_text); - }); - - setTimeout(() => { - testServer.close(() => { - // make sure the client gets the close message - setTimeout(() => { - browser.close().then(() => { - expect(res).toMatchSnapshot(); - done(); - }); - }, 1000); - }); - }, 3000); - }); - }); - }); - }); - - describe('custom client', () => { - beforeAll((done) => { - const options = { - port, - host: '0.0.0.0', - inline: true, + }, + }, + { + title: 'custom client', + options: { clientMode: require.resolve( '../fixtures/custom-client/CustomSockJSClient' ), - }; - testServer.startAwaitingCompilation(config, options, done); - }); + }, + }, + ]; - describe('on browser client', () => { - it('logs additional messages to console', (done) => { - runBrowser().then(({ page, browser }) => { - const res = []; - page.goto(`http://localhost:${port}/main`); - page.on('console', ({ _text }) => { - res.push(_text); - }); + modes.forEach((mode) => { + describe(mode.title, () => { + beforeAll((done) => { + const options = Object.assign( + {}, + { + port, + host: '0.0.0.0', + inline: true, + }, + mode.options + ); + testServer.startAwaitingCompilation(config, options, done); + }); - setTimeout(() => { - testServer.close(() => { - // make sure the client gets the close message - setTimeout(() => { - browser.close().then(() => { - expect(res).toMatchSnapshot(); - done(); - }); - }, 1000); + describe('on browser client', () => { + it('logs correctly', (done) => { + runBrowser().then(({ page, browser }) => { + const res = []; + page.goto(`http://localhost:${port}/main`); + page.on('console', ({ _text }) => { + res.push(_text); }); - }, 3000); + + setTimeout(() => { + testServer.close(() => { + // make sure the client gets the close message + setTimeout(() => { + browser.close().then(() => { + expect(res).toMatchSnapshot(); + done(); + }); + }, 1000); + }); + }, 3000); + }); }); }); }); diff --git a/test/e2e/__snapshots__/ClientMode.test.js.snap b/test/e2e/__snapshots__/ClientMode.test.js.snap index bd0977d6d3..1e49239690 100644 --- a/test/e2e/__snapshots__/ClientMode.test.js.snap +++ b/test/e2e/__snapshots__/ClientMode.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`clientMode custom client on browser client logs additional messages to console 1`] = ` +exports[`clientMode custom client on browser client logs correctly 1`] = ` Array [ "Hey.", "open", @@ -13,7 +13,7 @@ Array [ ] `; -exports[`clientMode sockjs on browser client logs as usual 1`] = ` +exports[`clientMode sockjs on browser client logs correctly 1`] = ` Array [ "Hey.", "[WDS] Live Reloading enabled.", @@ -21,7 +21,7 @@ Array [ ] `; -exports[`clientMode ws on browser client logs as usual 1`] = ` +exports[`clientMode ws on browser client logs correctly 1`] = ` Array [ "Hey.", "[WDS] Live Reloading enabled.",