ClojureScript is Clojure compiled to JavaScript. The language, the reader and the data structures are the same; the host is different, so Java interop is replaced by JavaScript interop and some features of the JVM (refs, agents, ratios, the numeric tower, classes) are missing. A .cljs file is ClojureScript only; a .cljc file is read by several platforms, using reader conditionals to choose what each one sees.
Reader conditionals
Added in Clojure 1.7, they are a reader form that starts with #? or #?@ and holds alternating features and expressions, like cond. They are allowed only in .cljc files and at the REPL. Each platform has a well-known feature keyword that the reader checks in order; the first matching one's expression is read and returned, and the expression of every other branch is read and skipped. If nothing matches and there is no :default, the reader returns nothing at all: not nil, but as if the form had not been written.
| Feature | Platform |
|---|---|
:clj | Clojure on the JVM |
:cljs | ClojureScript |
:cljr | ClojureCLR |
:default | always matches: the fall-through |
Unqualified features are reserved for the official platforms; an unofficial platform should use a qualified keyword. (Babashka and others add their own, see dialects.) The Clojure reader always adds :clj to the feature set it is given, so platform-neutral reading needs tools.reader.
#?( … )is a single expression:#?(:clj Double/NaN :cljs js/NaN :default nil)reads asDouble/NaNin Clojure,js/NaNin ClojureScript andnilelsewhere.#?@( … )splices a collection into the surrounding form:[1 2 #?@(:clj [3 4] :cljs [5 6])]is[1 2 3 4]in Clojure,[1 2 5 6]in ClojureScript and[1 2]elsewhere. It cannot splice several top-level forms (the reader throws "Reader conditional splicing not allowed at the top level"); wrap them in ado, or make one conditional per form.readandread-stringtake an options map::read-condis:allow(process them) or:preserve(keep every branch as data, in a type with:formand:splicing?), and:featuresis the set of active features.reader-conditional,reader-conditional?,tagged-literalandtagged-literal?make and test the preserved forms.data_readers.cljcis read with the same rules.
(ns app.util
(:require #?(:clj [clojure.test :refer :all]
:cljs [cljs.test :refer-macros [is]])
#?(:clj [app.jvm :as jvm])))
(defn str->int [s]
#?(:clj (java.lang.Integer/parseInt s)
:cljs (js/parseInt s)))
(try
(risky)
(catch #?(:clj Exception :cljs js/Object) e
(handle e)))
catch, after the Clojure guide on reader conditionals.ClojureScript namespaces and macros
- A
nsform is the same, with its own macro clauses:(:require-macros [lib :refer [m]]), and in:requirethe options:refer-macrosand:include-macros true. A macro in ClojureScript is written in Clojure, in a.cljor.cljcfile, and runs at compile time. :importworks for Google Closure classes:(:import [goog.net XhrIo]). Thegooglibrary andgoog.stringare available.(catch :default e …)catches any thrown value;js/Errorandjs/Objectare classes of JavaScript.- Numbers are JavaScript numbers (no
Long, ratios orBigIntliterals), strings are JavaScript strings, and a character is a one-character string.
JavaScript interop
| Written | Means |
|---|---|
js/console, js/Date | a global JavaScript name: the js namespace is the global scope |
(.method obj args*), (.-prop obj) | a method call; a property read |
(set! (.-prop obj) value) | a property write |
(js/Date.), (new js/Date) | a constructor call |
#js {:a 1}, #js [1 2] | a JavaScript object or array literal (shallow: nested forms stay ClojureScript data unless also tagged) |
clj->js, js->clj, js-obj, js-keys, js-delete, js-invoke | convert between ClojureScript data and JavaScript values; make an object; list keys; delete a key; call a method by name |
aget, aset | read and write by key or index on arrays and objects |
^js, ^:export | a hint that a value is a JavaScript value (so that its property names are not renamed by the advanced compiler); a marker that a name is kept for use from JavaScript |
Compiler options
The ClojureScript compiler is configured with a map of options (in a build file such as build.edn, shadow-cljs.edn or the options of cljs.build.api). The common ones from the reference are :main, :output-to, :output-dir, :asset-path, :optimizations, :source-map, :target, :externs, :foreign-libs, :modules, :npm-deps, :install-deps, :preloads, :pretty-print, :stable-names, :verbose, :checked-arrays, :bundle-cmd and :global-goog-object&array; many less common ones follow (:closure-defines, :language-in and :language-out, :elide-asserts, :fn-invoke-direct, :optimize-constants and others).
| Option | Values and meaning |
|---|---|
:optimizations | :none (default; recommended for development), :whitespace, :simple, :advanced (recommended for production, with the Google Closure compiler's renaming and dead-code removal; it needs externs for outside JavaScript). Bootstrapped ClojureScript supports only :none and :simple |
:target | none given means a browser; :nodejs, :webworker, :bundle (for dependencies in node_modules, passed on to another JavaScript tool) or :none (no particular environment) |
:source-map | produce source maps |
Not described
Known, not yet covered
- Reference: only parts of the ClojureScript reference were downloaded (compiler options, JavaScript API, module support, dependencies, REPL options, source maps, advanced compilation, bootstrapping, Closure library, async functions) and only the compiler options, optimisations, target and module-support pages were read; each option's value type and default are not tabulated, and
cljs.coreas a whole (its differences fromclojure.core) is described by this page only in outline. - Reference: the self-hosted (bootstrapped) compiler, the REPL types and the build APIs (
cljs.build.api) are not described.
Unknown
- Reference: the claims that
#jsis shallow, that^jsand^:exportwork as described, that a ClojureScript character is a string, that refs, agents and ratios are missing from ClojureScript, and that#queueis a ClojureScript literal, are from knowledge of ClojureScript and not from the downloaded pages.
See also
- Java interop – the JVM side
- Tooling – shadow-cljs and the other ClojureScript build tools
- Dialects – ClojureCLR, Babashka and the others