Skip to content

Instantly share code, notes, and snippets.

@grncdr
Created October 25, 2013 19:25
Show Gist options
  • Select an option

  • Save grncdr/7160392 to your computer and use it in GitHub Desktop.

Select an option

Save grncdr/7160392 to your computer and use it in GitHub Desktop.
$ lein cljsbuild clean && lein cljsbuild once
Deleting files generated by lein-cljsbuild.
Compiling ClojureScript.
Compiling "client.js" from ["src/cljs"]...
WARNING: No such namespace: core at line 524 resources/js/cljs/core/async.cljs
WARNING: No such namespace: java.util.Arrays at line 675 resources/js/cljs/core/async.cljs
Successfully compiled "client.js" in 39.640165 seconds.

Improving the "new user" experience with ClojureScript

Here's my take on how to improve the "new user" story for Clojure(Script) I was inspired to write this by this twitter thread

  1. The compiler is too damn slow. If I just require a few popular libs (core.async, dommy) and don't write any of my own code, I'm looking at >30 seconds for lein cljsbuild once to finish. A bit of googling tells me the startup time is to blame, and that lein cljsbuild auto will keep the compiler in memory and automatically recompile as I make changes. Great, awesome, let's do that... emezeske/lein-cljsbuild#249 :frown:. So I rollback to the working version and recompiles only take a about 5-7 seconds. This is tolerable (though still way slower than I'm used to) but it took me a good 30-40 minutes to realize that there was a problem, search for a github issue, and then create one.
  2. Which segues into point #1: way too many half-working things (this may be partly my fault, for immediately gravitating to the new & shiny). With the same project, (still containing none of my own code) the compiler will occasionally throw an NPE from deep within it's bowels on the first recompile. No useful message to indicate wtf went wrong. The only way to get out of that situation seems to be cleaning the project and compiling again.
  3. I have no idea what I'm doing. While this is clearly my fault, only an asshat would see that as a satisfactory resolution. Every how-to blog post I find assumes I want to write a server-side clojure application and immediately has me setting up a minimal (or not so minimal) ring project with routes and templating languages and god knows what else. I just want to write some ClojureScript code in a file and run it in a browser, this shouldn't require any additional tools.
  4. I know that you can actually serve static files via the browser repl because I read an @swannodette blog post but that only works if I dump everything in my project root. This seems like low-hanging fruit to me, a slightly more flexible static-file-and-browser-repl-server (basically let me set the root directory to serve) that was documented in the lein cljsbuild readme would be a huge improvement.
  5. Really unclear node.js story. You could argue that this is unimportant, but I don't think I'm the only node user interested in writing ClojureScript in that environment.
  • Apparently I need to assign a magic global *main-cli-function* to a function and that will work.
  • There's a noderepl plugin, but it also has issues
  • How do I integrate ClojureScript with node's view of the world? (Do things like go blocks work if I'm calling functions from external javascript in them?).
(defproject gallery "0.1.0-SNAPSHOT"
:description "FIXME: write this!"
:url "http://example.com/FIXME"
:plugins [[lein-resource "0.3.1"]
[lein-cljsbuild "0.3.3"]]
:dependencies [[org.clojure/clojure "1.5.1"]
[org.clojure/clojurescript "0.0-1934"]
[org.clojure/core.async "0.1.242.0-44b1e3-alpha"]
[prismatic/dommy "0.1.1"]
[monet "0.1.0-SNAPSHOT"]]
:cljsbuild {
:builds [{:source-paths ["src/cljs"]
:compiler {:output-to "client.js"
:output-dir "resources/js"
:optimizations :simple
:source-map "client.js.map"}}]}
:resource {:resource-paths ["resources"]
:target-path "target/app/resources"}
:hooks [leiningen.cljsbuild leiningen.resource])
(ns boom.core
(:require
[cljs.core.async :refer [>! <! chan put! close! timeout map<]])
(:require-macros
[cljs.core.async.macros :refer [go go-loop alt!]]))
@swannodette

Copy link
Copy Markdown
  1. Recompiles should be subsecond, like less than a tenth of second. During development you should not use any Closure optimizations this just slows things down.
  2. You cannot currently use source maps with incremental compilation, this means auto builds. This is documented on the ClojureScript Wiki. Not sure how we can make this more prominent. It will eventually work but there are a lot of little things to be done. Also note source maps do not work if you don't use some Closure optimization level - also will be fixed eventually. Source maps also slow down compile times. Also note that source maps work best under :whitespace optimization, :simple and :advanced are loss-y because of inlining and dead code elimination.
  3. Unfortunately this not a common use case, not sure what can be done but to write about your own experience and help come up with a solution - see 4. I really sympathize here for most of my noodling around I just create an index.html files and that's it. I also have a fairly simple setup for my Jekyll based blog that works for me - http://github.com/swannodette/swannodette.github.com
  4. Gotta take this one up with the lein-cljsbuild folks, but honestly this seems like a separate CLJS tool to me.
  5. Node.js integration is not well documented though it is used by a few people. The only way to improve the situation is to ask questions from people who use it and help document it. Go blocks should work fine.

@cemerick

Copy link
Copy Markdown

Not a lot to add to David's points, but:

  1. I personally don't use cljsbuild much at all, just as I very rarely use AOT compilation in Clojure. 90% of my workflow is REPL-based, thus my focus on bringing the ClojureScript REPL experience as close to par with Clojure's as possible (getting there). Your cycle time will always be shorter if you're developing via a REPL (i.e. incrementally loading whole files/namespaces of code into the environment, not necessarily doing interactive bits at a prompt). Not to discount the value of a fast compilation cycle, this is just how I work.

(Sorry about emekeze/lein-cljsbuild#249; shit happens, eh? :-( A v1.0.0 will be out next week that fixes it. In any case, I'll always default to failing forward, i.e. happy to put out a bum release, as long as the next one isn't far behind.)

  1. After v1.0 is out the door, v2 of lein-cljsbuild will be dropping (IMO) extraneous non-build stuff, including all REPL helpers. All of it is supersetted by Austin, which I'm supporting/building/etc as actively/enthusiastically as I can. Recent enhancements there from people that aren't using ring, etc., have apparently made the non-Clojure-backend use cases much more amenable (tutorials are likely lagging in those cases, but they'll be along eventually).

Beyond that, I'll +1 David's sentiment that stuff like browser-only and node.js support this is only going to get better with people who are doing these things picking up the banner and pushing stuff forward, writing their own blog posts, etc.

@grncdr

grncdr commented Oct 26, 2013

Copy link
Copy Markdown
Author

Ha sorry for what turned out to be a hit and run, I wrote it in a bit of a rush because I wanted to get out into the sunshine for the rest of the day. In particular, I didn't mean to "call you out" or anything @cemerick, I'm hugely appreciative of the work you do. I actually hadn't heard of Austin yet, it looks like it might solve a lot of my pain points. (I <3 repls)

Thanks @swannodette for the tips regarding Closure :optimization settings, I probably blindly copy-pasted that from somebodies blog post long ago. I knew vaguely what they meant but I should've paid closer attention. I want to make sure I understand you correctly: right now the fastest way to have source-maps for clojurescript is using :optimizations :whitespace (because with no optimizations source-maps aren't generated at all). In the future I'll be able to disable optimizations for development builds entirely, and still get source maps.

Beyond my specific problems, I'd like to talk about one of the pillars (from my perspective as a relative outsider) of Clojure's mythology: by being easy enough to integrate into an existing Java project, teams could "sneak it in" without having to commit a lot of time and effort, and this sped a lot of the early adoption.

I think that use-cases like browser-only and Node.js modules are uncommon at the moment because it requires a disproportionate commitment of time and energy to get things going. I personally get a bit frustrated because things like core.async (not to mention core.match and core.typed!) in previously JavaScript-only environments offer an incredibly compelling reason to try ClojureScript, but having to figure out new tools that don't always work correctly is frustrating and discouraging.

Anyways, I'll keep at it and try to document what I learn as I learn it. Thank you both for taking the time to respond :)

@swannodette

Copy link
Copy Markdown

@grncdr yes the fastest way to get source maps is with :whitespace optimizations. But it's important to note you should not try to use source maps with auto builds - also only generate source maps after having run lein cljsbuild clean. I personally only use source maps only if I cannot spot my mistake quickly with a couple of printlns.

In the future source maps will not always require clean builds and you won't need to specify a Closure optimization step.

I certainly sympathize with your frustrations, but I'm not sure I can offer much more than an assurance that these things will get fixed eventually and contributions are always welcome. Despite it's outward sophistication, ClojureScript is relatively simple system - the compiler and analyzer are both around the 1000 LOCs, and the Closure support is also 1000 LOCs. Which is to the say the code is suprisingly approachable and it's possible to have a considerable amount of impact with little effort! :)

@swannodette

Copy link
Copy Markdown

@grncdr I went ahead and fixed the source map support so that it plays better with incremental compilation. You can now use :optimizations :none with :source-map true (the file name doesn't matter since in this case we make a source map for each file). Please give it a spin when the next ClojureScript release goes out which should be in a few days. Also I encourage opening tickets in JIRA when you encounter a reproducible ClojureScript error, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment