From e03944a4b32ca855d1a8c2f52e47bb3f955936fb Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Fri, 22 Mar 2019 12:20:41 -0500 Subject: [PATCH 01/13] HMR plugin insertion and inline entry insertion in server --- lib/Server.js | 55 +++++++++++++++++++++++++ lib/utils/addEntries.js | 14 ++++++- package-lock.json | 41 +++++-------------- test/Client.test.js | 2 - test/Entry.test.js | 15 +++++++ test/Hot.test.js | 89 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 182 insertions(+), 34 deletions(-) create mode 100644 test/Hot.test.js diff --git a/lib/Server.js b/lib/Server.js index ff21ccee94..f005f9a435 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -89,6 +89,61 @@ class Server { throw new Error("'filename' option must be set in lazy mode."); } + if (options.inline !== false) { + const findHMRPlugin = (config) => { + if (!config.plugins) { + return undefined; + } + + return config.plugins.find( + (plugin) => plugin.constructor === webpack.HotModuleReplacementPlugin + ); + }; + + const compilers = []; + const compilersWithoutHMR = []; + let webpackConfig; + if (compiler.compilers) { + webpackConfig = []; + compiler.compilers.forEach((compiler) => { + webpackConfig.push(compiler.options); + compilers.push(compiler); + if (!findHMRPlugin(compiler.options)) { + compilersWithoutHMR.push(compiler); + } + }); + } else { + webpackConfig = compiler.options; + compilers.push(compiler); + if (!findHMRPlugin(compiler.options)) { + compilersWithoutHMR.push(compiler); + } + } + + // it's possible that we should clone the config before doing + // this, but it seems safe not to since it actually reflects + // the changes we are making to the compiler + // important: this relies on the fact that addEntries now + // prevents duplicate new entries. + Server.addDevServerEntrypoints(webpackConfig, options); + compilers.forEach((compiler) => { + const config = compiler.options; + compiler.hooks.entryOption.call(config.context, config.entry); + }); + + // do not apply the plugin unless it didn't exist before. + if (options.hot || options.hotOnly) { + compilersWithoutHMR.forEach((compiler) => { + // addDevServerEntrypoints above should have added the plugin + // to the compiler options + const plugin = findHMRPlugin(compiler.options); + if (plugin) { + plugin.apply(compiler); + } + }); + } + } + this.stats = options.stats && Object.keys(options.stats).length ? options.stats diff --git a/lib/utils/addEntries.js b/lib/utils/addEntries.js index f4aa210b39..6fc0f21114 100644 --- a/lib/utils/addEntries.js +++ b/lib/utils/addEntries.js @@ -8,6 +8,7 @@ function addEntries(config, options, server) { // we're stubbing the app in this method as it's static and doesn't require // a server to be supplied. createDomain requires an app with the // address() signature. + const app = server || { address() { return { port: options.port }; @@ -35,13 +36,22 @@ function addEntries(config, options, server) { const clone = {}; Object.keys(entry).forEach((key) => { - clone[key] = entries.concat(entry[key]); + // entry[key] should be a string here + clone[key] = prependEntry(entry[key]); }); return clone; } - return entries.concat(entry); + // in this case, entry is a string or an array. + // make sure that we do not add duplicates. + const entriesClone = entries.slice(0); + [].concat(entry).forEach((newEntry) => { + if (entriesClone.indexOf(newEntry) === -1) { + entriesClone.push(newEntry); + } + }); + return entriesClone; }; // eslint-disable-next-line no-shadow diff --git a/package-lock.json b/package-lock.json index ee87d059ce..eb33db2233 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4674,8 +4674,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -4693,13 +4692,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4712,18 +4709,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -4826,8 +4820,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -4837,7 +4830,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4850,20 +4842,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4880,7 +4869,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -4953,8 +4941,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -4964,7 +4951,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5040,8 +5026,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -5071,7 +5056,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5089,7 +5073,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5128,13 +5111,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, diff --git a/test/Client.test.js b/test/Client.test.js index 641d059876..1969938dcb 100644 --- a/test/Client.test.js +++ b/test/Client.test.js @@ -3,7 +3,6 @@ const express = require('express'); const httpProxy = require('http-proxy-middleware'); const request = require('supertest'); -const addEntries = require('../lib/utils/addEntries'); const helper = require('./helper'); const config = require('./fixtures/client-config/webpack.config'); const runBrowser = require('./helpers/run-browser'); @@ -33,7 +32,6 @@ describe('Client code', () => { poll: true, }, }; - addEntries(config, options); helper.start(config, options, done); }); diff --git a/test/Entry.test.js b/test/Entry.test.js index b6bbb76732..0cd67d76ce 100644 --- a/test/Entry.test.js +++ b/test/Entry.test.js @@ -254,6 +254,21 @@ describe('Entry', () => { ]); }); + it('can prevent duplicate entries from successive calls', () => { + const webpackOptions = Object.assign({}, config); + const devServerOptions = { hot: true }; + + addEntries(webpackOptions, devServerOptions); + addEntries(webpackOptions, devServerOptions); + + expect(webpackOptions.entry.length).toEqual(3); + + const result = webpackOptions.entry.filter((entry) => + normalize(entry).includes('webpack/hot/dev-server') + ); + expect(result.length).toEqual(1); + }); + it('supports entry as Function', () => { const webpackOptions = Object.assign({}, configEntryAsFunction); const devServerOptions = {}; diff --git a/test/Hot.test.js b/test/Hot.test.js new file mode 100644 index 0000000000..f30a411caa --- /dev/null +++ b/test/Hot.test.js @@ -0,0 +1,89 @@ +'use strict'; + +const helper = require('./helper'); +const config = require('./fixtures/client-config/webpack.config'); +const multiCompilerConfig = require('./fixtures/multi-compiler-config/webpack.config'); +const runBrowser = require('./helpers/run-browser'); + +describe('Hot Module Replacement', () => { + describe('simple hot config', () => { + jest.setTimeout(30000); + + beforeAll((done) => { + const options = { + port: 9000, + host: '0.0.0.0', + hot: true, + }; + helper.start(config, options, done); + }); + + afterAll(helper.close); + + it('should log that it is using HMR to the console as first message', (done) => { + runBrowser().then(({ page, browser }) => { + page.once('console', (msg) => { + expect(msg.args().length).toEqual(1); + expect(msg.text()).toMatch(/\[HMR\]/); + browser.close(); + done(); + }); + page.goto('http://localhost:9000/main'); + }); + }); + }); + + describe('multi compiler hot config', () => { + jest.setTimeout(30000); + + beforeAll((done) => { + const options = { + port: 9000, + host: '0.0.0.0', + hot: true, + }; + helper.start(multiCompilerConfig, options, done); + }); + + afterAll(helper.close); + + it('should log that it is using HMR to the console as first message', (done) => { + runBrowser().then(({ page, browser }) => { + page.once('console', (msg) => { + expect(msg.args().length).toEqual(1); + expect(msg.text()).toMatch(/\[HMR\]/); + browser.close(); + done(); + }); + page.goto('http://localhost:9000/main'); + }); + }); + }); + + describe('hot disabled', () => { + jest.setTimeout(30000); + + beforeAll((done) => { + const options = { + port: 9000, + host: '0.0.0.0', + hot: false, + }; + helper.start(config, options, done); + }); + + afterAll(helper.close); + + it('should not log HMR use to the console', (done) => { + runBrowser().then(({ page, browser }) => { + page.once('console', (msg) => { + expect(msg.args().length).toEqual(1); + expect(msg.text()).toMatch(/Hey\./); + browser.close(); + done(); + }); + page.goto('http://localhost:9000/main'); + }); + }); + }); +}); From 18f0bc6e4bea699ddd3196ad6ccef85d794063c3 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 23 Mar 2019 14:31:44 -0500 Subject: [PATCH 02/13] changed tests with puppeteer slightly --- test/Client.test.js | 5 ++--- test/Hot.test.js | 15 +++++-------- test/helpers/run-browser.js | 45 ++++++++++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/test/Client.test.js b/test/Client.test.js index 1969938dcb..f146053152 100644 --- a/test/Client.test.js +++ b/test/Client.test.js @@ -32,7 +32,7 @@ describe('Client code', () => { poll: true, }, }; - helper.start(config, options, done); + helper.startAwaitingCompilation(config, options, done); }); afterAll(helper.close); @@ -63,8 +63,7 @@ describe('Client code', () => { expect(requestObj.url()).toMatch( /^http:\/\/localhost:9000\/sockjs-node/ ); - browser.close(); - done(); + browser.close().then(done); }); page.goto('http://localhost:9000/main'); }); diff --git a/test/Hot.test.js b/test/Hot.test.js index f30a411caa..a59f3b0956 100644 --- a/test/Hot.test.js +++ b/test/Hot.test.js @@ -15,7 +15,7 @@ describe('Hot Module Replacement', () => { host: '0.0.0.0', hot: true, }; - helper.start(config, options, done); + helper.startAwaitingCompilation(config, options, done); }); afterAll(helper.close); @@ -25,8 +25,7 @@ describe('Hot Module Replacement', () => { page.once('console', (msg) => { expect(msg.args().length).toEqual(1); expect(msg.text()).toMatch(/\[HMR\]/); - browser.close(); - done(); + browser.close().then(done); }); page.goto('http://localhost:9000/main'); }); @@ -42,7 +41,7 @@ describe('Hot Module Replacement', () => { host: '0.0.0.0', hot: true, }; - helper.start(multiCompilerConfig, options, done); + helper.startAwaitingCompilation(multiCompilerConfig, options, done); }); afterAll(helper.close); @@ -52,8 +51,7 @@ describe('Hot Module Replacement', () => { page.once('console', (msg) => { expect(msg.args().length).toEqual(1); expect(msg.text()).toMatch(/\[HMR\]/); - browser.close(); - done(); + browser.close().then(done); }); page.goto('http://localhost:9000/main'); }); @@ -69,7 +67,7 @@ describe('Hot Module Replacement', () => { host: '0.0.0.0', hot: false, }; - helper.start(config, options, done); + helper.startAwaitingCompilation(config, options, done); }); afterAll(helper.close); @@ -79,8 +77,7 @@ describe('Hot Module Replacement', () => { page.once('console', (msg) => { expect(msg.args().length).toEqual(1); expect(msg.text()).toMatch(/Hey\./); - browser.close(); - done(); + browser.close().then(done); }); page.goto('http://localhost:9000/main'); }); diff --git a/test/helpers/run-browser.js b/test/helpers/run-browser.js index 53993d88d1..224b8a3f97 100644 --- a/test/helpers/run-browser.js +++ b/test/helpers/run-browser.js @@ -19,7 +19,50 @@ function runBrowser(config) { puppeteer .launch({ headless: true, - args: ['--no-sandbox', '--disable-setuid-sandbox'], + // args from: https://github.com/alixaxel/chrome-aws-lambda/blob/master/source/index.js + args: [ + '--disable-accelerated-2d-canvas', + '--disable-background-timer-throttling', + '--disable-breakpad', + '--disable-client-side-phishing-detection', + '--disable-cloud-import', + '--disable-default-apps', + '--disable-dev-shm-usage', + '--disable-extensions', + '--disable-gesture-typing', + '--disable-gpu', + '--disable-hang-monitor', + '--disable-infobars', + '--disable-notifications', + '--disable-offer-store-unmasked-wallet-cards', + '--disable-offer-upload-credit-cards', + '--disable-popup-blocking', + '--disable-print-preview', + '--disable-prompt-on-repost', + '--disable-setuid-sandbox', + '--disable-software-rasterizer', + '--disable-speech-api', + '--disable-sync', + '--disable-tab-for-desktop-share', + '--disable-translate', + '--disable-voice-input', + '--disable-wake-on-wifi', + '--enable-async-dns', + '--enable-simple-cache-backend', + '--enable-tcp-fast-open', + '--hide-scrollbars', + '--media-cache-size=33554432', + '--metrics-recording-only', + '--mute-audio', + '--no-default-browser-check', + '--no-first-run', + '--no-pings', + '--no-sandbox', + '--no-zygote', + '--password-store=basic', + '--prerender-from-omnibox=disabled', + '--use-mock-keychain', + ], }) .then((launchedBrowser) => { browser = launchedBrowser; From 2babccf34a36cf5ffc96c51cd1809d9995abcd30 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sat, 23 Mar 2019 15:27:08 -0500 Subject: [PATCH 03/13] make sure that page.goto is done before closing browser and proceeding to next puppeteer test --- package-lock.json | 6 +++--- package.json | 2 +- test/Hot.test.js | 33 +++++++++++++++++++++++++++------ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb33db2233..2b5f7a6110 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9905,9 +9905,9 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "puppeteer": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.12.2.tgz", - "integrity": "sha512-xWSyCeD6EazGlfnQweMpM+Hs6X6PhUYhNTHKFj/axNZDq4OmrVERf70isBf7HsnFgB3zOC1+23/8+wCAZYg+Pg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.13.0.tgz", + "integrity": "sha512-LUXgvhjfB/P6IOUDAKxOcbCz9ISwBLL9UpKghYrcBDwrOGx1m60y0iN2M64mdAUbT4+7oZM5DTxOW7equa2fxQ==", "dev": true, "requires": { "debug": "^4.1.0", diff --git a/package.json b/package.json index 94d0633c4b..0243981c51 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "marked": "^0.6.1", "nyc": "^13.3.0", "prettier": "^1.16.3", - "puppeteer": "^1.12.2", + "puppeteer": "^1.13.0", "rimraf": "^2.6.2", "standard-version": "^5.0.0", "style-loader": "^0.23.1", diff --git a/test/Hot.test.js b/test/Hot.test.js index a59f3b0956..98a1a95c7e 100644 --- a/test/Hot.test.js +++ b/test/Hot.test.js @@ -5,6 +5,17 @@ const config = require('./fixtures/client-config/webpack.config'); const multiCompilerConfig = require('./fixtures/multi-compiler-config/webpack.config'); const runBrowser = require('./helpers/run-browser'); +let readyCount = 0; +// logging could happen before page.goto is finished, so +// we need to wait for everything before closing browser +const awaitManyBeforeClose = (expectedCount, browser, done) => { + readyCount += 1; + if (readyCount === expectedCount) { + readyCount = 0; + browser.close().then(done); + } +}; + describe('Hot Module Replacement', () => { describe('simple hot config', () => { jest.setTimeout(30000); @@ -20,14 +31,20 @@ describe('Hot Module Replacement', () => { afterAll(helper.close); + beforeEach(() => { + readyCount = 0; + }); + it('should log that it is using HMR to the console as first message', (done) => { runBrowser().then(({ page, browser }) => { page.once('console', (msg) => { expect(msg.args().length).toEqual(1); expect(msg.text()).toMatch(/\[HMR\]/); - browser.close().then(done); + awaitManyBeforeClose(2, browser, done); + }); + page.goto('http://localhost:9000/main').then(() => { + awaitManyBeforeClose(2, browser, done); }); - page.goto('http://localhost:9000/main'); }); }); }); @@ -51,9 +68,11 @@ describe('Hot Module Replacement', () => { page.once('console', (msg) => { expect(msg.args().length).toEqual(1); expect(msg.text()).toMatch(/\[HMR\]/); - browser.close().then(done); + awaitManyBeforeClose(2, browser, done); + }); + page.goto('http://localhost:9000/main').then(() => { + awaitManyBeforeClose(2, browser, done); }); - page.goto('http://localhost:9000/main'); }); }); }); @@ -77,9 +96,11 @@ describe('Hot Module Replacement', () => { page.once('console', (msg) => { expect(msg.args().length).toEqual(1); expect(msg.text()).toMatch(/Hey\./); - browser.close().then(done); + awaitManyBeforeClose(2, browser, done); + }); + page.goto('http://localhost:9000/main').then(() => { + awaitManyBeforeClose(2, browser, done); }); - page.goto('http://localhost:9000/main'); }); }); }); From a96e4f449d21840913bf8fb7dd81db24c16c2cc1 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sun, 24 Mar 2019 14:26:02 -0500 Subject: [PATCH 04/13] better test suite for hot and inline, removed hot puppeteer tests --- lib/utils/addEntries.js | 2 +- test/Hot.test.js | 231 +++++++++++++++++++++++++++--------- test/helper.js | 7 +- test/helpers/run-browser.js | 45 +------ 4 files changed, 180 insertions(+), 105 deletions(-) diff --git a/lib/utils/addEntries.js b/lib/utils/addEntries.js index 6fc0f21114..a4d28d9830 100644 --- a/lib/utils/addEntries.js +++ b/lib/utils/addEntries.js @@ -47,7 +47,7 @@ function addEntries(config, options, server) { // make sure that we do not add duplicates. const entriesClone = entries.slice(0); [].concat(entry).forEach((newEntry) => { - if (entriesClone.indexOf(newEntry) === -1) { + if (!entriesClone.includes(newEntry)) { entriesClone.push(newEntry); } }); diff --git a/test/Hot.test.js b/test/Hot.test.js index 98a1a95c7e..6e16f23a70 100644 --- a/test/Hot.test.js +++ b/test/Hot.test.js @@ -1,107 +1,222 @@ 'use strict'; +const request = require('supertest'); const helper = require('./helper'); const config = require('./fixtures/client-config/webpack.config'); const multiCompilerConfig = require('./fixtures/multi-compiler-config/webpack.config'); -const runBrowser = require('./helpers/run-browser'); - -let readyCount = 0; -// logging could happen before page.goto is finished, so -// we need to wait for everything before closing browser -const awaitManyBeforeClose = (expectedCount, browser, done) => { - readyCount += 1; - if (readyCount === expectedCount) { - readyCount = 0; - browser.close().then(done); - } -}; - -describe('Hot Module Replacement', () => { - describe('simple hot config', () => { - jest.setTimeout(30000); +describe('Hot Module Replacement (hot/hotOnly options)', () => { + let server; + let req; + + describe('simple hot config entries', () => { beforeAll((done) => { const options = { port: 9000, host: '0.0.0.0', hot: true, }; - helper.startAwaitingCompilation(config, options, done); + server = helper.startAwaitingCompilation(config, options, done); + req = request(server.app); }); afterAll(helper.close); - beforeEach(() => { - readyCount = 0; + it('should include hot script in the bundle', (done) => { + req.get('/main.js').expect(200, /webpack\/hot\/dev-server\.js/, done); }); + }); - it('should log that it is using HMR to the console as first message', (done) => { - runBrowser().then(({ page, browser }) => { - page.once('console', (msg) => { - expect(msg.args().length).toEqual(1); - expect(msg.text()).toMatch(/\[HMR\]/); - awaitManyBeforeClose(2, browser, done); - }); - page.goto('http://localhost:9000/main').then(() => { - awaitManyBeforeClose(2, browser, done); - }); - }); + describe('simple hotOnly config entries', () => { + beforeAll((done) => { + const options = { + port: 9000, + host: '0.0.0.0', + hotOnly: true, + }; + server = helper.startAwaitingCompilation(config, options, done); + req = request(server.app); }); - }); - describe('multi compiler hot config', () => { - jest.setTimeout(30000); + afterAll(helper.close); + + it('should include hotOnly script in the bundle', (done) => { + req + .get('/main.js') + .expect(200, /webpack\/hot\/only-dev-server\.js/, done); + }); + }); + describe('multi compiler hot config entries', () => { beforeAll((done) => { const options = { port: 9000, host: '0.0.0.0', hot: true, }; - helper.startAwaitingCompilation(multiCompilerConfig, options, done); + server = helper.startAwaitingCompilation( + multiCompilerConfig, + options, + done + ); + req = request(server.app); }); afterAll(helper.close); - it('should log that it is using HMR to the console as first message', (done) => { - runBrowser().then(({ page, browser }) => { - page.once('console', (msg) => { - expect(msg.args().length).toEqual(1); - expect(msg.text()).toMatch(/\[HMR\]/); - awaitManyBeforeClose(2, browser, done); - }); - page.goto('http://localhost:9000/main').then(() => { - awaitManyBeforeClose(2, browser, done); - }); - }); + it('should include hot script in the bundle', (done) => { + req.get('/main.js').expect(200, /webpack\/hot\/dev-server\.js/, done); }); }); - describe('hot disabled', () => { - jest.setTimeout(30000); - + describe('hot disabled entries', () => { beforeAll((done) => { const options = { port: 9000, host: '0.0.0.0', hot: false, }; - helper.startAwaitingCompilation(config, options, done); + server = helper.startAwaitingCompilation(config, options, done); + req = request(server.app); }); afterAll(helper.close); - it('should not log HMR use to the console', (done) => { - runBrowser().then(({ page, browser }) => { - page.once('console', (msg) => { - expect(msg.args().length).toEqual(1); - expect(msg.text()).toMatch(/Hey\./); - awaitManyBeforeClose(2, browser, done); - }); - page.goto('http://localhost:9000/main').then(() => { - awaitManyBeforeClose(2, browser, done); + it('should NOT include hot script in the bundle', (done) => { + req + .get('/main.js') + .expect(200) + .then(({ text }) => { + expect(text).not.toMatch(/webpack\/hot\/dev-server\.js/); + done(); }); + }); + }); + + // the following cases check to make sure that the HMR + // plugin is actually added + + describe('simple hot config HMR plugin', () => { + it('should register the HMR plugin before compilation is complete', (done) => { + let pluginFound = false; + const options = { + port: 9000, + host: '0.0.0.0', + hot: true, + }; + const fullSetup = helper.startAwaitingCompilationFullSetup( + config, + options, + () => { + expect(pluginFound).toBeTruthy(); + done(); + } + ); + + const compiler = fullSetup.compiler; + compiler.hooks.compilation.intercept({ + register: (tapInfo) => { + if (tapInfo.name === 'HotModuleReplacementPlugin') { + pluginFound = true; + } + return tapInfo; + }, }); }); + + afterAll(helper.close); + }); + + describe('simple hotOnly config HMR plugin', () => { + it('should register the HMR plugin before compilation is complete', (done) => { + let pluginFound = false; + const options = { + port: 9000, + host: '0.0.0.0', + hotOnly: true, + }; + const fullSetup = helper.startAwaitingCompilationFullSetup( + config, + options, + () => { + expect(pluginFound).toBeTruthy(); + done(); + } + ); + + const compiler = fullSetup.compiler; + compiler.hooks.compilation.intercept({ + register: (tapInfo) => { + if (tapInfo.name === 'HotModuleReplacementPlugin') { + pluginFound = true; + } + return tapInfo; + }, + }); + }); + + afterAll(helper.close); + }); + + describe('multi compiler hot config HMR plugin', () => { + it('should register the HMR plugin before compilation is complete', (done) => { + let pluginFound = false; + const options = { + port: 9000, + host: '0.0.0.0', + hot: true, + }; + const fullSetup = helper.startAwaitingCompilationFullSetup( + multiCompilerConfig, + options, + () => { + expect(pluginFound).toBeTruthy(); + done(); + } + ); + + const compiler = fullSetup.compiler.compilers[0]; + compiler.hooks.compilation.intercept({ + register: (tapInfo) => { + if (tapInfo.name === 'HotModuleReplacementPlugin') { + pluginFound = true; + } + return tapInfo; + }, + }); + }); + + afterAll(helper.close); + }); + + describe('hot disabled HMR plugin', () => { + it('should NOT register the HMR plugin before compilation is complete', (done) => { + let pluginFound = false; + const options = { + port: 9000, + host: '0.0.0.0', + hot: false, + }; + const fullSetup = helper.startAwaitingCompilationFullSetup( + config, + options, + () => { + expect(pluginFound).toBeFalsy(); + done(); + } + ); + + const compiler = fullSetup.compiler; + compiler.hooks.compilation.intercept({ + register: (tapInfo) => { + if (tapInfo.name === 'HotModuleReplacementPlugin') { + pluginFound = true; + } + return tapInfo; + }, + }); + }); + + afterAll(helper.close); }); }); diff --git a/test/helper.js b/test/helper.js index 412652cdaa..1d6af942fb 100644 --- a/test/helper.js +++ b/test/helper.js @@ -28,7 +28,7 @@ module.exports = { compiler, }; }, - startAwaitingCompilation(config, options, done) { + startAwaitingCompilationFullSetup(config, options, done) { let readyCount = 0; const ready = () => { readyCount += 1; @@ -41,7 +41,10 @@ module.exports = { // wait for compilation, since dev server can start before this // https://github.com/webpack/webpack-dev-server/issues/847 fullSetup.compiler.hooks.done.tap('done', ready); - return fullSetup.server; + return fullSetup; + }, + startAwaitingCompilation(config, options, done) { + return this.startAwaitingCompilationFullSetup(config, options, done).server; }, start(config, options, done) { return this.startFullSetup(config, options, done).server; diff --git a/test/helpers/run-browser.js b/test/helpers/run-browser.js index 224b8a3f97..53993d88d1 100644 --- a/test/helpers/run-browser.js +++ b/test/helpers/run-browser.js @@ -19,50 +19,7 @@ function runBrowser(config) { puppeteer .launch({ headless: true, - // args from: https://github.com/alixaxel/chrome-aws-lambda/blob/master/source/index.js - args: [ - '--disable-accelerated-2d-canvas', - '--disable-background-timer-throttling', - '--disable-breakpad', - '--disable-client-side-phishing-detection', - '--disable-cloud-import', - '--disable-default-apps', - '--disable-dev-shm-usage', - '--disable-extensions', - '--disable-gesture-typing', - '--disable-gpu', - '--disable-hang-monitor', - '--disable-infobars', - '--disable-notifications', - '--disable-offer-store-unmasked-wallet-cards', - '--disable-offer-upload-credit-cards', - '--disable-popup-blocking', - '--disable-print-preview', - '--disable-prompt-on-repost', - '--disable-setuid-sandbox', - '--disable-software-rasterizer', - '--disable-speech-api', - '--disable-sync', - '--disable-tab-for-desktop-share', - '--disable-translate', - '--disable-voice-input', - '--disable-wake-on-wifi', - '--enable-async-dns', - '--enable-simple-cache-backend', - '--enable-tcp-fast-open', - '--hide-scrollbars', - '--media-cache-size=33554432', - '--metrics-recording-only', - '--mute-audio', - '--no-default-browser-check', - '--no-first-run', - '--no-pings', - '--no-sandbox', - '--no-zygote', - '--password-store=basic', - '--prerender-from-omnibox=disabled', - '--use-mock-keychain', - ], + args: ['--no-sandbox', '--disable-setuid-sandbox'], }) .then((launchedBrowser) => { browser = launchedBrowser; From 1575ec772f64456c0cce01c994d31d299f353dba Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sun, 24 Mar 2019 14:27:57 -0500 Subject: [PATCH 05/13] added inline tests --- test/Inline.test.js | 81 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 test/Inline.test.js diff --git a/test/Inline.test.js b/test/Inline.test.js new file mode 100644 index 0000000000..2a19e0dac4 --- /dev/null +++ b/test/Inline.test.js @@ -0,0 +1,81 @@ +'use strict'; + +const request = require('supertest'); +const helper = require('./helper'); +const config = require('./fixtures/client-config/webpack.config'); +const multiCompilerConfig = require('./fixtures/multi-compiler-config/webpack.config'); + +describe('inline', () => { + let server; + let req; + + describe('simple inline config entries', () => { + beforeAll((done) => { + const options = { + port: 9000, + host: '0.0.0.0', + inline: true, + }; + server = helper.startAwaitingCompilation(config, options, done); + req = request(server.app); + }); + + afterAll(helper.close); + + it('should include inline client script in the bundle', (done) => { + req + .get('/main.js') + .expect(200, /client\/index\.js\?http:\/\/0\.0\.0\.0:9000/, done); + }); + }); + + describe('multi compiler inline config entries', () => { + beforeAll((done) => { + const options = { + port: 9000, + host: '0.0.0.0', + inline: true, + }; + server = helper.startAwaitingCompilation( + multiCompilerConfig, + options, + done + ); + req = request(server.app); + }); + + afterAll(helper.close); + + it('should include inline client script in the bundle', (done) => { + req + .get('/main.js') + .expect(200, /client\/index\.js\?http:\/\/0\.0\.0\.0:9000/, done); + }); + }); + + describe('inline disabled entries', () => { + beforeAll((done) => { + const options = { + port: 9000, + host: '0.0.0.0', + inline: false, + }; + server = helper.startAwaitingCompilation(config, options, done); + req = request(server.app); + }); + + afterAll(helper.close); + + it('should NOT include inline client script in the bundle', (done) => { + req + .get('/main.js') + .expect(200) + .then(({ text }) => { + expect(text).not.toMatch( + /client\/index\.js\?http:\/\/0\.0\.0\.0:9000/ + ); + done(); + }); + }); + }); +}); From 538229970a7ca83339f319bcabfca3bce187d834 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sun, 24 Mar 2019 16:08:43 -0500 Subject: [PATCH 06/13] set polling to true for test environment --- test/Hot.test.js | 32 ++++++++++++++++++++++++-------- test/Inline.test.js | 9 +++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/test/Hot.test.js b/test/Hot.test.js index 6e16f23a70..2674849528 100644 --- a/test/Hot.test.js +++ b/test/Hot.test.js @@ -13,8 +13,10 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { beforeAll((done) => { const options = { port: 9000, - host: '0.0.0.0', hot: true, + watchOptions: { + poll: true, + }, }; server = helper.startAwaitingCompilation(config, options, done); req = request(server.app); @@ -31,8 +33,10 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { beforeAll((done) => { const options = { port: 9000, - host: '0.0.0.0', hotOnly: true, + watchOptions: { + poll: true, + }, }; server = helper.startAwaitingCompilation(config, options, done); req = request(server.app); @@ -51,8 +55,10 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { beforeAll((done) => { const options = { port: 9000, - host: '0.0.0.0', hot: true, + watchOptions: { + poll: true, + }, }; server = helper.startAwaitingCompilation( multiCompilerConfig, @@ -73,8 +79,10 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { beforeAll((done) => { const options = { port: 9000, - host: '0.0.0.0', hot: false, + watchOptions: { + poll: true, + }, }; server = helper.startAwaitingCompilation(config, options, done); req = request(server.app); @@ -101,8 +109,10 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { let pluginFound = false; const options = { port: 9000, - host: '0.0.0.0', hot: true, + watchOptions: { + poll: true, + }, }; const fullSetup = helper.startAwaitingCompilationFullSetup( config, @@ -132,8 +142,10 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { let pluginFound = false; const options = { port: 9000, - host: '0.0.0.0', hotOnly: true, + watchOptions: { + poll: true, + }, }; const fullSetup = helper.startAwaitingCompilationFullSetup( config, @@ -163,8 +175,10 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { let pluginFound = false; const options = { port: 9000, - host: '0.0.0.0', hot: true, + watchOptions: { + poll: true, + }, }; const fullSetup = helper.startAwaitingCompilationFullSetup( multiCompilerConfig, @@ -194,8 +208,10 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { let pluginFound = false; const options = { port: 9000, - host: '0.0.0.0', hot: false, + watchOptions: { + poll: true, + }, }; const fullSetup = helper.startAwaitingCompilationFullSetup( config, diff --git a/test/Inline.test.js b/test/Inline.test.js index 2a19e0dac4..948e2710b0 100644 --- a/test/Inline.test.js +++ b/test/Inline.test.js @@ -15,6 +15,9 @@ describe('inline', () => { port: 9000, host: '0.0.0.0', inline: true, + watchOptions: { + poll: true, + }, }; server = helper.startAwaitingCompilation(config, options, done); req = request(server.app); @@ -35,6 +38,9 @@ describe('inline', () => { port: 9000, host: '0.0.0.0', inline: true, + watchOptions: { + poll: true, + }, }; server = helper.startAwaitingCompilation( multiCompilerConfig, @@ -59,6 +65,9 @@ describe('inline', () => { port: 9000, host: '0.0.0.0', inline: false, + watchOptions: { + poll: true, + }, }; server = helper.startAwaitingCompilation(config, options, done); req = request(server.app); From 241831a84132dfe24b184e6cb998e49cba3b7dae Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sun, 24 Mar 2019 16:34:32 -0500 Subject: [PATCH 07/13] attempt to pass CI with https test changes --- test/Https.test.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/Https.test.js b/test/Https.test.js index 449a9f3a92..539b520d37 100644 --- a/test/Https.test.js +++ b/test/Https.test.js @@ -21,11 +21,14 @@ describe('HTTPS', () => { describe('is boolean', () => { beforeAll((done) => { - server = helper.start( + server = helper.startAwaitingCompilation( config, { contentBase: contentBasePublic, https: true, + watchOptions: { + poll: true, + }, }, done ); @@ -35,15 +38,11 @@ describe('HTTPS', () => { it('Request to index', (done) => { req.get('/').expect(200, /Heyo/, done); }); - - afterAll(() => { - helper.close(); - }); }); describe('ca, pfx, key and cert are buffer', () => { beforeAll((done) => { - server = helper.start( + server = helper.startAwaitingCompilation( config, { contentBase: contentBasePublic, @@ -60,6 +59,9 @@ describe('HTTPS', () => { ), passphrase: 'webpack-dev-server', }, + watchOptions: { + poll: true, + }, }, done ); @@ -73,7 +75,7 @@ describe('HTTPS', () => { describe('ca, pfx, key and cert are paths', () => { beforeAll((done) => { - server = helper.start( + server = helper.startAwaitingCompilation( config, { contentBase: contentBasePublic, @@ -84,6 +86,9 @@ describe('HTTPS', () => { cert: path.join(httpsCertificateDirectory, 'server.crt'), passphrase: 'webpack-dev-server', }, + watchOptions: { + poll: true, + }, }, done ); @@ -97,7 +102,7 @@ describe('HTTPS', () => { describe('ca, pfx, key and cert are raw strings', () => { beforeAll((done) => { - server = helper.start( + server = helper.startAwaitingCompilation( config, { contentBase: contentBasePublic, @@ -117,6 +122,9 @@ describe('HTTPS', () => { .toString(), passphrase: 'webpack-dev-server', }, + watchOptions: { + poll: true, + }, }, done ); From af8741b636bdd963e3c08d6e27333fdbc60d71fb Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sun, 24 Mar 2019 17:24:42 -0500 Subject: [PATCH 08/13] disabled inline for util test --- test/Util.test.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/Util.test.js b/test/Util.test.js index 14c5034b7c..a87f6cbe8a 100644 --- a/test/Util.test.js +++ b/test/Util.test.js @@ -92,7 +92,14 @@ describe('check utility functions', () => { it(`test createDomain '${test.name}'`, (done) => { const { options, expected } = test; - server = new Server(compiler, options); + const nonInlineOptions = Object.assign( + { + inline: false, + }, + options + ); + + server = new Server(compiler, nonInlineOptions); server.listen(options.port, options.host, (err) => { if (err) { From d674ee631fac459d18decf2b5ed63ac4001d9b92 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sun, 24 Mar 2019 20:50:26 -0500 Subject: [PATCH 09/13] reverted some pointless https changes, tried changing some tests where jest could be detecting open handles --- test/Https.test.js | 20 ++++---------------- test/MultiCompiler.test.js | 2 +- test/Routes.test.js | 4 ++-- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/test/Https.test.js b/test/Https.test.js index 539b520d37..affc68dad1 100644 --- a/test/Https.test.js +++ b/test/Https.test.js @@ -21,14 +21,11 @@ describe('HTTPS', () => { describe('is boolean', () => { beforeAll((done) => { - server = helper.startAwaitingCompilation( + server = helper.start( config, { contentBase: contentBasePublic, https: true, - watchOptions: { - poll: true, - }, }, done ); @@ -42,7 +39,7 @@ describe('HTTPS', () => { describe('ca, pfx, key and cert are buffer', () => { beforeAll((done) => { - server = helper.startAwaitingCompilation( + server = helper.start( config, { contentBase: contentBasePublic, @@ -59,9 +56,6 @@ describe('HTTPS', () => { ), passphrase: 'webpack-dev-server', }, - watchOptions: { - poll: true, - }, }, done ); @@ -75,7 +69,7 @@ describe('HTTPS', () => { describe('ca, pfx, key and cert are paths', () => { beforeAll((done) => { - server = helper.startAwaitingCompilation( + server = helper.start( config, { contentBase: contentBasePublic, @@ -86,9 +80,6 @@ describe('HTTPS', () => { cert: path.join(httpsCertificateDirectory, 'server.crt'), passphrase: 'webpack-dev-server', }, - watchOptions: { - poll: true, - }, }, done ); @@ -102,7 +93,7 @@ describe('HTTPS', () => { describe('ca, pfx, key and cert are raw strings', () => { beforeAll((done) => { - server = helper.startAwaitingCompilation( + server = helper.start( config, { contentBase: contentBasePublic, @@ -122,9 +113,6 @@ describe('HTTPS', () => { .toString(), passphrase: 'webpack-dev-server', }, - watchOptions: { - poll: true, - }, }, done ); diff --git a/test/MultiCompiler.test.js b/test/MultiCompiler.test.js index 2e8984a877..907f3e5a6a 100644 --- a/test/MultiCompiler.test.js +++ b/test/MultiCompiler.test.js @@ -8,7 +8,7 @@ describe('MultiCompiler', () => { let server; let req; beforeAll((done) => { - server = helper.start(config, {}, done); + server = helper.startAwaitingCompilation(config, {}, done); req = request(server.app); }); diff --git a/test/Routes.test.js b/test/Routes.test.js index df5f875ff5..8789ce2463 100644 --- a/test/Routes.test.js +++ b/test/Routes.test.js @@ -69,7 +69,7 @@ describe('Routes', () => { describe('headers as a string', () => { beforeAll((done) => { - server = helper.start( + server = helper.startAwaitingCompilation( config, { headers: { 'X-Foo': '1' }, @@ -91,7 +91,7 @@ describe('Routes', () => { describe('headers as an array', () => { beforeAll((done) => { - server = helper.start( + server = helper.startAwaitingCompilation( config, { headers: { 'X-Bar': ['key1=value1', 'key2=value2'] }, From f5e46a0065a14526eeeb1a340a60f75e69fa25ae Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sun, 24 Mar 2019 21:58:06 -0500 Subject: [PATCH 10/13] added non inline for https tests --- test/Https.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Https.test.js b/test/Https.test.js index affc68dad1..f2af77133f 100644 --- a/test/Https.test.js +++ b/test/Https.test.js @@ -26,6 +26,7 @@ describe('HTTPS', () => { { contentBase: contentBasePublic, https: true, + inline: false, }, done ); @@ -56,6 +57,7 @@ describe('HTTPS', () => { ), passphrase: 'webpack-dev-server', }, + inline: false, }, done ); @@ -80,6 +82,7 @@ describe('HTTPS', () => { cert: path.join(httpsCertificateDirectory, 'server.crt'), passphrase: 'webpack-dev-server', }, + inline: false, }, done ); @@ -113,6 +116,7 @@ describe('HTTPS', () => { .toString(), passphrase: 'webpack-dev-server', }, + inline: false, }, done ); From 41ecdc129338300fb04edcb32ee3d210e61776c5 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Sun, 24 Mar 2019 22:30:37 -0500 Subject: [PATCH 11/13] switched back to original https changes --- test/Https.test.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/Https.test.js b/test/Https.test.js index f2af77133f..539b520d37 100644 --- a/test/Https.test.js +++ b/test/Https.test.js @@ -21,12 +21,14 @@ describe('HTTPS', () => { describe('is boolean', () => { beforeAll((done) => { - server = helper.start( + server = helper.startAwaitingCompilation( config, { contentBase: contentBasePublic, https: true, - inline: false, + watchOptions: { + poll: true, + }, }, done ); @@ -40,7 +42,7 @@ describe('HTTPS', () => { describe('ca, pfx, key and cert are buffer', () => { beforeAll((done) => { - server = helper.start( + server = helper.startAwaitingCompilation( config, { contentBase: contentBasePublic, @@ -57,7 +59,9 @@ describe('HTTPS', () => { ), passphrase: 'webpack-dev-server', }, - inline: false, + watchOptions: { + poll: true, + }, }, done ); @@ -71,7 +75,7 @@ describe('HTTPS', () => { describe('ca, pfx, key and cert are paths', () => { beforeAll((done) => { - server = helper.start( + server = helper.startAwaitingCompilation( config, { contentBase: contentBasePublic, @@ -82,7 +86,9 @@ describe('HTTPS', () => { cert: path.join(httpsCertificateDirectory, 'server.crt'), passphrase: 'webpack-dev-server', }, - inline: false, + watchOptions: { + poll: true, + }, }, done ); @@ -96,7 +102,7 @@ describe('HTTPS', () => { describe('ca, pfx, key and cert are raw strings', () => { beforeAll((done) => { - server = helper.start( + server = helper.startAwaitingCompilation( config, { contentBase: contentBasePublic, @@ -116,7 +122,9 @@ describe('HTTPS', () => { .toString(), passphrase: 'webpack-dev-server', }, - inline: false, + watchOptions: { + poll: true, + }, }, done ); From b6dda6f10d14ab2a0a49d111d6ab81f7cf4b8381 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Mon, 25 Mar 2019 11:21:38 -0500 Subject: [PATCH 12/13] moved compiler updates to helper file, extreme forcing of tests to wait for compilation and not use inline --- lib/Server.js | 56 ++----------------------------- lib/utils/updateCompiler.js | 67 +++++++++++++++++++++++++++++++++++++ test/Client.test.js | 1 + test/Hot.test.js | 8 +++++ test/Lazy.test.js | 2 +- test/helper.js | 28 +++++++++++++++- 6 files changed, 106 insertions(+), 56 deletions(-) create mode 100644 lib/utils/updateCompiler.js diff --git a/lib/Server.js b/lib/Server.js index f005f9a435..b69663c4d1 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -33,6 +33,7 @@ const historyApiFallback = require('connect-history-api-fallback'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware'); +const updateCompiler = require('./utils/updateCompiler'); const createLogger = require('./utils/createLogger'); const createCertificate = require('./utils/createCertificate'); @@ -89,60 +90,7 @@ class Server { throw new Error("'filename' option must be set in lazy mode."); } - if (options.inline !== false) { - const findHMRPlugin = (config) => { - if (!config.plugins) { - return undefined; - } - - return config.plugins.find( - (plugin) => plugin.constructor === webpack.HotModuleReplacementPlugin - ); - }; - - const compilers = []; - const compilersWithoutHMR = []; - let webpackConfig; - if (compiler.compilers) { - webpackConfig = []; - compiler.compilers.forEach((compiler) => { - webpackConfig.push(compiler.options); - compilers.push(compiler); - if (!findHMRPlugin(compiler.options)) { - compilersWithoutHMR.push(compiler); - } - }); - } else { - webpackConfig = compiler.options; - compilers.push(compiler); - if (!findHMRPlugin(compiler.options)) { - compilersWithoutHMR.push(compiler); - } - } - - // it's possible that we should clone the config before doing - // this, but it seems safe not to since it actually reflects - // the changes we are making to the compiler - // important: this relies on the fact that addEntries now - // prevents duplicate new entries. - Server.addDevServerEntrypoints(webpackConfig, options); - compilers.forEach((compiler) => { - const config = compiler.options; - compiler.hooks.entryOption.call(config.context, config.entry); - }); - - // do not apply the plugin unless it didn't exist before. - if (options.hot || options.hotOnly) { - compilersWithoutHMR.forEach((compiler) => { - // addDevServerEntrypoints above should have added the plugin - // to the compiler options - const plugin = findHMRPlugin(compiler.options); - if (plugin) { - plugin.apply(compiler); - } - }); - } - } + updateCompiler(compiler, options); this.stats = options.stats && Object.keys(options.stats).length diff --git a/lib/utils/updateCompiler.js b/lib/utils/updateCompiler.js new file mode 100644 index 0000000000..4f6619874a --- /dev/null +++ b/lib/utils/updateCompiler.js @@ -0,0 +1,67 @@ +'use strict'; + +/* eslint-disable + no-shadow, + no-undefined +*/ +const webpack = require('webpack'); +const addEntries = require('./addEntries'); + +function updateCompiler(compiler, options) { + if (options.inline !== false) { + const findHMRPlugin = (config) => { + if (!config.plugins) { + return undefined; + } + + return config.plugins.find( + (plugin) => plugin.constructor === webpack.HotModuleReplacementPlugin + ); + }; + + const compilers = []; + const compilersWithoutHMR = []; + let webpackConfig; + if (compiler.compilers) { + webpackConfig = []; + compiler.compilers.forEach((compiler) => { + webpackConfig.push(compiler.options); + compilers.push(compiler); + if (!findHMRPlugin(compiler.options)) { + compilersWithoutHMR.push(compiler); + } + }); + } else { + webpackConfig = compiler.options; + compilers.push(compiler); + if (!findHMRPlugin(compiler.options)) { + compilersWithoutHMR.push(compiler); + } + } + + // it's possible that we should clone the config before doing + // this, but it seems safe not to since it actually reflects + // the changes we are making to the compiler + // important: this relies on the fact that addEntries now + // prevents duplicate new entries. + addEntries(webpackConfig, options); + compilers.forEach((compiler) => { + const config = compiler.options; + compiler.hooks.entryOption.call(config.context, config.entry); + }); + + // do not apply the plugin unless it didn't exist before. + if (options.hot || options.hotOnly) { + compilersWithoutHMR.forEach((compiler) => { + // addDevServerEntrypoints above should have added the plugin + // to the compiler options + const plugin = findHMRPlugin(compiler.options); + if (plugin) { + plugin.apply(compiler); + } + }); + } + } +} + +module.exports = updateCompiler; diff --git a/test/Client.test.js b/test/Client.test.js index f146053152..db317eaf07 100644 --- a/test/Client.test.js +++ b/test/Client.test.js @@ -27,6 +27,7 @@ describe('Client code', () => { port: 9001, host: '0.0.0.0', disableHostCheck: true, + inline: true, hot: true, watchOptions: { poll: true, diff --git a/test/Hot.test.js b/test/Hot.test.js index 2674849528..5db2b2038b 100644 --- a/test/Hot.test.js +++ b/test/Hot.test.js @@ -13,6 +13,7 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { beforeAll((done) => { const options = { port: 9000, + inline: true, hot: true, watchOptions: { poll: true, @@ -33,6 +34,7 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { beforeAll((done) => { const options = { port: 9000, + inline: true, hotOnly: true, watchOptions: { poll: true, @@ -55,6 +57,7 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { beforeAll((done) => { const options = { port: 9000, + inline: true, hot: true, watchOptions: { poll: true, @@ -79,6 +82,7 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { beforeAll((done) => { const options = { port: 9000, + inline: true, hot: false, watchOptions: { poll: true, @@ -109,6 +113,7 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { let pluginFound = false; const options = { port: 9000, + inline: true, hot: true, watchOptions: { poll: true, @@ -142,6 +147,7 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { let pluginFound = false; const options = { port: 9000, + inline: true, hotOnly: true, watchOptions: { poll: true, @@ -175,6 +181,7 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { let pluginFound = false; const options = { port: 9000, + inline: true, hot: true, watchOptions: { poll: true, @@ -208,6 +215,7 @@ describe('Hot Module Replacement (hot/hotOnly options)', () => { let pluginFound = false; const options = { port: 9000, + inline: true, hot: false, watchOptions: { poll: true, diff --git a/test/Lazy.test.js b/test/Lazy.test.js index 21d733ceb1..c64487a8b4 100644 --- a/test/Lazy.test.js +++ b/test/Lazy.test.js @@ -15,7 +15,7 @@ describe('Lazy', () => { }); it('with filename option should not throw an error', (done) => { - helper.start( + helper.startBeforeCompilation( config, { lazy: true, diff --git a/test/helper.js b/test/helper.js index 1d6af942fb..f5ebea05ce 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,5 +1,8 @@ 'use strict'; +/* eslint-disable + no-undefined +*/ const webpack = require('webpack'); const Server = require('../lib/Server'); @@ -9,10 +12,26 @@ module.exports = { // start server, returning the full setup of the server // (both the server and the compiler) startFullSetup(config, options, done) { - // eslint-disable-next-line no-undefined if (options.quiet === undefined) { options.quiet = true; } + // originally, inline was not working by default for tests with the API + // if you need to test inline, I think it should be set explicitly, + // rather than expecting it to be defaulted to + // (the only test that relied on inline before this point was Client.test.js) + if ( + options.inline === undefined && + options.hot === undefined && + options.hotOnly === undefined + ) { + options.inline = false; + } + // defaulting to this will hopefully help with problems on OSX in tests + if (options.watchOptions === undefined) { + options.watchOptions = { + poll: true, + }; + } const compiler = webpack(config); server = new Server(compiler, options); @@ -47,6 +66,13 @@ module.exports = { return this.startAwaitingCompilationFullSetup(config, options, done).server; }, start(config, options, done) { + // I suspect that almost all tests need to wait for compilation to + // finish, because not doing so leaves open handles for jest, + // in the case where a compilation didn't finish before destroying + // the server and moving on + return this.startAwaitingCompilation(config, options, done); + }, + startBeforeCompilation(config, options, done) { return this.startFullSetup(config, options, done).server; }, close(done) { From 6fb88a87553a88213ac74ae6e42fb1e407106895 Mon Sep 17 00:00:00 2001 From: Kirill Nagaitsev Date: Mon, 25 Mar 2019 12:09:00 -0500 Subject: [PATCH 13/13] removed changes in tests out of the scope of this PR --- package-lock.json | 6 +++--- package.json | 2 +- test/Https.test.js | 24 ++++++++---------------- test/MultiCompiler.test.js | 2 +- test/Routes.test.js | 4 ++-- test/Util.test.js | 9 +-------- test/helper.js | 6 ++++-- 7 files changed, 20 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b5f7a6110..eb33db2233 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9905,9 +9905,9 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "puppeteer": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.13.0.tgz", - "integrity": "sha512-LUXgvhjfB/P6IOUDAKxOcbCz9ISwBLL9UpKghYrcBDwrOGx1m60y0iN2M64mdAUbT4+7oZM5DTxOW7equa2fxQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.12.2.tgz", + "integrity": "sha512-xWSyCeD6EazGlfnQweMpM+Hs6X6PhUYhNTHKFj/axNZDq4OmrVERf70isBf7HsnFgB3zOC1+23/8+wCAZYg+Pg==", "dev": true, "requires": { "debug": "^4.1.0", diff --git a/package.json b/package.json index 0243981c51..94d0633c4b 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "marked": "^0.6.1", "nyc": "^13.3.0", "prettier": "^1.16.3", - "puppeteer": "^1.13.0", + "puppeteer": "^1.12.2", "rimraf": "^2.6.2", "standard-version": "^5.0.0", "style-loader": "^0.23.1", diff --git a/test/Https.test.js b/test/Https.test.js index 539b520d37..449a9f3a92 100644 --- a/test/Https.test.js +++ b/test/Https.test.js @@ -21,14 +21,11 @@ describe('HTTPS', () => { describe('is boolean', () => { beforeAll((done) => { - server = helper.startAwaitingCompilation( + server = helper.start( config, { contentBase: contentBasePublic, https: true, - watchOptions: { - poll: true, - }, }, done ); @@ -38,11 +35,15 @@ describe('HTTPS', () => { it('Request to index', (done) => { req.get('/').expect(200, /Heyo/, done); }); + + afterAll(() => { + helper.close(); + }); }); describe('ca, pfx, key and cert are buffer', () => { beforeAll((done) => { - server = helper.startAwaitingCompilation( + server = helper.start( config, { contentBase: contentBasePublic, @@ -59,9 +60,6 @@ describe('HTTPS', () => { ), passphrase: 'webpack-dev-server', }, - watchOptions: { - poll: true, - }, }, done ); @@ -75,7 +73,7 @@ describe('HTTPS', () => { describe('ca, pfx, key and cert are paths', () => { beforeAll((done) => { - server = helper.startAwaitingCompilation( + server = helper.start( config, { contentBase: contentBasePublic, @@ -86,9 +84,6 @@ describe('HTTPS', () => { cert: path.join(httpsCertificateDirectory, 'server.crt'), passphrase: 'webpack-dev-server', }, - watchOptions: { - poll: true, - }, }, done ); @@ -102,7 +97,7 @@ describe('HTTPS', () => { describe('ca, pfx, key and cert are raw strings', () => { beforeAll((done) => { - server = helper.startAwaitingCompilation( + server = helper.start( config, { contentBase: contentBasePublic, @@ -122,9 +117,6 @@ describe('HTTPS', () => { .toString(), passphrase: 'webpack-dev-server', }, - watchOptions: { - poll: true, - }, }, done ); diff --git a/test/MultiCompiler.test.js b/test/MultiCompiler.test.js index 907f3e5a6a..2e8984a877 100644 --- a/test/MultiCompiler.test.js +++ b/test/MultiCompiler.test.js @@ -8,7 +8,7 @@ describe('MultiCompiler', () => { let server; let req; beforeAll((done) => { - server = helper.startAwaitingCompilation(config, {}, done); + server = helper.start(config, {}, done); req = request(server.app); }); diff --git a/test/Routes.test.js b/test/Routes.test.js index 8789ce2463..df5f875ff5 100644 --- a/test/Routes.test.js +++ b/test/Routes.test.js @@ -69,7 +69,7 @@ describe('Routes', () => { describe('headers as a string', () => { beforeAll((done) => { - server = helper.startAwaitingCompilation( + server = helper.start( config, { headers: { 'X-Foo': '1' }, @@ -91,7 +91,7 @@ describe('Routes', () => { describe('headers as an array', () => { beforeAll((done) => { - server = helper.startAwaitingCompilation( + server = helper.start( config, { headers: { 'X-Bar': ['key1=value1', 'key2=value2'] }, diff --git a/test/Util.test.js b/test/Util.test.js index a87f6cbe8a..14c5034b7c 100644 --- a/test/Util.test.js +++ b/test/Util.test.js @@ -92,14 +92,7 @@ describe('check utility functions', () => { it(`test createDomain '${test.name}'`, (done) => { const { options, expected } = test; - const nonInlineOptions = Object.assign( - { - inline: false, - }, - options - ); - - server = new Server(compiler, nonInlineOptions); + server = new Server(compiler, options); server.listen(options.port, options.host, (err) => { if (err) { diff --git a/test/helper.js b/test/helper.js index f5ebea05ce..e45144f9d3 100644 --- a/test/helper.js +++ b/test/helper.js @@ -16,7 +16,7 @@ module.exports = { options.quiet = true; } // originally, inline was not working by default for tests with the API - // if you need to test inline, I think it should be set explicitly, + // if you need to test inline, it should be set explicitly, // rather than expecting it to be defaulted to // (the only test that relied on inline before this point was Client.test.js) if ( @@ -69,7 +69,9 @@ module.exports = { // I suspect that almost all tests need to wait for compilation to // finish, because not doing so leaves open handles for jest, // in the case where a compilation didn't finish before destroying - // the server and moving on + // the server and moving on. Thus, the default "start" should wait + // for compilation, and only special cases where you don't expect + // a compilation happen should use startBeforeCompilation return this.startAwaitingCompilation(config, options, done); }, startBeforeCompilation(config, options, done) {