Homebase React 0.5.4 | Custom Chrome Console Formatters

Homebase React 0.5.4 | Custom Chrome Console Formatters

Chris Smothers
Co-founder and CTO
ยท 11 minutes

Early users may have noticed logging out a database entity, our primary datatype, resulted in output like this.

console.log(entity) before custom formatters

Not what you'd hope for when trying to inspect your data.

This is the result of bringing non-native lazy data structures to JSON.

With the release of homebase-react 0.5.4 we address this problem with custom chrome formatters.

console.log(entity) after custom chrome formatters

Custom formatters are an experimental feature of chrome that have not yet been reified into the core docs, instead living in this google doc. Despite their obscurity they are incredibly powerful and provide an interface to evaluate code when rendering and expanding logs.

To understand how homebase-react arrived at implementing custom formatters I need to explain a bit of our history.

If you're not interested in the backstory you can skip to enabling custom formatters so you can experience them yourself.

Javascript vs. ClojureScript

Homebase React uses ClojureScript and its corresponding data format EDN internally. We then compile all of that to Javascript using the Google Closure Compiler (closure not Clojure) to get as small a bundle as possible. Yes, it's not as small as other state managers, but it's also an entire RDF style Datalog graph database and we think that tradeoff is worth it, especially for write-heavy applications and applications working towards being local-first. Finally we provide APIs (react hooks) that accept JSON and do all the conversion to EDN and back again behind the scenes.

Our hope is to bring some of the benefits of these technologies to a wider audience, without everyone having to learn an entirely new paradigm or port a bunch of code.

In particular, EDN and Clojure provide far more safety and extensibility than JSON and Javascript; Clojure being immutable by default and EDN being extensible. This lets us build and support features that would be unwieldy in JSON and JS while still exposing most of the benefits with familiar JSON and JS idioms.

See Out of the Tar Pit for a more thorough justification of the benefit of immutability and functional purity in maintaining complex systems.

Should everyone give a functional immutable lisp a try once in their life? We think so, but we're also pragmatic. We realize that the barriers to entry are high and the benefits are often unclear if you haven't already experienced them. Giving more developers an experience of just a few of those benefits is a big reason why homebase-react exists.

However, there are clearly some tradeoffs.

Tradeoffs

  1. A larger bundle size. Some of the Clojure runtime cannot be compiled away even though the closure compiler is really aggressive. In particular the implementation of immutable data structures and the functions that operate on them. In many ways adding CLJS is like adding Immutable JS with an even larger standard library of "immutable" functions. In fact, Immutable JS implements the same persistent tree-shaped data structures as Clojure. We also add EDN with its first class support for keywords, symbols, sets and custom data readers that go far beyond the expressivity of JSON and realize a lot of the promise of JSON-LD, but without feeling shoehorned in.
  2. Clojure error messages sometimes leak into JS land. We try to annotate the ones we know about so they make sense to JS devs, and we'll tell you exactly how to fix problems when we can, but it's far from perfect and if you see something weird please create an issue.
  3. Our code is released already minified. We do this because most people do not develop with the google closure compiler and other build tools are not nearly as effective at optimizing ClojureScript code. This makes debugging homebase-react while developing a bit harder since the code is not very readable, but we think the tradeoff is worth it to provide a smaller bundle size. To ameliorate this problem we are building devtools like these formatters so you don't need to read the compiled source. We could compile away everything but the names and leave minification to library consumers, however this would balloon the bundle size on NPM even more and we've yet to see 'legibility of the compiled source' be a root problem for any homebase-react users.
  4. Confusing console logs. AKA why I'm writing this blog. EDN data looks different from JSON data and to add to that, homebase-react mostly outputs entities, which are lazy datatypes and not very helpful when logged out with the default console formatting. So to summarize, EDN already looks foreign and now we're logging out the compiled and minified EDN of a datatype that's essentially just a pointer(db cursor) and does not even contain any interesting data.

Enabling Custom Formatters

So, how do you turn them on? It's not hard, but there are some gotchas to be aware of.

If you develop with Chrome or a Chromium browser like Brave or Edge you're in luck! You can enable custom formatters and you'll get significantly more meaningful logs for entities.

1const [myEntity] = useEntity(1)
2console.log(myEntity)
3
4// or 
5
6const [myEntities] = useQuery({ item: { name: '$any' } })
7console.log(myEntities)

These custom formatters allow us to perform lazy database queries to fetch all of an entity's attributes, including references to other entities and all reverse references to the current entity. They let you access your entire data graph from the console, with any entity you log out as an entry point.

Steps to enable custom formatters

1. Open the preferences panel in chrome devtools by clicking the cog.

enable chrome formatters 1

2. Toggle Enabled custom formatters on.

enable chrome formatters 2

3. Keep the chrome console open and refresh the page. Any logged out entities should now have the custom formatting.

enable chrome formatters 3

Live demo: open the console while on the todo example page.

Remember: for custom formatters to work console.log(anEntity) must be called after you open the chrome console. Anything logged out before you open the console will not have custom formatting applied because chrome processes those logs in the background.

Why do attribute names look different?

The chrome formatters log out the raw underlying EDN, not JSON. Maybe one day we'll add the option to have the output transformed into JSON before it's rendered, but we think reading EDN is pretty intuitive even if you don't know it and we wanted to get this feature out quickly.

It's also a good way to get more familiar with the underlying data structures so when you do want to write more sophisticated queries that are not supported by our simplified JSON API, you won't be scared off by all the colons or lack of commas.

So how do I read this data?

The only thing you really need to understand is keywords and how they map to JSON.

A keyword is a Clojure datatype primarily used for attribute names in key value style data structures. It has the format :some.arbitrary.namespace/some-name. So it starts with a : and then has an optional namespace/ and a name.

1:email
2:user/email
3:io.homebase.user/email

So doing this in homebase-react

1transact({ item: { id: 42, name: "Deep Thought", motto: "DON'T PANIC" } })

Looks like this in EDN

1{:db/id 42
2 :item/name "Deep Thought"
3 :item/motto "DON'T PANIC"}

The word item is not represented internally as the name of a table or even a key to a { id: 1, name: "Deep Thought", motto: "DON'T PANIC" } value. It's more flexible than that. It's basically RDF.

item is just the namespace prepended to each attribute other than :db/id. :db/id being a required attribute on all entities.

That's about it. You can now read EDN keywords and map them back to the JSON that created them.

What is a reverse reference?

All ref attributes in homebase-react are directed, but can be queried and traversed bi-directionally. This is denoted by the _ in attributes like :todo/_owner (see the screenshot above). This is a reverse ref; a set of all entities that have a :todo/owner ref to this "Stella" entity.

From the other side you can see that the "Fix Ship" entity does in fact have a :todo/owner ref pointing to "Stella".

_recentlyTouchedAttributes

DEPRECATED

Our first attempt to make entities less opaque took the form of entity._recentlyTouchedAttributes. An object containing all the the recently queried attributes of an entity that we'd tack onto entities when you set <HomebaseProvider config={{ debug: true }}>. It was far from what anyone wanted and is now deprecated with the introduction of custom formatters.

cljs-devtools

None of this would have been possible without a project that we rely on internally and that forms the foundation for our homebase-react custom formatters cljs-devtools. This is one of the first and undoubtedly one of the most sophisticated uses of chrome formatters. Without this project custom formatters would likely have been cut from chromium a while ago.

EDN

I also want to shoutout EDN. It's an incredible serialization format and the primary reason we're willing to jump through all these hoops and build any of this. I'm sure we'll write more about it in coming posts, but in the meantime, if you're intrigued, the spec is incredibly friendly and section 3.1.2 and 3.1.1 of A History of Clojure cover the rationale behind it.


Thanks for reading all the way here! I hope you enjoy these formatters and please keep letting us know where the sticking points are.