Hacker News new | comments | show | ask | jobs | submit login
Faster, Better DOM manipulation with Dommy and ClojureScript (getprismatic.com)
162 points by zackzackzack 1491 days ago | hide | past | web | 49 comments | favorite

ClojureScript is so different from JavaScript that it makes sense write a more native DOM library for it. As someone with a lisp background, it's very cool to see a high-profile company like Prismatic using it.

However, macros for javascript are being worked on. sweet.js is coming along really well, and just needs to continue ironing out bugs and maturing. http://sweetjs.org/ I believe that it could have a profound effect on JavaScript, such as parsing selector string at expand time.

Other languages can totally have macros, but unless you have homoiconicity, which lisp languages do, macros aren't easy to use or support. In ClojureScript, macros are built into the language.

That really only applies to `define-macro` style macros. You can have syntax-case style macros if the language can disambiguate reading and parsing, which a lot of them have trouble with, and why they don't support them.

sweet.js figured out how to do that in javascript, so we have full syntax-case macros just as easily.

Wait so does sweet.js support arbitrary JS execution in macros now?

Not yet, but it's theoretically possibly with all the groundwork that's already been done. See https://github.com/mozilla/sweet.js/issues/12

So I should have said syntax-rules (for now). My bad!

Seems like you would still need to provide something like syntax objects and a special API to manipulate them?

This involves a longer discussion, and I'm not sure what Tim is thinking along those lines. Suffice to say, Clojure's macros are always going to be the best, but I have pretty high hopes that you can get 95% of the way with sweet.js. :)

Yes indeed. Sweet.js already uses syntax objects internally so the groundwork is there for syntax-case. We "just" need to implement and surface the API :)

Agreed. But it's not like macros without homoiconicity are totally useless. They are just more cumbersome to write.

In Groovy, for example, you have the equivalent of macros in the form of AST Transformations †, that look just like Java annotations, but can actually convert the code their are applied to (or, more specifically, the AST node) and transform it into something else at compile time. Writing AST Transformations in Groovy is not as smooth as writing macros in Lisp, as you have to deal with the AST nodes as objects (there are DSLs for this fortunately), instead of using the same language syntax directly, but they provide awesome metaprogramming capabilities as well. The Groovy standard library includes some very useful AST Transformations and frameworks like Grails use them to a great extent too.

So, i think that, even though macros (or equivalent code transformations) are not very accessible to the average programmer if the language does not have homoiconicity, they can be of great value for library writers. As the linked article shows, they could be used to make something that looks like jQuery but generates much more efficient code :D


> you have to deal with the AST nodes as objects (there are DSLs for this fortunately)

I tried using D'Arcy's DSL for Grϕϕvy's AST a few years ago but it was very slow.

Interesting, but I'm not sure how appropriate it is to be comparing it to jQuery in such a head-to-head fashion such as by saying "15x slower", etc. Does Dommy offer the same features (including the browser support) that jQuery does? If not, then you're just comparing apples to oranges, and it doesn't sound like it does (or even plans to) on account that it sounds like there are design decisions that separate the two with regards to the published API ("Inspired by jQuery, but adapted to be functional in order to better fit with ClojureScript core").

Also, wouldn't it be more accurate for the selector testing to actually test against Sizzle[1]?

Other features aren't even present in jQuery, such as templating, and so I'm not sure why you'd even compare that. Yes people can and do use templates with jQuery, but that's an implementation detail and is not a concern of the library itself; jQuery does not coerce or force you to use a slow templating system, and most any good templating system will also have a compilation step that is run at build-time. So yeah, you can take some ugly userland jQuery example code and make specific code that is faster..

1. https://github.com/jquery/sizzle

> Does Dommy offer the same features (including the browser support) that jQuery does?

No, currently Dommy has a smaller feature set that we think covers a majority of use cases, while maintaining reasonable browser support. Our general philosophy is to add functionality as we need it or others request it. I don't think this makes the comparison invalid. We're comparing the general use cases (selectors, basic dom manipulation, etc.) that any DOM library should have. Can you point out a specific comparison that doesn't apply because of api differences or browser support?

> wouldn't it be more accurate for the selector testing to actually test against Sizzle[1]?

Part of the point is to show that you can achieve the same elegant chaining-like syntax as jQuery without wrapping selections, so maintaining the wrapped jQuery selectors are important for comparison.

> Other features aren't even present in jQuery, such as templating, and so I'm not sure why you'd even compare that.

The point of the templating comparison is to show that macros can provide a significant performance boost while maintaining a sane syntax.

Isn't Dommy basically testing plain JS vs. jQuery at this point? That's kind of my point.

I'm also curious how well Dommy would perform in the 'real world'; seeing as everything is compiled down to plain JS, then the divide between library and implementation is removed. jQuery is a larger download hit on the first visit, but if it's already in cache (perhaps before they even reach your site) then the only code required to be downloaded is your app's specific implementation.

In the Dommy example for the templates, the jQuery code is 10 lines -- when formatted like generated.js (the equivalent Dommy code) and removing 2 unnecessary variable declarations. By contrast, generated.js is around 60 lines (giving you a few on account that there's some empty else-statements...). I know minification can do wonders, but the jQuery implementation is clearly going to be much fewer bytes. At what point does it eclipse the size of the jQuery lib itself? I guess that's up to usage.

If your library is the implementation, then it'd seem like you'd have a lot more data being sent over the wire, and likely more often as well on account of any changes to the Dommy which alters the output will have an effect on the cached files for each of them where that functionality was used. On the other hand, updating jQuery means only the 1 file has to be re-downloaded -- and not every userland implementation which uses the API.

This is super impressive. I'm going to use this for my frontend work now. On a side note, what's the idiomatic structure, or "design pattern" for cljs front end code? I would assume something based on MVC, with models comprising of atoms with watches, Views consisting of hiccup templates and controllers gluing dom events to models and views. Am I close?

At Relevance we've released a "framework" for this; it's not MVC, but it addresses the same concerns. It's still in an alpha/beta state, but you should check it out if this stuff interests you: http://pedestal.io/

I'm planning to use it in my next pet project. What I like the most in your framework is the URL generation feature. IMHO it was missing in all other Clojure web libraries. So thank you for your contribution!

I'm not sure there's an agreed upon design pattern. At Prismatic we've adopted a component-based system where a component is essentially the MVC for a single logically-grouped part of the application. Its defined as a record that binds its own events and renders markup from it's template. We try to push mutability and asynchronous functions (xhr, file io, etc.) as far up as possible to avoid unnecessary state manipulation and callbacks everywhere. The idea of reactive programming in ClojureScript is also very interesting as it would make event-handling much more manageable, simplifying event-dom glue code and removing unwieldy callback chains.

I'm extremely interested in this component based system -- I'm currently working on a project in the Seaside framework for Smalltalk and have fallen in love with the composability offered by this approach. However, I'm also a fan of Clojure, and it also seems like Clojure has a stronger community at this time. I'd be very happy if you release this at some point!

We internally have a nice component/widget abstraction we use which is not quite MVC, but we think cleans up a lot of web development. We will release in the near future once we perfect it.

Cool! I am wondering if you have worked with other frameworks like Pedestal.io, C2, or WebFUI, Ganelon. Or any others. What do you think of them?

I am just getting started with Clojure/CS and am evaluating the different options.

http://pedestal.io/ http://keminglabs.com/c2/ https://github.com/drcode/webfui http://ganelon.tomeklipski.com/

Take a look at Luminus.

Are you going to re-release the stuff you yanked off your github?

>> Again the time saving comes from macros moving the work of parsing the template data structures from runtime to compile-time and directly generating efficient JavaScript.

If it ends being javascript string in the ends, how is it 7x faster (or whatever number)? I mean, sure the compiler could optimize a part of my code, but other javascript compiler could do it to.. right?

Although I find the Clojure example very sexy, I don't like how the examples tries to compare to contrived Javascript. I.e. I've been coding javascript for a couple years and I've never used jQuery "sort/filter/slice". But more importantly, it uses pre-defined function in Dommy but not in javascript. For instance:

  (->> (sel [:ul.my-list :li])
     (sort-by #(-> % (sel :input.last-name) .-value))
This uses sort-by where there's:

  function(a, b) {
    return (
in Javascript.. Obviously, I can say:

  $('ul.my-list li').sortBy(function(x) { return x.attr('last-name'); }
My point isn't that much that jQuery is longer/shorter, but mostly that if two examples in two different languages are to be compared, it makes sense to use the same function.. where in this case sort-by is a high-level sort.

>> One is the loneliest number

It's not just "1" or "more than one", it's also zero. Iterating on a list makes it such that you can freely think in a high-level way about the operation and not about the exceptional cases. I.e. $('.blabla').hide(); It Just Works.

>> (mapv #(add-class! % :first-ten-adults)))

Yes, of course you can use map.. but using $('..').addClass('first-ten-adults') makes it so much easier to deal with, instead of knowing what works on one object, what works on multiple objects, what will throw an error if there's nothing, etc.


I think Dommy is a great library but the author didn't do a great job at explaining why it rocks. I guess attacking a very popular language with its most popular library doesn't help :p

Re sort-by: the point there was to show how Dommy gets for free "features" that have to be added to jQuery and contribute to its code size.

Re one/map: you can still do operations on sets of 0, 1, or any number of elements, without knowing how many, by always doing map. There's nothing to keep track of, everything always only works on one object, so you're explicit about whether you're operating on exactly one object, or whether you don't care how many objects you're operating on. The latter is just as convenient as jQuery; the former is safer and deliberate. In jQuery, on the other hand, you have to keep track of what works on one object and what works on multiple objects: .text() and .text('a string') are asymmetrical, for example.

Whilst I'm sure this is clever - I guess I'll never understand the obsession with micro DOM optimisations. When, ever, are we severely held back by the 'speed' of our selectors? It just doesn't happen!

There are some web apps out there that are sluggish as hell if one doesn't have the latest beefy machine. Gmail or Google Doc running on old hardwere are examples of this. "Mobile web" apps are very sensitive to DOM manipulation performance too.

I'm not saying that all sluggish performance in web apps is caused by inefficient DOM manipulations, but having a DRY and efficient way to express DOM manipulations could help a lot in that regard =D

On mobile devices, we were hitting significant performance barriers from DOM and class manipulation. YMMV but it seems DOM performance is an issue on non-beefy boxes.

Author here. Happy to answer any questions/concerns.

does Dommy provide a pure API on top of the DOM? (e.g., lens based) I don't immediately understand how one would make that performant given that the DOM is a mutable data structure.

edit: WebFUI[1] is one clojurescript project trying to provide a pure interface for dom manipulation; there are others.

[1] https://github.com/drcode/webfui

No. We're not trying to do something pure on top of the DOM. That isn't feasible for performant manipulation. Being functional is about more than just immutability.

We use standard manipulation.

> I don't immediately understand how one would make that performant given that the DOM is a mutable data structure.

As a general rule, mutable API's tend to outperform immutable ones.

How mature is ClojureScript these days? I remember people saying it and Clojure on Android was rough when I was exploring Clojure a while back. Does the compiler eliminate unused code from the final output?

> How mature is ClojureScript these days?

Seems to be pretty mature, there are apps in App Store written in it, and there are companies, using it for their front-end (i.e. Prismatic :)).

> Does the compiler eliminate unused code from the final output?

That's done by Google Closure Compiler in advanced mode.

I'm led to believe the issues with Clojure (and other JVM languages) on Android are due to Dalvik not being as friendly to other languages as the Sun JVM. I don't blame Google, but it's unfortunate.

There are actually games written in Clojure that run on Android quite well. A bit slow to start up, but not nearly as bad as I had expected. Take a look at https://play.google.com/store/apps/details?id=com.friendlyvi... and check out their development blog.

EDIT: The guy who did NightWeb talks about Clojure on Android here: http://nightweb.net/blog/clojure-on-android.html

Did the speedup of script execution have other costs? One would imagine that using Clojurescript would incur some other costs somewhere. This feels like a free lunch somehow.

ClojureScript generates a little more ephemeral garbage than native JavaScript and the Clojure data structures are slower, but in reality, the bottlenecks tend to be things which cross the DOM barrier and macros can make those bits much more efficiently. So it's not a free lunch, but it's very very cheap and tasty.

It's incompatible with standard Javascript libraries, which is why you would use Dommy because you can't use jQuery. The ClojureScript compiler is also fairly slow (due to google closure compiler I think) so even with a running jvm it takes several seconds to compile small example files. To slow for me so I switched back to CoffeScript which is lightning fast.

"It's incompatible with standard Javascript libraries, which is why you would use Dommy because you can't use jQuery."

Not true.

    (def $ js/jQuery)
    (.append ($ "body") "hello world")
The reasons you'd use Dommy over jQuery are outlined in the original post.

Not quite. The following shows what doesn't work:

    (def $ js/jQuery)
    (.each ($ "body") (fn [idx, val]
                        (.write js/document "In a lambda")))
The Google Closure Compiler munges names of functions so that any function declared in ClojureScript cannot be called from jQuery which means that no jQuery function that takes a callback can be used. And you really want to use GCC because without its dead code removal a simple helloworld.js file takes more than 700kb.

You just need to compile your ClojureScript with the popular jQuery externs file.

You can mark things so that their names aren't mudged via ^:export.

Would it be possible to (document how to) replace jQuery in Backbone apps with Dommy?

Carthago delenda est: when are you re-releasing the software you yanked from your Github?

We're working on it! We removed the software because it needed extensive cross-project reorganization, consolidation, and cleanup which just wasn't possible to do with a clean upgrade path using available resources (3 backend engineers). Plans are to re-release all of this and more, and we've already started with Plumbing and Graph: https://github.com/prismatic/plumbing. Which librar(ies) in particular are you most interested in?

All this talk of jQuery and no mention of domina (https://github.com/levand/domina) the native clojurescript dom manipulation library? I've since abandoned css and jquery selectors in favor of using domina.xpath.

I also want to mention webfui (https://github.com/drcode/webfui). It is authored by Conrad Barski, who also wrote Land of Lisp. He gave a nice talk about it at the Chicago Clojure meetup group a few months back. He is a fellow hacker news user so I figured he deserved a shout out.

I'd love to use Clojure and clojurescript to replace js/ruby. It's a shame I never come across consulting gigs that use it. Or know any local clojure devs, so I'm still wary of using it on my own projects.

So it continues to only be the ideal choice for pet projects.

It would be more interesting if someone wrote something like this for standard jQuery-compatible syntax. jQuery code in, compiled selectors out. And it's just standard javascript, so no learning curve to learn or anything.

Yes, and a compile step is necessary to produce JS code for those macros. Sounds like it would be really good with coffeescript, like.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact