day8 / re-frame-10x

A debugging dashboard for re-frame. X-ray vision as tooling.
MIT License
632 stars 68 forks source link
debugging re-frame reagent tracing

Clojars Project GitHub issues GitHub

re-frame-10x

re-frame-10x lets you instrument, and then inspect, the inner workings of a running re-frame application. It presents as a programmer's dashboard, delivering curated insight and illumination.

It helps you to find your false assumptions faster.

Show Me

Describe It To Me

It Is Epoch Oriented

re-frame applications are computationally regular. First an event happens, and then boom, boom, boom go a series of known computational steps (aka dominoes), in a known order. When this chain reaction completes, a re-frame app enters a quiescent state waiting for another event to kick off the next iteration of the same process.

Each re-frame event and its consequent computation forms a bounded "epoch" which can be inspected, analysed and understood independently of other epochs. This tool is epoch-oriented - it shows you one at a time.

And, yes, it has "time travel debugger" capabilities - you can go backwards and forwards through epochs - but that's really not the most interesting or powerful aspect of what re-frame-10x delivers.

It Is About Trace Data

As it runs, re-frame logs "trace" as data, and this provides an x-ray (MRI?) of your app's inner functions. At its most basic level, re-frame-10x is a consumer, processor and presenter of trace data.

It Is About The Data Flow

re-frame is a functional framework but it's design is "data oriented". It "flows" data, in a loop, through the functions you provide. To understand what is happening in your re-frame app, you must understand what data is "happening".

It Is Always About The Data

So, that's two good re-frame-specific reasons why data is at the core of re-frame-10x, but actually, the importance of data is even more fundamental that that.

Each time you put a println into your program, you are printing out what? And why? Invariably, it is data which fuels your debugging investigation, confirming your current hypothesis, or not.

And when you write your unit tests, you represent your expections as what? Code is proved correct by the data it produces.

So, for debugging and understanding activities, "more data, more easily" is winning. If re-frame-10x does its job, it shouldn't be necessary for you to add printlns. The data you need should be captured and presented, and, if further experimentation is required, it should be available in your REPL too.

Data Brings Code To Life

Perhaps you have seen LightTable in action?

In the small, it is a delightfully productive debugging environment because it co-renders code with the data generated by running the code. The data provides a "paper trail" which brings the code to life, revealing its dynamics and enriching a programmer's understanding.

re-frame-10x has a similar goal, although the method is different.

It Is A Data Dashboard

Observing raw data trace is both interesting and valuable, but it isn't enough. First, we want to leverage this data for insights. And, second, there's often too much data - you can drown in the detail.

So, re-frame-10x tries to be a "dashboard" which curates this "raw data" into "information" through various kinds of analysis and "roll ups". It should deliver insight "at a glance", while still allowing you to drill down into the detail.

Helps Me How?

Four ways:

  1. It helps you to learn re-frame. Simply looking at the "raw traces" provides insight into how it operates. Even experienced re-framians, er, like me, have learned a lot.

  2. It helps you to explore and learn an unfamiliar re-frame codebase. When I click, over here, on this "X" button, it shows me what event is dispatch-ed and in which namespace the associated event handler is registered. And, "oh look, that's interesting - four subscriptions recalculated". Etc.

  3. It helps you with debugging. You see an x-ray of your app's functioning. In particular, it will assist you to write and debug event handlers, which is useful because they hold most of the logic in your re-frame apps.

  4. It helps you to find performance problems and/or detect where there is unnecessary computation occurring.

Point 3, is primary, of course. But Point 2 is almost as important because we all spend a lot of our time groking unfamiliar codebases. Being able to observe the inner workings of a running app is a great way to bring code to life, reveal key features, and build a cognitive map of how the code is structured.

Temporary Warning

Some of the claims above are aspirational. re-frame-10x remains a WIP.

Of Sausage And Sizzle

Internal discussion about a name meandered for a while. Initially, it was re-frame-trace, which is accurate, sure, but it is also 100% sausage because it talks about low level function, and not higher level benefit (sizzle, sizzle). Side stepping the issue, I wanted to call it vox-datum (voice of the data) but that was cruelly rejected, for reasons I don't care to remember. The pain. I mean, who the hell doesn't like a Latin name?? Philistines.

If benefit was a must, then there was -insight and -illumination, but adding either made the name waaaay too long. Naming things - it really is a nightmare!

Finally, -10x cheekily materialised, delivering 100 decibels of audacious sizzle, and consequently a challenge for us to live up to. A 10x programmer starts by having 10x more knowledge and insight - so go make that tool, smarty pants.

Installation

re-frame-10x configuration is per-project, only one person in your team needs to configure a project to use it, and then everyone else can benefit.

If you are using leiningen, modify project.clj in the following ways. When puzzling over the various possible leiningen configurations, it's often helpful to look at a sample project.clj.

IMPORTANT PREREQUISITES

If you don't meet those pre-requisites, see the docs on advanced setups for other ways to install re-frame-10x.

Clojars Project

Easy setup

cljs-devtools is not required to use re-frame-10x, but it is highly recommended.

Compatibility Matrix

Reagent Versions React Versions re-frame-10x Artifact Status
1.2.x 17.x - 18.x clojars ci
1.0.x 17.x [day8.re-frame/re-frame-10x "1.8.1"] Frozen
0.10.x 16.13.x [day8.re-frame/re-frame-10x "0.7.0"] Frozen
0.9.x 16.9.x [day8.re-frame/re-frame-10x "0.5.2"] Frozen
0.8.x 16.x.x - 16.8.6 [day8.re-frame/re-frame-10x "0.4.3"] Frozen
0.6.x - 0.7.x 15.x [day8.re-frame/re-frame-10x "0.3.7"] Frozen

For React 18: Consider using {:preloads [day8.re-frame-10x.preload.react-18]}. This will use React's new render API. Otherwise, expect deprecation warnings from React.

For versions < 0.4.0: If your project uses React 16 and Reagent 0.8.0-alpha2 (or higher) then you will need to add the qualifier -react16 to the version, e.g. [day8.re-frame/re-frame-10x "VERSION-react16"].

Note: If also using re-com then on upgrading reagent you may also need to upgrade re-com.

Code Tracing

re-frame-10x includes an experimental code tracing feature for tracing the code in your event handlers. See day8/re-frame-debux for instructions on how to set it up.

Project configuration

For some settings, re-frame-10x supports project-level configuration via additional closure-defines. This way, some default behavior of 10x can be version-controlled alongside your project. Example:

{:closure-defines
      {re-frame.trace.trace-enabled?        true
       day8.re-frame.tracing.trace-enabled? true
       day8.re-frame-10x.hidden-namespaces "[com.me.uninteresting re-com.box]"}}

Supported keys:

name description type example
...history-size how many epochs to retain integer 25
...ignored-events ignore events of this type seq of keywords "[:init :event-B]"
...hidden-namespaces hide trace from these namespaces seq of symbols "[re-com.box re-com.input-text]"
...time-travel? selecting an event reverts your app-db boolean true
...ignored-libs ignore low-level trace seq of keywords "[:reagent :re-frame]"
...ns-aliases display aliased keywords in data inspectors map of symbol->symbol "{long-namespace ln}"
...trace-when trace your app's events always, never, or only when the panel is open (default) :panel, :always or :never :always

Usage

Controlling the Trace Panel

By default, press ctrl-shift-x to open or close the trace panel. You can re-bind or disable this keystroke in the settings menu.

You can also control the panel programmatically:

Use Cases

app-db

app-db-panel

Subs

subs-panel

Timing

timing-panel

Event

event-panel

Trace

trace-panel

Troubleshooting

Factory reset

re-frame-10x persists its state in local storage. If you experience a bug, this mechanism may be reproducing your bug, even after the root cause is fixed. To fully refresh the state:

If the re-frame-10x window won't show up when pressing ctrl-shift-x

If re-frame-10x throws an exception on startup

Some parts of re-frame-10x seem to work but others don't

The pop-out doesn't respond to input

How does it work?

re-frame is instrumented - all important activity generates trace data.

re-frame-10x consumes this trace data and renders useful visualisations of the re-frame process. Currently, re-frame's tracing capabilities are in alpha and are subject to change at any time. We're testing the utility of the the trace by building an app on top.

By default, re-frame tracing is "compiled out", so it won't impose a performance cost in production. The trade-off here is that you need to explicitly enable it in development.

The preloads option (:preloads [day8.re-frame-10x.preload]) has to be set in order to automatically monkeypatch Reagent to add appropriate lifecycle hooks. Yes this is gross, and yes we will make a PR to reagent to add proper hooks, once we know exactly what we need. The preload namespace also injects a div containing the devtools panel into the DOM.

Developing/Contributing

If you want to work on re-frame-10x, see DEVELOPERS.md.

Citations