Save 37% off PRO during our Black Friday Sale! »

Targeting Clojure & ClojureScript from a Single Codebase

Targeting Clojure & ClojureScript from a Single Codebase

Talk video available @

Clojure and ClojureScript have been cast from the same mold, to the extent that it is possible to write programs (or, parts of programs) that are entirely portable without changes. However, their lineage and target runtimes necessitate some differences, and others arise because of decisions made in ClojureScript informed by the experiences of building and using Clojure for years prior. While relatively minor, these differences can provoke significant pain when building applications targeting both languages / runtimes, including forcing the maintenance of duplicate codebases, and splitting up namespaces and projects to localize language- or runtime-specific code.

cljx will be presented as a solution to this difficulty, an implementation of "feature expressions" similar to that found in other lisps. Many applications and libraries have used cljx over the past year to eliminate duplication in their cross-language codebases without impacting downstream users (they only ever see "regular" Clojure and ClojureScript), and without sacrificing development-time conveniences (e.g. cljx's integration with nREPL allows you to load code into either Clojure or ClojureScript REPLs as-is, with the necessary transformations applied on the fly). Real-world usage of cljx in the community will be exhibited, and its use in Clojure and ClojureScript REPLs will be demonstrated.


Chas Emerick

March 25, 2014


  1. Chas Emerick @cemerick ClojureWest March 25, 2014 Targeting Clojure &

    ClojureScript Targeting Clojure & ClojureScript from a Single Codebase from a Single Codebase
  2. Writing portable Clojure[Script]: Writing portable Clojure[Script]: Why? Why? • Validation

    • JavaScript environments are useful for general-purpose computation – node.js – Browsers: OpenGL, IndexedDB, WebRTC, typed arrays, web workers, etc… • JavaScript environments can go where the JVM can't
  3. A Brief History of ClojureScript A Brief History of ClojureScript

    • First commit 3+ years after Clojure became public • Implemented assuming protocols – Simpler, easier set of base abstractions • Portability not an objective, so lots of oh-so-close naming differences – cljs.core vs. clojure.lang – INamed vs. Named – c.l.IPersistentCollection → #{ICollection ICounted IEmptyableCollection}
  4. ClojureScript's material differences ClojureScript's material differences • Hybrid compilation model

    – Macros written and evaluated in Clojure – :require-macros, :use-macros • No runtime namespaces • JavaScript host – “missing” primitives – Different, less capable execution model – All the wat you could want
  5. Writing portable Clojure[Script]: Writing portable Clojure[Script]: Options & Problems Options

    & Problems • Don't – Maintain separate codebases • Carefully keep portable and non-portable code separate – Single non-portable call/type/name wags the namespace dog – Lowest common denominator – lein-cljsbuild “crossovers” • Write code once, translate it for each compilation target
  6. Preprocessors in 60 seconds Preprocessors in 60 seconds Preprocessor Source

    files Compilation environment Env-suitable sources Compiler
  7. #ifdef SHARC millis = sharc_dtime(); #else if defined(VxWorks) && defined(X86)

    millis = read_386_counter(); #else if defined(VxWorks) millis = read_moto_counter(); #else if defined(WIN_32) millis = read_windows_timer(); #endif 30 seconds to go… 30 seconds to go…
  8. Feature expressions in LISP Feature expressions in LISP (cons #+Lispm

    "x" #+Spice "y" #-(or Lispm Spice) "z" x) When `Lispm` is defined: (CONS "x" X) When `Spice` is defined: (CONS "y" X) When neither are defined: (CONS "z" X)
  9. cljx: feature expressions for cljx: feature expressions for Clojure[Script] Clojure[Script]

    • Operates entirely outside of (before) the reading, macroexpansion, and compilation of Clojure[Script] • Transformation rules applied to a lossless representation of Clojure[Script] source code (Christophe Grand's sjacket) • Downstream dependencies only ever see the generated Clojure or ClojureScript code; cljx doesn't leak out of your project • Available via Leiningen and in the (n)REPL
  10. cljx .cljx sources project.clj config, nREPL session type ClojureScript sources

    ClojureScript Compiler Clojure sources Clojure Compiler
  11. cljx example cljx example (ns cemerick.pprng #+cljs (:require math.seedrandom [cljs.core

    :as lang]) #+clj (:require [clojure.core :as lang]) #+clj (:import java.util.Random) (:refer-clojure :exclude (double float int long boolean)))
  12. Transformation targeting Clojure Transformation targeting Clojure (ns cemerick.pprng (:require math.seedrandom

    [cljs.core :as lang]) (:refer-clojure :exclude (double float int long boolean)))
  13. Transformation targeting Transformation targeting ClojureScript ClojureScript (ns cemerick.pprng (:require [clojure.core

    :as lang]) (:import java.util.Random) (:refer-clojure :exclude (double float int long boolean)))
  14. (n)REPL support (n)REPL support • Problem: want to be able

    to load/eval cljx code in a REPL session • Problem': want to be able to load cljx files — if they exist — that correspond to namespaces interactively loaded via ns, require, load-namespace, etc. • Solution: cljx nREPL middleware hooks clojure.core/load + ClojureScript's – This is safer than it sounds
  15. cljx in the wild: notable projects cljx in the wild:

    notable projects schema formative pprng sablano double-check garden hickory validateur frak cellular enoki inflections-clj pathetic
  16. cljx challenges and opportunities cljx challenges and opportunities • Macros

    • Line noise • Feature expression flexibility: custom transformations • #lang-style syntax control (viz. Racket)
  17. Resources Resources • cljx: • “Official” wiki re: feature

    expressions in Clojure[Script]: • Common Lisp hyperspec, feature expressions: Thank you! Thank you! Chas Emerick