umbrella
umbrella copied to clipboard
[hiccup/hdom] support clj/cljs vectors/maps
Currently the hiccup/hdom tree normalization stage assumes components are defined using native JS types only (arrays / objects), which precludes idiomatic usage in Clojure/ClojureScript, where one would usually use persistent vectors & maps to define components.
Adding support for these custom types could be done in several ways:
- Add additional type checks for namespaced
clojure.core.*
types / protocols
Pros: trivial implementation Cons: Additional work at runtime, possible performance impact
- Port (part of) to Clojure/ClojureScript
Pros: Idiomatic usage, no perf impact (compared to 1) Cons: implementation & maintenance effort
- Refactor
normalizeTree()
/normalizeElement()
to inject type checks via IOC / config object
Pros: most flexible (also potentially interesting for other langs) Cons: none really?
Thoughts?
Tweet for reference: https://twitter.com/danpeddle/status/1034706731790729216
For a trial run, it seems to me that the trivial implementation on a branch would be enough to play with and do some kind of discovery of whether it's worth putting in extra work.
The shape of using the library / sharing cljc
components etc would be mostly the same regardless of which of the options were chosen?
Sounds reasonable, @dazld - I might not have bandwidth to work on this until mid Oct though, but will try...
would it help if I do some stuff on a fork..? if you can point me at where to start on option 1, that would be enough to get going.
current idea for steps to prove it's workable would be something like:
- render a bunch of markup on the server with hiccup
cljc
components - take over rendered markup on the client with a bundle built using the same components and the library
- do some data changes and observe it does the right thing
for context - I'm really looking for something that lets us avoid having to pull in react, but still get all the benefits from not applying changes to the dom when they aren't needed, pure (as possible!) components etc.
So I was thinking about this a lot over the past few days and was considering to start doing some experiments on this, but then decided to first prioritize the long planned generalization of hdom (#4) to not just work with HTML DOMs, but also other use cases (e.g. scene graphs). There's a kind of pressing need for this right now and having this out of the way (not quite yet, though there's some WIP) will also hopefully give us more guidance and avoid additional refactoring later WRT how this might fit into a CLJS setup.
The first route from above (additional type checks for CLJS vectors/maps) won't be quite enough (due to different APIs used) and so I think to make the most of it, the following functions should be ported to CLJ(S) proper:
-
normalizeTree()
-
normalizeElement()
-
diffElement()
&diffAttributes()
(incl. a port of @thi.ng/diff) -
createDOM()
If you still want to have a go (without starting a whole port), then start updating the above functions in the listed order (that's also how the trees are processed). diffElement
is little cryptic at the moment, but I've started updating it to make it more legible on the hdom-v5
feature branch...
I got as far as invoking it from CLJS, and clearly, it doesn't know what to do with things like cljs.core.persistentVector
as you mentioned - however, when using clj->js
recursively (much like reagent / pump etc do..ish) everything works just fine.. at least, so far.
For ref: https://github.com/dazld/hello-hdom/blob/master/src/hello_hdom/core.cljs (using a hand rolled, standalone UMD build of hdom, as couldn't figure out how to make the various cljs toolings directly consume the module, yay).
So, wrapping the hdom
api in something that gatekeeps cljs data structures going into it is an option that unblocks your work and my experimentation. If able to directly invoke in the future, cool. The performance will potentially be pretty shonky and unoptimised, but if not doing 60fps dom manipulation, shouldn't be such a big deal.. what do you think?
also, starting to think your third option way up at the top is the best one. IOC / injecting knowledge into generalised library seems the cleanest.
clj->js
works indeed, but really becomes suboptimal for realworld UIs, since you essentially create a copy of every single component, but yeah for starters & not too complex UIs it might be more than sufficient...
Am surprised that there're module import issues, since I thought CLJS can consume CommonJS modules out of the box? Maybe the ES6 syntax causes issues? There's an open issue (#23) about providing ES5 outputs, but I find that whole situation and related complexity hard to understand and justify to spend time on...
In terms of "gatekeeping" CLS types, you should also try out how the cljs->js
approach work with components with lifecycle methods. That's a part I haven't thought about yet (or tried out) at all...
The big (thought) wheel keeps on spinning... :)
I'll definitely try the lifecycle stuff - also getting handles on the underlying DOM nodes etc - is there an equivalent for {:ref fn-that-operates-on-DOM}
from react land..? In terms of state management, thinking a global atom will be enough for now too.
Hi @dazld - just a quick note to say that I'm almost done with the hdom v5 update and now "only" need to update all the related docs. This new version includes an IOC mechanism and various (optional) control attributes to enable branch-local (in the component tree) behaviors. I haven't really had bandwidth to think about the impact on a CLJ(S) version of this, but just wanted to let you know that this isn't forgotten...
Ps. all the new stuff is still on the develop branch only
cool stuff, will take a look when it's baked! do you think this API will be stable for a while..?
Once it's released, yes! And even if there're some changes, they should not be drastic, as far as I can tell...