Inspector and parent window freeze when attempting to serialize an HTML element
Took me a while to track this one down and I believe it's related to:
https://github.com/statelyai/xstate/issues/4645 https://github.com/statelyai/xstate/issues/4985
I was able to patch @statelyai/inspect package at version 0.4.0 to fix this issue:
diff --git a/dist/index.js b/dist/index.js
index 844efec2f70099dd0a5bc807d191a9f54bef28c6..237e6ef3a9e0a832781e33e043b906b26c2aa839 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -111,6 +111,9 @@ function idleCallback(cb) {
// src/createInspector.ts
var import_safe_stable_stringify = __toESM(require("safe-stable-stringify"));
+var safe_stable_stringify = import_safe_stable_stringify.configure({
+ maximumDepth: 10
+})
function getRoot(actorRef) {
let marker = actorRef;
do {
@@ -273,7 +276,7 @@ function convertXStateEvent(inspectionEvent) {
return {
type: "@xstate.snapshot",
event: inspectionEvent.event,
- snapshot: JSON.parse((0, import_safe_stable_stringify.default)(inspectionEvent.snapshot)),
+ snapshot: JSON.parse((0, safe_stable_stringify)(inspectionEvent.snapshot)),
sessionId: inspectionEvent.actorRef.sessionId,
_version: package_default.version,
createdAt: Date.now().toString(),
@@ -323,7 +326,7 @@ function createBrowserInspector(options) {
...defaultInspectorOptions,
url: "https://stately.ai/inspect",
filter: () => true,
- serialize: (inspectionEvent) => JSON.parse((0, import_fast_safe_stringify.default)(inspectionEvent)),
+ serialize: (inspectionEvent) => JSON.parse((0, import_fast_safe_stringify.default)(inspectionEvent, null, 2, { depthLimit: 10, edgesLimit: 10 })),
autoStart: true,
iframe: null,
...options,
I'm not sure if these max depth values are the best they could be, but this did resolve my issue.
Please provide a CodeSandbox reproduction. You can use one of the templates here: https://github.com/statelyai/xstate?tab=readme-ov-file#templates
Same issue. We are storing complex BabylonJS objects with circular references in our machine context which is causing the inspector to freeze the whole app when it tries to serialize our snapshots. An option to specify maximum depth would solve this without us needing to manually patch the library. I'm currently experimenting with the serialize option on this package, but it's poorly documented so I'm hitting a lot of walls.
Just hit this when storing an HTMLDivElement ref in context.
Here is the repo for my use case.
https://codesandbox.io/p/devbox/weathered-silence-85pjyp
My patch is a bit different. I can use the inspector again with this.
diff --git a/node_modules/@statelyai/inspect/dist/index.js b/node_modules/@statelyai/inspect/dist/index.js
index 844efec..47f6483 100644
--- a/node_modules/@statelyai/inspect/dist/index.js
+++ b/node_modules/@statelyai/inspect/dist/index.js
@@ -111,6 +111,9 @@ function idleCallback(cb) {
// src/createInspector.ts
var import_safe_stable_stringify = __toESM(require("safe-stable-stringify"));
+var safe_stable_stringify = import_safe_stable_stringify.configure({
+ maximumDepth: 10
+})
function getRoot(actorRef) {
let marker = actorRef;
do {
@@ -273,7 +276,7 @@ function convertXStateEvent(inspectionEvent) {
return {
type: "@xstate.snapshot",
event: inspectionEvent.event,
- snapshot: JSON.parse((0, import_safe_stable_stringify.default)(inspectionEvent.snapshot)),
+ snapshot: JSON.parse((0, safe_stable_stringify)(inspectionEvent.snapshot)),
sessionId: inspectionEvent.actorRef.sessionId,
_version: package_default.version,
createdAt: Date.now().toString(),
@@ -323,7 +326,7 @@ function createBrowserInspector(options) {
...defaultInspectorOptions,
url: "https://stately.ai/inspect",
filter: () => true,
- serialize: (inspectionEvent) => JSON.parse((0, import_fast_safe_stringify.default)(inspectionEvent)),
+ serialize: (inspectionEvent) => JSON.parse((0, safe_stable_stringify)(inspectionEvent)),
autoStart: true,
iframe: null,
...options,
@jensen we actually came up with a fun workaround a while back. For complex objects simply store object references instead of the actual objects. To do this just wrap your complex object in a closure.
Instead of this:
context: {
htmlElement: myDiv
}
Do this:
context: {
htmlElement: () => myDiv
}
Because the closures are just functions, they get completely stripped out during serialization, avoiding the problem entirely while still being able to access those objects in the actors/actions where they are needed.
Do the same for any event inputs or actor outputs as those all get serialized as well.
I'd appreciate a PR for this, or I can get to it this week.