From 1768d6b7913055dad02318a49de65df2e93baa4f Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Wed, 18 Aug 2021 19:31:05 +0300 Subject: [PATCH] fix: initial reloading for lazy compilation (#3662) --- client-src/index.js | 21 ++--- client-src/utils/reloadApp.js | 30 +++++++- .../__snapshots__/index.test.js.snap.webpack4 | 1 - .../__snapshots__/index.test.js.snap.webpack5 | 1 - test/client/index.test.js | 76 +++++++++---------- test/client/utils/reloadApp.test.js | 21 +++-- test/e2e/lazy-compilation.test.js | 54 +++++++++++++ test/ports-map.js | 1 + 8 files changed, 140 insertions(+), 65 deletions(-) create mode 100644 test/e2e/lazy-compilation.test.js diff --git a/client-src/index.js b/client-src/index.js index a7dff9a5b0..c20ddb3fbb 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -1,4 +1,4 @@ -/* global __resourceQuery */ +/* global __resourceQuery, __webpack_hash__ */ import webpackHotLog from "webpack/hot/log.js"; import stripAnsi from "./modules/strip-ansi/index.js"; @@ -10,11 +10,16 @@ import sendMessage from "./utils/sendMessage.js"; import reloadApp from "./utils/reloadApp.js"; import createSocketURL from "./utils/createSocketURL.js"; -const status = { isUnloading: false, currentHash: "" }; +const status = { + isUnloading: false, + // TODO Workaround for webpack v4, `__webpack_hash__` is not replaced without HotModuleReplacement + // eslint-disable-next-line camelcase + currentHash: typeof __webpack_hash__ !== "undefined" ? __webpack_hash__ : "", +}; +// console.log(__webpack_hash__); const options = { hot: false, liveReload: false, - initial: true, progress: false, overlay: false, }; @@ -110,10 +115,6 @@ const onSocketMessage = { hide(); } - if (options.initial) { - return (options.initial = false); - } - reloadApp(options, status); }, // TODO: remove in v5 in favor of 'static-changed' @@ -157,10 +158,6 @@ const onSocketMessage = { show(warnings, "warnings"); } - if (options.initial) { - return (options.initial = false); - } - reloadApp(options, status); }, errors(errors) { @@ -184,8 +181,6 @@ const onSocketMessage = { if (needShowOverlay) { show(errors, "errors"); } - - options.initial = false; }, error(error) { log.error(error); diff --git a/client-src/utils/reloadApp.js b/client-src/utils/reloadApp.js index 153093f29b..83368e4010 100644 --- a/client-src/utils/reloadApp.js +++ b/client-src/utils/reloadApp.js @@ -1,8 +1,30 @@ +/* global __webpack_hash__ */ + import hotEmitter from "webpack/hot/emitter.js"; import { log } from "./log.js"; -function reloadApp({ hot, liveReload }, { isUnloading, currentHash }) { - if (isUnloading) { +function reloadApp({ hot, liveReload }, status) { + if (status.isUnloading) { + return; + } + + // TODO Workaround for webpack v4, `__webpack_hash__` is not replaced without HotModuleReplacement plugin + const webpackHash = + // eslint-disable-next-line camelcase + typeof __webpack_hash__ !== "undefined" + ? // eslint-disable-next-line camelcase + __webpack_hash__ + : status.previousHash || ""; + const isInitial = status.currentHash.indexOf(webpackHash) === 0; + + if (isInitial) { + const isLegacyInitial = + webpackHash === "" && hot === false && liveReload === true; + + if (isLegacyInitial) { + status.previousHash = status.currentHash; + } + return; } @@ -22,11 +44,11 @@ function reloadApp({ hot, liveReload }, { isUnloading, currentHash }) { if (hot && allowToHot) { log.info("App hot update..."); - hotEmitter.emit("webpackHotUpdate", currentHash); + hotEmitter.emit("webpackHotUpdate", status.currentHash); if (typeof self !== "undefined" && self.window) { // broadcast update to window - self.postMessage(`webpackHotUpdate${currentHash}`, "*"); + self.postMessage(`webpackHotUpdate${status.currentHash}`, "*"); } } // allow refreshing the page only if liveReload isn't disabled diff --git a/test/client/__snapshots__/index.test.js.snap.webpack4 b/test/client/__snapshots__/index.test.js.snap.webpack4 index 786d04b519..6705d56be4 100644 --- a/test/client/__snapshots__/index.test.js.snap.webpack4 +++ b/test/client/__snapshots__/index.test.js.snap.webpack4 @@ -23,7 +23,6 @@ exports[`index should run onSocketMessage.ok 1`] = `"Ok"`; exports[`index should run onSocketMessage.ok 2`] = ` Object { "hot": false, - "initial": false, "liveReload": false, "logging": "info", "overlay": false, diff --git a/test/client/__snapshots__/index.test.js.snap.webpack5 b/test/client/__snapshots__/index.test.js.snap.webpack5 index 786d04b519..6705d56be4 100644 --- a/test/client/__snapshots__/index.test.js.snap.webpack5 +++ b/test/client/__snapshots__/index.test.js.snap.webpack5 @@ -23,7 +23,6 @@ exports[`index should run onSocketMessage.ok 1`] = `"Ok"`; exports[`index should run onSocketMessage.ok 2`] = ` Object { "hot": false, - "initial": false, "liveReload": false, "logging": "info", "overlay": false, diff --git a/test/client/index.test.js b/test/client/index.test.js index 8e5ebf2ff3..8d9826b63b 100644 --- a/test/client/index.test.js +++ b/test/client/index.test.js @@ -16,6 +16,7 @@ describe("index", () => { beforeEach(() => { global.__resourceQuery = "foo"; + global.__webpack_hash__ = "mock-hash"; // log jest.setMock("../../client-src/utils/log.js", { @@ -61,10 +62,8 @@ describe("index", () => { // issue: https://github.com/jsdom/jsdom/issues/2112 delete window.location; - window.location = { - ...locationValue, - reload: jest.fn(), - }; + + window.location = { ...locationValue, reload: jest.fn() }; require("../../client-src"); onSocketMessage = socket.mock.calls[0][1]; @@ -83,16 +82,19 @@ describe("index", () => { test("should run onSocketMessage.hot", () => { onSocketMessage.hot(); + expect(log.log.info.mock.calls[0][0]).toMatchSnapshot(); }); test("should run onSocketMessage.liveReload", () => { onSocketMessage.liveReload(); + expect(log.log.info.mock.calls[0][0]).toMatchSnapshot(); }); test("should run onSocketMessage['still-ok']", () => { onSocketMessage["still-ok"](); + expect(log.log.info.mock.calls[0][0]).toMatchSnapshot(); expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); expect(overlay.hide).not.toBeCalled(); @@ -100,6 +102,7 @@ describe("index", () => { // change flags onSocketMessage.overlay(true); onSocketMessage["still-ok"](); + expect(overlay.hide).toBeCalled(); }); @@ -109,6 +112,7 @@ describe("index", () => { msg: "mock-msg", percent: "12", }); + expect(log.log.info).not.toBeCalled(); expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); @@ -117,6 +121,7 @@ describe("index", () => { msg: "mock-msg", percent: "12", }); + expect(log.log.info.mock.calls[0][0]).toMatchSnapshot(); }); @@ -127,6 +132,7 @@ describe("index", () => { percent: "12", pluginName: "mock-plugin", }); + expect(log.log.info).not.toBeCalled(); expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); @@ -136,27 +142,24 @@ describe("index", () => { percent: "12", pluginName: "mock-plugin", }); + expect(log.log.info.mock.calls[0][0]).toMatchSnapshot(); }); test("should run onSocketMessage.ok", () => { - { - const res = onSocketMessage.ok(); - expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); - expect(res).toEqual(false); - } + onSocketMessage.ok(); - // change flags - { - onSocketMessage.errors([]); - onSocketMessage.hash("mock-hash"); + expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); + + onSocketMessage.errors([]); + onSocketMessage.hash("mock-hash"); + + const res = onSocketMessage.ok(); - const res = onSocketMessage.ok(); - expect(reloadApp).toBeCalled(); - expect(reloadApp.mock.calls[0][0]).toMatchSnapshot(); - // eslint-disable-next-line no-undefined - expect(res).toEqual(undefined); - } + expect(reloadApp).toBeCalled(); + expect(reloadApp.mock.calls[0][0]).toMatchSnapshot(); + // eslint-disable-next-line no-undefined + expect(res).toEqual(undefined); }); test("should run onSocketMessage['content-changed']", () => { @@ -188,40 +191,29 @@ describe("index", () => { }); test("should run onSocketMessage.warnings", () => { - { - const res = onSocketMessage.warnings([ - "warn1", - "\u001B[4mwarn2\u001B[0m", - "warn3", - ]); - expect(res).toEqual(false); - - expect(log.log.warn.mock.calls[0][0]).toMatchSnapshot(); - expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); - expect(log.log.warn.mock.calls.splice(1)).toMatchSnapshot(); - } + onSocketMessage.warnings(["warn1", "\u001B[4mwarn2\u001B[0m", "warn3"]); + + expect(log.log.warn.mock.calls[0][0]).toMatchSnapshot(); + expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); + expect(log.log.warn.mock.calls.splice(1)).toMatchSnapshot(); // change flags - { - onSocketMessage.overlay({ - warnings: true, - }); - const res = onSocketMessage.warnings([]); - // eslint-disable-next-line no-undefined - expect(res).toEqual(undefined); + onSocketMessage.overlay({ warnings: true }); + onSocketMessage.warnings([]); - expect(overlay.show).toBeCalled(); - expect(reloadApp).toBeCalled(); - } + expect(overlay.show).toBeCalled(); + expect(reloadApp).toBeCalled(); }); test("should run onSocketMessage.error", () => { onSocketMessage.error("error!!"); + expect(log.log.error.mock.calls[0][0]).toMatchSnapshot(); }); test("should run onSocketMessage.close", () => { onSocketMessage.close(); + expect(log.log.error.mock.calls[0][0]).toMatchSnapshot(); expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); }); @@ -230,6 +222,7 @@ describe("index", () => { // enabling hot onSocketMessage.hot(); onSocketMessage.close(); + expect(log.log.error.mock.calls[0][0]).toMatchSnapshot(); expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); }); @@ -238,6 +231,7 @@ describe("index", () => { // enabling liveReload onSocketMessage.liveReload(); onSocketMessage.close(); + expect(log.log.error.mock.calls[0][0]).toMatchSnapshot(); expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); }); diff --git a/test/client/utils/reloadApp.test.js b/test/client/utils/reloadApp.test.js index 7819d9cd4a..d527df19ab 100644 --- a/test/client/utils/reloadApp.test.js +++ b/test/client/utils/reloadApp.test.js @@ -10,6 +10,8 @@ describe("'reloadApp' function", () => { let locationValue; beforeEach(() => { + global.__webpack_hash__ = "mock-hash"; + locationValue = self.location; self.postMessage = jest.fn(); @@ -38,10 +40,19 @@ describe("'reloadApp' function", () => { }); test("should do nothing when isUnloading is true or hotReload is false", () => { - // eslint-disable-next-line no-undefined - expect(reloadApp({}, { isUnloading: false })).toEqual(undefined); + expect( + reloadApp({}, { isUnloading: false, currentHash: "mock-hash" }) + ).toEqual( + // eslint-disable-next-line no-undefined + undefined + ); expect(log.getLogger.mock.results[0].value.info).not.toBeCalled(); - expect(reloadApp({ hotReload: false }, { isUnloading: false })).toEqual( + expect( + reloadApp( + { hotReload: false }, + { isUnloading: false, currentHash: "other-mock-hash" } + ) + ).toEqual( // eslint-disable-next-line no-undefined undefined ); @@ -75,7 +86,7 @@ describe("'reloadApp' function", () => { reloadApp( { hot: false, hotReload: true, liveReload: true }, - { isUnloading: false } + { isUnloading: false, currentHash: "other-mock-hash" } ); setTimeout(() => { @@ -90,7 +101,7 @@ describe("'reloadApp' function", () => { test("should run liveReload when protocol is http:", (done) => { reloadApp( { hot: false, hotReload: true, liveReload: true }, - { isUnloading: false } + { isUnloading: false, currentHash: "other-mock-hash" } ); setTimeout(() => { diff --git a/test/e2e/lazy-compilation.test.js b/test/e2e/lazy-compilation.test.js new file mode 100644 index 0000000000..ca7855b273 --- /dev/null +++ b/test/e2e/lazy-compilation.test.js @@ -0,0 +1,54 @@ +"use strict"; + +const webpack = require("webpack"); +const Server = require("../../lib/Server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../ports-map")["lazy-compilation"]; + +describe("lazy compilation", () => { + // TODO jest freeze due webpack do not close `eventsource`, we should uncomment this after fix it on webpack side + it.skip(`should work`, async () => { + const compiler = webpack({ + ...config, + experiments: { + lazyCompilation: true, + }, + }); + const server = new Server({ port }, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", (message) => { + consoleMessages.push(message.text()); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/main`, { + waitUntil: "domcontentloaded", + }); + await new Promise((resolve) => { + const interval = setInterval(() => { + if (consoleMessages.includes("Hey.")) { + clearInterval(interval); + + resolve(); + } + }, 100); + }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + + await browser.close(); + await server.stop(); + }); +}); diff --git a/test/ports-map.js b/test/ports-map.js index 88d64ff4f7..9e877ea9cc 100644 --- a/test/ports-map.js +++ b/test/ports-map.js @@ -69,6 +69,7 @@ const listOfTests = { "allowed-hosts": 2, "host-and-port": 1, api: 1, + "lazy-compilation": 1, }; let startPort = 8089;