Skip to content

Effection CLI developer experience #265

@cowboyd

Description

@cowboyd

Currently there is a big DX gap from the value added to a project by using interactors via an agent, and what it takes to develop those agent-driven interactors from zero to fully covering the components in your design system.

This is because the interactors are no longer directly bundled into your test suite, but are instead bundled separately with the agent. However, this means that you need to have some code that both lifts the agent into an HTML runtime (like playwright) and then you need a way to call the interactors contained in that agent against a version of your component that's loaded into a document so you can try it out interactively, or in a very tightly controlled feedback loop such as a REPL.

If agent-driven interactors are going to work, they need to be as easy to develop as their direct-bundled counterparts. I'm imagining two separate commands:

build

This is the command to build the actual interactor agent and the builder functions that construct the values that can be sent across the wire to it.

$ interactors build [...MODULES] --outdir DIR [--transform MODULE]

This compiles the following files.

${outdir}/agent.js
${outdir}/definitions.js
${outdir}/definitions.d.ts
${outdir}/constructors.js
${outdir}/constructors.d.ts
  • agent.js: contains the actual agent with the symbol table lookup
  • definitions.js: has pure metadata about which actions and filters are on an interactor
  • constructors.js Actual functions that return values for interactions. E.g. Button().exists() These are sent across the wire.

If the --transform MODULE option is set, then a transform will be applied to the definition to create a custom constructor such that. This is useful for plugging into existing systems that may have strict data formats. Effectively this lets you build interactions that are boxed into the native data format. The module should have a default export defining the transform as a function from interaction to some other value T. For example:

import type { Interaction} from "@interactors/core";
import type { InteractionEvent } from "./my-types.ts"

export default method(interaction: Interaction): InteractionEvent {
  return {
    event: interaction.name,
    data: interaction,
  }
} 

This creates a transformed module:

${outdir}/${transform}.js

From will automatically create builders that return the transformed interactions. So for example --outdir build --transform interaction-events.ts would result in:

build/interaction-events.js
build/interaction-events.d.ts

and importing from the transformed builder would result in InteractionEvents being returned instead of Interaction values:

import { Button } from './build/interaction-events.ts';

Button().exists() // => InteractionEvent

dev

Building the agent isn't enough. You also need a way to quickly iterate on it quickly. To do this, I propose adding a dev command that takes a URL and automatically loads the agent into it.

$ interactors dev [...MODULES] --url URL

This builds the agent, browses URL using playwright and injects the resulting agent script into the DOM there. At the same time, it will use esbuild live reload to constantly rebuild the agent script when necessary and re-browse to URL after each rebuild. That way the agent is always up-to-date.

Finally, it also opens up a REPL so that interactions can be sent immediately across the wire. The interaction builders contained in the repl must also be reloaded after each rebuild of the agent code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions