diff --git a/bin/options.js b/bin/options.js index 3c46d9be9f..424ac2a21a 100644 --- a/bin/options.js +++ b/bin/options.js @@ -37,6 +37,10 @@ const options = { describe: 'Inline mode (set to false to disable including client scripts like livereload)', }, + profile: { + type: 'boolean', + describe: 'Print compilation profile data for progress steps', + }, progress: { type: 'boolean', describe: 'Print compilation progress in percentage', diff --git a/bin/webpack-dev-server.js b/bin/webpack-dev-server.js index deb015f6f1..26de7d4ba6 100755 --- a/bin/webpack-dev-server.js +++ b/bin/webpack-dev-server.js @@ -97,12 +97,6 @@ function startDevServer(config, options) { throw err; } - if (options.progress) { - new webpack.ProgressPlugin({ - profile: argv.profile, - }).apply(compiler); - } - try { server = new Server(compiler, options, log); } catch (err) { diff --git a/lib/Server.js b/lib/Server.js index 26d16dfadf..5538f6f454 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -154,23 +154,25 @@ class Server { } setupProgressPlugin() { - const progressPlugin = new webpack.ProgressPlugin( - (percent, msg, addInfo) => { - percent = Math.floor(percent * 100); + // for CLI output + new webpack.ProgressPlugin({ + profile: !!this.options.profile, + }).apply(this.compiler); - if (percent === 100) { - msg = 'Compilation completed'; - } + // for browser console output + new webpack.ProgressPlugin((percent, msg, addInfo) => { + percent = Math.floor(percent * 100); - if (addInfo) { - msg = `${msg} (${addInfo})`; - } + if (percent === 100) { + msg = 'Compilation completed'; + } - this.sockWrite(this.sockets, 'progress-update', { percent, msg }); + if (addInfo) { + msg = `${msg} (${addInfo})`; } - ); - progressPlugin.apply(this.compiler); + this.sockWrite(this.sockets, 'progress-update', { percent, msg }); + }).apply(this.compiler); } setupApp() { diff --git a/lib/options.json b/lib/options.json index f410e4927b..26dba30afc 100644 --- a/lib/options.json +++ b/lib/options.json @@ -258,6 +258,9 @@ } ] }, + "profile": { + "type": "boolean" + }, "progress": { "type": "boolean" }, @@ -426,6 +429,7 @@ "pfx": "should be {String|Buffer} (https://webpack.js.org/configuration/dev-server/#devserverpfx)", "pfxPassphrase": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserverpfxpassphrase)", "port": "should be {Number|String|Null} (https://webpack.js.org/configuration/dev-server/#devserverport)", + "profile": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverprofile)", "progress": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverprogress---cli-only)", "proxy": "should be {Object|Array} (https://webpack.js.org/configuration/dev-server/#devserverproxy)", "public": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserverpublic)", diff --git a/lib/utils/createConfig.js b/lib/utils/createConfig.js index 1902466620..0211561235 100644 --- a/lib/utils/createConfig.js +++ b/lib/utils/createConfig.js @@ -47,6 +47,10 @@ function createConfig(config, argv, { port }) { options.liveReload = false; } + if (argv.profile) { + options.profile = argv.profile; + } + if (argv.progress) { options.progress = argv.progress; } diff --git a/test/cli/cli.test.js b/test/cli/cli.test.js index 8bd1e04ddd..91e82b7bc6 100644 --- a/test/cli/cli.test.js +++ b/test/cli/cli.test.js @@ -20,6 +20,23 @@ describe('CLI', () => { .then((output) => { expect(output.code).toEqual(0); expect(output.stderr.includes('0% compiling')).toBe(true); + // should not profile + expect( + output.stderr.includes('ms after chunk modules optimization') + ).toBe(false); + done(); + }) + .catch(done); + }); + + it('--progress --profile', (done) => { + testBin('--progress --profile') + .then((output) => { + expect(output.code).toEqual(0); + // should profile + expect( + output.stderr.includes('ms after chunk modules optimization') + ).toBe(true); done(); }) .catch(done); diff --git a/test/e2e/Progress.test.js b/test/e2e/Progress.test.js new file mode 100644 index 0000000000..dd1a3588a6 --- /dev/null +++ b/test/e2e/Progress.test.js @@ -0,0 +1,71 @@ +'use strict'; + +/* eslint-disable + no-undef +*/ +const fs = require('fs'); +const { resolve } = require('path'); +const testServer = require('../helpers/test-server'); +const reloadConfig = require('../fixtures/reload-config/webpack.config'); +const runBrowser = require('../helpers/run-browser'); +const port = require('../ports-map').Progress; + +const cssFilePath = resolve(__dirname, '../fixtures/reload-config/main.css'); + +describe('client progress', () => { + describe('using hot', () => { + beforeAll((done) => { + fs.writeFileSync( + cssFilePath, + 'body { background-color: rgb(0, 0, 255); }' + ); + const options = { + port, + host: '0.0.0.0', + inline: true, + hot: true, + progress: true, + watchOptions: { + poll: 500, + }, + }; + testServer.startAwaitingCompilation(reloadConfig, options, done); + }); + + afterAll((done) => { + fs.unlinkSync(cssFilePath); + testServer.close(done); + }); + + describe('on browser client', () => { + it('should console.log progress', (done) => { + runBrowser().then(({ page, browser }) => { + const res = []; + page.waitForNavigation({ waitUntil: 'load' }).then(() => { + fs.writeFileSync( + cssFilePath, + 'body { background-color: rgb(255, 0, 0); }' + ); + page.waitFor(10000).then(() => { + browser.close().then(() => { + // check that there is some percentage progress output + const regExp = /^\[WDS\] [0-9]{1,3}% - /; + const match = res.find((line) => { + return regExp.test(line); + }); + // eslint-disable-next-line no-undefined + expect(match).not.toEqual(undefined); + done(); + }); + }); + }); + + page.goto(`http://localhost:${port}/main`); + page.on('console', (data) => { + res.push(data.text()); + }); + }); + }); + }); + }); +}); diff --git a/test/options.test.js b/test/options.test.js index 3ba18e17f7..a515ea28dc 100644 --- a/test/options.test.js +++ b/test/options.test.js @@ -309,6 +309,10 @@ describe('options', () => { success: ['', 0, null], failure: [false], }, + profile: { + success: [false], + failure: [''], + }, progress: { success: [false], failure: [''], diff --git a/test/ports-map.js b/test/ports-map.js index 6c56f8100c..551a9f514f 100644 --- a/test/ports-map.js +++ b/test/ports-map.js @@ -40,6 +40,9 @@ const portsList = { WebsocketServer: 1, ClientMode: 1, 'clientMode-option': 1, + Progress: 1, + 'progress-option': 1, + 'profile-option': 1, }; let startPort = 8079; diff --git a/test/server/profile-option.test.js b/test/server/profile-option.test.js new file mode 100644 index 0000000000..025e502d4f --- /dev/null +++ b/test/server/profile-option.test.js @@ -0,0 +1,53 @@ +'use strict'; + +const webpack = require('webpack'); +const Server = require('../../lib/Server'); +const config = require('../fixtures/simple-config/webpack.config'); +const port = require('../ports-map')['profile-option']; + +describe('profile', () => { + describe('output', () => { + let mockStderr; + + beforeAll(() => { + mockStderr = jest + .spyOn(process.stderr, 'write') + .mockImplementation(() => {}); + }); + + it('should show percentage progress with profile data', (done) => { + const compiler = webpack(config); + const server = new Server(compiler, { + port, + // profile will only have an effect when progress is enabled + progress: true, + profile: true, + }); + + compiler.hooks.done.tap('webpack-dev-server', () => { + const calls = mockStderr.mock.calls; + mockStderr.mockRestore(); + let foundProgress = false; + let foundProfile = false; + calls.forEach((call) => { + if (call[0].includes('0% compiling')) { + foundProgress = true; + } + + // this is an indicator that the profile option is enabled, + // so we should expect to find it in stderr since profile is enabled + if (call[0].includes('ms after chunk modules optimization')) { + foundProfile = true; + } + }); + expect(foundProgress).toBeTruthy(); + expect(foundProfile).toBeTruthy(); + + server.close(done); + }); + + compiler.run(() => {}); + server.listen(port, 'localhost'); + }); + }); +}); diff --git a/test/server/progress-option.test.js b/test/server/progress-option.test.js new file mode 100644 index 0000000000..ae2d23c07f --- /dev/null +++ b/test/server/progress-option.test.js @@ -0,0 +1,51 @@ +'use strict'; + +const webpack = require('webpack'); +const Server = require('../../lib/Server'); +const config = require('../fixtures/simple-config/webpack.config'); +const port = require('../ports-map')['progress-option']; + +describe('progress', () => { + describe('output', () => { + let mockStderr; + + beforeAll(() => { + mockStderr = jest + .spyOn(process.stderr, 'write') + .mockImplementation(() => {}); + }); + + it('should show percentage progress without profile data', (done) => { + const compiler = webpack(config); + const server = new Server(compiler, { + port, + progress: true, + }); + + compiler.hooks.done.tap('webpack-dev-server', () => { + const calls = mockStderr.mock.calls; + mockStderr.mockRestore(); + let foundProgress = false; + let foundProfile = false; + calls.forEach((call) => { + if (call[0].includes('0% compiling')) { + foundProgress = true; + } + + // this is an indicator that the profile option is enabled, + // so we should expect to not find it in stderr since it is not enabled + if (call[0].includes('ms after chunk modules optimization')) { + foundProfile = true; + } + }); + expect(foundProgress).toBeTruthy(); + expect(foundProfile).toBeFalsy(); + + server.close(done); + }); + + compiler.run(() => {}); + server.listen(port, 'localhost'); + }); + }); +});