• Stars
    star
    400
  • Rank 105,404 (Top 3 %)
  • Language
    Clojure
  • License
    Other
  • Created over 12 years ago
  • Updated about 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Write a portable codebase targeting Clojure/ClojureScript
            /$$
           | $$
  /$$$$$$$ | $$    /$$   /$$   /$$
 /$$_____/ | $$   |__/  |  $$ /$$/
| $$       | $$    /$$   \  $$$$/
| $$       | $$   | $$    >$$  $$
|  $$$$$$$ | $$   | $$   /$$/\  $$
 \_______/ |__/   | $$  |__/  \__/
             /$$  | $$
            |  $$$$$$/
             \______/

This plugin is deprecated

After three years of auto-generating files on disk, cluttering up project.clj configurations, and otherwise being a useful but ugly hack, I am happy to announce that the reign of cljx is over: Clojure 1.7 brings us glorious Reader Conditionals (AKA .cljc files). Reader Conditionals provide richer semantics than cljx like default expressions and form splicing. As an addition to the core language, Reader Conditionals have the vastly improved user experience of "no build tool configuration necessary".

I encourage all projects that can use Clojure 1.7 or higher to use Reader Conditionals rather than cljx.

If you need reader conditional support for projects using Clojure 1.6 or earlier, see the cljsee Leiningen plugin.

Chas and I will not be doing additional work on cljx, but we are available for commerical support if you absolutely need it.

For more details on cross-platform code rewriting and the historical context leading up to Reader Conditionals, see:

README for deprecated plugin follows

cljx is a Leiningen plugin and nREPL middleware that produces Clojure and ClojureScript code from a single annotated codebase. Effectively, it is an s-expression preprocessor that yields either Clojure and ClojureScript sources on disk (e.g. for inclusion in jars or for input to compilation tools like Clojure AOT-compilation and lein-cljsbuild):

             +---------+
             | .cljx   |
             | sources |
             +-----+---+
                   |
                   |
                   |
             +-----v-----+       +----------------+
             | cljx      <-------+  configuration |
             | Leiningen |       |       +        |
             | plugin    |       |     rules      |
             +--+--+-----+       +----------------+
                |  |
                |  |
 +------------+ |  | +------------+
 |   .clj     <-+  +->   .cljs    |
 |   sources  |      |   sources  |
 +------------+      +------------+

…or, when used in a REPL, cljx automatically applies the same transformation to any namespaces to be loaded (e.g. as a result of :require declarations) from .cljx files before they are consumed by the Clojure or ClojureScript compiler:

                          +------------+       +---------------+
     +---------+          | cljx       |       | configuration |
     | .cljx   |          | nREPL      |       |      +        |
     | sources +----------> middleware <-------+    rules      |
     +---------+          +-----+------+       +---------------+
                                |
                                |
                                |
                        +-------v---------+
                        | `require`       |
                        | `:require`      |
      +---------+       | `load-namespace`|        +---------+
      | .cljs   +-------> ...etc...       <--------+ .cljs   |
      | sources |       +------+--+-------+        | sources |
      +---------+              |  |                +---------+
                               |  |
                +------------+ |  | +---------------+
                |  Clojure   <-+  +-> ClojureScript |
                |  compiler  |      | compiler      |
                +------------+      +---------------+

When using cljx, you put APIs and implementations that are meant to be fundamentally portable between Clojure and ClojureScript into one annotated .cljx codebase, and leave things that are necessarily tied to a single compilation target in their "native" language (e.g. macros should always be in Clojure sources, DOM manipulation stuffs always in ClojureScript sources, etc).

Does this seem crazy? Crazy awesome, maybe.

Projects that use cljx

Here's some real-world examples of projects that use cljx (when you feel in trouble, refer to these for usage and configuration examples):

"Installation"

To use it, add to the :dev profile in your project.clj:

:profiles {:dev {:plugins [[com.keminglabs/cljx "0.6.0"]]}}
:cljx {:builds [{:source-paths ["src/cljx"]
                 :output-path "target/classes"
                 :rules :clj}

                {:source-paths ["src/cljx"]
                 :output-path "target/classes"
                 :rules :cljs}]}

A more comprehensive configuration example can be found here.

If you want cljx to be invoked automatically as part of the Leiningen compilation process (e.g. before cutting a jar, performing a release), add cljx to your :prep-tasks vector in project.clj. The :prep-tasks project value will replace the default leiningen setting (below) which is required for AOT compilation:

:prep-tasks ["javac" "compile"] 

With cljx once:

:prep-tasks [["cljx" "once"] "javac" "compile"]

Note that any Clojure tests generated by cljx will not be picked up by Leiningen's test runner (see this for some background). In general, it is probably advisable to set up aliases so you can completely control what Leiningen will run:

:aliases {"cleantest" ["do" "clean," "cljx" "once," "test," "cljsbuild" "test"]}

Changelog

See CHANGES.md at the root of this repo.

(You'll especially want to look at the entry for 0.3.0 if you've been using older versions of cljx, as things have changed [of course, we think significantly for the better :-P].)

Usage

There are two ways in which the cljx transformation can be made: via a Leiningen task (necessary when you need the transformation result on disk for e.g. packaging into a jar for distribution), and/or via an nREPL middleware that makes using the Leiningen task unnecessary in REPL sessions.

cljx's Leiningen task can be run once or auto; if the latter (e.g. lein cljx auto), it will watch all source-paths for changes to .cljx files. once is the default.

Each build (i.e. maps in the :builds vector in the :cljx configuration) can be configured with the following options:

  • :source-paths, a sequence of the source roots that contain your .cljx files. Note that putting your .cljx files in your "regular" Leiningen project's :source-paths (by default, "src") is not recommended; doing so will likely lead to them being included in e.g. jar files created by Leiningen. Better to keep them separate, and use cljx to direct Clojure and ClojureScript sources whereever they will be picked up by other tooling.
  • :output-path, the root directory where cljx's output will land. Common options are "target/classes" for both Clojure and ClojureScript files you plan on distributing as a library; or, in an application project using lein-cljsbuild to produce deployable JavaScript, sending cljx-produced Clojure output to "target/classes" (so it's on the classpath and available to be added to a jar/war) and ClojureScript output to a dummy directory (e.g. "target/generated/cljs") that can be a source path in your lein-cljsbuild configuration(s).
  • :rules can be one of:
    • :clj or :cljs to use cljx's default Clojure or ClojureScript ruleset (cljx.rules/clj-rules and cljx.rules/cljs-rules, respectively)
    • a map that specifies the three slots that make up a cljx ruleset:
      • :filetype, a string that defines what the extension of output filenames will be, e.g. "cljs"
      • :features, a set of strings, each naming an enabled "feature"; code in .cljx files that is annotated with a feature that is not included in this set will be pruned in the output
      • :transforms, a sequence of functions that are applied to each expression in each input file, and can modify that expression without constraint
    • a fully-qualified symbol that names a var containing a map as described above

In general, you'll never need to go beyond the named cljx-provided rules.

E.g., the .cljx source containing

(ns example
  (#+clj :use #+cljs :use-macros [c2.macros :only (combine-with)]))

(defn x-to-string
  [x]
  (let [buf #+clj (StringBuilder.) #+cljs (gstring/StringBuffer.)]
    (.append buf "x is: ")
    (.append buf (str x))))

(reify
  #+clj clojure.lang.IFn
  #+cljs cljs.core.IFn
  (invoke [_ x] (inc x)))

…will, when transformed using the :cljs ruleset, yield:

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

(defn x-to-string
  [x]
  (let [buf                               (gstring/StringBuffer.)]
    (.append buf "x is: ")
    (.append buf (str x))))

(reify

         cljs.core.IFn
  (invoke [_ x] (inc x)))

Notice that only the #+cljs-annotated expressions remain, and that everything is still in the same position as it was in the .cljx file; this last fact means that line and column numbers produced by the resulting Clojure/ClojureScript code (e.g. in error messages, stack traces/frames, debuggers, source maps, etc) will remain true to the original sources.

The #+feature-name "annotation" syntax is shamelessly stolen from Common Lisp (and is perhaps being considered for inclusion in Clojure[Script] itself?...see feature expressions). cljx only supports the simplest form of the syntax; other forms can be considered valid TODOs:

  • Exclusionary annotations, e.g. #-cljs
  • "Union" annotations, e.g. #+(or clj clr)

Clojure is a hosted language, in all flavours

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, and so on.

cljx only tries to unify Clojure/ClojureScript abstractions when it makes sense. E.g., converting clojure.lang.IFn into IFn when generating ClojureScript. The rest is up to you, in annotating your code to include or exclude what's needed by each runtime.

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 cider, Counterclockwise, etc.

When you add cljx as a :plugin to your Leiningen project:

  1. The cljx and Piggieback nREPL middlewares will automatically be added to your :repl-options
  2. cljx itself will be added as a project dependency (this will only affect REPL processes, and won't leak out into your project's pom.xml (as long as you add cljx to your :dev profile), influencing downstream users of your library, if you're writing one)

(Note that this does not conflict with using the Austin plugin to automate the configuration of your project to use Piggieback. In fact, the pairing is highly recommended for making the ClojureScript REPL side of your cljx project easy-peasy.)

With cljx installed as a plugin, all nREPL 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).

Misc

Syntax highlighting

Get the same syntax highlighting of .cljx files as you currently do for .clj files!

Emacs

clojure-mode supports .cljx source files out-of-the-box. It will even font-lock features properly.

Vim

autocmd BufNewFile,BufReadPost *.cljx setfiletype clojure

Eclipse + CounterClockwise
  1. In Preferences, go to General > Editors > File Associations.
  2. Add a *.cljx file type in the upper list.
  3. Add an editor association for that *.cljx file type to Counterclockwise's Clojure Editor.

Thanks

  • @jonase and @ohpauleez for enabling the first kibit-based cljx
  • @cemerick for design chats, maintaining and extending cljx, and rewriting the core to use sjacket
  • @cgrand and @trptcolin for sjacket
  • @swannodette for core.match

More Repositories

1

c2

Declarative data visualization in Clojure(Script).
JavaScript
643
star
2

subform-layout

Embeddable layout engine. Like flexbox, but with fewer concepts, applied uniformly.
190
star
3

reflex

Automatic state propogation in ClojureScript
Clojure
186
star
4

zmq-async

Threadsafe Clojure core.async interface to ZeroMQ
Clojure
167
star
5

vagrant-ec2

Use the same chef to provision Vagrant VMs and EC2 instances
Ruby
166
star
6

sandboxtron

Shell
120
star
7

jetty7-websockets-async

Clojure core.async interface to Jetty7's websockets.
Clojure
107
star
8

cljs-d3

A ClojureScript façade for the D3 JavaScript DOM-manipulation library
Clojure
86
star
9

todoFRP

Functional reactive todo lists
JavaScript
81
star
10

singult

JavaScript Hiccup compiler
CoffeeScript
51
star
11

cljs-react-perf

Performance experiments w/ CLJS React libraries and techniques.
Clojure
40
star
12

json-tagged-literals

More palatable JSON serialization
CoffeeScript
40
star
13

c2-demos

Example C2 visualizations and applications
Clojure
37
star
14

svd2zig

Generate Zig API from SVD register definitions.
Zig
33
star
15

clj-liblinear

A Clojure wrapper for LIBLINEAR, a linear support vector machine library
Clojure
28
star
16

cassowary-coffee

CoffeeScript port of the Cassowary linear constraint solver
CoffeeScript
24
star
17

hicada

A cljs hiccup compiler that helps you be deliberate about runtime interpretation.
Clojure
20
star
18

touchtron

Rust touchpad / usb experiments
Rust
16
star
19

clojurescript-compiler-proposal

Request for comments on ClojureScript compiler interface updates
Clojure
14
star
20

cljs-chosen

ClojureScript interface to Harvest's Chosen <select> library
JavaScript
12
star
21

tidy-codebase-starter-kit

Shell
9
star
22

vomnibus

Assortment of useful geographic data, color schemes, &c.
Clojure
8
star
23

vcf

Genetic variant analysis tool.
JavaScript
8
star
24

YALL1

All your sparse bases are belong to us.
Objective-C
8
star
25

lorax

Provably efficient deep learning
Clojure
5
star
26

prote.cs

Compressed sensing based protein fold search
JavaScript
4
star
27

denizen-demo-compojure

Demo Clojure web app using Denizen for user management
Clojure
4
star
28

question-rust-inlining

A question about inlining and match vs lookup tables in Rust.
LLVM
4
star
29

Rliblinear

R interface to LIBLINEAR, a linear support vector machine library
C++
3
star
30

cljs-hiccup-inference

Minimal example repo of a CLJS Hiccup->React compiler w/ type inference
Clojure
3
star
31

question-rust-websocket

Rust
2
star
32

profile-cljs

Wherein I examine ClojureScript performance.
Clojure
2
star
33

splot

Rust
1
star
34

datomic-fuse-AOT

A hellscape of Clojure+Datomic lazy loading errors.
Clojure
1
star
35

eui

Rust
1
star