Smart Clojure/ClojureScript code sharing
Pull request Compare This branch is 91 commits behind lynaghk:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


           | $$                
  /$$$$$$$ | $$    /$$   /$$   /$$
 /$$_____/ | $$   |__/  |  $$ /$$/
| $$       | $$    /$$   \  $$$$/ 
| $$       | $$   | $$    >$$  $$ 
|  $$$$$$$ | $$   | $$   /$$/\  $$
 \_______/ |__/   | $$  |__/  \__/
             /$$  | $$          
            |  $$$$$$/  Your code is, like, data, bro.        

Cljx is a Lein plugin that emits Clojure and ClojureScript code from a single metadata-annotated codebase.

To use it, add it to your project.clj:

:plugins [[com.keminglabs/cljx "0.2.2"]]
:cljx {:builds [{:source-paths ["src/cljx"]
                 :output-path ".generated/clj"
                 :rules cljx.rules/clj-rules}
                {:source-paths ["src/cljx"]
                 :output-path ".generated/cljs"
                 :extension "cljs"
                 :include-meta true
                 :rules cljx.rules/cljs-rules}]}

Can be run "once" or "auto", in which case it will watch all source-paths for changes to .cljx files. Defaults to "once".


:hooks [cljx.hooks]

to automatically run cljx before starting a REPL, cutting a JAR, etc.

Available options include:

  • :nested-exclusions — When true, ^:clj and ^:cljs metadata (used to indicate target-specific inclusions/exclusions) may be used on "nested" (non-top-level) forms (defaults false)
  • :maintain-form-position – When true, the line positions of transformed cljx forms are maintained, which aligns error and debug info (e.g. line numbers in stack traces, ClojureScript source maps, etc) in the generated files with those in the source cljx files (defaults false)
  • :include-meta — pass code-level metadata along to generated Clojure and ClojureScript (defaults false)
  • :extension — a string indicating the target of a given "build" (defaults "clj")
  • :rules — a fully-qualified symbol that names a var containing the rules to be used

The included clj and cljs rule sets will remove forms marked with platform-specific metadata and rename protocols as appropriate.

E.g., the .cljx source containing

^:clj (ns c2.maths
        (:use [c2.macros :only [combine-with]]))
^:cljs (ns c2.maths
         (:use-macros [c2.macros :only [combine-with]]))

(defn ^:clj sin [x] (Math/sin x))
(defn ^:cljs sin [x] (.sin js/Math x))

  (invoke [_ x] (inc x)))

will, when run through cljx.rules/cljs-rules, yield:

(ns c2.maths
  (:use-macros [c2.macros :only [combine-with]]))

(defn sin [x] (.sin js/Math x))

  (invoke [_ x] (inc x)))

The value associated with :rules should be a symbol naming a var containing the rules to use for that build. cljx.rules/cljs-rules and cljx.rules/clj-rules are provided as a convenience, but you can extend those (or replace them entirely). For example, a namespace on your classpath like this defines some rules:

(ns my.rules
  (:require [kibit.rules.util :refer (compile-rule defrules)]))

(defrules rules
  [(+ ?x 1) (inc ?x)]
  [(- ?x 1) (dec ?x)])

Now you can use those rules in a cljx build like so:

:rules my.rules/rules

The var's namespace will be automatically loaded by cljx (i.e. no need to do so manually via the :injections key in your project.clj).

Forms that are converted into :cljx.core/exclude will be excluded from the output. See Kibit for more info on writing rules, and C2 for a project that uses .cljx heavily.

Clojure is a hosted language

Cljx does not try to hide implementation differences between host platforms. Clojure has ints, floats, longs, &c., ClojureScript has number; Clojure regular expressions act differently than ClojureScript regular expressions, because they are different.

Cljx only tries to unify Clojure/ClojureScript abstractions when it makes sense. E.g., converting clojure.lang.IFn into IFn when generating ClojureScript.

Also, note that cljx has no effect on code produced by macros. Macroexpansion occurs long after cljx touches your code.

REPL Integration

Cljx provides an nREPL middleware that allows you to work with .cljx files in the same way you work with regular .clj files from any toolchain with good nREPL support, like nrepl.el, Counterclockwise, etc.

In your project, in addition to adding cljx as a plugin, just add its middleware in your :dev profile (along with Piggieback's, assuming you're going to be interacting with ClojureScript REPLs as well):

:profiles {:dev {:dependencies [[com.keminglabs/cljx "0.2.2"]]
                 :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl

Now all REPL evaluations and load-file operations will be processed by cljx appropriately before they reach the Clojure or ClojureScript compiler. Whether cljx code is processed for Clojure or ClojureScript is determined by the existence [or not] of a Piggieback ClojureScript environment in your current nREPL session's environment; this is entirely automatic.

Currently, only cljx's default rulesets are used in this case (though you can work around this by making your own higher-order cljx nREPL middleware that uses whatever rulesets you want).


Emacs users, want syntax highlighting? Add to your emacs config: (add-to-list 'auto-mode-alist '("\\.cljx\\'" . clojure-mode)).


  • CLJS: Remove docstrings from namespaces.
  • Explore providing an API that macros can easily use to transform their results


@jonase & @ohpauleez for kibit @swannodette for core.logic