Skip to content

Commit 7907c8e

Browse files
committed
fix: remove xstate
1 parent a61563c commit 7907c8e

File tree

9 files changed

+717
-597
lines changed

9 files changed

+717
-597
lines changed

client-src/index.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ const onSocketMessage = {
142142

143143
// Fixes #1042. overlay doesn't clear if errors are fixed but warnings remain.
144144
if (options.overlay) {
145-
overlay.send("DISMISS");
145+
overlay.send({ type: "DISMISS" });
146146
}
147147

148148
sendMessage("Invalid");
@@ -199,7 +199,7 @@ const onSocketMessage = {
199199
log.info("Nothing changed.");
200200

201201
if (options.overlay) {
202-
overlay.send("DISMISS");
202+
overlay.send({ type: "DISMISS" });
203203
}
204204

205205
sendMessage("StillOk");
@@ -208,7 +208,7 @@ const onSocketMessage = {
208208
sendMessage("Ok");
209209

210210
if (options.overlay) {
211-
overlay.send("DISMISS");
211+
overlay.send({ type: "DISMISS" });
212212
}
213213

214214
reloadApp(options, status);
@@ -317,7 +317,7 @@ const onSocketMessage = {
317317
log.info("Disconnected!");
318318

319319
if (options.overlay) {
320-
overlay.send("DISMISS");
320+
overlay.send({ type: "DISMISS" });
321321
}
322322

323323
sendMessage("Close");

client-src/overlay.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import ansiHTML from "ansi-html-community";
55
import { encode } from "html-entities";
6-
import { interpret } from "@xstate/fsm";
76
import {
87
containerStyle,
98
dismissButtonStyle,
@@ -143,7 +142,7 @@ const createOverlay = (options) => {
143142
closeButtonElement.ariaLabel = "Dismiss";
144143
closeButtonElement.addEventListener("click", () => {
145144
// eslint-disable-next-line no-use-before-define
146-
overlayService.send("DISMISS");
145+
overlayService.send({ type: "DISMISS" });
147146
});
148147

149148
contentElement.appendChild(headerElement);
@@ -254,16 +253,13 @@ const createOverlay = (options) => {
254253
}, trustedTypesPolicyName);
255254
}
256255

257-
const overlayMachine = createOverlayMachine({
256+
const overlayService = createOverlayMachine({
258257
showOverlay: ({ level = "error", messages }) =>
259258
show(level, messages, options.trustedTypesPolicyName),
260259
hideOverlay: hide,
261260
});
262261

263-
const overlayService = interpret(overlayMachine).start();
264-
265262
listenToRuntimeError((err) => {
266-
console.log(err);
267263
overlayService.send({
268264
type: "RUNTIME_ERROR",
269265
messages: [err.message],

client-src/overlay/fsm.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @typedef {Object} StateDefinitions
3+
* @property {{[event: string]: { target: string; actions?: Array<string> }}} [on]
4+
*/
5+
6+
/**
7+
* @typedef {Object} Options
8+
* @property {{[state: string]: StateDefinitions}} states
9+
* @property {object} context;
10+
* @property {string} initial
11+
*/
12+
13+
/**
14+
* @typedef {Object} Implementation
15+
* @property {{[actionName: string]: (ctx: object, event: any) => object}} actions
16+
*/
17+
18+
/**
19+
* A simplified `createMachine` from `@xstate/fsm` with the following differences:
20+
*
21+
* - the returned machine is technically a "service". No `interpret(machine).start()` is needed.
22+
* - the state definition only support `on` and target must be declared with { target: 'nextState', actions: [] } explicitly.
23+
* - event passed to `send` must be an object with `type` property.
24+
* - actions implementation will be [assign action](https://xstate.js.org/docs/guides/context.html#assign-action) if you return any value.
25+
* Do not return anything if you just want to invoke side effect.
26+
*
27+
* The goal of this custom function is to avoid installing the entire `'xstate/fsm'` package, while enabling modeling using
28+
* state machine. You can copy the first parameter into the editor at https://stately.ai/viz to visualize the state machine.
29+
*
30+
* @param {Options} options
31+
* @param {Implementation} implementation
32+
*/
33+
function createMachine({ states, context, initial }, { actions }) {
34+
let currentState = initial;
35+
let currentContext = context;
36+
37+
return {
38+
send: (event) => {
39+
const transitionConfig = states[currentState].on?.[event.type];
40+
41+
if (transitionConfig) {
42+
currentState = transitionConfig.target;
43+
transitionConfig.actions?.forEach((actName) => {
44+
const nextContextValue = actions[actName]?.(currentContext, event);
45+
if (nextContextValue) {
46+
currentContext = {
47+
...currentContext,
48+
...nextContextValue,
49+
};
50+
}
51+
});
52+
}
53+
},
54+
};
55+
}
56+
57+
export default createMachine;

client-src/overlay/state-machine.js

+27-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createMachine, assign } from "@xstate/fsm";
1+
import createMachine from "./fsm.js";
22

33
/**
44
* @typedef {Object} ShowOverlayData
@@ -19,15 +19,13 @@ const createOverlayMachine = (options) => {
1919
const { hideOverlay, showOverlay } = options;
2020
const overlayMachine = createMachine(
2121
{
22-
id: "overlay",
2322
initial: "hidden",
2423
context: {
2524
level: "error",
2625
messages: [],
2726
},
2827
states: {
2928
hidden: {
30-
entry: "hideOverlay",
3129
on: {
3230
BUILD_ERROR: {
3331
target: "displayBuildError",
@@ -41,7 +39,10 @@ const createOverlayMachine = (options) => {
4139
},
4240
displayBuildError: {
4341
on: {
44-
DISMISS: { target: "hidden", actions: "dismissMessages" },
42+
DISMISS: {
43+
target: "hidden",
44+
actions: ["dismissMessages", "hideOverlay"],
45+
},
4546
BUILD_ERROR: {
4647
target: "displayBuildError",
4748
actions: ["appendMessages", "showOverlay"],
@@ -50,7 +51,10 @@ const createOverlayMachine = (options) => {
5051
},
5152
displayRuntimeError: {
5253
on: {
53-
DISMISS: { target: "hidden", actions: "dismissMessages" },
54+
DISMISS: {
55+
target: "hidden",
56+
actions: ["dismissMessages", "hideOverlay"],
57+
},
5458
RUNTIME_ERROR: {
5559
target: "displayRuntimeError",
5660
actions: ["appendMessages", "showOverlay"],
@@ -65,18 +69,24 @@ const createOverlayMachine = (options) => {
6569
},
6670
{
6771
actions: {
68-
dismissMessages: assign({
69-
messages: [],
70-
level: "error",
71-
}),
72-
appendMessages: assign({
73-
messages: (context, event) => context.messages.concat(event.messages),
74-
level: (context, event) => event.level || context.level,
75-
}),
76-
setMessages: assign({
77-
messages: (_, event) => event.messages,
78-
level: (context, event) => event.level || context.level,
79-
}),
72+
dismissMessages: () => {
73+
return {
74+
messages: [],
75+
level: "error",
76+
};
77+
},
78+
appendMessages: (context, event) => {
79+
return {
80+
messages: context.messages.concat(event.messages),
81+
level: event.level || context.level,
82+
};
83+
},
84+
setMessages: (context, event) => {
85+
return {
86+
messages: event.messages,
87+
level: event.level || context.level,
88+
};
89+
},
8090
hideOverlay,
8191
showOverlay,
8292
},

package-lock.json

-11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
"@types/serve-static": "^1.13.10",
5252
"@types/sockjs": "^0.3.33",
5353
"@types/ws": "^8.5.1",
54-
"@xstate/fsm": "^2.0.0",
5554
"ansi-html-community": "^0.0.8",
5655
"bonjour-service": "^1.0.11",
5756
"chokidar": "^3.5.3",

test/client/index.test.js

+47-16
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,21 @@ describe("index", () => {
3535
jest.setMock("../../client-src/socket.js", jest.fn());
3636
socket = require("../../client-src/socket");
3737

38+
const send = jest.fn();
39+
3840
// overlay
3941
jest.setMock("../../client-src/overlay.js", {
40-
hide: jest.fn(),
41-
show: jest.fn(),
42+
createOverlay: () => {
43+
return {
44+
send,
45+
};
46+
},
4247
formatProblem: (item) => {
4348
return { header: "HEADER warning", body: `BODY: ${item}` };
4449
},
4550
});
46-
overlay = require("../../client-src/overlay");
51+
const { createOverlay } = require("../../client-src/overlay");
52+
overlay = createOverlay();
4753

4854
// reloadApp
4955
jest.setMock("../../client-src/utils/reloadApp.js", jest.fn());
@@ -89,13 +95,13 @@ describe("index", () => {
8995

9096
expect(log.log.info.mock.calls[0][0]).toMatchSnapshot();
9197
expect(sendMessage.mock.calls[0][0]).toMatchSnapshot();
92-
expect(overlay.hide).not.toBeCalled();
98+
expect(overlay.send).not.toBeCalledWith({ type: "DISMISS" });
9399

94100
// change flags
95101
onSocketMessage.overlay(true);
96102
onSocketMessage["still-ok"]();
97103

98-
expect(overlay.hide).toBeCalled();
104+
expect(overlay.send).toHaveBeenCalledWith({ type: "DISMISS" });
99105
});
100106

101107
test("should run onSocketMessage.progress and onSocketMessage['progress-update']", () => {
@@ -191,9 +197,14 @@ describe("index", () => {
191197

192198
// change flags
193199
onSocketMessage.overlay({ warnings: true });
194-
onSocketMessage.warnings([]);
200+
onSocketMessage.warnings(["warning message"]);
195201

196-
expect(overlay.show).toBeCalled();
202+
expect(overlay.send).toHaveBeenCalledTimes(1);
203+
expect(overlay.send).toHaveBeenCalledWith({
204+
type: "BUILD_ERROR",
205+
level: "warning",
206+
messages: ["warning message"],
207+
});
197208
});
198209

199210
test("should parse overlay options from resource query", () => {
@@ -202,51 +213,71 @@ describe("index", () => {
202213
global.__resourceQuery = `?overlay=${encodeURIComponent(
203214
`{"warnings": false}`
204215
)}`;
205-
overlay.show.mockReset();
216+
overlay.send.mockReset();
206217
socket.mockReset();
207218
jest.unmock("../../client-src/utils/parseURL.js");
208219
require("../../client-src");
209220
onSocketMessage = socket.mock.calls[0][1];
210221

211222
onSocketMessage.warnings(["warn1"]);
212-
expect(overlay.show).not.toBeCalled();
223+
expect(overlay.send).not.toBeCalled();
213224

214225
onSocketMessage.errors(["error1"]);
215-
expect(overlay.show).toBeCalledTimes(1);
226+
expect(overlay.send).toBeCalledTimes(1);
227+
expect(overlay.send).toHaveBeenCalledWith({
228+
type: "BUILD_ERROR",
229+
level: "error",
230+
messages: ["error1"],
231+
});
216232
});
217233

218234
jest.isolateModules(() => {
219235
// Pass JSON config with errors disabled
220236
global.__resourceQuery = `?overlay=${encodeURIComponent(
221237
`{"errors": false}`
222238
)}`;
223-
overlay.show.mockReset();
239+
overlay.send.mockReset();
224240
socket.mockReset();
225241
jest.unmock("../../client-src/utils/parseURL.js");
226242
require("../../client-src");
227243
onSocketMessage = socket.mock.calls[0][1];
228244

229245
onSocketMessage.errors(["error1"]);
230-
expect(overlay.show).not.toBeCalled();
246+
expect(overlay.send).not.toBeCalled();
231247

232248
onSocketMessage.warnings(["warn1"]);
233-
expect(overlay.show).toBeCalledTimes(1);
249+
expect(overlay.send).toBeCalledTimes(1);
250+
expect(overlay.send).toHaveBeenCalledWith({
251+
type: "BUILD_ERROR",
252+
level: "warning",
253+
messages: ["warn1"],
254+
});
234255
});
235256

236257
jest.isolateModules(() => {
237258
// Use simple boolean
238259
global.__resourceQuery = "?overlay=true";
239260
jest.unmock("../../client-src/utils/parseURL.js");
240261
socket.mockReset();
241-
overlay.show.mockReset();
262+
overlay.send.mockReset();
242263
require("../../client-src");
243264
onSocketMessage = socket.mock.calls[0][1];
244265

245266
onSocketMessage.warnings(["warn2"]);
246-
expect(overlay.show).toBeCalledTimes(1);
267+
expect(overlay.send).toBeCalledTimes(1);
268+
expect(overlay.send).toHaveBeenLastCalledWith({
269+
type: "BUILD_ERROR",
270+
level: "warning",
271+
messages: ["warn2"],
272+
});
247273

248274
onSocketMessage.errors(["error2"]);
249-
expect(overlay.show).toBeCalledTimes(2);
275+
expect(overlay.send).toBeCalledTimes(2);
276+
expect(overlay.send).toHaveBeenLastCalledWith({
277+
type: "BUILD_ERROR",
278+
level: "error",
279+
messages: ["error2"],
280+
});
250281
});
251282
});
252283

0 commit comments

Comments
 (0)