From 7907c8e3d9158d71a03487786634448ba1a8902d Mon Sep 17 00:00:00 2001 From: Malcolm Kee Date: Sun, 27 Nov 2022 12:25:38 +1100 Subject: [PATCH] fix: remove xstate --- client-src/index.js | 8 +- client-src/overlay.js | 8 +- client-src/overlay/fsm.js | 57 + client-src/overlay/state-machine.js | 44 +- package-lock.json | 11 - package.json | 1 - test/client/index.test.js | 63 +- .../multi-compiler.test.js.snap.webpack5 | 12 +- .../overlay.test.js.snap.webpack5 | 1110 +++++++++-------- 9 files changed, 717 insertions(+), 597 deletions(-) create mode 100644 client-src/overlay/fsm.js diff --git a/client-src/index.js b/client-src/index.js index 706c77cfe1..981bb99ee8 100644 --- a/client-src/index.js +++ b/client-src/index.js @@ -142,7 +142,7 @@ const onSocketMessage = { // Fixes #1042. overlay doesn't clear if errors are fixed but warnings remain. if (options.overlay) { - overlay.send("DISMISS"); + overlay.send({ type: "DISMISS" }); } sendMessage("Invalid"); @@ -199,7 +199,7 @@ const onSocketMessage = { log.info("Nothing changed."); if (options.overlay) { - overlay.send("DISMISS"); + overlay.send({ type: "DISMISS" }); } sendMessage("StillOk"); @@ -208,7 +208,7 @@ const onSocketMessage = { sendMessage("Ok"); if (options.overlay) { - overlay.send("DISMISS"); + overlay.send({ type: "DISMISS" }); } reloadApp(options, status); @@ -317,7 +317,7 @@ const onSocketMessage = { log.info("Disconnected!"); if (options.overlay) { - overlay.send("DISMISS"); + overlay.send({ type: "DISMISS" }); } sendMessage("Close"); diff --git a/client-src/overlay.js b/client-src/overlay.js index 7e2ac75e96..d475a37500 100644 --- a/client-src/overlay.js +++ b/client-src/overlay.js @@ -3,7 +3,6 @@ import ansiHTML from "ansi-html-community"; import { encode } from "html-entities"; -import { interpret } from "@xstate/fsm"; import { containerStyle, dismissButtonStyle, @@ -143,7 +142,7 @@ const createOverlay = (options) => { closeButtonElement.ariaLabel = "Dismiss"; closeButtonElement.addEventListener("click", () => { // eslint-disable-next-line no-use-before-define - overlayService.send("DISMISS"); + overlayService.send({ type: "DISMISS" }); }); contentElement.appendChild(headerElement); @@ -254,16 +253,13 @@ const createOverlay = (options) => { }, trustedTypesPolicyName); } - const overlayMachine = createOverlayMachine({ + const overlayService = createOverlayMachine({ showOverlay: ({ level = "error", messages }) => show(level, messages, options.trustedTypesPolicyName), hideOverlay: hide, }); - const overlayService = interpret(overlayMachine).start(); - listenToRuntimeError((err) => { - console.log(err); overlayService.send({ type: "RUNTIME_ERROR", messages: [err.message], diff --git a/client-src/overlay/fsm.js b/client-src/overlay/fsm.js new file mode 100644 index 0000000000..95abd60f58 --- /dev/null +++ b/client-src/overlay/fsm.js @@ -0,0 +1,57 @@ +/** + * @typedef {Object} StateDefinitions + * @property {{[event: string]: { target: string; actions?: Array }}} [on] + */ + +/** + * @typedef {Object} Options + * @property {{[state: string]: StateDefinitions}} states + * @property {object} context; + * @property {string} initial + */ + +/** + * @typedef {Object} Implementation + * @property {{[actionName: string]: (ctx: object, event: any) => object}} actions + */ + +/** + * A simplified `createMachine` from `@xstate/fsm` with the following differences: + * + * - the returned machine is technically a "service". No `interpret(machine).start()` is needed. + * - the state definition only support `on` and target must be declared with { target: 'nextState', actions: [] } explicitly. + * - event passed to `send` must be an object with `type` property. + * - actions implementation will be [assign action](https://xstate.js.org/docs/guides/context.html#assign-action) if you return any value. + * Do not return anything if you just want to invoke side effect. + * + * The goal of this custom function is to avoid installing the entire `'xstate/fsm'` package, while enabling modeling using + * state machine. You can copy the first parameter into the editor at https://stately.ai/viz to visualize the state machine. + * + * @param {Options} options + * @param {Implementation} implementation + */ +function createMachine({ states, context, initial }, { actions }) { + let currentState = initial; + let currentContext = context; + + return { + send: (event) => { + const transitionConfig = states[currentState].on?.[event.type]; + + if (transitionConfig) { + currentState = transitionConfig.target; + transitionConfig.actions?.forEach((actName) => { + const nextContextValue = actions[actName]?.(currentContext, event); + if (nextContextValue) { + currentContext = { + ...currentContext, + ...nextContextValue, + }; + } + }); + } + }, + }; +} + +export default createMachine; diff --git a/client-src/overlay/state-machine.js b/client-src/overlay/state-machine.js index 5f322daa3f..d9ed764198 100644 --- a/client-src/overlay/state-machine.js +++ b/client-src/overlay/state-machine.js @@ -1,4 +1,4 @@ -import { createMachine, assign } from "@xstate/fsm"; +import createMachine from "./fsm.js"; /** * @typedef {Object} ShowOverlayData @@ -19,7 +19,6 @@ const createOverlayMachine = (options) => { const { hideOverlay, showOverlay } = options; const overlayMachine = createMachine( { - id: "overlay", initial: "hidden", context: { level: "error", @@ -27,7 +26,6 @@ const createOverlayMachine = (options) => { }, states: { hidden: { - entry: "hideOverlay", on: { BUILD_ERROR: { target: "displayBuildError", @@ -41,7 +39,10 @@ const createOverlayMachine = (options) => { }, displayBuildError: { on: { - DISMISS: { target: "hidden", actions: "dismissMessages" }, + DISMISS: { + target: "hidden", + actions: ["dismissMessages", "hideOverlay"], + }, BUILD_ERROR: { target: "displayBuildError", actions: ["appendMessages", "showOverlay"], @@ -50,7 +51,10 @@ const createOverlayMachine = (options) => { }, displayRuntimeError: { on: { - DISMISS: { target: "hidden", actions: "dismissMessages" }, + DISMISS: { + target: "hidden", + actions: ["dismissMessages", "hideOverlay"], + }, RUNTIME_ERROR: { target: "displayRuntimeError", actions: ["appendMessages", "showOverlay"], @@ -65,18 +69,24 @@ const createOverlayMachine = (options) => { }, { actions: { - dismissMessages: assign({ - messages: [], - level: "error", - }), - appendMessages: assign({ - messages: (context, event) => context.messages.concat(event.messages), - level: (context, event) => event.level || context.level, - }), - setMessages: assign({ - messages: (_, event) => event.messages, - level: (context, event) => event.level || context.level, - }), + dismissMessages: () => { + return { + messages: [], + level: "error", + }; + }, + appendMessages: (context, event) => { + return { + messages: context.messages.concat(event.messages), + level: event.level || context.level, + }; + }, + setMessages: (context, event) => { + return { + messages: event.messages, + level: event.level || context.level, + }; + }, hideOverlay, showOverlay, }, diff --git a/package-lock.json b/package-lock.json index a5e41f09c6..ef55211816 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "@types/serve-static": "^1.13.10", "@types/sockjs": "^0.3.33", "@types/ws": "^8.5.1", - "@xstate/fsm": "^2.0.0", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.0.11", "chokidar": "^3.5.3", @@ -3999,11 +3998,6 @@ } } }, - "node_modules/@xstate/fsm": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-2.0.0.tgz", - "integrity": "sha512-p/zcvBMoU2ap5byMefLkR+AM+Eh99CU/SDEQeccgKlmFNOMDwphaRGqdk+emvel/SaGZ7Rf9sDvzAplLzLdEVQ==" - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -18770,11 +18764,6 @@ "dev": true, "requires": {} }, - "@xstate/fsm": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-2.0.0.tgz", - "integrity": "sha512-p/zcvBMoU2ap5byMefLkR+AM+Eh99CU/SDEQeccgKlmFNOMDwphaRGqdk+emvel/SaGZ7Rf9sDvzAplLzLdEVQ==" - }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", diff --git a/package.json b/package.json index 78e9dccbd4..0ea6a2d3c7 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "@types/serve-static": "^1.13.10", "@types/sockjs": "^0.3.33", "@types/ws": "^8.5.1", - "@xstate/fsm": "^2.0.0", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.0.11", "chokidar": "^3.5.3", diff --git a/test/client/index.test.js b/test/client/index.test.js index a1ed197be5..c3883e5fd4 100644 --- a/test/client/index.test.js +++ b/test/client/index.test.js @@ -35,15 +35,21 @@ describe("index", () => { jest.setMock("../../client-src/socket.js", jest.fn()); socket = require("../../client-src/socket"); + const send = jest.fn(); + // overlay jest.setMock("../../client-src/overlay.js", { - hide: jest.fn(), - show: jest.fn(), + createOverlay: () => { + return { + send, + }; + }, formatProblem: (item) => { return { header: "HEADER warning", body: `BODY: ${item}` }; }, }); - overlay = require("../../client-src/overlay"); + const { createOverlay } = require("../../client-src/overlay"); + overlay = createOverlay(); // reloadApp jest.setMock("../../client-src/utils/reloadApp.js", jest.fn()); @@ -89,13 +95,13 @@ describe("index", () => { expect(log.log.info.mock.calls[0][0]).toMatchSnapshot(); expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); - expect(overlay.hide).not.toBeCalled(); + expect(overlay.send).not.toBeCalledWith({ type: "DISMISS" }); // change flags onSocketMessage.overlay(true); onSocketMessage["still-ok"](); - expect(overlay.hide).toBeCalled(); + expect(overlay.send).toHaveBeenCalledWith({ type: "DISMISS" }); }); test("should run onSocketMessage.progress and onSocketMessage['progress-update']", () => { @@ -191,9 +197,14 @@ describe("index", () => { // change flags onSocketMessage.overlay({ warnings: true }); - onSocketMessage.warnings([]); + onSocketMessage.warnings(["warning message"]); - expect(overlay.show).toBeCalled(); + expect(overlay.send).toHaveBeenCalledTimes(1); + expect(overlay.send).toHaveBeenCalledWith({ + type: "BUILD_ERROR", + level: "warning", + messages: ["warning message"], + }); }); test("should parse overlay options from resource query", () => { @@ -202,17 +213,22 @@ describe("index", () => { global.__resourceQuery = `?overlay=${encodeURIComponent( `{"warnings": false}` )}`; - overlay.show.mockReset(); + overlay.send.mockReset(); socket.mockReset(); jest.unmock("../../client-src/utils/parseURL.js"); require("../../client-src"); onSocketMessage = socket.mock.calls[0][1]; onSocketMessage.warnings(["warn1"]); - expect(overlay.show).not.toBeCalled(); + expect(overlay.send).not.toBeCalled(); onSocketMessage.errors(["error1"]); - expect(overlay.show).toBeCalledTimes(1); + expect(overlay.send).toBeCalledTimes(1); + expect(overlay.send).toHaveBeenCalledWith({ + type: "BUILD_ERROR", + level: "error", + messages: ["error1"], + }); }); jest.isolateModules(() => { @@ -220,17 +236,22 @@ describe("index", () => { global.__resourceQuery = `?overlay=${encodeURIComponent( `{"errors": false}` )}`; - overlay.show.mockReset(); + overlay.send.mockReset(); socket.mockReset(); jest.unmock("../../client-src/utils/parseURL.js"); require("../../client-src"); onSocketMessage = socket.mock.calls[0][1]; onSocketMessage.errors(["error1"]); - expect(overlay.show).not.toBeCalled(); + expect(overlay.send).not.toBeCalled(); onSocketMessage.warnings(["warn1"]); - expect(overlay.show).toBeCalledTimes(1); + expect(overlay.send).toBeCalledTimes(1); + expect(overlay.send).toHaveBeenCalledWith({ + type: "BUILD_ERROR", + level: "warning", + messages: ["warn1"], + }); }); jest.isolateModules(() => { @@ -238,15 +259,25 @@ describe("index", () => { global.__resourceQuery = "?overlay=true"; jest.unmock("../../client-src/utils/parseURL.js"); socket.mockReset(); - overlay.show.mockReset(); + overlay.send.mockReset(); require("../../client-src"); onSocketMessage = socket.mock.calls[0][1]; onSocketMessage.warnings(["warn2"]); - expect(overlay.show).toBeCalledTimes(1); + expect(overlay.send).toBeCalledTimes(1); + expect(overlay.send).toHaveBeenLastCalledWith({ + type: "BUILD_ERROR", + level: "warning", + messages: ["warn2"], + }); onSocketMessage.errors(["error2"]); - expect(overlay.show).toBeCalledTimes(2); + expect(overlay.send).toBeCalledTimes(2); + expect(overlay.send).toHaveBeenLastCalledWith({ + type: "BUILD_ERROR", + level: "error", + messages: ["error2"], + }); }); }); diff --git a/test/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 b/test/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 index 764980bfe3..5da317a992 100644 --- a/test/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 +++ b/test/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 @@ -51,13 +51,13 @@ Array [ "[HMR] Cannot apply update. Need to do a full reload!", "[HMR] Error: Aborted because ./browser.js is not accepted Update propagation: ./browser.js - at applyHandler (http://127.0.0.1:8103/browser.js:1044:31) - at http://127.0.0.1:8103/browser.js:743:21 + at applyHandler (http://127.0.0.1:8103/browser.js:1077:31) + at http://127.0.0.1:8103/browser.js:776:21 at Array.map () - at internalApply (http://127.0.0.1:8103/browser.js:742:54) - at http://127.0.0.1:8103/browser.js:712:26 - at waitForBlockingPromises (http://127.0.0.1:8103/browser.js:666:48) - at http://127.0.0.1:8103/browser.js:710:24", + at internalApply (http://127.0.0.1:8103/browser.js:775:54) + at http://127.0.0.1:8103/browser.js:745:26 + at waitForBlockingPromises (http://127.0.0.1:8103/browser.js:699:48) + at http://127.0.0.1:8103/browser.js:743:24", "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled.", "[HMR] Waiting for update signal from WDS...", "Hello from the browser", diff --git a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 index c85c4cf4cc..33fc1dbc70 100644 --- a/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 +++ b/test/e2e/__snapshots__/overlay.test.js.snap.webpack5 @@ -82,36 +82,38 @@ exports[`overlay should not show initially, then show on an error and allow to c > × -
-
- ERROR in ./foo.js 1:1 -
+
- Module parse failed: Unterminated template (1:1) You may need an - appropriate loader to handle this file type, currently no loaders are - configured to process this file. See - https://webpack.js.org/concepts#loaders > \`; +
+ ERROR in ./foo.js 1:1 +
+
+ Module parse failed: Unterminated template (1:1) You may need an + appropriate loader to handle this file type, currently no loaders are + configured to process this file. See + https://webpack.js.org/concepts#loaders > \`; +
@@ -206,36 +208,38 @@ exports[`overlay should not show initially, then show on an error, then hide on > × -
-
- ERROR in ./foo.js 1:1 -
+
- Module parse failed: Unterminated template (1:1) You may need an - appropriate loader to handle this file type, currently no loaders are - configured to process this file. See - https://webpack.js.org/concepts#loaders > \`; +
+ ERROR in ./foo.js 1:1 +
+
+ Module parse failed: Unterminated template (1:1) You may need an + appropriate loader to handle this file type, currently no loaders are + configured to process this file. See + https://webpack.js.org/concepts#loaders > \`; +
@@ -330,36 +334,38 @@ exports[`overlay should not show initially, then show on an error, then show oth > × -
+
- ERROR in ./foo.js 1:1 -
-
- Module parse failed: Unterminated template (1:1) You may need an - appropriate loader to handle this file type, currently no loaders are - configured to process this file. See - https://webpack.js.org/concepts#loaders > \`; +
+ ERROR in ./foo.js 1:1 +
+
+ Module parse failed: Unterminated template (1:1) You may need an + appropriate loader to handle this file type, currently no loaders are + configured to process this file. See + https://webpack.js.org/concepts#loaders > \`; +
@@ -417,36 +423,38 @@ exports[`overlay should not show initially, then show on an error, then show oth > × -
-
- ERROR in ./foo.js 1:1 -
+
- Module parse failed: Unterminated template (1:1) You may need an - appropriate loader to handle this file type, currently no loaders are - configured to process this file. See - https://webpack.js.org/concepts#loaders > \`;a +
+ ERROR in ./foo.js 1:1 +
+
+ Module parse failed: Unterminated template (1:1) You may need an + appropriate loader to handle this file type, currently no loaders are + configured to process this file. See + https://webpack.js.org/concepts#loaders > \`;a +
@@ -570,31 +578,33 @@ exports[`overlay should show a warning after invalidation: overlay html 1`] = ` > × -
-
- WARNING -
+
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -673,58 +683,60 @@ exports[`overlay should show a warning and error for initial compilation and pro > × -
-
- WARNING -
+
- <strong>strong</strong> -
-
-
-
- ERROR +
+ ERROR +
+
+ <strong>strong</strong> +
- <strong>strong</strong> +
+ ERROR +
+
+ <strong>strong</strong> +
@@ -803,139 +815,141 @@ exports[`overlay should show a warning and error for initial compilation: overla > × -
-
- WARNING -
-
- Warning from compilation -
-
-
-
- WARNING -
-
- Warning from compilation -
-
-
-
- ERROR -
+
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Warning from compilation +
-
-
- ERROR +
+ ERROR +
+
+ Warning from compilation +
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
-
-
- ERROR +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -1014,31 +1028,33 @@ exports[`overlay should show a warning and hide them after closing connection: o > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1125,31 +1141,33 @@ exports[`overlay should show a warning for initial compilation: overlay html 1`] > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1228,31 +1246,33 @@ exports[`overlay should show a warning when "client.overlay" is "true": overlay > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1331,31 +1351,33 @@ exports[`overlay should show a warning when "client.overlay.errors" is "true": o > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1434,31 +1456,33 @@ exports[`overlay should show a warning when "client.overlay.warnings" is "true": > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -1537,42 +1561,44 @@ exports[`overlay should show an ansi formatted error for initial compilation: ov > × -
+
- ERROR -
-
- - 18 | - Render - ansi formatted text +
+ + 18 | + Render + ansi formatted text +
@@ -1651,31 +1677,33 @@ exports[`overlay should show an error after invalidation: overlay html 1`] = ` > × -
-
- ERROR -
+
- Error from compilation +
+ ERROR +
+
+ Error from compilation +
@@ -1754,31 +1782,33 @@ exports[`overlay should show an error for initial compilation: overlay html 1`] > × -
+
- ERROR -
-
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -1857,31 +1887,33 @@ exports[`overlay should show an error when "client.overlay" is "true": overlay h > × -
+
- ERROR -
-
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -1960,31 +1992,33 @@ exports[`overlay should show an error when "client.overlay.errors" is "true": ov > × -
-
- ERROR -
+
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +
@@ -2063,31 +2097,33 @@ exports[`overlay should show an error when "client.overlay.warnings" is "true": > × -
+
- WARNING -
-
- Warning from compilation +
+ WARNING +
+
+ Warning from compilation +
@@ -2166,31 +2202,33 @@ exports[`overlay should show overlay when Trusted Types are enabled: overlay htm > × -
+
- ERROR -
-
- Error from compilation. Can't find 'test' module. +
+ ERROR +
+
+ Error from compilation. Can't find 'test' module. +