Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-implementation of DevTools integration in XState #709

Closed
amitnovick opened this issue Oct 8, 2019 · 1 comment
Closed

Re-implementation of DevTools integration in XState #709

amitnovick opened this issue Oct 8, 2019 · 1 comment

Comments

@amitnovick
Copy link
Contributor

amitnovick commented Oct 8, 2019

Goal

We want to add more extensions:

  • Immediately: XState DevTools
  • Maybe in the future: 1. other browser extensions, 2. other tools listening to messages from the service (e.g. services running in Node.js, visualized in a separate program listening to the state of the service via HTTP requests)

Obstacles

  1. The existing implementation of XState only supports a single browser extension as an inspection tool (Redux DevTools)
  2. We want to keep XState core as small as possible, but logic for conforming to each listening device's interface gets in the way of that goal

Solutions

There are three approaches:
A. Move the DevTools logic completely outside of XState core (See implementation details at the bottom)
B. Keep the DevTools logic in XState core, but separate it from interpreter.ts (e.g. the proposed BrowserExtensionsManager class, which with the above context can be renamed to a more general ExternalDevices class)
C. Keep the DevTools logic in XState core, in interpreter.ts

Impact on end-user API:

A. User must import external devices and plug them in directly:

import { reduxDevtools, xstateDevtools } from '@xstate/externalDevices'

interpret(machine, { externalDevices: [reduxDevtools, xstateDevtools] })

B. and C. : no impact, same API:

interpret(machine, { devTools: true})

A - Implementation Details

  • Make XState itself completely agnostic to DevTools. For most intents and purposes, XState just knows that some external device wants to know when the service:
    • starts
    • is updated
    • stops

And for each of these events, some data useful to that external device will be sent:

  • the machine being executed (on start)
  • the state of the service (on update)
  • the last XState event received (on update)

In this approach, XState has no logic dedicated to the external device, and it relies on it being set up externally (via another package, imported and passed in the options object as shown above)

In XState, this would look like:

// interpreter.ts
class Interpreter {
  update() {
    this.options.externalDevices.forEach(externalDevice => externalDevice.update({state, event}))
  }
  
  start() {
    this.options.externalDevices.forEach(externalDevice => externalDevice.start({machine}))
  }
    
  stop() {
    this.options.externalDevices.forEach(externalDevice => externalDevice.stop())
  }
}
@amitnovick
Copy link
Contributor Author

Just realized there's another approach, it's like A but actually it's even more agnostic to XState than was presented there.

D.

Overview

Hook into listeners on the Interpreter class instance:

  • onChange
  • onDone
  • onEvent
  • onSend
  • onStop (for sure)
  • onTransition

Implementation Details

// app.js
import { interpret, Machine } from `xstate`;
import xstateDevtools from '@xstate/devtools;

const machine = Machine({ ... })
const service = interpret(machine);
xstateDevtools.listen(service); // 👈
service.start();

// ...
service.send('CLICK');
// @xstate/devtools.js
const xstateDevtools = {
  listen: function(service) {
    devTools.connect(machine);
    service.onTransition((state, event) => { // 👈
      devTools.send(event, state);
    }
    service.onStop(() => { // 👈
      devTools.unsubscribe();
    }
  }
}

export default xstateDevtools;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants