dosync Archive Pages Categories Tags

Scripting ClojureScript with JavaScript

10 March 2015

The following code demonstrates how to script the ClojureScript compiler with the Nashorn JavaScript engine that ships with Java 8.

Create a file called build.js with the following contents:

// Use the Java interop clojure.lang.RT namespace to get at 
// Clojure vars
var ArrayList = java.util.ArrayList,
    RT = Packages.clojure.lang.RT,
    seq = RT.var("clojure.core", "seq");

// ================================================================================
// Bootstrap

// Nashorn JavaScript arrays don't satisfy java.util.List
// convert them into ArrayLists which do
var arrayToArrayList = function(arr) {
    var ret = new ArrayList();
    for(var i = 0; i < arr.length; i++) {
        ret.add(arr[i]);
    }
    return ret;
};

// Given a Clojure namespace and name get the var
// and return a JavaScript function which can invoke it
// in the usual JavaScript way
var varToFn = function(ns, name) {
    // if only given one argument assume the namespace
    // is clojure.core
    if(name == null) {
        name = ns;
        ns = "clojure.core";
    }
    var v = RT.var(ns, name);
    return function() {
        var args = arrayToArrayList(arguments);
        return v.applyTo(seq.invoke(args));
    };
};

var hashMap = varToFn("hash-map"),
    keyword = varToFn("keyword");

// Helper to convert JavaScript objects into Clojure
// hash maps
var objectToMap = function(obj) {
    var arr = new ArrayList();
    for(var p in obj) {
        arr.add(keyword(p.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()));
        arr.add(obj[p]);
    }
    return hashMap.apply(null, arr);
};

// =============================================================================
// Fun Starts Here

var symbol = varToFn("symbol"),
    require = varToFn("require");

// Load cljs.closure
require(symbol("cljs.closure"));

// The actual build fn
var build_ = varToFn("cljs.closure", "build");

// Define a build function that can take an JavaScript object
// with camelCase keys
function build(src, obj) {
    return build_(src, objectToMap(obj));
}

// Build!
build("src", {outputTo: "out/main.js"});

You can use this script in place of the build.clj script described in the new ClojureScript Quick Start by running the following instead:

jjs -J-Djava.class.path=cljs.jar build.js

Fun stuff.