8 ms·
Show HN: Thi.ng/hdom – S-expression based, pure ES6 UI/VDOM components
https://github.com/thi-ng/umbrella/blob/master/packages/hdom/README.md https://github.com/thi-ng/umbrella/blob/master/packages/hdom...
Lightweight UI component tree definition syntax, DOM creation and differential updates using only vanilla JS data structures (arrays, iterators, closures, attribute objects or objects with life cycle functions, closures). By default targets the browser's native DOM, but supports other arbitrary target implementations in a branch-local manner, e.g. to define scene graphs for a canvas element as part of the normal UI tree.
Benefits:
- Use the full expressiveness of ES6 / TypeScript to define user interfaces
- No enforced opinion about state handling, very flexible
- Clean, functional component composition & reuse
- No source pre-processing, transpiling or string interpolation
- Less verbose than HTML / JSX, resulting in smaller file sizes
- Supports arbitrary elements (incl. SVG), attributes and events in uniform, S-expression based syntax
- Supports branch-local custom update behaviors & arbitrary (e.g. non-DOM) target data structures to which tree diffs are applied to
- Suitable for server-side rendering and then "hydrating" listeners and components with life cycle methods on the client side
- Can use JSON for static components (or component templates)
- Optional user context injection (an arbitrary object/value passed to all component functions embedded in the tree)
- Default implementation supports CSS conversion from JS objects for style attribs
- Auto-expansion of embedded values / types which implement the IToHiccup or IDeref interfaces (e.g. atoms, cursors, derived views, streams etc.)
- Only ~5.5KB gzipped
- chris_wot 8y agoI've never quite understood S-expressions. Can someone give me a good primer on them?
- CamTin 8y agoS-expressions are just the lowest common denominator of programming language syntax and more generally trees (aka lists of atoms and lists). The idea is that all program listings are essentially just lists of things, including other lists: A Javascript source file can be thought of as a list of lines, or more semantically a list of statements and expressions. Function calls are just the name of a function and a list of arguments. Each different kind of statement or expression has its own kind of syntax: a `function(baz) { yadda(baz); }' expression uses different syntax than a `if (something) { yadda(foo); } else { blah(bar); }' statement. S-expression languages just use one kind of syntax for everything, so you can do `(lambda (baz) (yadda baz))' and `(if something (yadda foo) (blah bar))' instead. This uniformity of syntax allows most of the interesting features of lisps, like that everything is an expression (returns a value), and macros, which are kind of a souped-up version of C preprocessor `#define's.
- galfarragem 8y agoA great explanation. It's a pity that is buried in the thread.
- ikfmpwdsoz 8y agoThere is (almost) nothing to get. They're just lists. The lists can contain anything you want, including other lists.
- tln 8y agoOP, a README in that directory would be useful I found some docs: http://docs.thi.ng/umbrella/hdom/ http://docs.thi.ng/umbrella/hdom/
- paradite 8y agoDid you miss it somehow? https://github.com/thi-ng/umbrella/blob/master/packages/hdom/README.md https://github.com/thi-ng/umbrella/blob/master/packages/hdom... https://github.com/thi-ng/umbrella/commits/master/packages/hdom/README.md https://github.com/thi-ng/umbrella/commits/master/packages/h...
- toxmeister 8y agoI dunno, there's a 1000+ LOC readme there. Also updated the above link to point to it directly. It's a shame that GH is automatically displaying the readme on desktops, but not in mobile version...
- tln 8y agoDoh! I did miss it
- nothrabannosir 8y ago> - Less verbose than HTML / JSX, resulting in smaller file sizes I always wondered: if you use reasonable compression (what is reasonably expected to be provided by CDNs these days? brotli? I'm so out of the loop..), if you use this, how much does file size actually matter? Isn't it about entropy, rather than size? Say, if every HTML tag required 136 <'s to open, and 136 > characters to close. How would it actually affect different compression algorithms? My intuition says that we're all on this wild goose chase for smaller file size while it may (should) not matter at all. For example, I took Wikipedia's page on entropy and replaced each < by 136*<, same for >. I bring you the file sizes for different algorithms: normal crazy % larger raw 423953 3470903 718 % Gzip -9 78897 134319 70 % Bzip2 -9 64286 64441 0.24 % (!) I don't know what compression algo is de rigeur these days, and 136 <s is obviously not the same as substituting double tags for sexprs. Still, I hope we can put this file size boondoggle in the perspective of entropy one day, instead of just mindlessly chasing the character count dragon. EDIT: turns out you can convert HTML to pseudo sexprs with some regex. here are more realistic numbers: sgml sexpr % diff raw 423953 403777 -4.8 % Gzip -9 78897 76936 -2.5 % Bzip2 -9 64286 63590 -1.1 % Less radical, but still following the trend.
- toxmeister 8y agoAgreed. I also don't see it as key feature, but listed it more in nod to point out that hiccup format is less verbose. smaller file size also means less to read for humans (although, in general that itself doesn't mean better legibility :)
- Jakob 8y agoOut of curiosity I added brotli to the test results: normal crazy % larger brotli 58667 76202 30 % $ curl -s https://en.wikipedia.org/wiki/Entropy | sed s/\</$(printf '<%.0s' {1..136})/g | sed s/\>/$(printf '>%.0s' {1..136})/g | brotli | wc -c
- jjnoakes 8y agoFor me personally, smaller file sizes and less verbosity both improve readability.
- Fifer82 8y agoThanks for sharing and Kudos for TypeScript! This question is not aimed at you and certainly not at your work. I used to be a game developer from an OOP background. I find this really hard to read. https://github.com/thi-ng/umbrella/blob/master/examples/hdom-canvas-draw/src/index.ts https://github.com/thi-ng/umbrella/blob/master/examples/hdom... Would anyone be able to give 2 cents about where the industry is headed in this regard? Will I be looked down on that my codebase is OOP in the future? Also for those who were cut from my cloth, did you find the transition difficult?
- toxmeister 8y agoThanks & I used to do 12+ years of Java and other OOP languages myself before I started using Clojure in 2011 and ever since got hooked on the more functional approach without trying to become a fundamentalist FP. These days I'm mainly using TS, Clojure/ClojureScript, Go, C (not C++)... Re: the above example - you didn't really explain which aspects you find alienating, but finding this hard to read, could have to do with the fact that this demo uses a few other concepts & libs not very popular in the JS world (specifically transducers)? I totally grant that without prior knowledge of those constructs this specific code is not the easiest to comprehend (though I've tried to comment every step). In general though, IMHO this approach is a lot more direct and lightweight than any OOP solution. Having your entire UI defined using native ES6 primitve data structures, provides huge potential and endless options to transform raw app state into UI components. If you ever worked with a Lisp, you'll also find the nested inline definitions not that hard to read. To me it's also more legible than having an (often) over-engineered, multi-layered OOP architecure, riddled with inheritance and so on... Though again, this all is highly subjective. There's certainly ample opportunity to refactor this example into smaller functions and I might as well do that when I get a chance...
- pjmlp 8y agoNot at all. All successful mainstream languages have embraced a mix of OOP and FP. OOP is not a synomim for Java and C++ way of programming with classes. There are other ways of doing OOP, even all those map/filter/fold patterns were already present in Smalltalk. I learned OOP with Turbo Pascal 5.5, followed by C++, Clipper, Smalltalk, Eiffel, Oberon, Component Pascal, and many others. Parallel to that, also SWI Prolog, Lisp and Caml Light as introduction to other paradigms. It helps to keep an open mind, and approach learning by thinking about paradigms in abstract and not getting too focused on language details.
- aaron-santos 8y agoThere is an interesting synergy at play amongst UI as s-exprs, functional programming, and components/vdom where the whole is more valuable than the sum of its parts. I was surprised to find no mention of Reagent[1] nor Om[2] both ClojureScript interfaces to React. They share a remarkable similarity with Umbrella especially the s-expr syntax. However, Reagent[1] and Om[2]'s language choice pushes the functional composition further. I'm interested to see this how this combination of ideas spreads outside the browser and into other applications of UI. As for myself, I've been experimenting with a writing a terminal emulator/component lib[3] using Clojure that is inspired by Reagent/Om and React Native. It's been satisfying seeing the way these ideas compose. [1] - https://reagent-project.github.io/ https://reagent-project.github.io/ [2] - https://github.com/omcljs/om https://github.com/omcljs/om [3] - https://github.com/aaron-santos/zaffre/blob/master/src/examples/components.clj https://github.com/aaron-santos/zaffre/blob/master/src/examp...
- geokon 8y agoThis is by the same guy who wrote the massive thi-ng/geom library for Clojure, so the influence is implied ;)
- toxmeister 8y agoI actually did mention Reagent in the readme in this section (below the diagram): https://github.com/thi-ng/umbrella/tree/master/packages/hdom#the-hdom-data-flow https://github.com/thi-ng/umbrella/tree/master/packages/hdom... Quote: "The syntax is inspired by Clojure's Hiccup and Reagent projects, which themselves were influenced by prior art by Phil Wadler at Edinburgh University, who pioneered this approach in Lisp back in 1999. hdom offers several additional features to these established approaches." However, Reagent also still relies on React under the hood whereas hdom is completely standalone. Also, hdom has a lot more features in terms of what kind of values are supported within an hiccup tree and additionally, in this latest version (5.0), hdom already has been extended to support non-DOM targets to update. Currently, the only user of this feature is the hdom-canvas package (https://github.com/thi-ng/umbrella/blob/master/packages/hdom-canvas/README.md#about https://github.com/thi-ng/umbrella/blob/master/packages/hdom...), which allows you to define canvas scene graphs in the same way as the rest of the UI, but those virtual shape elements are translated into canvas draw calls. A WebGL version of this is in early progress. In general, by implementing a certain interface you can support any other target context/data structure. Ps. I've never used Om so didn't list it as an active influence.
- TheRealPomax 8y agoIs the DOM an afterthought here, like in React, or is everything strongly tied to the DOM?
- toxmeister 8y ago"Afterthought" is too a strong a term, but the largest aspect of that latest version of the library was to generalize the approach to aim at other non-DOM targets. I left another comment about this somewhere else in this thread. Basically, you can choose branch-local implementations of the main hdom interface. If none is provided, it uses the default one which targets the DOM...
- tofflos 8y agoNice visualizations. I particularly liked https://github.com/thi-ng/umbrella/tree/feature/estuary/packages/estuary https://github.com/thi-ng/umbrella/tree/feature/estuary/pack.... Where can I find more stuff like this? :)
- rgovostes 8y agoKarsten is an accomplished computational artist: http://postspectacular.com http://postspectacular.com http://toxi.co.uk http://toxi.co.uk
- toxmeister 8y agohehe... just not an accomplished documenter of his works. those "websites" are all ancient. don't bother visiting!
- rgovostes 8y agoI was a middle school student probably when I saw your hair demo in Processing. Still looks great!
- toxmeister 8y agoThanks! The estuary package started in Java[1] back in 2011, but after years of learning from mistakes, several attempts in Clojure & endless refactoring has now morphed in this TS package[2], which I'm slowly getting more happy with... That demo will need to be refactored too and the plan is to extract those SVG components into their own package sometime during the winter break... [1] https://www.flickr.com/photos/toxi/albums/72157627351329656 https://www.flickr.com/photos/toxi/albums/72157627351329656 [2] https://github.com/thi-ng/umbrella/blob/master/packages/rstream/README.md https://github.com/thi-ng/umbrella/blob/master/packages/rstr...
- spankalee 8y agoI'd still rather write markup in a format that looks like markup. I think this is why JSX is so popular. This is why I prefer something like lit-html with actual markup. Example from hdom readme: // component w/ local state const counter = (i = 0) => { return () => ["button", { onclick: () => (i++) }, `clicks: ${i}`]; }; In lit-html: const counter = (i = 0) => { return () => html`<button @click=${() => i++}>clicks: ${i}</button>`; } Yeah, there some extra weight to the close tag, but it looks like HTML. Disclaimer: lit-html author. https://github.com/Polymer/lit-html https://github.com/Polymer/lit-html
- toxmeister 8y agoI completely appreciate that there're many people who much prefer the separation between code and markup, however, if you ever worked w/ Lisp you'll also learn to appreciate that code is just data and that the earlier separation really is just a convention. Maybe that convention is much better suited for supporting different job descriptions in companies (UX vs. dev), but it's also somewhat arbitrary... The benefits of the component as plain, non-stringified data are not the avoidance of closing tags, but what you can do with those components computationally. E.g. instrumentation, behavior/attribute injections, configuration, filtering etc. An approach based on string interpolation can only go this far, whereas having everyting as arrays, objects, iterators your possibilities of composition & transformation are almost endless... Also, take for example the hdom-canvas support library which doesn't target the DOM itself, but defines virtual elements translated into drawing calls. Using the string interpolation approach would be complete overkill since these elements would have to be re-parsed again, in which case SVG would be faster. But I can easily manage 10k+ moving particles @ 60fps with the hdom approach: https://demo.thi.ng/umbrella/hdom-canvas-shapes/#points-10k https://demo.thi.ng/umbrella/hdom-canvas-shapes/#points-10k
- spankalee 8y agoWriting HTML in HTML isn't so much about separation of code and markup (for everyone at least, for some it admittedly is), but about representing DOM in the same way as HTML. The rules and semantics are the same (because we use the browser's HTML parser), it looks the same, children and attributes are represented the same. It'll be familiar and understandable to programmers new the library, but familiar with the web. FYI, lit-html isn't string interpolation and templates are data, so you can transform and compose templates, operate on arrays/iterables of them, or Promises or async iterables of them, etc. And it doesn't re-parse HTML (because it's not string interpolation), so that particle demo could also be fast - elements can also represent draw calls like a scene graph. I've been meaning to do a similar demo actually...
- geokon 8y agoA bit off-topic, but have you moved away from Clojure entirely? I guess it sorta makes sense b/c creative coding in Clojure never quite took off (though there are some very nice individual libraries) I really loved your geom library btw, thank you!
- toxmeister 8y agoI commented on this recently on twitter & reddit: https://twitter.com/toxi/status/1041143431786115072 https://twitter.com/toxi/status/1041143431786115072 https://twitter.com/toxi/status/1041144813092044800 https://twitter.com/toxi/status/1041144813092044800 https://www.reddit.com/r/Clojure/comments/9deyxe/thinggeom_appears_to_be_back_in_active_development/e6984at/ https://www.reddit.com/r/Clojure/comments/9deyxe/thinggeom_a... Hth!
- geokon 8y agoThanks for the context :) Yeah, I understand it's a bit hard to work on a massive project of that scale with little feedback! It can be a bit draining. I funny enough came at CLJ in part for the math functionality - but I might hit the awkwardness you mentioned. Mostly I'm quite interested in Neanderthal which runs native (MKL) and OpenCL. But living outside of the browser and JS/CLJS presents other limitations (like mixing geom with JavaFX for GUIs has some clunkyness - and the JavaFX wrapper libraries like fnfx are great but not polished) It's great to see the no-org branch! I love org-mode and use it a lot (for templates and examples) but it always ends up a bit tricky for me to massage the code layout to fit the right narrative structure - so I get why you're moving away to encourage contribution :) Thanks again for this awesome piece of tech. The API is just so fun to use once you learn how it works (it's so different and ergonomic from anything else I've used in C++/Cinder/etc.), and the end result looks very sexy. I'll keep using it and maybe add some PRs with examples once no-org merges in
- toxmeister 8y agoPs. Thanks for the kind words!!! :)
- uxcolumbo 8y agoWow - this is an impressive body of work. Particularly like the Estuary and chart samples. You mentioned you still use ClojureScript - but I'm guessing you're not using CLJS for front-end stuff anymore and just stick to your ES6 libs?
- toxmeister 8y agoMerci beaucoup & please see my other comment about this...
- uxcolumbo 8y agoYes, saw you posted it after my comment. You were one of the reasons why I got interested in Clojure / CLJS. Even visited a workshop of yours a couple of years ago ;) I'm still in dabbling mode unfortunately. So my understanding after reading your tweets - is that for your use cases ES6/TS is better suited for front-end stuff. But Clojure is still worth digging into - if only for learning the philosophy, principles, ideas and simplicity of FP. For what use cases would you use ClJ / CLJS nowadays? Anyway, thanks again for your work - I like the hiccup style of building UIs - so will be checking out hdom. Edit: typo
- toxmeister 8y agoOh hello & wow... now if only I knew who you are... :) Those tweets really weren't meant to discourage people from using CLJ(S) at all!! I was merely trying to explain why, personally, I think TS is better suited for my own use cases (and those of my job)... I'm not building "standard" websites and for the tools I'm dealing with, CPU performance is more important than for most other web apps. I sometimes just felt constrained by some of the features & indirections people usually come to Clojure/Script for in the first place. For example, when generating or processing large amounts of geometry I really don't want or even need immutable data structures by default and so I wanted to have the freedom of being able to break out from this more easily. In some cases I too wanted to have more of a 1:1 correspondence between the code I write and what it ends up as in JS, something the combo of CLJS + GCC made hard(er). ES6 syntax offers many niceties (e.g. array and object destructuring, iterators, async fns) and without those I'd probably be still using CLJS more.
- jefozabuss 8y agoFYI the "Mouse gesture analysis" example has performance issues after 300~ samples (e.g. keep drawing big circles continuously) Win10/Chrome/i7 4790k/GTX 970
- toxmeister 8y agoThat's because that demo is using SVG for graphics and hence updating 300+ DOM elements every frame will of course start stuttering... Once I've updated that example to use the hdom-canvas support lib (https://github.com/thi-ng/umbrella/blob/master/packages/hdom-canvas/README.md https://github.com/thi-ng/umbrella/blob/master/packages/hdom...) this will be a non-issue. E.g. here's another demo which draws 10k rectangles @ 60fps. Only once I go >25K, I'm starting to loose framerate (MBP 2016): https://demo.thi.ng/umbrella/hdom-canvas-shapes/#points-10k https://demo.thi.ng/umbrella/hdom-canvas-shapes/#points-10k https://demo.thi.ng/umbrella/hdom-canvas-shapes/#points-50k https://demo.thi.ng/umbrella/hdom-canvas-shapes/#points-50k
- jefozabuss 8y agoI found a similar issue with the git commit log example (when filtering from initial state and clearing filter) - I guess it can be reproduced via a "heavier" example e.g. a couple hundred nodes with more frequent state changing.
- toxmeister 8y agoThe commit table has a 100ms throttle on key press, but I think might have a bug, need to inquire more... :) There's also a stress test here which updates several hundred nodes each frame: https://github.com/thi-ng/umbrella/tree/master/packages/hdom#benchmark https://github.com/thi-ng/umbrella/tree/master/packages/hdom... E.g. each cell shown requires 3 DOM updates per frame, so in the 256 cells config it updates 768 DOM nodes. On my MBP2016 this config runs at ~35 fps, 192 cells (576 real DOM updates) at 59-60fps
- ricardobeat 8y agoWhat makes this different from Mercury (https://github.com/Raynos/mercury https://github.com/Raynos/mercury) and a ton of other pre-React frameworks?
- toxmeister 8y agoI don't want to sound snarky (and please don't see it as such), but did you read the readme? I'm not familiar with Mercury, but looking at the code examples, some obvious differences are: - hdom does NOT care about state, where it comes from or how you manage it - hdom uses vanilla arrays(*) instead of `h()` function wrappers, which not just create more legible (IMHO) style, but also open components up for more options in terms of construction/composition and also transformation. I think that last aspect is seldom talked about in all those discussions and comparisons w/ React & co. Entire 3rd party frameworks are invented & maintained just to provide augmentation, configuration & injection features for React style components, issues which can be much easier solved if your components are in a less abstract & more native data format already. ES6 provides a ton of nice features to construct and transform arrays without any additional support structures. hdom also supports & fully embraces ES6 iterators and the more modern ways of transforming data they support. There's a blog post about this forthcoming, so apols for not going into more detail here now...
- ricardobeat 8y agoNeither do most of the other libraries care about how you manage state. There are also other libraries that can take arrays/data as input, we ended up with inline functions mostly due to performance. There is no intrinsic advantage to plain arrays other than being serializable without an intermediate step, everything else is the same (jsx pragma and an array-based tree are fully interchangeable since you can easily generate one from the other). Looking forward to that post as I’m always happy to be proven wrong!
- localvoid 8y agoI hope you are aware about this attack vector[1] that was fixed in React long time ago. 1. http://danlec.com/blog/xss-via-a-spoofed-react-element http://danlec.com/blog/xss-via-a-spoofed-react-element
- toxmeister 8y agoThanks & I wasn't (aware of it). hdom doesn't use `.innerHTML` anywhere, though. So I'd dare to say less dangerous... though not saying it's out of danger!