NIKA:\clojurescript-meteor\> list
A few weeks ago, I released a plugin for Meteor, meteor-clojurescript. The goal of this plugin was to make developing with Meteor in ClojureScript as easy as with any of the other languages which Meteor supports. The plugin certainly isn't at that state yet, but it's getting closer.
Meteor has a build system which it uses for compiling and aggregating files. Whenever a file changes, it walks the project's directory structure, calling package-provided callbacks for each file with a specific extension it finds. This callback can then add raw css and javascript assets to the project, which will be compiled and served.
This system works very well with compile-to-js languages like CoffeeScript, as they map one source file to one javascript output file. Working with languages with more complicated relationships (such as less files), has always been a bit of a pain point in Meteor (all @imported less files have to be given the extension .lessimport
, as every file with the extension .less
is compiled seperately).
Choosing the leiningen project.clj
file which accompanies most ClojureScript projects as the base file to compile, it is possible to fire lein cljsbuild once
every time a file changes, which will cause the clojurescript to be compiled into a file (presumably in an ignored directory), which is then read and provided to Meteor. Unfortunately, this is unacceptably slow. Every time lein cljsbuild once
is called, the JVM has to start up (twice), Clojure has to bootstrap itself, the ClojureScript compiler needs to initialize, the directory structure must be analyzed, and every file must be compiled, then the Google Closure compiler must be run to merge the inputs. On my computer, this would consistently take ~ 60 seconds on a small test project.
That was unacceptable. Fortunately, the problem of faster compile times had already been, at least partially, solved. The command lein cljsbuild auto
would start the JVM and initialize the ClojureScript compiler, and then watch the directory structure for changes. Whenever something changed, it would recompile (only the differences), and produce a new output file. Using lein cljsbuild auto
, compilations often only a few seconds (after the initial startup and first compile).
By configuring leiningen to output the compiled code into a folder watched by Meteor, we could then cause the Meteor asset pipeline to re-run whenever the ClojureScript code was changed.
However, simply dropping the JS code into the directory structure would be a bad idea, as source maps would not be correctly handled by Meteor, which would make development with ClojureScript much more difficult. In addition, the code generated by the :nodejs
target includes require()
calls, and a shebang at the top of the file, which would cause an error if passed into Meteor.
To solve this problem, the code outputted by leiningen was given a .cjs
extension, and the sourcemap was outputted into the same directory. A Meteor extension handler was then created for the .cjs
extension, which would both load the source map, and perform the transformations on the code generated by the :nodejs
target.
The leiningen subprocess was also started when the compiler plugin was loaded, and run in the background, such that a seperate leiningen process would not have to be started for development.
This works fairly well, and you can use it today (just mrt add clojurescript
), however it has a few problems:
- Meteor Smart Packages are completely unsupported, every top level project has exactly one ClojureScript compile target, and it is defined by the project.clj
file in the root of the project. This isn't as big of a problem (due to the package system already avaliable through leiningen for ClojureScript), and it does make sense to not have multiple copies of the ClojureScript/Closure core libraries.
- Explicit control over which files are compiled is needed, rather than the implicit mechanism which is generally used by Meteor, meaning that ClojureScript doesn't feel as much like it is part of the Meteor ecosystem
- Advanced compilation is not yet fully supported, as an externs file for Meteor hasn't been made, so any calls to the Meteor APIs will be munged
- The compiler creates intermediate files on the disk within the project directory
However, the potential in using Meteor's powerful reactivity API with the expressive power of ClojureScript is great. I can only see the plugin getting better as I gain familiarity with ClojureScript and the Meteor plugin system. For example, I have done some initial work on a hiccup-style templating interface for meteor-clojurescript, and it is turning out quite well.